开了审计日志,一小时写 27 MB:轮转与那次 45 秒中断
改完 apiserver 配置,四十五秒连不上,被当成集群故障。审计日志实测 7.7 KB/s,1 GB 只够存一天半;顺带把静态 Pod 清单的写入顺序和回退闸门理清了。
安全整改清单里有一条写得很简单:"控制面开启审计日志。" 我按这条改完重启 apiserver,四十五秒后 kubectl 报连接被拒,值班同事以为集群挂了,电话直接打过来。
这次中断本身是重启的正常代价,但复盘时发现两件更值得记的事:一是这条配置真正落地需要改四个地方,不是一行;二是审计日志的写入量比我们预估的高一个数量级,如果不算清楚,磁盘会在两天内被写满。
一、先想清楚"记什么",再想"存哪里"
审计策略本质是一份规则表:哪些资源、哪些动词、记到哪一级。三个级别的差别很大——None 不记,Metadata 只记谁在什么时候对什么对象做了什么,Request 连请求体一起记。
我们的选择是按敏感度分级:
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
# Secret 只记元数据,绝不能让明文口令进日志
- level: Metadata
resources:
- group: ""
resources: ["secrets", "configmaps"]
# 权限变更、进入容器、工作负载变更,记请求体
- level: Request
verbs: ["create", "update", "patch", "delete"]
resources:
- group: "rbac.authorization.k8s.io"
resources: ["roles", "rolebindings", "clusterroles", "clusterrolebindings"]
- group: ""
resources: ["pods/exec", "pods/attach", "pods/portforward"]
# 其余只记元数据
- level: Metadata
omitStages: ["RequestReceived"]
omitStages: ["RequestReceived"] 这一条能砍掉将近一半的写入量:每个请求本来会产生两条记录(收到、完成),只看完成那条就够了。分级的顺序也有讲究,规则从上往下匹配,第一条命中就停,所以 secrets 必须写在通用的 Request 规则前面。
如果顺序写反了,Secret 的请求体就会整段落进日志——这是审计配置里最危险的一种错误,日志文件本身成了泄密源。
二、四个地方要一起改,少一个都不生效
apiserver 是静态 Pod,配置在 manifest 里。这一次改了七组参数、三处挂载:
# /etc/kubernetes/manifests/kube-apiserver.yaml(节选)
spec:
containers:
- command:
- kube-apiserver
- --audit-policy-file=/etc/kubernetes/audit/audit-policy.yaml
- --audit-log-path=/var/log/kubernetes/audit/audit.log
- --audit-log-maxsize=100
- --audit-log-maxbackup=10
- --audit-log-maxage=7
- --audit-log-format=json
- --audit-log-compress=true
volumeMounts:
- {name: audit-policy, mountPath: /etc/kubernetes/audit, readOnly: true}
- {name: audit-log, mountPath: /var/log/kubernetes/audit}
volumes:
- name: audit-policy
hostPath:
path: /etc/kubernetes/audit
type: DirectoryOrCreate
- name: audit-log
hostPath:
path: /var/log/kubernetes/audit
type: DirectoryOrCreate
参数、策略文件、两个挂载缺一不可:缺策略文件直接起不来,缺日志目录挂载则日志写进容器临时层。
三、写 manifest 的顺序错了会把节点搞挂
第一版我图省事,直接 vim /etc/kubernetes/manifests/kube-apiserver.yaml,保存的瞬间 apiserver 就被 kubelet 重建了,配置还没写完。
正确做法是:新文件先生成到 /tmp,校验 YAML 和字段之后再 cp 覆盖。因为 kubelet 会扫描 manifests/ 目录下的任意文件当作静态 Pod 清单,哪怕名字是 kube-apiserver.yaml.bak,只要扩展名像 YAML、内容能解析,它就会尝试创建——一个写了一半的备份文件,足以让控制面进入反复重启。
# 先写到临时位置,确认能解析、字段齐全
python -c "import yaml,sys; yaml.safe_load(open('/tmp/kube-apiserver.new.yaml'))" && echo YAML_OK
diff -u /etc/kubernetes/manifests/kube-apiserver.yaml /tmp/kube-apiserver.new.yaml | head -40
# 再覆盖,覆盖即触发重启
sudo cp /tmp/kube-apiserver.new.yaml /etc/kubernetes/manifests/kube-apiserver.yaml
四、27 MB 一小时,留存只有 37 小时
改完稳定运行后测了一次写入速率:7.7 KB/s,折算 27 MB/h,一天约 650 MB。按 maxsize=100、maxbackup=10 算,最多保留 1 GB 左右,也就是三十七小时。这意味着"出事后回头查日志",窗口只有一天半。
这个数字和直觉差一个数量级。所以配置前先测速再定轮转:
# 连续两次采样,算实际速率
A=$(sudo stat -c %s /var/log/kubernetes/audit/audit.log); sleep 60
B=$(sudo stat -c %s /var/log/kubernetes/audit/audit.log)
echo "rate=$(( (B-A)/60 )) B/s daily=$(( (B-A)*1440/1024/1024 )) MB"
统计动词分布,如果 get/list 占绝大多数,说明策略太宽:健康检查、控制器、指标采集每秒都在 list,把这类请求设成 None 能降一个数量级。
| 参数 | 我们的值 | 实际影响 |
|---|---|---|
audit-log-maxsize |
100(MB) | 单个文件上限,写满即轮转 |
audit-log-maxbackup |
10 | 最多 10 个历史文件,约 1 GB |
audit-log-maxage |
7(天) | 到期删除,实际受 backup 数量约束 |
| 实测速率 | 7.7 KB/s | 1 GB 约可存 37 小时 |
五、就绪探测要用集群自己的凭据
重启期间 kubectl 连不上,很容易误判成"配置写坏了"。这里有个权限细节:/etc/kubernetes/admin.conf 是 root-only,普通用户读不到,用它做探活会一直失败,看起来像 apiserver 没起来。我们用的是节点上那份属于集群管理员自己的 kubeconfig,并且把探活和自动回退绑在一起:启动后 180 秒内没有恢复健康,自动把 manifest 换回备份版本。
# 180 秒闸门(简化版)
for i in $(seq 1 36); do
if kubectl --kubeconfig "$PROBE_KUBECONFIG" get --raw /healthz >/dev/null 2>&1; then
echo "HEALTHY after ${i}0s"; exit 0
fi
sleep 5
done
echo "TIMEOUT, rolling back"
sudo cp /root/bak/kube-apiserver-before.yaml /etc/kubernetes/manifests/kube-apiserver.yaml
开头那次四十五秒的中断,其实是 apiserver 正常重启;如果是配置错误导致的 CrashLoop,这个闸门就是唯一的退路。
六、加密密钥和备份必须分开保管
审计和加密是同一轮做的,顺序上有个依赖:先开静态加密再开审计,否则日志记着"某 Secret 被读取",而 etcd 里躺的还是明文。
加密配置生效后要做两件事验证:一是看 etcd 里的原始值,应该是 k8s:enc:secretbox:v1:key1: 开头而不是明文;二是把已有的 Secret 全部重写一遍——加密只对写入生效,历史数据不会自动加密。
# 逐个 replace,让旧 Secret 以加密形式重写
kubectl get secret -A -o jsonpath='{range .items[*]}{.metadata.namespace}{" "}{.metadata.name}{"\n"}{end}' \
| while read -r ns name; do
kubectl -n "$ns" get secret "$name" -o json | kubectl replace -f - >/dev/null
done
真正致命的是密钥保管:加密密钥丢了,etcd 快照恢复出来的六十个 Secret 全部不可读,集群能起来但所有服务拿不到凭据。所以密钥文件必须和快照分开存放、分别加密,并且写进交接文档。更早的两份快照是加密前做的,里面仍是明文,这类历史备份要单独标记,别当成可用件。
小结
- 审计策略先定级别再定存储,
secrets必须只记Metadata且排在Request规则之前。 omitStages: ["RequestReceived"]能砍掉近一半写入量,改完要复测。- 静态 Pod 清单先生成到
/tmp再cp,kubelet 会把manifests/下的任意文件当清单。 - 审计日志的写入量必须先测再定轮转,7.7 KB/s 就意味着 1 GB 只够存一天半。
- 探活要用集群自己的 kubeconfig(
admin.conf是 root-only,会误判),并配 180 秒自动回退闸门。 - 静态加密只对写入生效,历史 Secret 需逐个重写;密钥与快照必须分开保管。
正在加载评论…