路线图

面试 · 场景排查型

星辉 2026-07-02 阅读 44 min 9,219 字 路线图
面试 · 场景排查型 封面

答题结构约定(题干 vs 参考答案)

  • 题干:每题的「故障现象」(排查题)或「场景描述」(方案题)即面试官抛出的问题,是题干。
  • 参考答案:其后的「排查路径(决策树)→ 关键命令 → 根因 → 修复 → 预防」全部属于参考答案。
  • 排查题遵循统一范式:现象 → 分层排查树 → 根因 → 修复 → 预防,答题时先说分层思路再落命令,别一上来堆命令。
  • 子类 1–6 为纯排查题;后半部分「部署/配置/安全/弹性场景题」偏方案设计,与《5-方案设计型.md》有重叠,按需取用。

子类 1:Pod 异常排障题 — Pod 起不来、行为异常的排查#

题目 1:Pod 长时间 Pending — 资源不足排查#

故障现象: 新部署的 Deployment 所有 Pod 始终处于 Pending 状态,kubectl get pods 显示容器未创建,describe 显示 FailedScheduling 事件。

排查路径(决策树):

text
Pod Pending
├── describe pod → 看 Events 中 FailedScheduling 原因
│   ├── "Insufficient cpu/memory" → 资源不足
│   │   ├── kubectl top node → 确认节点资源使用
│   │   ├── kubectl describe node → 看 Allocated resources
│   │   └── 修复:调低 requests / 扩节点 / 启用 Cluster Autoscaler(如 Karpenter)
│   ├── "node(s) had taint" → Taint/Toleration 不匹配
│   │   ├── kubectl describe node | grep Taints → 查看 Node Taint
│   │   ├── kubectl get pod -o yaml | grep tolerations → 检查 Pod Toleration
│   │   └── 修复:添加匹配的 toleration 或移除 Node Taint
│   ├── "no nodes match pod topology spread" → 拓扑约束过严
│   │   └── 修复:放宽 topologySpreadConstraints 或增加节点
│   ├── "Insufficient aliyun/vpc-eni-ip" → ENI IP 资源耗尽
│   │   ├── kubectl get nodeeni -A → 检查节点 IP 可用数
│   │   ├── 检查是否为 Terway IPAM 泄漏(见综合排障题)
│   │   └── 修复:手动释放泄漏 IP / 升级 Terway / 增加 ENI
│   └── "persistent volume not found" → PVC 绑定失败
│       └── 转到存储排障题

关键命令:

bash
kubectl describe pod <pod> -n <ns>
kubectl get events -n <ns> --sort-by='.lastTimestamp'
kubectl top node
kubectl describe node <node> | grep -A5 "Allocated resources"
kubectl get pods --field-selector spec.nodeName=<node> --all-namespaces

常见原因:

  • resources.requests 设置过高,超过节点可用资源
  • 节点池所有节点规格一致,缺少灵活调度能力
  • CA/Karpenter 未配置或响应延迟
  • Taint/Toleration 配置遗漏

修复方案:

  1. 短期:调低 Pod requests,使值和实际用量匹配(参考 VPA 推荐值)
  2. 中期:启用 Karpenter 并配置多规格 NodePool,开启 Consolidation
  3. 长期:配置 HPA + VPA,让资源申请和实际需求对齐;设置 Prometheus 告警提前预警节点资源

题目 2:Pod ImagePullBackOff — 镜像拉取失败排查#

故障现象: Pod 状态显示 ImagePullBackOffErrImagePull,容器无法启动。

排查路径(决策树):

text
ImagePullBackOff
├── kubectl describe pod → 看 Events 中拉取错误信息
│   ├── "image not found" / "manifest unknown"
│   │   ├── 检查镜像 tag:kubectl get pod -o jsonpath='{.spec.containers[*].image}'
│   │   ├── 本地 docker pull 测试 → 确认镜像是否存在
│   │   └── 修复:修正 tag / 推镜像
│   ├── "401 Unauthorized" / "authentication required"
│   │   ├── 检查 imagePullSecrets:kubectl get pod -o jsonpath='{.spec.imagePullSecrets}'
│   │   ├── 检查 Secret 是否过期(ECR token 每 12 小时)
│   │   ├── 确认 Secret 关联的 ServiceAccount:kubectl get sa -o yaml
│   │   └── 修复:更新 pull secret / 配置 IRSA(AWS) / RAM Role(阿里云)
│   ├── "dial tcp: i/o timeout" → 网络不通
│   │   ├── 从节点测试 registry 连通性
│   │   ├── 检查是否需要配置代理
│   │   └── 修复:配置 VPC Endpoint(如 ECR VPC Endpoint)/ 配置代理
│   ├── "toomanyrequests" → Registry Rate Limit
│   │   ├── Docker Hub 免费用户受限(6h 内 100 pulls)
│   │   └── 修复:使用 ECR/ACR 镜像缓存 / 配置镜像拉取凭据
│   └── Serverless K8s 老 Pod 重启后跨境源拉不到
│       ├── 老 Pod 在节点有镜像缓存 → "镜像可正常拉取"的错觉
│       ├── rollout restart 后新 Pod 分到新 virtual-kubelet → 无缓存
│       └── 修复:跨境源优先换 daocloud mirror / skopeo copy 到内网 ACR

关键命令:

bash
kubectl describe pod <pod> -n <ns> | grep -A10 Events
kubectl get pod <pod> -n <ns> -o jsonpath='{.spec.containers[*].image}'
kubectl get pod <pod> -n <ns> -o jsonpath='{.spec.imagePullSecrets}'
kubectl describe serviceaccount <sa> -n <ns>

常见原因:

  • 镜像 tag 拼写错误或不存在
  • ECR/ACR 凭据过期(kubelet 自动刷新但不一定及时)
  • Serverless K8s 跨境拉取 quay.io/gcr.io 超时
  • Docker Hub rate limit

修复方案:

  1. 使用 latest tag 的风险:建议用 Git commit SHA 作为 tag
  2. 生产环境配置 imagePullSecrets + IRSA/RAM Role 自动刷新
  3. 关键镜像提前 skopeo copy 到集群同 region 的私有仓库
  4. 配置 VPC Endpoint 消除 NAT Gateway 的跨 AZ 流量费和延迟

题目 3:Pod CrashLoopBackOff — 启动即崩溃排查#

故障现象: Pod 反复启动后立即崩溃,状态在 Running 和 CrashLoopBackOff 之间循环。describe 显示多次重启计数。

排查路径(决策树):

text
CrashLoopBackOff
├── kubectl logs <pod> --previous → 看上一次崩溃日志
│   ├── 日志为空 → 崩溃太快(进程还没来得及写日志就退出)
│   │   ├── 崩溃中的容器无法 exec 进入 → 用 ephemeral container 共享其命名空间排查
│   │   │   kubectl debug -it <pod> --image=busybox:1.36 --target=<container>
│   │   │   (ephemeral container 自 1.25 GA;--target 共享目标容器的 PID/网络 ns,
│   │   │     但如果目标已退出则看不到其进程,此时改用下面的 --copy-to)
│   │   ├── 复制一份 Pod 并把入口改成 sleep,进去看文件系统/环境变量/手动跑启动命令
│   │   │   kubectl debug <pod> --copy-to=debug --container=<container> -- sleep 1d
│   │   └── 检查 ENTRYPOINT/CMD 是否正确、配置/依赖是否就位
│   ├── 日志有堆栈/error → 应用级错误
│   │   ├── 分析错误信息
│   │   └── 修复代码配置
│   └── 日志被截断 → 查看全部 kubectl logs <pod> --tail=200
├── 查看退出码
│   ├── kubectl get pod -o jsonpath='{.status.containerStatuses[0].lastState.terminated.exitCode}'
│   ├── 退出码 0 → 程序主动退出(配置错误 / 任务完成即退出)
│   ├── 退出码 1 → 应用抛出未捕获异常
│   ├── 退出码 137 → SIGKILL,大概率 OOMKilled(转到 OOMKilled 题)
│   ├── 退出码 139 → SIGSEGV 段错误(C/C++ 扩展 bug)
│   └── 退出码 143 → SIGTERM 优雅终止超时(preStop 超时 / 关闭太慢)
└── 检查 readiness/liveness probe
    ├── liveness 失败 → 健康检查端口不对 / 路径不对 / 阈值太紧
    ├── initialDelaySeconds 太短 → 应用启动慢但 probe 已经开始探测
    └── 修复:调整探针配置;区分 liveness 和 readiness

关键命令:

bash
kubectl logs <pod> -n <ns> --previous --tail=200
kubectl get pod <pod> -n <ns> -o jsonpath='{.status.containerStatuses[*].lastState.terminated}'
kubectl describe pod <pod> -n <ns> | grep -A10 "Last State"
kubectl debug -it <pod> -n <ns> --image=busybox:1.36 --target=<container>

常见原因与修复:

  1. 配置文件缺失:ConfigMap/Secret 未挂载或 key 拼写错误 → 检查 volumeMount 和 volume 匹配
  2. 依赖服务未就绪:启动时需要连接数据库/Redis/Kafka → 添加 initContainer 等待依赖 / 业务层加 retry
  3. 权限不足:容器以非 root 运行但挂载了 root 拥有的目录 → 配置 fsGroup 或 initContainer 修改权限
  4. Java 应用:OOMKilled 常见于 -Xmx 接近 memory limit → limit 要大于 -Xmx 至少 20-30%;启用 -XX:+UseContainerSupport

题目 4:Pod OOMKilled — 内存溢出排查#

故障现象: 容器被杀死,describe 显示 Reason: OOMKilled,退出码 137。重启次数不断增加,但每次都在运行一段时间后被杀。

先厘清机制(面试高频追问):

  • OOMKilled 是内核干的,不是 K8s:容器内存用量触碰 cgroup 限制(cgroup v1 memory.limit_in_bytes / cgroup v2 memory.max)→ 内核 cgroup OOM killer 杀进程 → 收到 SIGKILL → 退出码 137(= 128 + 9)。kubelet 只是事后把 reason 标成 OOMKilled。
  • 与"节点级 OOM"区分:容器超自己的 limit → 只杀这个容器(cgroup OOM,reason=OOMKilled);节点整体内存耗尽 → 要么 kubelet 先行驱逐(reason=Evicted,见节点排障题),要么内核全局 OOM killer 按 oom_score_adj 挑受害者(见节点排障"内存压力"题)。
  • 退出码 137 不一定是 OOM:任何来源的 SIGKILL 都是 137,需结合 reason 字段确认。

排查路径(决策树):

text
OOMKilled
├── 确认是 cgroup OOM 而非其他 SIGKILL
│   ├── kubectl get pod <pod> -o jsonpath='{.status.containerStatuses[*].lastState.terminated.reason}'
│   │   → 必须是 OOMKilled(仅 exitCode=137 不够,可能是被 drain/preStop kill)
│   ├── kubectl describe pod | grep -A5 "Last State" → Reason: OOMKilled / Exit Code: 137
│   └── 节点级 OOM 看 Node 事件:kubectl get events --field-selector reason=SystemOOM
│       (kubelet oomWatcher 从内核日志抓到 OOM 后在 Node 上打 SystemOOM 事件,
│         注意没有 reason=OOMKilling 这个事件类型)
├── 分析内存使用模式
│   ├── kubectl top pod → 看当前内存
│   ├── Prometheus:container_memory_working_set_bytes → 看历史趋势
│   ├── 内存持续增长直到 OOM → 内存泄漏
│   │   ├── Java:-XX:+HeapDumpOnOutOfMemoryError 抓 heap dump
│   │   ├── Python:检查循环引用、C 扩展泄漏
│   │   ├── Go:pprof 分析 goroutine 泄漏 / 未关闭的 body
│   │   └── 修复:改代码 + 调高 limit 作为短期止血
│   ├── 内存在某个时间点突然飙升 → 流量洪峰 / 大对象操作
│   │   ├── 检查 HPA 配置 → 是否及时扩容
│   │   ├── 检查是否有批量查询 / 大数据导出操作
│   │   └── 修复:拆分大操作 / 加 pagination / 配置 HPA
│   └── 内存在启动时就接近 limit → limit 设置过低
│       ├── VPA Recommender 查看推荐值
│       └── 修复:调高 limit / 调整 -Xmx
└── 检查节点内存压力
    ├── kubectl top node → 确认节点内存状态
    ├── kubectl describe node | grep MemoryPressure
    └── 节点内存不足时 kubelet 可能主动驱逐 Pod

关键命令:

bash
kubectl describe pod <pod> -n <ns> | grep -A10 "Last State"
kubectl get pod <pod> -n <ns> -o jsonpath='{.status.containerStatuses[*].lastState.terminated.reason}'
kubectl top pod <pod> -n <ns>
kubectl get events -n <ns> --field-selector reason=SystemOOM   # 节点级 OOM(Node 对象上的事件)

根因分析与修复:

模式根因修复
内存持续增长内存泄漏修复代码(heap dump/pprof 定位)+ 短期调高 limit
突发飙升流量洪峰 / 大操作配置 HPA + 大操作拆分 + 限流
启动即 OOMlimit 过低参考 VPA 推荐值调整 limits

预防措施:

  1. 启用 VPA Recommender 模式(不自动更新,只给推荐)
  2. Java 应用用 -XX:+UseContainerSupport(JDK 11+)
  3. Java 应用 limit.memory > -Xmx * 1.3(留 30% 给 Native Memory + Metaspace)
  4. OOMKilled 告警规则:kube_pod_container_status_last_terminated_reason{reason="OOMKilled"} == 1

姊妹问题:CPU 超 limit 不会被杀,而是被"限流"(CFS throttling)

  • 内存超 limit → OOMKilled(杀);CPU 超 limit → 内核 CFS 限流(不杀,只是被强制降速),典型现象是 CPU 用量看着没打满 limit,但 P99 延迟周期性抖动、GC 停顿变长。
  • 排查:container_cpu_cfs_throttled_periods_total / container_cpu_cfs_periods_total(被限流的调度周期占比)> 0 且明显时即存在 throttling;也可 exec 进容器看 /sys/fs/cgroup/cpu.statnr_throttled/throttled_usec
  • 根因:CPU limit 设得过低,或多线程应用在极短窗口内瞬时打满配额(quota/period 被 100ms 周期切片放大)。
  • 修复:调高或干脆不设 CPU limit(延迟敏感服务常见做法,只保留 requests 保证调度份额);Java/Go 显式设 GOMAXPROCS/-XX:ActiveProcessorCount 与 limit 对齐,避免线程数远超可用核。

题目 5:Init Container 阻塞 — Init Container 未完成排查#

故障现象: Pod 状态显示 Init:0/2Init:Error,主容器未创建。describe 显示某个 initContainer 一直处于 Running 或 CrashLoopBackOff。

排查路径(决策树):

text
Init Container 阻塞
├── kubectl describe pod → 看 Init Containers 状态
│   ├── Init Container 卡在 Running(正常执行但未退出)
│   │   ├── kubectl logs <pod> -c <init-container> → 看输出
│   │   ├── 检查是否在等待外部资源(数据库/API)
│   │   └── 修复:加 timeout / 检查依赖服务可达性
│   ├── Init Container CrashLoopBackOff
│   │   ├── kubectl logs <pod> -c <init-container> --previous
│   │   ├── 检查 init 脚本逻辑 / 依赖服务是否就绪
│   │   └── 修复:修正 init 脚本 / 确保依赖先部署
│   └── Init Container Error / ImagePull
│       ├── 检查 init 容器镜像是否可拉取
│       └── 修复:转到 ImagePullBackOff 排查
└── 常见 Init Container 使用场景排查
    ├── 等待依赖服务:init 用 until curl/wait-for-it.sh 轮询
    │   └── 检查目标 service 和端口是否正确
    ├── 数据库迁移:init 跑 alembic/migrate
    │   └── 检查数据库连接配置、迁移脚本是否有误
    ├── 权限设置:init 执行 chown/chmod
    │   └── 检查 securityContext 和 volume 权限
    └── ConfigMap/Secret 预处理:init 生成配置文件
        └── 检查 ConfigMap 内容是否合法

关键命令:

bash
kubectl describe pod <pod> -n <ns> | grep -A20 "Init Containers"
kubectl logs <pod> -n <ns> -c <init-container-name>
kubectl logs <pod> -n <ns> -c <init-container-name> --previous

常见根因与修复:

  1. 依赖服务未部署:initContainer 在等待尚不存在的 Service → 先部署依赖,再部署此 Pod
  2. 数据库连接配置错误:迁移脚本连不上 DB → 检查 Secret 中 DB_HOST/DB_PASS
  3. 权限问题:非 root init container 操作 root 拥有的 mount → 设置 securityContext.fsGroup
  4. 无限等待:init 中没有设置 timeout → 加 set -e 和 timeout 机制

题目 6:Pod Evicted — 驱逐排查#

故障现象: Pod 状态变为 Evicted,或被自动驱逐后重建。describe 看到 The node was low on resource。多个 Pod 同时被驱逐,然后新 Pod 卡在 Pending。

排查路径(决策树):

text
Pod Evicted
├── 确认驱逐原因
│   ├── kubectl describe pod → 看 Status 和 Message
│   ├── kubectl get events -A --sort-by='.metadata.creationTimestamp' | grep Evicted
│   ├── 驱逐类型判断:
│   │   ├── "low on resource: ephemeral-storage" → 磁盘压力
│   │   ├── "low on resource: memory" → 内存压力
│   │   └── "low on resource: inodes" → inode 耗尽
├── 磁盘压力排查(ephemeral-storage)
│   ├── ssh 到节点查磁盘(现代集群运行时是 containerd,node 上没有 docker)
│   │   ├── df -h / df -i → 查看磁盘和 inode 使用率
│   │   ├── du -sh /var/log/pods/* | sort -rh | head -20
│   │   ├── du -sh /var/lib/containerd/* | sort -rh | head -10   # 老集群才看 /var/lib/docker
│   │   └── crictl rmi --prune(清无用镜像)/ crictl rmp -a(清已退出的 sandbox)
│   ├── kubectl describe node <node> | grep DiskPressure
│   └── 修复:清理日志和镜像 / 配置日志轮转(Fluentd/Filebeat / logrotate)
├── 内存压力排查
│   ├── kubectl top node
│   ├── kubectl describe node | grep MemoryPressure
│   └── 修复:降低 Pod requests / 扩节点 / 排查内存泄漏
└── 驱逐后的连锁反应(关键!驱逐是"强制删除",不走优雅关闭)
    ├── 可能触发 CNI IP 泄漏(Terway / Calico IPAM)→ 完整案例见【综合排障 · 题目 1】
    │   └── 一句话:强删跳过 preStop/回收钩子 → IP 未释放 → 后续 Pod 无 IP 可分 → Pending
    ├── 检查是否引发"调度风暴"(大量 Pod 同时被驱逐+重调度 → 瞬时资源压力)
    │   └── 修复:配置 PDB 限制同时不可用副本数
    └── 检查数据库/消息队列连接:强制驱逐不等 in-flight 处理 → 连接丢失 / 消息丢失

关键命令:

bash
kubectl get events -A --sort-by='.metadata.creationTimestamp' | grep -i evict
kubectl describe node <node> | grep -A10 Conditions
kubectl top node
kubectl get nodeeni -A     # Terway CRD 模式下检查 IP 状态

修复方案:

  1. 短期:清理磁盘 → crictl rmi --prune(containerd)→ ssh 到节点手动释放
  2. 中期
    • 配置 Prometheus 磁盘告警(> 80% 提前预警,不等 kubelet 的 90% 阈值)
    • 配置日志轮转和自动清理
    • 设置 PodDisruptionBudget 保护关键服务
  3. 长期
    • 升级 CNI 版本修复 IP 泄漏 bug
    • 添加 ENI IP 使用率监控(terway_node_available_ip < 3)
    • kubelet eviction 阈值调优 → 延迟驱逐但提前告警

子类 2:网络排障题 — Service 不通、DNS 解析失败的排查#

题目 1:Service 无法访问 — 从 Service 到 Pod 全链路排查#

故障现象: Pod 内通过 Service 名称访问下游服务报 connection refusedno route to host

排查路径(决策树):

text
Service 不通
├── 逐层排查(从下往上)
├── 第一层:Pod 自身
│   ├── kubectl get pod <pod> -o wide → 确认 Running + Ready
│   ├── kubectl exec <pod> -- curl http://localhost:<port>/health → 容器内自检
│   └── Pod 不 Ready → 检查 readiness probe / 应用启动日志
├── 第二层:同节点 Pod 间通信
│   ├── kubectl exec <debug-pod> -- curl http://<pod-ip>:<port> → 用 Pod IP 直连
│   └── 不通 → CNI 插件问题(Calico/Flannel/Cilium/Terway 故障)
├── 第三层:跨节点 Pod 间通信
│   ├── 在另一节点的 Pod 里 curl Pod IP
│   └── 不通 → CNI 跨节点路由问题 / 安全组 / VPC 路由表
├── 第四层:Service 访问
│   ├── kubectl exec <pod> -- curl http://<service-clusterip>:<port>
│   ├── 不通 → 检查 EndpointSlice / kube-proxy
│   └── kubectl get endpointslices -n <ns> -l kubernetes.io/service-name=<service>
│       (Endpoints API 自 1.33 已弃用,kube-proxy 实际消费的是 EndpointSlice;
│         kubectl get endpoints 仍可用但只是兼容视图)
│       ├── 无 ready 端点 → Service selector 不匹配 Pod labels,或 Pod 未 Ready
│       │   ├── kubectl get pod -n <ns> --show-labels
│       │   ├── kubectl describe svc <service> -n <ns> | grep Selector
│       │   └── 修复:对齐 selector 和 Pod labels / 修 readinessProbe
│       └── 有端点但仍不通 → kube-proxy 数据面规则问题(先确认 kube-proxy 模式)
│           ├── kubectl get cm kube-proxy -n kube-system -o yaml | grep mode
│           ├── iptables 模式:iptables -t nat -L KUBE-SERVICES -n | grep <clusterIP>
│           ├── IPVS 模式:ipvsadm -Ln | grep <clusterIP>(缺 ip_vs 等内核模块会静默失败)
│           ├── nftables 模式(1.33 GA,新集群渐成默认):nft list table ip kube-proxy
│           └── 修复:重启 kube-proxy DaemonSet / 补内核模块 / 核对模式配置
├── 第五层:DNS 解析
│   ├── kubectl exec <pod> -- nslookup <service>.<namespace>.svc.cluster.local
│   └── 不通 → 转到 DNS 排障题
└── 第六层:跨 namespace 访问
    ├── 确认用了完整 DNS 名:<service>.<namespace>.svc.cluster.local
    └── 确认 NetworkPolicy 未拦截跨 namespace 流量

关键命令:

bash
kubectl get endpointslices -n <ns> -l kubernetes.io/service-name=<service>
kubectl get endpoints <service> -n <ns>          # 兼容视图,1.33 起 Endpoints API 已弃用
kubectl exec <pod> -n <ns> -- curl -v http://<service>:<port>/healthz
kubectl describe svc <service> -n <ns>
kubectl get pod -n <ns> --show-labels

根因分析(按出现频率):

  1. EndpointSlice 无 ready 端点(selector 不匹配 / Pod 未 Ready)— 最常见
  2. targetPort 写错 — Service 的 targetPort 与容器实际监听端口不一致
  3. NetworkPolicy 拦截 — 默认拒绝所有时忘记放行(表现为 timeout 而非 refused)
  4. kube-proxy 模式问题 — IPVS 模式缺 ip_vs 内核模块 / nftables 模式与旧 iptables 规则残留冲突

补充:外部访问的源 IP(externalTrafficPolicy)

  • NodePort/LoadBalancer 默认 externalTrafficPolicy: Cluster:流量到任一节点后可能再转发到别的节点,途中做 SNAT → 后端看到的源 IP 是节点 IP,客户端真实 IP 丢失,但负载更均衡。
  • externalTrafficPolicy: Local:只转发给本节点上的 Pod、不跨节点、不 SNAT → 保留客户端源 IP;代价是本节点无该服务 Pod 时健康检查失败、流量可能不均。
  • 排查"拿不到真实客户端 IP / 健康检查异常"时,先看这个字段;云 LB 还需配合 healthCheckNodePort

题目 2:DNS 解析失败排查 — CoreDNS 问题深度分析#

故障现象: 应用启动时报 dial tcp: lookup svc-name: no such host,Pod 内 nslookup 解析 Service 名返回 NXDOMAIN。

排查路径(决策树):

text
DNS 解析失败
├── 第一步:确认 CoreDNS Pod 是否健康
│   ├── kubectl get pods -n kube-system -l k8s-app=kube-dns
│   ├── kubectl logs -n kube-system -l k8s-app=kube-dns --since=5m
│   └── CoreDNS OOMKilled → 调高 memory limit(大集群默认 170Mi 不够)
├── 第二步:查看故障 Pod 的 resolv.conf
│   ├── kubectl exec <pod> -- cat /etc/resolv.conf
│   │   输出示例:nameserver 10.96.0.10 / search production.svc.cluster.local svc.cluster.local cluster.local / options ndots:5
│   └── 确认 nameserver 指向 CoreDNS Service ClusterIP
├── 第三步:逐步测试 DNS 解析
│   ├── kubectl exec <pod> -- nslookup <service>
│   ├── kubectl exec <pod> -- nslookup <service>.<namespace>
│   ├── kubectl exec <pod> -- nslookup <service>.<namespace>.svc.cluster.local
│   └── kubectl exec <pod> -- nslookup <service>.<namespace>.svc.cluster.local 10.96.0.10
│       └── 直接指定 CoreDNS IP 绕过 resolv.conf 的 search 域
├── 第四步:区分域名类型
│   ├── 集群内域名不通 → CoreDNS 的 kubernetes 插件问题
│   │   └── 检查 CoreDNS ConfigMap 中 cluster.local 域配置
│   ├── 外部域名不通 → CoreDNS forward 上游问题
│   │   ├── 检查节点的 /etc/resolv.conf(CoreDNS 默认用节点 DNS 作上游)
│   │   └── 修复:在 Corefile 中指定可靠上游 DNS(如 8.8.8.8, 1.1.1.1)
│   └── 间歇性 NXDOMAIN → CoreDNS 缓存 / 副本问题
│       └── 修复:增加副本数(建议 3-4)+ 配置 Pod 反亲和性
└── 第五步:ndots:5 深度排查(见题目 4 综合排障)

关键命令:

bash
kubectl exec <pod> -n <ns> -- cat /etc/resolv.conf
kubectl exec <pod> -n <ns> -- nslookup <service>.<namespace>.svc.cluster.local
kubectl exec <pod> -n <ns> -- nslookup <service>.<namespace>.svc.cluster.local 10.96.0.10
kubectl get configmap coredns -n kube-system -o yaml

常见根因:

  1. 跨 namespace 访问没带命名空间my-service 应写成 my-service.other-namespace
  2. CoreDNS OOMKilled:大集群缓存增长超出默认 170Mi limit → 调高到 512Mi
  3. 上游 DNS 故障:节点 /etc/resolv.conf 指向的 DNS 不稳定 → 在 Corefile 中直接指定可靠上游
  4. CoreDNS 副本不足:2 副本在高并发下无法承载 → 3-4 副本 + HPA

题目 3:DNS 解析 5 秒延迟 — conntrack 竞态条件#

故障现象: 应用间歇性出现 DNS 解析需要 5 秒才返回,日志显示 i/o timeout 或请求超时。问题复现率低,但一旦出现影响严重。

排查路径(决策树):

text
DNS 5 秒延迟
├── 验证是否 conntrack 竞态
│   ├── tcpdump 抓包分析
│   │   ├── kubectl exec <pod> -- tcpdump -i any -nn port 53
│   │   ├── 触发高并发 DNS 查询:for i in $(seq 1 100); do nslookup api.example.com &; done
│   │   └── 观察抓包:正常 `query → response (3ms)` / 异常 `query → 5s 后重传 → response`
│   └── 确认并发 DNS 场景存在
├── 根因:Linux conntrack 竞态条件
│   ├── CoreDNS Service ClusterIP 经 DNAT 转换
│   ├── 两个 UDP DNS 包同时到达 → conntrack 表插入冲突
│   ├── 一个包被丢弃 → UDP 无重传 → 等 5 秒超时
│   └── 影响:ndots:5 时每个查询 4 次 UDP → 并发场景入口翻 4 倍
├── 修复方案优先级
│   ├── 方案0(生产首选、根治):部署 NodeLocal DNSCache
│   │   ├── 每节点一个 DaemonSet 缓存代理,Pod 通过 link-local 地址(默认 169.254.20.10)访问本机缓存
│   │   ├── 本机命中直接返回、不过 DNAT/conntrack → 从源头消除竞态;miss 时以 TCP 回源 CoreDNS
│   │   └── 附带收益:大幅降低 CoreDNS QPS 与跨节点 DNS 流量
│   ├── 方案1:dnsConfig 加 single-request-reopen
│   │   ├── glibc 让 A/AAAA 用不同 socket(不同源端口)查询 → 避开同 5 元组竞态
│   │   ├── 注意:只对 glibc 有效,Alpine/musl 无此选项(musl 本就并发查询,需换 glibc 基础镜像或用方案0)
│   │   └── Pod spec: dnsConfig.options: [{name: single-request-reopen}, {name: ndots, value: "2"}]
│   ├── 方案2:CoreDNS 用 TCP
│   │   ├── Corefile forward 中加 force_tcp
│   │   └── 完全避免 UDP 竞态,代价是 TCP 三次握手开销
│   ├── 方案3:调 conntrack 参数(节点级、缓解非根治)
│   │   └── sysctl net.netfilter.nf_conntrack_udp_timeout / 增大 nf_conntrack_max(新内核已修复部分插入竞态)
│   └── 方案4:调整 ndots → ndots:2 减少 search 域放大倍数(配合方案0/1)

关键命令:

bash
# 在 Pod 中抓包
kubectl exec <pod> -n <ns> -- tcpdump -i any -nn port 53 -c 100

# 复现测试
kubectl exec <pod> -n <ns> -- sh -c 'for i in $(seq 1 100); do nslookup api.example.com &; done; wait'

# 方案1 配置(Pod spec)
spec:
  dnsConfig:
    options:
      - name: single-request-reopen
      - name: ndots
        value: "2"
      - name: timeout
        value: "5"
      - name: attempts
        value: "2"

根因分析: 这个问题的本质是:CoreDNS 使用单个 DNS Service ClusterIP,所有 Pod 的 UDP DNS 请求都经过相同的 DNAT 转化。当同一 Pod 或同一节点上的多个 Pod 同时发出 DNS 查询时,conntrack 的 UDP 连接跟踪表可能出现插入竞争。


题目 4:Ingress 路由不生效排查#

故障现象: 配置了 Ingress/HTTPRoute,但外部请求返回 404 或连接被拒绝,流量未到达目标 Service。

排查路径(决策树):

text
Ingress 不生效
├── 第一层:确认 Ingress Controller 运行正常
│   ├── kubectl get pods -n ingress-nginx / istio-system
│   ├── kubectl logs -n ingress-nginx <ingress-controller-pod> --tail=100
│   └── Controller Pod 不 Ready → 排查 Pod 异常(转到 Pod 排障题)
├── 第二层:确认 Ingress 资源状态
│   ├── kubectl describe ingress <name> -n <ns>
│   ├── kubectl get ingress <name> -n <ns> -o yaml
│   ├── 检查 Address 字段是否有 LB 地址(为空 = Controller 未处理此 Ingress)
│   ├── 检查 Events 中是否有错误
│   └── 常见问题:
│       ├── Ingress class 不匹配:ingressClassName 为空或写错
│       ├── TLS secret 不存在或 namespace 不一致
│       └── host 规则不匹配请求的 Host header
├── 第三层:确认后端 Service 可达
│   ├── kubectl get svc <backend-service> -n <ns>
│   ├── kubectl get endpoints <backend-service> -n <ns> → 是否为空
│   ├── 检查 serviceName 和 servicePort 是否与 Ingress 声明一致
│   └── Endpoints 为空 → Service selector 不匹配
├── 第四层:确认 DNS 解析
│   ├── dig @8.8.8.8 <domain> → 确认公网 DNS 解析到 LB
│   ├── 检查是否 wildcard DNS 兜底导致打到错误集群
│   │   └── dig @8.8.8.8 +short <同环境已知域名> vs <新增域名> 比较 IP
│   └── 新增域名前必须验证 wildcard 指向
├── 第五层:HTTPRoute 特殊排查(Gateway API)
│   ├── Gateway 资源是否就绪(status.addresses 有值)
│   ├── HTTPRoute 的 parentRefs 是否正确引用 Gateway
│   └── 注意:HTTPRoute(南北流量)和 Istio VS(东西流量)是两套独立路由
└── 第六层:NetworkPolicy 和 TLS
    ├── NetworkPolicy 是否拦截了 Ingress Controller 到后端 Pod 的流量
    └── TLS 证书是否已过期

关键命令:

bash
kubectl describe ingress <name> -n <ns>
kubectl get ingress -A
kubectl get svc,endpoints -n <ns>
kubectl logs -n ingress-nginx deploy/ingress-nginx-controller --tail=100
dig @8.8.8.8 <domain>

根因分析(按频率):

  1. Ingress class 不匹配(40%)→ 检查 ingressClassName
  2. 后端 Service selector 不匹配 → Endpoints 为空
  3. TLS secret 不存在或过期 → 检查 secret 的 namespace
  4. DNS wildcard 误导向 → dig 验证
  5. HTTPRoute 和 Gateway 引用链断裂(Gateway API 场景)

题目 5:NetworkPolicy 误拦截排查#

故障现象: 服务之间突然不通,但 Pod 和 Service 都正常。curl 超时不返回任何响应(不是 connection refused,是一直卡住)。

排查路径(决策树):

text
怀疑 NetworkPolicy 拦截
├── kubectl get networkpolicy -n <ns> → 列出所有策略
├── kubectl describe networkpolicy <policy> -n <ns>
│   ├── 检查 podSelector → 哪些 Pod 受此策略影响
│   ├── 检查 ingress/egress 规则 → 允许/拒绝哪些流量
│   └── 注意:NetworkPolicy 默认拒绝所有(如果有一条 policy 匹配 Pod,则只允许 policy 声明的流量)
├── 常见误拦截场景
│   ├── 新增 Policy 选择所有 Pod(podSelector: {}),意外拦截基础设施流量
│   │   └── 修复:显式允许 kube-system / monitoring 命名空间的流量
│   ├── namespaceSelector 规则写反
│   │   └── 修复:确认目标 namespace 确实有匹配的 label
│   ├── 只允许了 ingress 但 egress 默认拒绝,DNS 查询被拦截(UDP 53)
│   │   └── 修复:加 egress 规则允许 UDP 53 到 kube-system
│   └── Istio sidecar + NetworkPolicy 叠加
│       └── 修复:确保 NetworkPolicy 允许 Pod 到 sidecar(15006/15001)和 sidecar 到 Pod 的流量
├── 诊断方法
│   ├── 临时删除 NetworkPolicy 验证:kubectl delete networkpolicy <name> -n <ns>
│   ├── 在 Pod 中测试目标端口(区分 timeout vs connection refused)
│   │   └── timeout = NetworkPolicy DROP,refused = 端口未监听
│   └── 使用 netshoot 调试 Pod:kubectl run tmp --rm -it --image=nicolaka/netshoot -- /bin/bash

关键命令:

bash
kubectl get networkpolicy -A
kubectl describe networkpolicy <name> -n <ns>
kubectl exec <pod> -n <ns> -- timeout 3 bash -c 'echo > /dev/tcp/<target-ip>/<port> && echo "OK" || echo "FAIL"'

题目 6:Istio Sidecar 导致服务不通#

故障现象: 启用了 Istio mesh 后,服务之间出现 503(no healthy upstream)、连接超时、或间歇性不通。

排查路径(决策树):

text
Istio Sidecar 问题
├── 确认 sidecar 是否注入
│   ├── kubectl get pod <pod> -o jsonpath='{.spec.containers[*].name}' → 应有 istio-proxy
│   ├── sidecar 未注入 → 检查 namespace label:istio-injection=enabled
│   └── sidecar 注入但异常 → kubectl logs <pod> -c istio-proxy
├── 503 UC(Upstream Connection Failure)排查
│   ├── 目标服务未就绪或 mTLS 不匹配
│   │   ├── kubectl exec <client-pod> -c istio-proxy -- pilot-agent request GET /clusters | grep outbound
│   │   └── 看不到目标 cluster → Sidecar egress 未配置
│   ├── excludeOutboundPorts 导致流量绕过 envoy
│   │   ├── 检查 Deployment annotation:traffic.sidecar.istio.io/excludeOutboundPorts
│   │   └── 排除的端口走不了 mesh 路由 → 删除 annotation 或加 ServiceEntry
│   ├── mTLS STRICT 但目标未注入 sidecar
│   │   └── 修复:给目标服务注入 sidecar 或改用 PERMISSIVE 模式
│   └── DNS 代理导致外部域名被解析为假 IP(240.240.0.x)
│       ├── mesh 启用后 ISTIO_META_DNS_CAPTURE=true
│       ├── 配合 excludeOutboundPorts → DNS 返回假 IP 但流量绕过了 sidecar → 不可达
│       └── 修复:excludeOutboundPorts 对应的端口加 ServiceEntry
├── 验证路由是否生效
│   ├── 必须从 business 容器 curl,不能从 istio-proxy 容器
│   │   └── istio-proxy 以 uid=1337 运行,iptables 规则跳过此 uid,curl 直接走 kube-proxy
│   ├── kubectl exec <client-pod> -c <app-container> -- curl <target-svc>
│   └── 查 Istio 访问日志:upstream_host IP → kubectl get pods -o wide | grep <ip> 反查 Pod
└── 故障预防
    ├── 启用 mesh 前:扫所有 workload 的 excludeOutboundPorts
    ├── 启用 mesh 后:至少观察 60 分钟(覆盖 Kafka metadata refresh 周期)
    └── 基础 Service selector 不能加 version 标签限制(会破坏 DR subset 路由)

子类 3:节点排障题 — 节点异常的排查#

题目 1:节点 NotReady — kubelet 异常排查#

故障现象: kubectl get nodes 显示节点状态为 NotReady。该节点上的 Pod 全部处于 Unknown 或 Terminating 状态。

排查路径(决策树):

text
节点 NotReady
├── kubectl describe node <node> → 检查 Conditions
│   ├── MemoryPressure: True → 内存不足(转到本子类 题目 4)
│   ├── DiskPressure: True → 磁盘不足(转到本子类 题目 2)
│   ├── PIDPressure: True → PID 耗尽(转到本子类 题目 5)
│   ├── NetworkUnavailable: True → CNI 网络插件未就绪(DaemonSet 未 Ready)
│   └── Ready: False → 看 Reason,最常见两类:
│       ├── "kubelet stopped posting node status" → kubelet 挂了/断连
│       └── "PLEG is not healthy" → 运行时卡死(见下方 PLEG)
├── 检查 kubelet 状态
│   ├── ssh 到节点:systemctl status kubelet
│   ├── journalctl -u kubelet -f --since "10 min ago" → 查看日志
│   ├── 常见问题:
│   │   ├── kubelet 进程挂掉 → systemctl restart kubelet
│   │   ├── kubelet 证书过期 → openssl x509 -in /var/lib/kubelet/pki/kubelet.crt -noout -dates(转到 题目 3)
│   │   ├── kubelet 磁盘满 → 无法写入日志/checkpoint → df -h 检查
│   │   └── kubelet 与 API Server 通信断开 → 检查网络 / DNS / 防火墙 / kube-apiserver 是否可达
├── PLEG(Pod Lifecycle Event Generator)不健康
│   ├── 日志出现 "PLEG is not healthy: pleg was last seen active Xm ago"
│   ├── 根因:kubelet 周期 relist 容器超时 → 通常是容器运行时(containerd)卡死、
│   │   节点 IO 打满、僵尸容器过多、镜像层损坏
│   └── 修复:先查运行时(crictl 是否响应)→ 必要时重启 containerd → 再重启 kubelet
└── 检查容器运行时(现代集群默认 containerd,dockershim 自 1.24 已移除)
    ├── systemctl status containerd     # 老集群才是 docker
    ├── crictl ps / crictl info → 确认运行时正常工作(crictl 才是排障工具,node 上没有 docker)
    └── 运行时挂了 → kubelet 无法管理 Pod → 修复运行时后重启 kubelet

节点 NotReady 后 Pod 的命运(面试常问的"5 分钟宽限"):

  • kubelet 停止上报状态,超过 --node-monitor-grace-period(默认约 40s)后 node-controller 把节点标记为 NotReady;
  • 随即给节点打上 node.kubernetes.io/not-ready:NoExecute(失联时是 unreachable:NoExecute)污点;
  • Pod 默认带有对这两个污点 tolerationSeconds: 300 的容忍(default-toleration-seconds admission 注入)→ 节点异常 5 分钟内不会驱逐 Pod,给节点自愈留窗口;超过 5 分钟才在其他节点重建;
  • 这也是"节点抖动后 Pod 不会立刻迁移"的原因,对延迟敏感服务可调小 tolerationSeconds。

关键命令:

bash
kubectl describe node <node> | grep -A10 Conditions
kubectl get node <node> -o jsonpath='{.status.conditions}' | jq
systemctl status kubelet
journalctl -u kubelet --no-pager -n 100
systemctl status containerd

Conditions 解读速查表:

ConditionStatus含义处理
MemoryPressureTrue节点内存不足清理内存 / 添加节点
DiskPressureTrue磁盘空间不足清理磁盘 / 扩展卷
PIDPressureTruePID 耗尽排查进程泄漏 / 调高 pid_max
NetworkUnavailableTrueCNI 未初始化检查 CNI DaemonSet / 网络插件
ReadyFalse节点不可调度根据 Reason 具体排查

处置动作:确认节点有问题后先隔离,再修复/换机(cordon / drain)

bash
kubectl cordon <node>                       # 只标记不可调度,不动现有 Pod(先止血,防新 Pod 进来)
kubectl drain <node> --ignore-daemonsets --delete-emptydir-data --grace-period=120
#   drain = cordon + 优雅驱逐 Pod(遵守 PDB,逐个迁走);修复无望时用于安全下线
kubectl uncordon <node>                      # 修复后恢复调度
  • cordon 与 not-ready 污点不同:cordon 只加 Unschedulable(不驱逐已有 Pod),not-ready 是 NoExecute(会按 5 分钟宽限驱逐)。
  • drain 卡住多半是 PDB 不允许再驱逐、或有裸 Pod / emptyDir 数据 → 按提示加对应 flag 或先处理 PDB。

题目 2:磁盘写满导致驱逐 — DiskPressure 全链路排查#

故障现象: 节点显示 DiskPressure: True,Pod 被大量驱逐。事件中出现 Evicted: The node was low on resource: ephemeral-storage

排查路径(决策树):

text
DiskPressure
├── 确认磁盘使用情况
│   ├── ssh 到节点:df -h(空间)+ df -i(inode,inode 耗尽也会 DiskPressure)
│   ├── du -sh /var/log/pods/* 2>/dev/null | sort -rh | head -20
│   ├── du -sh /var/lib/containerd/* 2>/dev/null | sort -rh | head -10  # 老集群才是 /var/lib/docker
│   └── du -sh /var/lib/kubelet/* 2>/dev/null | sort -rh | head -10
├── 确定根因
│   ├── /var/log/pods 暴涨 → 某个 Pod 日志输出过大
│   │   └── 修复:限制 Pod stdout 输出 / 配置 kubelet containerLogMaxSize+MaxFiles / EFK|Loki 收集
│   ├── /var/lib/containerd 过大 → 镜像层缓存 / overlayfs snapshot 膨胀
│   │   └── 修复:crictl rmi --prune;确认 kubelet 镜像 GC(imageGCHighThresholdPercent)是否失效
│   ├── /var/lib/kubelet 中 emptyDir 未清理 → Pod 使用了 emptyDir 且数据量大
│   │   └── 修复:给 emptyDir 设 sizeLimit / 排查用法
│   └── 系统日志 /var/log/syslog 或 /var/log/messages 过大
│       └── 修复:配置系统 logrotate
├── 短期清理命令(containerd 集群用 crictl,node 上没有 docker)
│   ├── crictl rmi --prune           # 删除无引用镜像
│   ├── crictl rmp -a                # 删除已退出的 sandbox(Pod)
│   └── 清理 /var/log/pods 中旧 Pod 日志(注意不要删运行中 Pod 的日志)
└── 驱逐连锁反应排查(重要!)
    └── DiskPressure 驱逐是强制删除(不等 preStop),可能连锁引发 CNI IP 泄漏 /
        调度风暴 / DB 连接异常断开 —— 完整链路见【综合排障 · 题目 1:Terway IP 泄漏】

修复方案:

  1. 短期:清磁盘 + 手动释放泄漏 IP(云厂商 API)
  2. 中期:Prometheus 告警 >80% 使用率提前预警;配置日志轮转
  3. 长期:配置 kubelet 日志最大文件数 / 镜像垃圾回收策略 / emptyDir sizeLimit

题目 3:kubelet 证书过期排查#

故障现象: 节点显示 NotReady,kubelet 日志报 certificate has expiredx509: certificate has expired

排查路径:

bash
# 1. 检查证书有效期
ssh <node>
openssl x509 -in /var/lib/kubelet/pki/kubelet.crt -noout -dates
# 输出示例:notAfter=Jan 15 12:00:00 2026 GMT (已过期)

# 2. 检查 kubelet 日志
journalctl -u kubelet --no-pager -n 50 | grep -i "cert\|expire\|tls"

# 3. 手动轮转证书(kubeadm 部署)
# 检查证书
kubeadm certs check-expiration
# 轮转所有证书
kubeadm certs renew all
# 重启 kubelet
systemctl restart kubelet

# 4. 如果是 managed K8s(如 ACK/EKS)→ 证书由云厂商自动管理
# 检查 kube-controller-manager 的 cluster-signing-cert-file 和 cluster-signing-key-file

常见原因:

  1. kubeadm 部署的集群,kubelet 证书默认 1 年有效期
  2. 手动轮转只 renew 了 API Server 证书,忘了 kubelet 证书
  3. 时钟不同步导致证书被认为过期(NTP 问题)

修复方案:

  1. kubeadm 集群:kubeadm certs renew all && systemctl restart kubelet
  2. 配置自动轮转:开启 kubelet 的 --rotate-certificates + controller manager 的自动批准
  3. 添加证书过期监控:ssl_certificate_expiry_seconds < 604800(7天)

题目 4:内存压力导致 Pod 被杀#

故障现象: 节点 MemoryPressure: True,Pod 被杀(reason 可能是 Evicted 或 OOMKilled)。top 显示节点内存使用接近 100%。

先分清两条杀 Pod 的路径(面试重点,别混为一谈):

  • kubelet 驱逐(软/硬 eviction,reason=Evicted):内存跌破 --eviction-hard(默认 memory.available<100Mi)阈值时,kubelet 主动、优雅地驱逐 Pod 腾内存。排序:先驱逐"用量超过自己 requests 最多"的 Pod,再按 Pod Priority,BestEffort/超卖的 Burstable 通常最先中招。
  • 内核全局 OOM killer(reason=OOMKilled):内存耗尽速度快过 kubelet 反应时,内核直接开杀,oom_score_adj 挑受害者,与 kubelet 驱逐排序不完全一致。

排查路径(决策树):

text
MemoryPressure + Pod 被杀
├── kubectl describe node <node> | grep -A5 "Allocated resources"
├── kubectl top node → 查看实际内存使用
├── kubectl top pod -A --sort-by=memory → 找出内存消耗最大的 Pod
├── 区分是 kubelet 驱逐还是内核 OOM(describe pod 看 reason:Evicted vs OOMKilled)
├── 根因分析
│   ├── Pod requests 设置过低 → 调度器超卖节点(requests 之和 < 实际用量)
│   │   └── 修复:调高 requests 至接近实际用量(参考 VPA 推荐)
│   ├── 某个 Pod 内存泄漏 → 持续增长
│   │   └── 修复:修泄漏 + 临时调高 limit
│   ├── Limit 未设置 → 无上限 Pod 吃光节点内存
│   │   └── 修复:加 LimitRange 兜底 + ResourceQuota 限总量
│   └── 节点规格不足 → 提升节点规格 / 扩节点
└── 内核 OOM killer 的挑选逻辑(按 oom_score_adj,越大越先被杀)
    ├── BestEffort(无任何 request/limit)      → oom_score_adj = 1000(最先杀)
    ├── Burstable(设了 request/limit 但 req≠limit)→ oom_score_adj ∈ [2, 999],内存 request 越大越难被杀
    ├── Guaranteed(每个容器 CPU+内存 request==limit)→ oom_score_adj = -997(最后杀)
    └── 修复:关键服务设为 Guaranteed QoS;给关键 Pod 设高 PriorityClass 降低被驱逐概率

QoS 定义纠偏:Burstable 不是"有 request 无 limit",而是"至少设了一项 request/limit 但没达到 Guaranteed 标准";Guaranteed 要求每个容器的 CPU 和内存都 request==limit。


题目 5:PID 耗尽排查#

故障现象: 节点 PIDPressure: True,新进程无法 fork。kubelet 日志报 fork/exec: resource temporarily unavailable

排查路径:

bash
# 1. 查看节点 PID 使用情况
ssh <node>
cat /proc/sys/kernel/pid_max
# 默认 32768 或 4194304
ps aux | wc -l

# 2. 找出 PID 消耗大户
ps aux --sort=-pid | head -20

# 3. 容器内 PID 消耗
for p in $(crictl pods -q); do
  crictl inspectp $p | jq '.status.linux.namespaces.options.pid'
done

常见原因与修复:

  1. 进程 fork 炸弹:代码 bug 导致无限 fork → 修复代码 + 设置 Pod PID limit
  2. 僵尸进程堆积:父进程未回收子进程 → init 进程需正确处理 SIGCHLD
  3. Pod PID 限制过低:kubelet 默认未限制 Pod PID → 配置 --pod-max-pids

子类 4:存储排障题 — PV/PVC/挂载相关排查#

题目 1:PVC Pending — 无法绑定 StorageClass#

故障现象: PVC 状态为 Pending,describe 显示 waiting for a volume to be createdno persistent volumes available

排查路径(决策树):

text
PVC Pending
├── kubectl describe pvc <pvc> -n <ns> → 查看 Events
│   ├── "storageclass.storage.k8s.io <name> not found"
│   │   → 检查 StorageClass 是否存在:kubectl get sc
│   │   → 修复:创建 StorageClass 或修正 PVC 上的 storageClassName
│   ├── "failed to provision volume" → Provisioner 创建失败
│   │   → kubectl logs -n kube-system deploy/ebs-csi-controller(以 AWS EBS 为例)
│   │   → 常见原因:IAM 权限不足 / 云配额耗尽 / AZ 不匹配
│   ├── "no persistent volumes available" → 静态 PV 不匹配
│   │   → kubectl get pv → 检查已有 PV 的 storageClassName / accessMode / capacity
│   │   → 修复:创建匹配的 PV 或改用动态 provisioning
│   └── "waiting for first consumer to be created before binding"
│       → StorageClass 的 volumeBindingMode: WaitForFirstConsumer(正常行为)
│       → 只有在 Pod 使用 PVC 时才会触发绑定和创建
└── 检查 PV 绑定约束
    ├── accessModes 是否匹配(RWO/RWX/ROX)
    ├── storage 容量是否满足(PV capacity < PVC request → 不匹配)
    └── nodeAffinity(静态 PV 绑定了特定节点)

关键命令:

bash
kubectl get pvc -n <ns>
kubectl describe pvc <pvc> -n <ns>
kubectl get sc
kubectl get pv

题目 2:Pod 挂载失败 — Multi-Attach 错误#

故障现象: Pod 无法启动,Events 显示 Multi-Attach error for volumeVolume is already used by pod(s),特别是使用 RWO (ReadWriteOnce) PV 时。

排查路径(决策树):

text
Pod 挂载失败
├── Multi-Attach Error(RWO PV 核心问题)
│   ├── kubectl describe pod <pod> → 查看 Volume 挂载错误
│   ├── PV 的 accessModes 是 RWO(只能同时挂载到一个节点)
│   ├── 旧 Pod 还在 Terminating → PV 未释放
│   │   └── kubectl get pods -A -o wide | grep <node> → 找旧 Pod
│   └── 修复:
│       ├── 等待旧 Pod 完全终止(包括 detach volume)
│       ├── 强制删除旧 Pod:kubectl delete pod <old-pod> --force --grace-period=0
│       └── 或改用 RWX 存储(如 EFS/NFS)
├── Mount 权限错误
│   ├── Permission denied → Pod securityContext 与目录权限不匹配
│   │   └── 修复:Pod spec 加 securityContext.fsGroup 匹配存储权限
│   └── 或加 initContainer chown/chmod
├── NFS/EFS mount timeout
│   ├── EFS security group 未放行 NFS 端口(2049)
│   │   └── 修复:检查并更新安全组规则
│   ├── EFS mount target 不在同一 VPC/子网
│   └── DNS 解析 EFS endpoint 失败
├── CSI Driver 问题
│   ├── kubectl get pods -n kube-system | grep csi → CSI Controller / Node Pod 是否 Running
│   ├── kubectl logs -n kube-system deploy/ebs-csi-controller --tail=100
│   └── CSI Node 未运行 → DaemonSet 调度失败
└── 节点 Attach/Detach 限制
    ├── AWS EC2 每实例最多挂载 28 个 EBS(包括 root)
    └── 修复:减少 EBS 挂载 / 使用更大实例

题目 3:存储扩容失败#

故障现象: 修改 PVC 的 spec.resources.requests.storage 后,PVC 显示 ResizingFileSystemResizePending,一直未完成。

排查路径:

bash
# 1. 检查 PVC 状态
kubectl get pvc -n <ns>
kubectl describe pvc <pvc> -n <ns>

# 2. 检查 StorageClass 是否允许扩容
kubectl get sc <sc-name> -o yaml | grep allowVolumeExpansion
# 必须是 true

# 3. 扩容步骤要求
# - 步骤1:PVC spec.resources.requests.storage 已更新 → Status 显示 Resizing
# - 步骤2:PV 已扩容(云厂商侧已调整)→ 但 filesystem 未 resize
#    → 需要 Pod 重启或在线 resize(取决于 CSI driver 和 FS 类型)
# - 步骤3:Pod 重启后 CSI Node Driver 执行 filesystem resize

# 4. 如果 Pod 使用了此 PVC 的子路径(subPath)
# → subPath 不支持在线扩容 → 必须重建 Pod

常见根因:

  1. StorageClass allowVolumeExpansion: false → 不支持动态扩容
  2. 云资源配额耗尽 → EBS/PVC 数量达到上限
  3. Pod 使用了 subPath 挂载 → 在线扩容不适用
  4. CSI Driver 不支持特定文件系统的在线扩容

题目 4:数据丢失风险 — PVC 孤儿与 StatefulSet 删除#

故障现象: 删除了 Deployment/StatefulSet 后,PVC 也被删除了(数据丢失)。或 PVC 未被删除但长期无人知晓(产生浪费)。

排查路径:

bash
# 1. 查找孤儿 PVC(无 Pod 使用)
kubectl get pvc -A -o json | jq -r '
  .items[] | select(.status.phase == "Bound") |
  .metadata.namespace as $ns | .metadata.name as $name |
  "\($ns)/\($name)"
' | while read ns_pvc; do
  ns=$(echo "$ns_pvc" | cut -d/ -f1)
  pvc=$(echo "$ns_pvc" | cut -d/ -f2)
  count=$(kubectl get pods -n "$ns" -o json | jq --arg pvc "$pvc" \
    '[.items[].spec.volumes[]? | select(.persistentVolumeClaim.claimName == $pvc)] | length')
  [ "$count" -eq 0 ] && echo "ORPHAN: $ns/$pvc"
done

# 2. StatefulSet 删除 PVC 策略
kubectl get statefulset <name> -n <ns> -o jsonpath='{.spec.persistentVolumeClaimRetentionPolicy}'
# 检查 WhenDeleted 是 Retain(保留)还是 Delete(删除)

修复方案:

  1. StatefulSet 设置 persistentVolumeClaimRetentionPolicy.whenDeleted: Retain(数据安全)
  2. 定期跑孤儿 PVC 巡检脚本(CronJob),输出报告人工确认后删除
  3. 对有状态服务操作前,先确认存储后端(filesystem vs 对象存储)
  4. kubectl delete statefulset --cascade=orphan 保留 PVC(旧版本默认行为)

子类 5:安全排障题 — RBAC/权限/安全策略排查#

题目 1:ServiceAccount 权限不足排查#

故障现象: Pod 或外部客户端调用 K8s API 报 403 Forbidden,日志显示 User "system:serviceaccount:<ns>:<sa>" cannot <verb> <resource>

排查路径(决策树):

text
RBAC 权限不足
├── 确认错误详情
│   ├── kubectl logs <pod> → 找到完整错误信息
│   ├── 记录:SA 名、namespace、操作动词(verb)、API 资源、资源名
│   └── 关键信息:cannot get/list/create/update/delete/watch <resource>
├── 分析权限链路
│   │
│   │   ServiceAccount → RoleBinding/ClusterRoleBinding → Role/ClusterRole
│   │
│   ├── kubectl get sa <sa> -n <ns> -o yaml → 确认 SA 存在
│   ├── kubectl get rolebinding,clusterrolebinding -A -o json |
│   │   jq '.items[] | select(.subjects[]?.name=="<sa>" and .subjects[]?.namespace=="<ns>")'
│   │   → 找到绑定了此 SA 的 RoleBinding/ClusterRoleBinding
│   ├── kubectl describe role <role> -n <ns> / kubectl describe clusterrole <role>
│   │   → 查看规则是否包含所需 verb + resource
│   └── 缺少权限 → 创建/修改 Role/ClusterRole + RoleBinding/ClusterRoleBinding
└── 常见权限缺口
    ├── Pod 需要读 ConfigMap → 缺少 "get" configmaps
    ├── ArgoCD SA 缺少对目标 namespace 的 create/update 权限
    ├── CI/CD SA 需要 create pods
    ├── Prometheus 需要 list/get/watch pods,services,endpoints(跨所有 namespace)
    └── Ingress Controller 需要 get/watch secrets(TLS)

修复示例:

yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: production
  name: pod-reader
rules:
- apiGroups: [""]
  resources: ["pods", "pods/log"]
  verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  namespace: production
  name: read-pods
subjects:
- kind: ServiceAccount
  name: my-sa
  namespace: production
roleRef:
  kind: Role
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io

题目 2:RBAC 排障实战 — kubectl auth can-i#

故障现象: 执行 kubectl 命令被拒绝,需要快速验证 SA 是否有某操作的权限。

排查命令:

bash
# 1. 验证当前用户(或指定 SA)能否执行某个操作
kubectl auth can-i get pods -n production
kubectl auth can-i create deployments -n production --as system:serviceaccount:production:deployer

# 2. 列出当前用户在某个 namespace 的所有权限
kubectl auth can-i --list -n production

# 3. 查看 RBAC 关系
kubectl get rolebindings,clusterrolebindings -A -o json | \
  jq '.items[] | {name: .metadata.name, namespace: .metadata.namespace, roleRef: .roleRef, subjects: .subjects}'

# 4. 从资源反向查谁有权限(需要 kubectl-who-can 插件)
kubectl who-can delete pods -n production

题目 3:NetworkPolicy 导致服务不通 — CRD 策略与丢包观测#

故障现象: 新增 NetworkPolicy 后服务调用中断,表现为 connection timeout(非 refused),流量被 DROP。

基础排查(timeout vs refused 判断、临时删策略验证、egress 忘放行 DNS、podSelector:{} 误伤、netshoot 测试)与【网络排障 · 题目 5:NetworkPolicy 误拦截】完全一致,此处不重复。本题只补充"原生 NetworkPolicy 之外"的两个安全向排查点。

补充点 1:别只看原生 NetworkPolicy,还有 CNI 自己的 CRD 策略

原生 networkpolicy 干净但功能弱;Calico/Cilium 有增强 CRD,且优先级/顺序可能覆盖原生策略,只看 kubectl get networkpolicy 会漏:

bash
kubectl get networkpolicy -A                          # K8s 原生
kubectl get networkpolicies.crd.projectcalico.org -A  # Calico namespaced
kubectl get globalnetworkpolicies.crd.projectcalico.org  # Calico 全局(易被忽略!)
kubectl get ciliumnetworkpolicies,ciliumclusterwidenetworkpolicies -A  # Cilium

补充点 2:直接观测"哪条规则丢了包",别靠猜

bash
# Cilium:Hubble 实时看被策略 DROP 的流(最直观)
hubble observe --verdict DROPPED --pod <ns>/<pod>
# Calico:开 policy drop 日志后看内核 calico-packet 日志,或用 calicoctl 查策略命中
kubectl exec -n <ns> <pod> -- nc -zv <target-ip> <port>   # timeout=被拦截

常见误配置(速记): ①egress 默认 deny 把 DNS(UDP/TCP 53) 也拦了;②podSelector:{} 误伤全 namespace;③namespaceSelector label 写错;④漏放行 kube-system CoreDNS 与监控抓取。


题目 4:PSA (Pod Security Admission) 阻止 Pod 创建#

故障现象: Pod 无法创建,Events 报 violates PodSecurity。错误信息包含 privilegedbaselinerestricted 等级别提示。

排查路径:

bash
# 1. 检查 namespace 的 PSA 标签
kubectl get ns <ns> -o yaml | grep pod-security
# 输出示例:pod-security.kubernetes.io/enforce: restricted

# 2. 查看违规详情
kubectl describe pod <pod> -n <ns> | grep -A5 Warning

# 3. PSA 三级别对照
# privileged: 无限制
# baseline: 禁止已知提权(hostNetwork/hostPID/privileged 容器等)
# restricted: 最严格(要求 runAsNonRoot、seccompProfile、drop ALL capabilities 等)

# 4. 修复方向
# a) 修改 Pod spec 符合安全策略
# b) 降低 namespace 的 enforce 等级(仅用于过渡期)
# c) 使用 admission controller 豁免特定 SA

子类 6:综合排障题 — 多层叠加的复杂故障#

题目 1:Terway IP 泄漏导致 Pod 无法调度 — 全链路连锁故障#

故障现象(完整时间线):

  • T0:节点磁盘使用率超过 90%
  • T0+1min:kubelet 检测到 DiskPressure,触发 Pod 驱逐
  • T0+2min:多个 Pod 被强制驱逐(Evicted: ephemeral-storage),不走正常 Graceful Termination
  • T0+5min:新 Pod 全部卡在 Pending,Events 报 0/6 nodes are available: 6 Insufficient aliyun/vpc-eni-ip
  • T0+10min:所有部署卡住,滚动更新失败,业务不可用

逐层排查过程:

bash
# 第一层:看到 Pod Pending,首查 Pod Events
kubectl describe pod my-app-7d9f8b-xxxxx -n production
# Events:
#   Warning  FailedScheduling  3m   default-scheduler
#     0/6 nodes are available: 6 Insufficient aliyun/vpc-eni-ip.
# 报错不是常见的 CPU/内存,而是 ENI IP——信号很不寻常

# 第二层:看节点状态
kubectl get nodes
# 所有节点都显示 Ready —— 和直觉(节点正常但 Pod 无法调度)矛盾

# 第三层:拉时间线找线索(关键动作!)
kubectl get events -A --sort-by='.metadata.creationTimestamp' | tail -60
# 发现 T0-T0+5min 间大量 Evicted 事件
# production   Warning   Evicted   Pod/my-app-old    kubelet
#   The node was low on resource: ephemeral-storage. Threshold: 10%, available: 7%.

# 第四层:确认 DiskPressure
kubectl describe node cn-hangzhou.x.x.x.x | grep -A10 Conditions
# DiskPressure: True

# 第五层:逻辑推理
# 磁盘满 → 驱逐 → 强制删除 Pod
# 但为什么 Pod 驱逐后反而调度不上去了?问题应该在网络层

# 第六层:排查 Terway CRD IPAM
kubectl get nodeeni -A
# NAME                    AVAILABLE   TOTAL   STATUS
# cn-hangzhou.x.x.x.x    0           14      Ready    ← IP 全部耗尽
# cn-hangzhou.x.x.x.x    0           14      Ready

# 但实际运行的 Pod 数只有 6 个!
kubectl get pods -A -o wide | grep cn-hangzhou.x.x.x.x | grep Running | wc -l
# 输出:6

# 第七层:确认 IP 泄漏
kubectl get nodeeni cn-hangzhou.x.x.x.x -o yaml
# status.enis[].assignedPrivateIPs[].podInfo.name → 指向已不存在的 Pod(被驱逐的)

# 第八层:查看 terway-daemon 日志
kubectl logs -n kube-system -l app=terway-daemon --tail=200 | grep -i "error\|recycle"
# ERR  failed to release IP for pod: pod not found, skip cleanup
# WARN gc: pod already deleted, but IP still allocated

根因完整链路:

text
磁盘 >90% → kubelet DiskPressure → 强制驱逐 Pod(不等 preStop)
→ Terway preStop/删除钩子未执行 → IP 未回收(CRD 状态仍为"已分配")
→ 节点 ENI IP 耗尽 → 新 Pod 调度时无可用 IP → Pending
→ 所有业务部署挂掉

修复方案:

  1. 立即止血:手动释放泄漏 IP(云厂商 API UnassignPrivateIpAddresses)
  2. 清磁盘crictl rmi --prune + crictl rmp -a(containerd;老 Docker 集群才用 docker system prune -af
  3. 监控补盲:加 Terway ENI IP 使用率监控(< 3 个告警)、磁盘 >80% 告警
  4. 长期修复:升级 Terway 版本(新版本 GC 逻辑更健壮)、调低 kubelet eviction 阈值

预防措施:

  • Prometheus 磁盘告警规则:(1 - node_filesystem_avail_bytes / node_filesystem_size_bytes) * 100 > 80
  • Terway IP 告警规则:terway_node_available_ip < 3
  • DiskPressure 的连锁影响覆盖范围文档化

题目 2:CoreDNS ndots:5 导致 DNS 解析超时 — 性能与正确性冲突#

故障现象: 服务调用外部 API 时出现间歇性超时,P99 延迟远超预期。日志显示 DNS 解析有时需要数秒。使用 dig/nslookup 手动测试时表现正常。

排查路径:

bash
# 第一步:查看 Pod DNS 配置
kubectl exec <pod> -- cat /etc/resolv.conf
# nameserver 10.96.0.10
# search production.svc.cluster.local svc.cluster.local cluster.local
# options ndots:5

# ndots:5 的含义:
# 查询的域名中点号 < 5 → 在每个 search 域逐一尝试后缀
# 例如查 api.example.com(2 个点):
# 1. api.example.com.production.svc.cluster.local → NXDOMAIN
# 2. api.example.com.svc.cluster.local            → NXDOMAIN
# 3. api.example.com.cluster.local                → NXDOMAIN
# 4. api.example.com.                             → 成功(但已经是第 4 次查询)
# 一次业务请求 → 4 次 UDP DNS 查询

# 在高并发下,这 4 倍放大再叠加上 conntrack 竞态条件 → 灾难

# 第二步:测量实际 DNS 延迟
kubectl exec <pod> -- time for i in $(seq 1 10); do nslookup api.example.com; done

# 第三步:对比直接查上游 DNS
kubectl exec <pod> -- time nslookup api.example.com 8.8.8.8

根因分析:

  • ndots:5 是 K8s 默认值,设计初衷是方便集群内 DNS 解析
  • 但它导致所有外部域名的 DNS 查询都被放大 3-4 倍
  • 配合 CoreDNS 的 conntrack 竞态条件(5 秒延迟),问题更加严重
  • 高并发 + ndots:5 + conntrack = DNS 雪崩

修复方案:

  1. 方案 1:调低 ndots 值(推荐)
    yaml
    spec:
      dnsConfig:
        options:
          - name: ndots
            value: "2"  # 只有 1 个点的域名才走 search 域
  2. 方案 2:外部域名使用 FQDN(末尾加点,跳过 search 域)
    python
    requests.get("http://api.example.com./v1/data")  # 末尾加 .
  3. 方案 3:结合 single-request-reopen 避免 conntrack 竞态
  4. 方案 4:CoreDNS cache 调优,缓存 NXDOMAIN 响应减少重复查询

题目 3:集群级连锁故障 — 僵尸集群导致系统级雪崩#

故障现象: kubectl apply 或 ArgoCD sync 周期性卡顿 30-32 秒后超时。argocd app list 和 UI 响应极慢。集群中所有 reconcile 都在排队。

排查路径(决策树):

text
集群级响应变慢 / 周期性 32 秒超时
├── 观察超时模式
│   ├── 周期性(每 N 分钟一波)→ 有定时 reconcile 的组件在超时
│   ├── 32 秒 = client-go / kube-apiserver 默认超时
│   └── 检查 ArgoCD controller 日志中的 reconcile 耗时
├── 排查 ArgoCD
│   ├── kubectl get applications -n argocd → 是否有 Unknown/Error 状态
│   ├── kubectl get secrets -n argocd | grep cluster → cluster secret 数量
│   ├── 检查是否有已删除的集群残留 cluster secret
│   │   → ArgoCD controller 每次 reconcile 都要连所有 cluster
│   │   → 死集群每次 32s timeout × N apps → controller 卡死
│   └── 修复:删除不再使用的 ArgoCD cluster secret
├── 排查代理/网络层
│   ├── 检查 tinyproxy/squid 状态(代理过载或死锁)
│   ├── 检查 NO_PROXY 列表是否完整(不该走代理的内部流量走了代理)
│   └── 检查 DNS 解析延迟
└── 排查 etcd
    ├── etcdctl endpoint health
    ├── etcdctl endpoint status
    ├── 磁盘 IO 饱和 → etcd 响应变慢 → apiserver 超时 → 全集群超时
    └── etcd 必须用 SSD(NVMe 最佳),不能与其他服务共享磁盘

根因分析(按频率):

  1. ArgoCD 残留 cluster secret 指向已删除集群(最常见)→ controller reconcile 排队卡死
  2. etcd 磁盘 IO 瓶颈 → 整个控制面板响应慢
  3. API Server 连接数过多 → 配置 max-requests-inflight 限制
  4. 代理服务器过载 → 内部流量被代理拖慢

预防措施:

  1. 删除集群时过四层清理(物理/逻辑/IaC/GitOps 层)
  2. 删除集群的 checklist 必须包含清理 ArgoCD cluster secret
  3. etcd 独立 SSD + 定期备份 + 定期 defrag
  4. Prometheus etcd 监控:etcd_disk_wal_fsync_duration_seconds_bucketetcd_server_has_leader

题目 4:Pod 被 ApplicationSet 反复复活#

故障现象: 尝试 kubectl delete application <name>argocd app delete <name> 删除 ArgoCD Application,Application 删除后立即又出现,状态和之前一样。

排查路径:

bash
# 1. 检查 ownerReferences
kubectl get application <name> -n argocd -o yaml | grep ownerReferences -A5
# 如果有 ownerReferences.kind: ApplicationSet → 由 ApplicationSet 管理

# 2. 查出是哪个 ApplicationSet
OWNER=$(kubectl get application <name> -n argocd -o jsonpath='{.metadata.ownerReferences[0].name}')
echo "Owner ApplicationSet: $OWNER"

# 3. 正确删除方式:从 git 源头入手
# 方式A:删除 gitops 仓库中对应 generator 的目录(如 clusters/<env>/applications/<path>/)
# 方式B:修改 ApplicationSet generator 的过滤规则排除此 app
# 方式C:在 ApplicationSet 中修改 prune 策略

# 不可用 kubectl delete,ApplicationSet controller 检测到 child 缺失会立即重建

题目 5:PrometheusRule label 不匹配导致告警 75 天未生效#

故障现象: 生产运维发现某集群的 PrometheusRule YAML 在集群中能看到(kubectl get prometheusrule),但相关告警从没触发过。专项巡检发现这些规则 75 天未被评估过。

排查路径:

bash
# 1. 检查 Prometheus 的 ruleSelector
kubectl get prometheus -n monitoring -o jsonpath='{.items[0].spec.ruleSelector}'
# 输出示例:{"matchLabels":{"release":"monitoring"}}

# 2. 检查 PrometheusRule 的 labels
kubectl get prometheusrule -A -o json | jq -r '.items[] | "\(.metadata.name): \(.metadata.labels)"'
# 某条输出:sandbox-rules: {"app":"monitoring"}  ← label 不匹配!

# 3. 对比规则数量
# yaml 中的告警规则数
kubectl get prometheusrule -A -o json | jq '[.items[].spec.groups[].rules[] | select(.alert)] | length'
# Prometheus 实际加载的告警规则数
curl -s http://prometheus.monitoring:9090/api/v1/rules | jq '[.data.groups[].rules[] | select(.type=="alerting")] | length'
# 两个数不一致 → 有规则未加载

根因: Prometheus Operator 通过 ruleSelector 选取 PrometheusRule,但 YAML 的 metadata.labels 与 selector 不匹配。资源在集群中存在(kubectl get 能看到)但 Operator 跳过了它,未注入 Prometheus 配置文件。

修复方案:

  1. 修正 PrometheusRule 的 labels 使其匹配 ruleSelector
  2. 建立每周巡检(CronJob)对比 git 规则数和集群实际加载规则数
  3. 任何 PrometheusRule 变更后必须从 /api/v1/rules 验证规则真的被加载

题目 6:跨环境数据污染事故 — 子环境隔离不全#

故障现象(完整时间线):

  • T-14 天:新子环境 env-AI 上线,复用 QA 环境的 Aurora cluster、RabbitMQ broker、Valkey
  • T-7 天:env-QA 数据库开始收到来自 env-AI 的脏消息
  • T0:env-QA 用户在自己老项目中看到不认识的对话记录(跨环境数据污染)
  • 原因确认:project.id 区间重叠(1-274)+ RabbitMQ fanout exchange 共用 + 消费者未按环境过滤 + Nacos 配置复制时漏改 host

逐层排查过程:

bash
# 第一层:从用户反馈倒推
# - QA 环境老项目出现了 AI 环境用户的数据 → 确定是交叉污染

# 第二层:查数据链路
# - 消息表 realtime_messages 的 project_id 在 1-274 区间
# - env-AI 的自增 id 才到 274,env-QA 的老项目大量分布在 1-274
# - 新消息的 created_at >= env-AI 上线日,但 project 创建日期 < env-AI 上线日 → 确认脏消息来源

# 第三层:查配置中心
# - Nacos 中 env-AI 的 RabbitMQ host 指向 QA 的 broker
# - env-AI 的 dispatcher 没有按 dispatch_env 过滤 → 两个环境的消息混在同一个 fanout exchange

# 第四层:查基础设施
# - Aurora cluster 共用 → 虽然不同 schema 但 AUTO_INCREMENT 起点都是 1
# - 两个环境的消息通过同一个 RabbitMQ exchange 分发 → 消费者无差别处理

根因(4 件套同时成立):

  1. 跨环境业务表 id 区间重叠
  2. 广播总线共用(同 broker 同 vhost 同 exchange)
  3. 消费者未按环境标签过滤
  4. 配置中心从老环境复制时漏改 host/vhost

修复方案:

  1. 清洗数据:CTAS 备份 → 临时表中转 → 分批 DELETE(每批 1000 行+0.2s 间隔)
  2. 基础设施隔离
    • 独立 Aurora cluster(Serverless v2, $50/月起)
    • 独立 RabbitMQ broker(mq.m7g.medium, $71/月)
    • 独立 Valkey 实例(cache.t4g.micro, $11/月)
  3. 编码层防御
    • 业务表 AUTO_INCREMENT 偏移 ≥ 10,000,000
    • 消费侧强制按 dispatch_env 过滤
  4. 流程改进:新环境上线必须过 7 条隔离 checklist

附 A:部署与发布场景题(偏方案设计,与《5-方案设计型.md》有重叠)#


题目 1:零停机滚动更新配置#

场景描述: Deployment 管理 5 个副本,上线新版本镜像,要求发布过程不丢失一个请求、发布期间服务能力不下降。

方案正本见《5-方案设计型.md》· 子类 4「题目 3:多环境发布策略设计」——「零停机的完整五要素」(RollingUpdate maxSurge:1/maxUnavailable:0 + readinessProbe + preStop sleep + terminationGracePeriodSeconds + PDB)、SIGTERM 摘流时序、以及渐进式发布工具(Argo Rollouts/Flagger,别手搓 Istio VS weight)讲解更完整,此处不再重复。


题目 2:金丝雀发布(Istio)#

场景描述: 你的团队有一个关键的用户服务需要上线新版本。要求在正式全量前,将 10% 的流量路由到新版本运行 30 分钟验证,如果 P99 延迟和错误率稳定,再逐步增加到 50% 和 100%。

解题思路:

使用 Istio VirtualService 的流量权重功能,配合 DestinationRule 定义版本子集。

  1. 部署新版本 Deployment(v2),确保与旧版本(v1)有区分性的 label(如 version: v2
  2. 创建 DestinationRule 定义 v1 和 v2 两个 subset
  3. 创建 VirtualService 配置流量权重分配
  4. 观察 Grafana 监控指标(P99 延迟、错误率),逐步调整权重

关键 YAML 配置:

yaml
# DestinationRule 定义版本子集
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: user-service
spec:
  host: user-service
  subsets:
    - name: v1
      labels:
        version: v1
    - name: v2
      labels:
        version: v2

---
# VirtualService 流量权重路由
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: user-service
spec:
  hosts:
    - user-service
  http:
    - route:
        - destination:
            host: user-service
            subset: v1
          weight: 90
        - destination:
            host: user-service
            subset: v2
          weight: 10

验证与推进:

bash
# 观察 P99 延迟和错误率 30 分钟
kubectl port-forward svc/kiali 20001:20001 -n istio-system

# 无异常后:50% 验证
kubectl patch vs user-service --type json \
  -p '[{"op":"replace","path":"/spec/http/0/route/0/weight","value":50},
       {"op":"replace","path":"/spec/http/0/route/1/weight","value":50}]'

# 再观察 30 分钟 → 100% 全量

注意事项:

  • 金丝雀期间不要修改其他部署参数(HPA、资源),避免干扰判断
  • 如果 v1 和 v2 依赖不同的数据库 Schema,Schema 必须向前兼容
  • 流量权重变更期间短时间的连接断开无法完全避免,需配合客户端重试

题目 3:快速回滚#

场景描述: 凌晨发布后,监控发现新版本错误率飙升 50%。你需要立即止损,同时保留现场用于后续 Root Cause 分析。

解题思路:

分紧急止血和保留现场两条线并行:

  1. 立即回滚:使用 Deployment 的 revision history 回滚到上一个稳定版本
  2. 保留现场:在回滚前导出故障 Pod 的日志和事件
bash
# 第一步:确认发布历史
kubectl rollout history deployment/user-service -n production

# 第二步:查看故障 Pod 日志(保留现场)
kubectl logs -l app=user-service,version=v2 -n production --tail=500 > /tmp/v2-logs.txt
kubectl describe pod -l app=user-service,version=v2 -n production > /tmp/v2-events.txt

# 第三步:立即回滚
kubectl rollout undo deployment/user-service -n production

# 如果有特定版本(REVISION=5)
kubectl rollout undo deployment/user-service --to-revision=5 -n production

# 第四步:确认回滚进度
kubectl rollout status deployment/user-service -n production

回滚后验证:

bash
# 确认所有 Pod 运行在旧版本
kubectl get pods -l app=user-service -o jsonpath='{.items[*].spec.containers[0].image}'

# 监控错误率是否恢复
kubectl top pods -l app=user-service

注意事项:

  • revisionHistoryLimit 默认保留 10 个历史版本,如果发布频率高要调大此值
  • 如果有 ConfigMap/Secret 变更,回滚 Deployment 不会自动回滚这些资源,需手动处理
  • 根因分析必须做:应用日志、OOM 事件、配置变更、依赖服务状态 逐项排查

题目 4:Helm Chart 多环境部署#

场景描述: 微服务需在 dev/staging/production 三环境部署,各环境配置差异大(DB 地址、副本数、资源、域名等),要求统一模板管理、避免每环境维护独立 YAML。

方案正本见《5-方案设计型.md》· 子类 4「题目 2:Helm vs Kustomize 配置管理」——含选型对比、"一个 Chart + 多 values 文件" 与 Kustomize base/overlay 两种模式及高频踩坑,此处不再重复。


题目 5:ArgoCD GitOps 部署#

场景描述: 要求所有 K8s 配置变更必须走 Git PR Review,禁止任何人直接 kubectl apply 到生产集群,搭建 GitOps 流程。

方案正本见《5-方案设计型.md》· 子类 4「题目 1:ArgoCD vs FluxCD 选型」——含架构对比、Application/syncPolicy(prune/selfHeal)、AppProject RBAC 与 Pull-based GitOps 取舍,此处不再重复。


题目 6:PR 隔离环境#

场景描述: 每个 PR 创建时自动部署独立预览环境,PR 合并后自动销毁,PR 环境与基线环境完全隔离、互不影响。

方案正本见《5-方案设计型.md》· 子类 4「题目 4:PR 隔离环境设计」——含 ApplicationSet PullRequest Generator、Service selector/HTTPRoute/DR-VS/数据库/消息队列隔离 Checklist 与生产事故坑点,此处不再重复。


附 B:配置与存储场景题(偏方案设计,与《5-方案设计型.md》有重叠)#


题目 1:ConfigMap 热更新方案#

场景描述: 业务服务有一些运行时开关(Feature Flag、限流阈值),需要在不停机的情况下修改并让所有 Pod 即时生效。

解题思路:

ConfigMap 通过 Volume 挂载 + 应用内文件监听(inotify)实现热更新。环境变量注入方式不支持热更新(需要重启 Pod)。

推荐方案:

  1. ConfigMap 以 Volume 方式挂载到容器(而非环境变量)
  2. 应用代码内监听文件变化(使用 inotify 或配置框架的热加载能力)
  3. 修改 ConfigMap 后,kubelet 会在约 1 分钟内同步到挂载目录(--sync-frequency 默认 1m)

关键 YAML 配置:

yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  feature-flags.yaml: |
    new_checkout_flow: false
    rate_limit_qps: 1000
---
apiVersion: apps/v1
kind: Deployment
spec:
  template:
    spec:
      containers:
        - name: app
          volumeMounts:
            - name: config
              mountPath: /etc/app/config
              readOnly: true
      volumes:
        - name: config
          configMap:
            name: app-config

如果应用不支持热加载(或 volume 方式的文件更新延迟不可接受):

使用"不可变 ConfigMap + 版本化命名 + 触发滚动更新"模式:

yaml
# 每次配置变更创建新的 ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config-v2    # 版本化命名
---
# 更新 Deployment 引用
spec:
  template:
    spec:
      containers:
        - name: app
          envFrom:
            - configMapRef:
                name: app-config-v2   # 改变引用触发滚动更新

注意事项:

  • subPath 方式挂载的 ConfigMap 不支持热更新(kubelet 不会同步 subPath 内容)
  • 应用需要在文件发生变化后重新加载配置(reload 逻辑),仅文件更新不意味着生效
  • kubelet 的同步周期是可配的(--sync-frequency),依赖热更新的系统需要注意生效延迟

题目 2:Secret 安全使用实践#

场景描述: 团队目前将数据库密码以 base64 写入 Secret 并直接 commit 到 Git 仓库。安全审计要求必须修复。你需要在保证 CI/CD 流程不受影响的前提下,让 Secret 不裸露在 Git 中。

解题思路:

采用分层方案,按安全等级从低到高选择:

方案一:Sealed Secrets(快速落地)

bash
# 在集群中部署 sealed-secrets controller
kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/latest/download/controller.yaml

# 本地用 kubeseal 加密 Secret
kubectl create secret generic db-creds \
  --from-literal=password=MySecret123 \
  --dry-run=client -o yaml | \
  kubeseal --format yaml > sealed-db-creds.yaml

# sealed-db-creds.yaml 可以安全存入 Git
# 集群内 controller 自动解密为真实 Secret

方案二:External Secrets Operator(企业级推荐)

yaml
# 在 Git 中存 ExternalSecret CR(不存数据,只存引用)
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: db-creds
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: aws-secrets-manager
    kind: ClusterSecretStore
  target:
    name: db-creds        # 目标 Secret 名称
  data:
    - secretKey: password
      remoteRef:
        key: prod/my-service/db
        property: password

方案三:SOPS + FluxCD(GitOps 原生集成)

用 SOPS 加密 Secret YAML,FluxCD 的 kustomize-controller 在 apply 前自动解密。

yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
spec:
  decryption:
    provider: sops
    secretRef:
      name: sops-age-key

注意事项:

  • Secret 默认 base64 编码不等于加密,任何能访问 Secret 的人都能解码
  • 生产环境 etcd 必须开启静态加密(--encryption-provider-config
  • 配合 RBAC 限制谁能 get/list Secret(最小权限原则)
  • 审计日志记录 Secret 访问事件,及时发现异常

题目 3:PVC 动态供给配置#

场景描述: 团队在 AWS EKS 上部署了 StatefulSet 管理的 Kafka 集群,每个 broker 需要独立的 EBS 卷(20 Gi, gp3)。要求卷能随 Pod 自动创建和绑定,Pod 重建后自动挂载到原有卷。

解题思路:

使用 StorageClass 动态 provisioning + StatefulSet 的 volumeClaimTemplates

  1. 创建 StorageClass 定义 EBS provisioner 和参数
  2. StatefulSet 中通过 volumeClaimTemplates 声明 PVC 模板
  3. K8s 为每个 Pod 自动创建独立的 PVC(如 data-kafka-0data-kafka-1

关键 YAML 配置:

yaml
# StorageClass
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: ebs-gp3
provisioner: ebs.csi.aws.com
parameters:
  type: gp3
  iops: "3000"          # gp3 用绝对值 iops/throughput(iopsPerGB 是 io1/io2 的写法,gp3 无效)
  throughput: "125"     # MiB/s
  encrypted: "true"
volumeBindingMode: WaitForFirstConsumer   # 延迟绑定:Pod 调度后再创建卷(确保卷在 Pod 所在 AZ)
allowVolumeExpansion: true                # 允许在线扩容
reclaimPolicy: Retain                     # 保留删除策略
---
# StatefulSet
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: kafka
spec:
  serviceName: kafka-headless
  replicas: 3
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes: ["ReadWriteOnce"]
        storageClassName: ebs-gp3
        resources:
          requests:
            storage: 20Gi
  template:
    spec:
      containers:
        - name: kafka
          volumeMounts:
            - name: data
              mountPath: /var/lib/kafka/data

PVC 绑定流程:

  1. StatefulSet 创建 Pod kafka-0,自动生成 PVC data-kafka-0
  2. StorageClass provisioner 在 pod 所在 AZ 创建 EBS 卷,生成 PV
  3. PV 与 PVC 绑定,卷挂载到 /var/lib/kafka/data

注意事项:

  • volumeBindingMode: WaitForFirstConsumer 对跨 AZ 部署至关重要——确保卷在 Pod 所在的 AZ,否则无法挂载
  • reclaimPolicy: Retain 防止误删 PVC 导致数据丢失
  • EBS 卷有 AZ 限制(不跨 AZ),Pod 调度到不同 AZ 时不能直接挂载原卷
  • 启用 allowVolumeExpansion: true 以支持在线扩容

题目 4:StatefulSet 存储扩容#

场景描述: Kafka 集群运行一段时间后,预估的 20 Gi 磁盘即将用满。需要将每个 broker 的数据卷扩容到 50 Gi,且不能中断服务。

解题思路:

使用 PVC 在线扩容功能(需 StorageClass 开启 allowVolumeExpansion: true):

  1. 先扩容一个 follower broker(影响小)
  2. 等待 PVC 状态从 Resizing 变为 Bound(由 CSI 驱动完成底层卷扩容)
  3. 验证数据完整性后,逐个扩容其余节点

操作步骤:

bash
# 第一步:确认 StorageClass 支持扩容
kubectl get sc ebs-gp3 -o jsonpath='{.allowVolumeExpansion}'
# 应返回 true

# 第二步:扩容单个 PVC(从 20Gi → 50Gi)
kubectl patch pvc data-kafka-2 -n kafka --patch '{"spec":{"resources":{"requests":{"storage":"50Gi"}}}}'

# 第三步:等待扩容完成
kubectl get pvc data-kafka-2 -n kafka -w

# 第四步:进入 Pod 验证
kubectl exec -it kafka-2 -n kafka -- df -h /var/lib/kafka/data

# 第五步:逐个扩容其余 PVC
for i in 1 0; do
  kubectl patch pvc data-kafka-$i -n kafka --patch '{"spec":{"resources":{"requests":{"storage":"50Gi"}}}}'
  kubectl wait --for=jsonpath='{.status.capacity.storage}'=50Gi pvc/data-kafka-$i -n kafka --timeout=300s
done

注意事项:

  • PVC 只能扩容不能缩容(大多数存储后端不支持)
  • 扩容期间 Pod 不需要重启,但某些存储类型可能需要解除挂载
  • StatefulSet 的 volumeClaimTemplates 本身不能更新(immutable),但单独编辑 PVC 的 spec.resources.requests.storage 是允许的
  • 扩容前确认底层存储有足够的容量和配额(云厂商的配额限制)

题目 5:环境变量多级管理#

场景描述: 一个微服务有四个环境(dev / qa / staging / production),环境变量数量超过 50 个。部分变量所有环境相同(如日志级别),部分环境特定(如数据库地址),还有少量是 Secret(密码)。需要结构化管理。

解题思路:

采用分层 ConfigMap + Secret 组合:

  • 公共配置:一个 ConfigMap(app-common-config),所有环境共用
  • 环境配置:每个环境一个 ConfigMap(app-<env>-config),覆盖环境特定值
  • 敏感配置:每个环境一个 Secret(app-<env>-secret),通过 ESO/Vault 管理

配置结构示例:

yaml
# app-common-config(跨环境共用)
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-common-config
data:
  LOG_LEVEL: "info"
  METRICS_PORT: "9090"
  GRPC_KEEPALIVE: "60s"
---
# app-production-config(生产环境特定)
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-production-config
  namespace: production
data:
  DB_HOST: "prod-db-master.internal:5432"
  REDIS_HOST: "prod-redis-cluster.internal:6379"
  REPLICA_COUNT: "5"
---
# Deployment 中按顺序引用
spec:
  template:
    spec:
      containers:
        - name: app
          envFrom:
            - configMapRef:
                name: app-common-config
            - configMapRef:
                name: app-production-config
            - secretRef:
                name: app-production-secret
          # 关键覆盖项用 env 显式声明(优先级最高)
          env:
            - name: POD_IP
              valueFrom:
                fieldRef:
                  fieldPath: status.podIP

注意事项:

  • envFrom 中后引用的 key 会覆盖先引用的同名 key
  • 显式 env 中定义的变量优先级最高
  • 避免将所有 50+ 变量堆在一个 ConfigMap 中,按功能模块拆分(数据库类、缓存类、业务参数类)
  • 配合 Helm 的 values.yaml 分层管理模板变量,减少 ConfigMap 数量

附 C:安全与权限场景题(偏方案设计,与《5-方案设计型.md》有重叠)#


题目 1:CI 机器人最小权限 RBAC 设计#

场景描述: CI/CD 流水线(GitLab CI/Argo Workflows)需要将构建好的镜像部署到 K8s 集群。要求 CI 机器人拥有最小的必要权限,不能拿到 cluster-admin。

解题思路:

  1. 在目标 Namespace 创建专用 ServiceAccount ci-deployer
  2. 创建 Role 授予对 Deployment、Service、ConfigMap 的 CRUD 权限(限定在该 Namespace)
  3. 通过 RoleBinding 绑定
  4. CI 使用该 ServiceAccount 的 Token 连接集群

完整配置:

yaml
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: ci-deployer
  namespace: production
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: ci-deployer
  namespace: production
rules:
  # 只给 Deployment 相关权限
  - apiGroups: ["apps"]
    resources: ["deployments", "deployments/scale"]
    verbs: ["get", "list", "watch", "update", "patch"]
  # 允许读取 Service 和 Endpoints(验证部署)
  - apiGroups: [""]
    resources: ["services", "endpoints"]
    verbs: ["get", "list", "watch"]
  # 允许读取 ConfigMap(配置管理)
  - apiGroups: [""]
    resources: ["configmaps"]
    verbs: ["get", "list", "watch", "update", "patch"]
  # 允许读取/创建 Events(发布状态跟踪)
  - apiGroups: [""]
    resources: ["events"]
    verbs: ["get", "list", "watch", "create"]
  # 允许读取 Pods(查看日志验证)
  - apiGroups: [""]
    resources: ["pods", "pods/log"]
    verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: ci-deployer
  namespace: production
subjects:
  - kind: ServiceAccount
    name: ci-deployer
    namespace: production
roleRef:
  kind: Role
  name: ci-deployer
  apiGroup: rbac.authorization.k8s.io

注意事项:

  • 不允许 delete Deployment(只允许 update/patch),防止误删
  • 不允许创建/修改 Secret(Secret 应通过 ESO/Vault 管理,CI 不持有敏感凭据)
  • 多环境部署时,为每个环境创建独立的 Role 和 ServiceAccount
  • CI 使用的 Token 应有过期时间并定期轮换(或使用短期 Token)

题目 2:NetworkPolicy 零信任策略#

场景描述: 生产 Namespace 对 frontend/backend/db 三类服务实施零信任——默认拒绝所有流量,只按需开放必要入站/出站。

方案正本见《5-方案设计型.md》· 子类 2「题目 4:网络隔离策略分层设计」(default-deny ingress/egress + allow-dns 的 YAML 与本题几乎逐行相同);L3/L4/L7 分层与 FQDN 出口另见子类 3「题目 4:NetworkPolicy 分层设计」。此处不再重复。


题目 3:PSA 安全策略落地#

场景描述: 安全审计要求生产 Namespace 中所有 Pod 必须满足以下条件:以非 root 用户运行、只读根文件系统、禁止特权提升、丢弃所有 Linux Capabilities。需要使用 Pod Security Admission 强制执行。

解题思路:

给生产 Namespace 打上 pod-security.kubernetes.io/enforce=restricted 标签,PSA 会自动拒绝不符合 Restricted 标准的 Pod。

yaml
apiVersion: v1
kind: Namespace
metadata:
  name: production
  labels:
    pod-security.kubernetes.io/enforce: restricted      # 强制拦截
    pod-security.kubernetes.io/enforce-version: v1.29
    pod-security.kubernetes.io/audit: restricted        # 审计日志记录
    pod-security.kubernetes.io/warn: restricted         # 警告提示

对应 Pod SecurityContext 配置:

yaml
apiVersion: apps/v1
kind: Deployment
spec:
  template:
    spec:
      securityContext:
        runAsNonRoot: true
        runAsUser: 1000
        runAsGroup: 1000
        fsGroup: 1000
      containers:
        - name: app
          securityContext:
            allowPrivilegeEscalation: false
            privileged: false
            readOnlyRootFilesystem: true
            capabilities:
              drop:
                - ALL
          volumeMounts:
            - name: tmp
              mountPath: /tmp        # 需要写临时文件时挂 emptyDir
            - name: app-cache
              mountPath: /var/cache  # 需要写缓存时挂 emptyDir
      volumes:
        - name: tmp
          emptyDir: {}
        - name: app-cache
          emptyDir: {}

渐进式落地策略:

  1. Week 1-2:先打 warn 标签,观察日志中的违规 Pod
  2. Week 3-4:整改不符合的 Pod 配置,同时开启 audit 记录
  3. Week 5:确认无违规后,切换到 enforce 强制拦截

注意事项:

  • readOnlyRootFilesystem: true 后应用写 /tmp 会失败,必须挂载 emptyDir
  • Istio sidecar 需要 NET_ADMINNET_RAW 能力,必要时配置豁免
  • 检查基础镜像的默认用户(某些镜像默认 root 运行),Dockerfile 中指定 USER 1000
  • warnauditenforce 三步走是安全推行 PSA 的最佳实践

题目 4:Secret 外部管理(Vault/ESO)#

场景描述: 公司统一使用 HashiCorp Vault 管理所有敏感凭据。需要将 Vault 中的数据库密码同步到 K8s Secret,并支持密码轮转时 Secret 自动更新。

解题思路:

部署 External Secrets Operator(ESO),通过 ClusterSecretStore 连接 Vault,用 ExternalSecret CR 定义同步规则。

完整配置:

yaml
# 第一步:ClusterSecretStore 连接 Vault
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: vault-backend
spec:
  provider:
    vault:
      server: "https://vault.internal:8200"
      path: "secret"
      version: "v2"
      auth:
        kubernetes:
          mountPath: "kubernetes"
          role: "production-reader"    # Vault Kubernetes Auth Role

---
# 第二步:ExternalSecret 同步数据库密码
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: db-creds
  namespace: production
spec:
  refreshInterval: 1h                  # 每 1 小时同步
  secretStoreRef:
    name: vault-backend
    kind: ClusterSecretStore
  target:
    name: db-creds                     # 目标 K8s Secret 名称
    creationPolicy: Owner
  data:
    - secretKey: username
      remoteRef:
        key: prod/database
        property: username
    - secretKey: password
      remoteRef:
        key: prod/database
        property: password
    - secretKey: connection-string
      remoteRef:
        key: prod/database
        property: connection_string

---
# 第三步:Deployment 引用同步后的 Secret
spec:
  containers:
    - name: app
      env:
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: db-creds
              key: password

密码轮转处理:

  • Vault 中密码更新后,ESO 在下一个 refreshInterval 周期同步到 K8s Secret
  • Secret 更新后,需要触发 Pod 重新加载配置或滚动重启
  • 配合 Reloader(Stakater Reloader)自动检测 Secret 变化并滚动重启 Deployment
bash
# 安装 Reloader
helm install reloader stakater/reloader
yaml
# Deployment annotation 触发自动重启
metadata:
  annotations:
    reloader.stakater.com/auto: "true"

注意事项:

  • Vault Kubernetes Auth 需要正确配置 ServiceAccount 和 role
  • refreshInterval 不宜过短(建议 >= 1h),避免频繁调用 Vault
  • 密码轮转时应用需要支持"灰度切换"(新/旧密码同时有效一段时间),否则切换瞬间会出错

题目 5:容器安全上下文(SecurityContext)#

场景描述: 安全团队要求所有生产容器必须配置严格的安全上下文:禁止 root 用户、禁止特权模式、只读根文件系统、禁止 Capabilities 提升。同时,应用需要在 /tmp/var/log/app 写入临时文件。

本题是【安全题 3:PSA 落地】的容器侧配套:题 3 讲 namespace 级如何用 PSA 强制,本题讲单个容器 securityContext 每个字段具体怎么填、卡在哪里。SecurityContext YAML 结构与题 3 基本一致,此处聚焦逐字段解释与踩坑。

解题思路:

在 Pod 和 Container 两层配置 SecurityContext,为需要写入的路径挂载 emptyDir。

完整配置:

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: secure-app
spec:
  template:
    spec:
      # Pod 级别 SecurityContext
      securityContext:
        runAsNonRoot: true
        runAsUser: 1000
        runAsGroup: 1000
        fsGroup: 1000
        seccompProfile:
          type: RuntimeDefault
      containers:
        - name: app
          image: myapp:v1.5.2
          # Container 级别 SecurityContext
          securityContext:
            allowPrivilegeEscalation: false
            privileged: false
            readOnlyRootFilesystem: true
            capabilities:
              drop:
                - ALL
          # 挂载可写路径
          volumeMounts:
            - name: tmp
              mountPath: /tmp
            - name: app-log
              mountPath: /var/log/app
      volumes:
        - name: tmp
          emptyDir:
            sizeLimit: 100Mi
        - name: app-log
          emptyDir:
            sizeLimit: 500Mi

各字段说明:

字段作用风险
runAsNonRoot: true禁止以 root 运行容器默认 root 时启动失败
allowPrivilegeEscalation: false禁止通过 setuid 提权某些需要 setuid 的工具失效
readOnlyRootFilesystem: true禁止写根文件系统应用写 /tmp 需挂 emptyDir
capabilities.drop: [ALL]丢弃所有 Linux Capabilities某些场景需要特定能力(NET_ADMIN、SYS_PTRACE)
seccompProfile: RuntimeDefault限制系统调用某些应用使用非白名单系统调用

注意事项:

  • Istio 注入的 sidecar 需要 NET_ADMINNET_RAW,不可对它 drop ALL
  • 某些基础镜像默认以 root 运行,需在 Dockerfile 中 USER 1000
  • 安全配置应在 QA 环境先验证,不要直接在生产 Namespace 上线 PSA enforce

附 D:弹性与调度场景题(偏方案设计,与《5-方案设计型.md》有重叠)#


题目 1:HPA 配置与调优#

场景描述: 电商平台核心订单服务,P95 日间 QPS 约 5000,P99 峰值 50000。使用 Deployment 部署,当前固定 10 个副本。要求:配置 HPA 根据 CPU 和 QPS 自动扩缩,防止流量峰值时服务不可用,但也不能频繁抖动。

解题思路:

  1. 设置资源 requests(CPU 500m),这是 HPA 计算 CPU 利用率的基础
  2. 配置 HPA 双指标:CPU 利用率目标 70% + 自定义 QPS 指标(每 Pod 目标 1000 QPS)
  3. 扩容要快、缩容要慢:behavior 中区分 scaleUp 和 scaleDown

关键 YAML 配置:

yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: order-service-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: order-service
  minReplicas: 3
  maxReplicas: 50
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70
    - type: Pods
      pods:
        metric:
          name: http_requests_per_second
        target:
          type: AverageValue
          averageValue: "1000"       # 每个 Pod 目标 1000 QPS
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 0   # 扩容立即响应
      policies:
        - type: Percent
          value: 100                   # 每次最多翻倍
          periodSeconds: 60
        - type: Pods
          value: 10                    # 或每次最多加 10 个
          periodSeconds: 60
      selectPolicy: Max
    scaleDown:
      stabilizationWindowSeconds: 300  # 缩容等 5 分钟,防抖
      policies:
        - type: Percent
          value: 10                    # 每次最多缩 10%
          periodSeconds: 60
      selectPolicy: Min                # 缩容取最小值,保守

Pod 资源配置:

yaml
resources:
  requests:
    cpu: "500m"
    memory: "1Gi"
  limits:
    memory: "2Gi"
    # CPU limits 不设,避免 throttling

调优要点:

  • stabilizationWindowSeconds: 300 是防抖关键——流量瞬间回落时不会立即缩容
  • 扩容策略选 Max(取两个策略的较大值),缩容选 Min(取较小值)
  • 自定义 QPS 指标需要部署 Prometheus Adapter 暴露 http_requests_per_second 指标
  • 内存指标不建议用于 HPA(Java/Go GC 导致内存释放慢,容易只扩不缩)

题目 2:KEDA 事件驱动扩缩(Kafka Consumer)#

场景描述: 订单消费者服务消费 Kafka orders topic,有 12 个分区。平时流量低(2-5 个副本足够),大促期间消息堆积时需要在 5 分钟内扩到 20+ 个副本。需要从零流量开始自动启动(scale from zero)。

解题思路:

使用 KEDA ScaledObject 监听 Kafka consumer lag,按 lag 自动扩缩副本数。KEDA 支持缩到 0(HPA 不支持)。

完整配置:

yaml
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: order-consumer-scaler
  namespace: order
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: order-consumer
  pollingInterval: 15           # 每 15 秒检查 lag
  cooldownPeriod: 300           # 无 lag 后等 5 分钟再缩
  idleReplicaCount: 0           # 缩到 0
  minReplicaCount: 0            # 最小 0(KEDA 特色)
  maxReplicaCount: 30
  fallback:
    failureThreshold: 3
    replicas: 3                 # Kafka 不可达时保持 3 个副本兜底
  advanced:
    horizontalPodAutoscalerConfig:
      behavior:
        scaleDown:
          stabilizationWindowSeconds: 600
          policies:
            - type: Percent
              value: 50
              periodSeconds: 60
        scaleUp:
          stabilizationWindowSeconds: 0
          policies:
            - type: Percent
              value: 100
              periodSeconds: 15
  triggers:
    - type: kafka
      metadata:
        bootstrapServers: kafka-bootstrap.kafka:9092
        consumerGroup: order-consumer-group
        topic: orders
        lagThreshold: "500"          # 每个 Pod 承担 500 lag
        offsetResetPolicy: latest
        allowIdleConsumers: "false"
        excludePersistentLag: "true"  # 排除死分区 lag
      authenticationRef:
        name: kafka-trigger-auth

计算公式: desiredReplicas = ceil(totalLag / lagThreshold)

调优要点:

  • excludePersistentLag: true 是关键——防止某个分区卡住导致无限扩容
  • cooldownPeriod: 300 防止 bursty topic 频繁起停 Pod
  • fallback 设置合理的兜底副本数,Kafka 短暂不可达时维持基线容量
  • 告警规则:副本数达到 maxReplicaCount 持续 10 分钟 → 异常信号(可能是消费者死循环或 poison message)
  • 使用 TriggerAuthentication 管理 Kafka SASL/TLS 认证,密码不要直接写在 metadata 里

题目 3:GPU 节点排他调度#

场景描述: 集群中 10 个节点配有 NVIDIA A100 GPU,用于 AI 推理任务。要求:只有声明了 GPU 请求的 Pod 才能调度到这些节点,普通业务 Pod 不允许占用 GPU 节点。

解题思路:

使用 Taint + Toleration 实现排他调度——给 GPU 节点打污点,GPU Pod 声明容忍。

操作步骤:

bash
# 第一步:给 GPU 节点打 taint
kubectl taint nodes gpu-node-01 nvidia.com/gpu=present:NoSchedule

# 同时打标签(亲和性使用)
kubectl label nodes gpu-node-01 node-type=gpu

GPU Pod 配置:

yaml
apiVersion: v1
kind: Pod
metadata:
  name: inference-pod
spec:
  tolerations:
    - key: nvidia.com/gpu
      operator: Equal
      value: "present"
      effect: NoSchedule
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
              - key: node-type
                operator: In
                values:
                  - gpu
  containers:
    - name: inference
      image: myapp:inference-v2
      resources:
        limits:
          nvidia.com/gpu: 1       # 声明 1 个 GPU
        requests:
          nvidia.com/gpu: 1
          cpu: "4"
          memory: "16Gi"

GPU 节点监控要点:

  • 部署 dcgm-exporter(DaemonSet)监控 GPU 利用率、显存、温度
  • GPU 资源碎片化问题——MIG(Multi-Instance GPU)可将 A100 切分为多份,提高小任务利用率
  • 如果 Pod 请求 GPU 但一直 Pending,排查:节点 GPU 是否被占用、device-plugin 是否健康、taint/toleration 是否匹配

题目 4:Pod 跨 AZ 拓扑分布#

场景描述: 生产集群部署在 3 个可用区(AZ-a, AZ-b, AZ-c),服务有 9 个副本。要求每个 AZ 至少 2 个 Pod,且同一节点上不超过 1 个 Pod,实现跨 AZ 高可用。

解题思路:

使用 topologySpreadConstraints 控制 Pod 跨 Zone 均匀分布,podAntiAffinity 控制同一节点不共存多个副本。

关键 YAML 配置:

yaml
apiVersion: apps/v1
kind: Deployment
spec:
  replicas: 9
  template:
    spec:
      topologySpreadConstraints:
        # 跨 AZ 分布约束
        - maxSkew: 1
          topologyKey: topology.kubernetes.io/zone
          whenUnsatisfiable: DoNotSchedule    # 严格满足
          labelSelector:
            matchLabels:
              app: order-service
        # 跨 Node 分布约束
        - maxSkew: 1
          topologyKey: kubernetes.io/hostname
          whenUnsatisfiable: ScheduleAnyway   # 尽力满足
          labelSelector:
            matchLabels:
              app: order-service
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 100
              podAffinityTerm:
                labelSelector:
                  matchLabels:
                    app: order-service
                topologyKey: kubernetes.io/hostname

验证分布:

bash
kubectl get pods -l app=order-service -o wide | awk '{print $7}' | sort | uniq -c

注意事项:

  • 跨 Zone 约束用 DoNotSchedule(严格——如果 AZ 不足阻止调度,宁可 Pod Pending)
  • 跨 Host 约束用 ScheduleAnyway(尽力——节点不足时允许调度到同一节点)
  • maxSkew: 1 表示任意两个 topology 域内 Pod 数量差值不超过 1
  • 副本数应为 AZ 数量的整数倍,否则 Skew 约束可能导致部分 Pod Pending

题目 5:VPA Request 自动调整 + HPA 协同#

场景描述: 新上线的微服务 requests 靠经验随手填,部分过高浪费、部分过低频繁 OOM,需自动分析用量给出合理建议,并与 HPA 协同不打架。

方案正本见《5-方案设计型.md》· 子类 6「题目 2:资源 Request 优化策略」(VPA Recommender/Off 模式 + Goldilocks + 团队提 PR 降 requests)与子类 7「题目 2:弹性伸缩体系设计」(VPA 与 HPA 不并用于同一维度:VPA 管内存、HPA 管 CPU/QPS)。此处不再重复。


题目 6:Descheduler 再平衡#

场景描述: 集群运行一段时间后节点负载失衡(部分 80%+、部分 10%),Pod 都正常运行、K8s 不会自动迁移,需要主动再平衡。

方案正本见《5-方案设计型.md》· 子类 6「题目 4:Karpenter vs Cluster Autoscaler 选型」——CA 场景用 Descheduler(LowNodeUtilization 插件)驱逐低利用率节点上的 Pod、由 CA 回收空节点(遵循 PDB);Karpenter 则原生做 Consolidation。此处不再重复。