Kubernetes 安全传感器容量、高可用、升级与退出
一次节点池扩容后,安全 DaemonSet 的控制台仍是绿色,集群级告警数量也没有明显变化。排查时才发现新节点带有不同污点和 CPU 架构,传感器既没有对应 toleration,也没有该架构镜像;desiredNumberScheduled 从未包含这些节点,所以“全部 Ready”只代表控制器选中的节点健康,新节点上的业务从加入集群起就处于观测空白。
另一次升级把传感器镜像、规则包和下游解析器分三个窗口发布。新传感器开始发送新增字段,旧转发器不断重试,队列等待年龄上升;团队看到节点 CPU 正常,便继续滚动。等到 SIEM 拒绝写入时,部分节点已无法回退到原规则格式,形成二进制、规则和 schema 的版本分裂。安全传感器的生命周期不是一次 Helm 安装,而是一组节点能力、采集链、版本和外部资产共同变化的事务。
把“传感器健康”拆成四层证据
第一层是调度覆盖:哪些节点应该运行传感器,哪些节点确实有 Pod。第二层是探针有效:Pod 内驱动、eBPF 程序或 LSM 连接已经建立,所需 BTF、内核符号、runtime socket 和挂载可用。第三层是事件可见:受控行为能生成带正确节点与容器上下文的事件,丢事件计数没有异常增长。第四层是交付可用:事件能经过转发、队列和解析器到达证据库或 SIEM,延迟、重复和拒绝可解释。
这四层不能互相替代。Ready 探针可能没有加载内核程序;受控事件可能在节点出现却被下游 429 丢弃;SIEM 有历史数据也不代表新节点已覆盖。团队应为每层定义独立状态和 owner:集群平台负责节点与调度事实,安全平台负责探针与规则,数据平台负责队列与存储,业务 owner 负责事件语义回归。
安全传感器常见形态是节点 DaemonSet、集中 Deployment 或 StatefulSet、ConfigMap/Secret、ServiceAccount/RBAC,以及工具特有 CRD。Kubernetes 官方的 DaemonSet 概念文档说明它保证“所有或部分匹配节点”运行 Pod;这里的“匹配”正是覆盖模型的边界,不能把集群节点总数直接等同于 desiredNumberScheduled。
先建立节点资格与安装权限清单
节点资格表至少记录节点池、操作系统、CPU 架构、内核、BTF、LSM、容器运行时、污点、业务敏感等级和期望传感器 profile。混合内核或混合架构集群可能需要多套 DaemonSet values,而不是用一套 privileged 配置掩盖差异。Windows 节点、Serverless 节点、托管控制面和平台禁止特权 Pod 的节点要显式列为不适用或使用其他证据源,不能从分母中静默消失。
安装者通常需要创建 namespace、DaemonSet、ServiceAccount、Role/ClusterRole、绑定、ConfigMap、Secret,某些产品还需要 CRD、webhook 或 Operator。运行时 Pod 可能需要 hostPID、hostPath、runtime socket、BPF capability、内核模块或 privileged。安装权限应通过临时变更身份授予,日常发布流水线只更新允许的 release 和规则对象;应用团队不应能修改传感器 namespace、hostPath 或集群级策略。
在安装或扩容前先生成资格事实:
kubectl get nodes -o custom-columns='NAME:.metadata.name,OS:.status.nodeInfo.operatingSystem,ARCH:.status.nodeInfo.architecture,KERNEL:.status.nodeInfo.kernelVersion,RUNTIME:.status.nodeInfo.containerRuntimeVersion'
kubectl get nodes -o json \
| jq -r '.items[] | [.metadata.name, (.spec.taints // [] | tostring), (.metadata.labels | tostring)] | @tsv'
kubectl auth can-i create daemonsets.apps -n <sensor-namespace>
kubectl auth can-i create clusterroles.rbac.authorization.k8s.io
kubectl auth can-i patch customresourcedefinitions.apiextensions.k8s.ioyes 只说明 API 授权,不证明 Pod Security、准入策略、云平台或节点内核允许探针运行。生产 values 中每项高权限都要映射到一个能力:读取 BTF、加载 eBPF、访问 CRI 元数据、观察主机进程或执行 LSM 策略。移除权限后的预期退化也要写清,才能判断权限最小化究竟删掉了风险还是删掉了关键证据。
用 DaemonSet 字段计算真实覆盖
status.desiredNumberScheduled 是控制器根据 selector、affinity、污点等条件判断应调度的节点数;currentNumberScheduled 是当前有 Pod 的目标节点数;numberReady 和 numberAvailable 分别反映 Ready 与可用状态;updatedNumberScheduled 表示使用最新模板的目标节点数;numberMisscheduled 表示跑到了本不应运行的节点。字段定义可以在 DaemonSet API 参考中核对。
先查询控制器状态,再把 Pod 所在节点与资格节点做集合比较:
NS=<sensor-namespace>
DS=<sensor-daemonset>
kubectl get ds "$DS" -n "$NS" -o json \
| jq '{generation:.metadata.generation,
observedGeneration:.status.observedGeneration,
desired:.status.desiredNumberScheduled,
current:.status.currentNumberScheduled,
ready:.status.numberReady,
available:.status.numberAvailable,
updated:.status.updatedNumberScheduled,
unavailable:.status.numberUnavailable,
misscheduled:.status.numberMisscheduled}'
kubectl get pods -n "$NS" -l '<sensor-selector>' \
-o custom-columns='POD:.metadata.name,NODE:.spec.nodeName,READY:.status.containerStatuses[*].ready,IMAGE:.status.containerStatuses[*].imageID'
kubectl describe ds "$DS" -n "$NS"健康判断至少要求 observedGeneration == generation、资格节点都在 Pod 节点集合中、Pod 可用、镜像 digest 与目标版本一致、探针加载指标成功。desired == ready 仍不足以证明全集群覆盖,因为错误的 nodeSelector 会同时缩小 desired 和 ready。分母必须来自独立维护的节点资格查询,并对每个排除节点保留原因与到期时间。
正向实验:证明新增节点能进入有效覆盖
正向实验在专用节点池或维护窗口执行。给一台实验节点添加传感器 selector 需要的标签,观察 DaemonSet 创建 Pod;再执行产品已批准的无破坏测试事件,确认事件带回该节点名、Pod UID 和规则版本。实验前记录原标签,避免清理时误删平台已有值。
NODE=<lab-node>
NS=<sensor-namespace>
DS=<sensor-daemonset>
kubectl get node "$NODE" --show-labels
kubectl label node "$NODE" security-sensor.example.com/enabled=true --overwrite
kubectl rollout status daemonset/"$DS" -n "$NS" --timeout=5m
kubectl get pod -n "$NS" -l '<sensor-selector>' \
--field-selector spec.nodeName="$NODE" -o wide预期证据是 DaemonSet 的独立资格分母增加,目标节点出现一个目标版本 Pod,探针加载状态成功,受控事件从该节点进入下游,端到端延迟处于演练基线内。若 Pod Ready 但受控事件没有出现,问题属于探针或规则层;若节点端能查到事件而下游没有,问题属于交付层。不要通过等待告警自然发生来证明覆盖,这既慢又无法区分“没有攻击”和“没有传感器”。
恢复原标签时要根据实验前记录决定删除还是还原旧值:
kubectl label node "$NODE" security-sensor.example.com/enabled-
kubectl get pods -n "$NS" -l '<sensor-selector>' -o wide若该标签本来就存在,不应执行删除命令,而应恢复记录的原值。清理完成后,资格节点集合、DaemonSet desired 和实际 Pod 集合应重新一致。
反向实验:让错误选择器暴露覆盖盲区
反向实验不修改真实传感器,而是在独立 namespace 创建一个无特权 canary DaemonSet,并故意要求不存在的节点标签。它验证团队的覆盖检查能否发现“desired 为零但集群有资格节点”这种假绿色。实验需要 namespace 内创建 DaemonSet 的权限,不需要 hostPath、hostPID 或 privileged。
apiVersion: v1
kind: Namespace
metadata:
name: sensor-coverage-lab
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: coverage-negative
namespace: sensor-coverage-lab
spec:
selector:
matchLabels:
app: coverage-negative
template:
metadata:
labels:
app: coverage-negative
spec:
nodeSelector:
security-sensor.example.com/nonexistent: "true"
containers:
- name: pause
image: registry.k8s.io/pause:3.10执行并读取证据:
kubectl apply -f coverage-negative.yaml
kubectl get ds coverage-negative -n sensor-coverage-lab
kubectl get ds coverage-negative -n sensor-coverage-lab \
-o jsonpath='desired={.status.desiredNumberScheduled} ready={.status.numberReady}{"\n"}'
kubectl get nodes --no-headers | wc -l
kubectl delete namespace sensor-coverage-lab --wait=true预期失败证据是集群有节点,而 DaemonSet 显示 DESIRED 0、READY 0。如果监控只计算 ready / desired 并把零除结果忽略,这个对象可能没有告警;正确检查应以独立资格节点集合为分母,并把“资格节点数大于零且 desired 为零”判为覆盖失败。若镜像拉取失败,说明实验进入了另一条失败路径,应先处理仓库访问,不能把 Pod Pending 冒充 selector 实验结果。
背压从内核缓冲区一直传到索引
运行时事件通常经过内核 ring/perf buffer、用户态读取与富化、进程内队列、网络输出、转发器、耐久队列、解析器和索引。每一段都有容量、丢弃和重试语义。内核侧丢失的事件通常无法补采;进程内无持久队列在 Pod 重启后会清空;耐久队列可以重放,但会制造延迟和重复;索引拒绝可能来自限流、schema 冲突、磁盘水位或凭证失效。
容量模型不要只看 Pod CPU。按节点和事件源记录输入事件率、过滤后事件率、平均及高分位事件大小、buffer 使用、drop 单调计数器、进程队列深度、最老消息年龄、输出成功/重试/拒绝、端到端延迟和存储写入字节。Falco 的 Metrics 文档给出了内核侧 event drop 与内部状态指标示例;Tetragon、Tracee、KubeArmor 应使用各自版本提供的等价指标和日志,不能硬套字段名。
估算链路容量可以从 峰值事件率 × 高分位事件大小 × 保留秒数 × 副本/重试放大 开始,再用代表性节点实测修正。开启完整命令行、环境变量、文件哈希、堆栈或宽网络字段会同时增加内核采集、用户态富化、序列化、网络和索引成本。过滤发生得越晚,越不能降低前段成本;只在 SIEM 丢弃噪声不会挽救节点 buffer。
背压策略按证据价值分层。高危原始事件优先进入耐久通道,低价值调试事件可采样;重试带指数退避、抖动和上限,超过预算进入死信而不是永久堵塞;接收端用事件 ID 幂等;队列接近容量时先停止扩大采集宽度和自动升级。生产阈值来自业务峰值、响应 SLO 和故障演练基线,示例数值不能直接成为所有节点池的统一门槛。
高可用要分别设计节点面与集中面
节点面 HA 的目标是每个资格节点都有一个有效传感器。通常不应在同一节点运行两个同类探针来追求副本数:它们可能重复采集、争夺内核与 CPU 资源、重复执行策略,也无法补回升级空窗中没有被观察的历史事件。真正减少空窗的方法是合理的 maxUnavailable/maxSurge、足够资源、探针就绪检查、节点池 canary 和明确的维护顺序。
集中面可以通过多副本转发器、耐久队列、分区消费、幂等写入和跨故障域存储提高可用性。副本共享一个无 HA 数据库、同一节点或同一凭证时,故障仍会共因传播。探针故障、转发故障、队列积压和索引拒绝要分别告警,恢复动作也不同:前者修节点与驱动,中间层扩容或排队,后者修 schema、磁盘或权限。
断网模式必须提前决定。传感器是本地丢弃、写有限磁盘 spool,还是交给节点外队列?spool 会占用主机磁盘并保存敏感数据,需要容量上限、加密、权限和过期清理;没有 spool 则要量化可接受丢失窗口。任何选择都应保证传感器不会因下游阻塞挤垮业务节点,执行型安全策略也不能依赖远端平台在线才能维持安全状态。
版本组合比单个镜像版本更重要
一次 release 至少固定 Chart、传感器镜像 digest、驱动或内核接口、规则/策略包、CRD、配置 schema、事件 schema 和下游解析器。Operator 还会引入 Operator、被管理组件和 CRD 版本。只记录 Helm release 版本无法回答节点究竟加载了什么,也无法在规则更新独立发生后复现历史判断。
版本清单应由流水线生成并签名,节点实例启动后上报实际组合。检查版本分裂时按节点输出镜像 ID、配置摘要、规则摘要和 schema 版本;updatedNumberScheduled == desiredNumberScheduled 只证明 Pod 模板相同,不证明挂载的可变 ConfigMap 或远端规则已经一致。规则热加载失败必须保留旧版本并报错,不能让进程继续 Ready 却使用未知组合。
兼容评审关注双向关系:新二进制能否读取旧配置和规则,旧二进制能否在回滚时读取已迁移对象,新 schema 是否被旧解析器接受,CRD 转换是否可逆,内核和 BTF 是否满足目标驱动。Chart 的 appVersion 也不等于所有依赖版本。每次变更先查目标 release 与官方兼容说明,再把经过验证的组合锁入仓库。
用 canary 和正反证据执行滚动升级
先在代表性节点池部署 canary,节点内核、架构、runtime、污点和业务事件率都应覆盖生产差异。canary 同时运行受控正向事件、驱动或权限反例、下游 429/500、规则 fixture 差分和资源峰值;比较事件字段、命中集合、drop、CPU、内存、队列年龄与端到端延迟。只有“Pod 启动成功”不能批准全集群滚动。
RollingUpdate 与 OnDelete 的行为、maxUnavailable、maxSurge 和 minReadySeconds 可在 Kubernetes DaemonSet 滚动更新文档核对。资源较重的探针使用 maxSurge 时,同一节点短时可能同时承担新旧 Pod,需预留资源;使用 maxUnavailable 时则接受明确观测空窗。示例策略必须结合节点资源和安全预算调整:
spec:
minReadySeconds: 30
updateStrategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 0升级时保存旧 release、values、镜像 digest、规则摘要、CRD 和下游 schema,再观察每批节点:
helm history <release> -n <sensor-namespace>
helm get values <release> -n <sensor-namespace> -a > sensor-values-before.yaml
kubectl get ds <sensor-daemonset> -n <sensor-namespace> -o yaml > sensor-ds-before.yaml
kubectl rollout status ds/<sensor-daemonset> -n <sensor-namespace> --timeout=10m
kubectl rollout history ds/<sensor-daemonset> -n <sensor-namespace>
kubectl get pods -n <sensor-namespace> -l '<sensor-selector>' -o wide任何一层出现不可解释差异就停止下一批:资格节点缺 Pod、探针未加载、规则摘要不一致、drop 上升、队列年龄持续增长、旧解析器拒绝新字段、敏感字段意外增加。停止不是回滚;先冻结规则和配置发布,保存失败批次证据,再决定修前进还是恢复旧组合。
回滚要处理 CRD、规则与外部 schema
Helm 回滚可以恢复 release 管理的清单和值,命令语义见 Helm rollback,但它不保证 CRD schema、外部数据库迁移、对象存储格式和 SIEM parser 自动降级。执行前要确认目标 revision、旧镜像仍可拉取、旧规则与当前二进制兼容、旧解析器仍在线,并判断已经创建的新版本自定义资源能否被旧控制器读取。
helm history <release> -n <sensor-namespace>
helm rollback <release> <known-good-revision> \
-n <sensor-namespace> --wait --timeout 10m
kubectl rollout status ds/<sensor-daemonset> \
-n <sensor-namespace> --timeout=10m回滚后重新执行覆盖、探针、正向事件和交付验证,并比较所有节点的版本组合。若 CRD 迁移不可逆,应采用并行旧字段、转换 webhook、备份恢复或前向修复,不能盲目降级控制器。下游 schema 则保持兼容窗口:消费者先支持新旧字段,再升级生产者;回滚生产者后确认新消费者仍能处理旧事件,最后才移除兼容分支。
失败升级可能留下两套节点。此时先把节点按实际组合分组,限制新工作负载进入未知节点,停止规则热更新,确保告警能标出来源版本。不要为了“版本统一”同时重启所有 Pod;应从有已知证据的稳定组合逐池恢复,持续保留至少一个可调查节点面。
敏感数据和供应链贯穿整个生命周期
传感器可能读取主机进程、容器 runtime socket、命令行、环境变量、文件路径、网络地址与 Kubernetes 元数据。runtime socket 往往具有超出只读元数据的控制风险,hostPath 也可能绕开普通 namespace 隔离。配置中应最小化挂载和 capability,用只读挂载、seccomp、AppArmor/SELinux、网络策略和独立 ServiceAccount 限制传感器本身。
输出凭证按接收端拆分,放入 Secret 或外部密钥系统,配置轮换与吊销;日志不得打印 token、TLS 私钥或完整认证头。规则仓库、Chart、镜像和驱动同样属于供应链:固定 digest,验证签名或来源,限制谁能发布,保留审计。升级时比较 SBOM 与权限变化,不能把“功能版本更新”当作唯一评审内容。
敏感字段按用途分层:节点本地只保留短时缓冲,受控证据库存原始事件,检索层保存必要键,通知只发事件 ID 与脱敏摘要。高基数字段既增加泄露面,也增加索引成本。调试模式应有到期时间和自动恢复,防止一次排障永久开启环境变量或完整 payload 采集。
卸载是一次可审计的资产核销
退出先停止新规则、策略与自动响应发布,导出需要保留的配置、规则摘要、版本清单和证据索引,确认替代检测已经通过正反验证。随后撤销外部写入或执行入口,再卸载工作负载;若先删控制器,带 finalizer 的自定义资源可能悬挂,若先删证据,则失去解释历史行为的能力。
Helm 的 uninstall 文档说明卸载 release 管理资源与历史记录选项。执行时先查看清单和 CRD 所有者,再按产品文档决定自定义资源、Operator、CRD 的删除顺序:
helm get manifest <release> -n <sensor-namespace> > sensor-manifest-before-uninstall.yaml
kubectl get crd | grep -Ei '<sensor-name>|<operator-name>' || true
kubectl get clusterrole,clusterrolebinding \
| grep -Ei '<sensor-name>|<operator-name>' || true
helm uninstall <release> -n <sensor-namespace> --wait
kubectl get all,configmap,secret,serviceaccount,role,rolebinding \
-n <sensor-namespace>删除 namespace 不能证明退出完成。节点侧还要检查内核模块、BPF 程序、pin、DKMS、driver loader、systemd 服务、hostPath 缓存和 spool;集群侧检查 CRD、webhook、APIService、ClusterRole/Binding、finalizer;外部侧检查队列 topic、对象存储、SIEM parser、dashboard、告警路由、镜像拉取凭证和云资源。证据按保留策略归档,不能为追求“干净”提前销毁,也不能留下无人负责的热索引持续计费。
卸载核销的完成证据包括:资格节点无目标探针,受控测试确认替代链生效,旧接收端不再接收事件,旧凭证已吊销,集群没有悬空高权限,节点磁盘和内核状态恢复,外部费用停止,历史事件仍可按批准策略查询。若计划回装,保留的是经过脱敏和批准的声明式配置与版本组合,不是未受控的旧 Secret 和节点缓存。
用机制与责任边界做长期选择
DaemonSet 适合需要每个节点观察内核或主机事实的传感器;集中 Deployment 适合转发、规则管理或聚合;耐久队列适合隔离短时下游故障;Operator 适合管理 CRD 和多组件生命周期,但也增加控制器、finalizer 与升级顺序。节点 systemd 适合 Kubernetes API 不可用时仍需运行的主机代理,却失去部分声明式管理便利。选择哪种形态取决于事件是否可补采、节点差异、权限模型、团队值守能力和退出成本。
长期 owner 不能只是“安全团队”。平台团队维护节点资格、污点和升级窗口,安全团队维护规则与事件语义,数据团队维护队列、schema 与保留,身份团队维护凭证,业务团队提供受控事件和误报反馈。每次节点池、内核、runtime、Pod Security、网络插件或下游 schema 变化,都应触发对应兼容检查,而不是等告警消失后再排查。
最终应持续成立六个不变量:资格节点都有有效探针;受控事件可见且字段完整;背压和丢失能定位到具体层;实际版本组合可查询;升级与回滚都经过正反证据;退出能核销节点、集群、凭证、数据和费用。只有这些证据同时成立,DaemonSet Ready 才是生命周期健康的一部分,而不是一块过早变绿的状态灯。
