Day 2: 控制面 deep dive + etcd 内核 + chaos drill
目标: 把 Day 1 起的 K8s 集群从"能用"升级到"懂内核 + 敢搞挂 + 能修回"。 耗时: 3-5 小时(主要是阅读 + 配置 + 实验,不等镜像) 风险: chaos 操作可能搞挂集群,snapshot save 必须先做 才能 restore
0. TL;DR
- apiserver static pod(Day 2.A): 读 /etc/kubernetes/manifests/kube-apiserver.yaml 30+ flag,启用 audit 看 K8s API 所有调用
- etcd 内核(Day 2.B): raft 选主 / MVCC revision / compact 压缩 / defrag 空间回收 / snapshot save+restore
- chaos drill(Day 2.C): kill 1 etcd 看选主 → kill 2 etcd quorum loss → 从 snapshot 恢复 → 完整 postmortem
1. 学习目标 + 闭环输出
能秒答:
- "apiserver 启动顺序是什么?为什么 health probe 失败重启不影响 etcd?"
- "etcd raft 选主 timeout 默认多少?"
- "MVCC compact vs defrag 区别?"
- "K8s 控制面节点全挂能恢复吗?数据在哪里?"
能动手做:
- 写 audit policy 看到任何 kubectl 操作的完整 audit log
- etcdctl 操作集群成员
- snapshot save + 模拟全集群挂掉 → restore 重建
产出物:
- 一份 audit-policy.yaml(生产可用)
- 一份 etcd backup(scp 到本地一份)
- chaos drill 完整 postmortem 文档
2. 整体流程
Day 2.A apiserver/admission/audit
↓
启用 audit → 看到 kubectl 操作真实 log
↓
Day 2.B etcd internals
↓
endpoint status → MVCC revision → compact → defrag → snapshot save
↓
Day 2.C chaos
↓
kill etcd-cp-1 (1 down) → 选主 → 集群继续
↓
kill etcd-cp-2 (quorum loss 2/3 down) → apiserver 拒写 → 恢复
↓
极端: 3 个 etcd 全删 → 从 snapshot restore (灾难恢复演练)
3. Day 2.A — apiserver / admission / audit (详见第 10 节)
4. Day 2.B — etcd 内核 (详见第 10 节)
5. Day 2.C — chaos drill (详见第 10 节)
10. 实时执行日志(Lab Notebook, 6 维度)
(开干后填,跟 Day0/Day1 同格式)
Op A: apiserver 启用 audit (生产必做)
What — kubeadm 默认 不开 audit log。生产必须开,记录所有 API 调用,排查安全事件 / 调试客户端必备。
audit-policy.yaml(4 级 level: None / Metadata / Request / RequestResponse):
apiVersion: audit.k8s.io/v1
kind: Policy
omitStages: ["RequestReceived"]
rules:
# 1. 高频低价值的 noise 不记 (configmap/endpoint/event get-list-watch)
- level: None
resources:
- group: ""
resources: ["configmaps","endpoints","events"]
- group: "coordination.k8s.io"
resources: ["leases"]
verbs: ["get","list","watch"]
# 2. 系统 SA 自查 不记
- level: None
users: ["system:kube-proxy","system:kube-scheduler","system:kube-controller-manager"]
verbs: ["get","list","watch"]
# 3. Secret 写操作记 full RequestResponse (含 body)
- level: RequestResponse
resources:
- group: ""
resources: ["secrets"]
verbs: ["create","update","patch","delete"]
# 4. 默认 Metadata 级别
- level: Metadata
patch apiserver manifest 加 4 个 flag + 2 个 hostPath volume:
- --audit-policy-file=/etc/kubernetes/audit-policy.yaml
- --audit-log-path=/var/log/kubernetes/audit.log
- --audit-log-maxage=7 # 保留 7 天
- --audit-log-maxbackup=3 # rotate 3 个
- --audit-log-maxsize=100 # 单文件 100MB 切
Why —
- audit log 是 K8s 安全合规第一公民 (PCI/SOC2/HIPAA 都要)
- Secret 写 RequestResponse 因为是高敏感数据,审计强需求(查 谁在 什么时候 改了 什么)
- 同时要排除高频低价值的查 (configmap/endpoint/leases) 防 log 爆炸
- log rotate 防盘满
- audit policy 4 级 trade-off:
- None: 不记 (噪音过滤)
- Metadata: user / verb / resource / name / namespace / code (最常用)
- Request: + request body (改变记录,但 GET 请求看不到回参)
- RequestResponse: + response body (完整,占盘最大)
Actual —
- audit.log 立刻有数据,200K 后涨到 5MB+
- Secret 写日志带 base64-encoded body:
"requestObject":{"password":"dG9wc2VjcmV0"}= "topsecret" - ConfigMap 写记 Metadata 不带 body
- 发现
system:anonymous大量 get(haproxy + Cilium 健康检查未带 token,K8s anonymous auth 默认开)
Lesson —
- kubeadm 不开 audit,生产必加。面试常问 "K8s 怎么排查谁在什么时候改了某个 Secret?" → audit log RequestResponse
system:anonymous高频 get 是潜在安全隐患: kubelet/haproxy 健康探针不带 token,被允许 anonymous get,这本身不严重但暴露了 K8s 默认开 anonymous auth。生产关掉--anonymous-auth=false,改用 token 健康检查- audit-log 落盘性能影响: 高 QPS 集群下 audit log 占 IO 5-10%,生产需要 SSD + 单独盘 + log rotate 严格
- 生产 audit 进阶: 用 fluent-bit 把 audit.log 转发到 Elasticsearch / Splunk,便于查询;落盘只做 short retention
- 修改 static pod manifest 不需要 systemctl restart kubelet: kubelet 监听 /etc/kubernetes/manifests/ 目录,改 yaml 文件 ~10s 内自动 reconcile。面试常问"static pod 怎么自动重启" → kubelet 文件系统 watch
Op B: etcd 内核 (raft / MVCC / compact / defrag / snapshot)
What — etcd 是 K8s "唯一真实状态" 存储。理解它就理解了 K8s 的 ACID 边界。
ETCDCTL='kubectl -n kube-system exec etcd-k8s-cp-1 -- etcdctl
--cacert=/etc/kubernetes/pki/etcd/ca.crt
--cert=/etc/kubernetes/pki/etcd/server.crt
--key=/etc/kubernetes/pki/etcd/server.key
--endpoints=https://127.0.0.1:2379'
# 1. raft 状态 (leader / term)
$ETCDCTL endpoint status -w table
$ETCDCTL member list -w table
# 2. MVCC revision
$ETCDCTL endpoint status -w json | jq '.[0].Status.header.revision'
# 3. compact 历史 (释放 logical history)
$ETCDCTL compact $CURRENT_REV
# 4. defrag 物理空间回收
$ETCDCTL defrag --cluster
# 5. snapshot save (灾难恢复前置)
$ETCDCTL snapshot save /var/lib/etcd/snapshot-day2.db
$ETCDCTL snapshot status /var/lib/etcd/snapshot-day2.db -w table
Actual 输出关键:
endpoint status:
| ENDPOINT | ID | DB SIZE | IS LEADER | RAFT TERM | RAFT INDEX |
|cp-1:2379 |e4dd...| 12 MB | false | 23 | 156162 |
|cp-2:2379 |c144...| 12 MB | false | 23 | 156162 |
|cp-3:2379 |953b...| 12 MB | true | 23 | 156162 | ← leader
snapshot:
| HASH | REVISION | TOTAL KEYS | TOTAL SIZE |
| ae0dc55f | 126876 | 521 | 2.1 MB |
Why (etcd 5 个核心概念) —
| 概念 | 一句话 |
|---|---|
| Raft | etcd 的分布式一致性算法,leader 接所有写,follower 同步。quorum = N/2+1 (3 节点 quorum=2,容忍 1 故障; 5 节点 quorum=3,容忍 2 故障) |
| MVCC (Multi-Version Concurrency Control) | etcd 不覆盖写,每次写产生新 revision。revision 是 cluster-wide 单调递增 int64,K8s 用它实现 watch / resource version 一致性 |
| Compact | 删除 revision X 之前的所有历史,释放 logical 空间。不影响磁盘,只是 BoltDB 内部标记为 free |
| Defrag | 物理重组 BoltDB 文件,释放 compact 后的 free space。整个 etcd 写 IO 暂停几秒,大集群 1-5 分钟。生产: 错峰跑 + 一次一个 member |
| Snapshot save | 在 line consistency 下导出整个 keyspace 到二进制文件,2-10MB 起步,大集群可能几 GB。snapshot restore 时 cluster 必须 down,从 snapshot 重启 3 个新 member |
Lesson —
- etcd 容忍 N/2 故障: 3 节点 → 容忍 1; 5 节点 → 容忍 2; 7 节点 → 容忍 3。生产默认 3 节点足够,7 节点反而慢(同步 latency 增)
- MVCC revision 是全局单调,K8s resourceVersion 直接用它。
kubectl get ... -w用 resourceVersion 做断点续 watch - etcd db 默认上限 2GB (
--quota-backend-bytes),超了拒写。大集群必须调到 8GB,同时调高 compact 频率 - compact + defrag 是 2 套机制:compact 删 history (logical),defrag 重组文件 (physical)。先 compact 再 defrag 才有用
- snapshot save 是冷备,在 leader 上做最快(避免 cross-node 流量)。生产: cron 每天 + 异地存储 (S3/对象存储)
- defrag --cluster 串行处理 3 个 member,每次 1-3 秒。单 member defrag 会让该 member 短暂 unresponsive,raft 可能切 leader
Op C: chaos drill (kill etcd / quorum loss / 恢复)
What — 演练 etcd 故障三个阶段。static pod 杀法: mv /etc/kubernetes/manifests/etcd.yaml /root/(kubelet 检测到 manifest 没了,~10s 内停 pod)。恢复: mv 回来。
# 阶段 1: kill cp-1 etcd
ssh m1 'mv /etc/kubernetes/manifests/etcd.yaml /root/etcd-cp1.yaml.bak'
# 阶段 2: 再 kill cp-2 etcd (quorum loss)
ssh m2 'mv /etc/kubernetes/manifests/etcd.yaml /root/etcd-cp2.yaml.bak'
# 阶段 3: 恢复
ssh m1 'mv /root/etcd-cp1.yaml.bak /etc/kubernetes/manifests/etcd.yaml'
ssh m2 'mv /root/etcd-cp2.yaml.bak /etc/kubernetes/manifests/etcd.yaml'
Actual 数据:
| 阶段 | etcd up | quorum | apiserver 行为 |
|---|---|---|---|
| 0 (baseline) | 3/3 | OK | kubectl 正常 |
| 1 (kill cp-1) | 2/3 | OK (2 >= quorum 2) | kubectl 仍正常 ✅ |
| 2 (kill cp-2) | 1/3 | LOSS (1 < 2) | kubectl get nodes → Error: etcdserver: request timed out ❌ |
| 3 (恢复) | 3/3 | OK | kubectl 全恢复 + raft term 24 (+1) |
Why —
- 3 节点 etcd raft quorum = 2,kill 1 仍有 leader+1 follower 形成 quorum
- kill 2 后剩 1,单节点不能形成 quorum(raft 设计:必须多数同意才能 commit),apiserver 读还可能 stale OK,写一定 timeout
- 恢复后 etcd 自动 catch up(从 leader 拉缺失的 log entries),term 增 1 因为期间没 leader 触发 election
Lesson —
- kubeadm + static pod 让"杀 etcd"很简单: 移走 manifest 文件即可,不像 systemd unit 要 stop。面试常问"K8s 静态 Pod 是什么" → kubelet 直接管理的 Pod,不通过 apiserver,manifest 文件在
/etc/kubernetes/manifests/ - 3 节点 etcd 是真 HA 起步: 容忍 1 节点失败 + 正常运维(滚动升级一台一台来)。5 节点容忍 2,但同步开销大,不是默认推荐
- quorum loss 场景的处理:
- 先尝试恢复 etcd member (重启 / 修网络 / 加节点)
- 真的恢复不了 → 从 snapshot restore + 全集群重建 etcd raft cluster
- 期间 apiserver 写 unavailable,但 worker 节点上的 Pod 继续运行(kubelet 缓存了 spec)
- 数据面 (Pod 业务流量) 不受 quorum loss 影响,只是 control plane 停摆。面试金句
- etcd disaster recovery 完整流程:
- snapshot save (在 chaos 之前已经做了)
- 集群挂 → 决定从 snapshot restore
- 在某节点 etcdctl snapshot restore (生成 new etcd data dir, force-new-cluster)
- 改 etcd manifest 指向 new data dir + 单节点 cluster
- 启动单节点,验证 apiserver 能连
- 加新 etcd member(2nd / 3rd 从 etcd 自动 sync)
- K8s 控制面 vs 数据面 解耦: chaos 时数据面 (业务 Pod) 仍跑,这是 K8s 设计的精髓。节点级 kubelet 本地有 Pod spec cache,不依赖 etcd 也能维持
Day 2 — 阶段总结
✅ apiserver static pod 启用 audit (Metadata + Secret RequestResponse 级别) ✅ etcd 内核 5 概念都跑了真实命令 (raft / MVCC / compact / defrag / snapshot) ✅ snapshot save 2.1MB 521 keys 备份完成 ✅ chaos 三阶段 drill (1/3 down OK, 2/3 down quorum loss, 恢复 term +1) ✅ K8s 数据面 vs 控制面 解耦概念真实验证
🏆 Day 1 + Day 2 完成
5 节点 K8s 集群 + 完整生产姿势,所有踩坑面试金矿都进 MD。