面试 · 场景排查型
答题结构约定(题干 vs 参考答案)
- 题干:每题的「故障现象」(排查题)或「场景描述」(方案题)即面试官抛出的问题,是题干。
- 参考答案:其后的「排查路径(决策树)→ 关键命令 → 根因 → 修复 → 预防」全部属于参考答案。
- 排查题遵循统一范式:现象 → 分层排查树 → 根因 → 修复 → 预防,答题时先说分层思路再落命令,别一上来堆命令。
- 子类 1–6 为纯排查题;后半部分「部署/配置/安全/弹性场景题」偏方案设计,与《5-方案设计型.md》有重叠,按需取用。
子类 1:Pod 异常排障题 — Pod 起不来、行为异常的排查#
题目 1:Pod 长时间 Pending — 资源不足排查#
故障现象:
新部署的 Deployment 所有 Pod 始终处于 Pending 状态,kubectl get pods 显示容器未创建,describe 显示 FailedScheduling 事件。
排查路径(决策树):
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 绑定失败
│ └── 转到存储排障题关键命令:
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 配置遗漏
修复方案:
- 短期:调低 Pod requests,使值和实际用量匹配(参考 VPA 推荐值)
- 中期:启用 Karpenter 并配置多规格 NodePool,开启 Consolidation
- 长期:配置 HPA + VPA,让资源申请和实际需求对齐;设置 Prometheus 告警提前预警节点资源
题目 2:Pod ImagePullBackOff — 镜像拉取失败排查#
故障现象:
Pod 状态显示 ImagePullBackOff 或 ErrImagePull,容器无法启动。
排查路径(决策树):
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关键命令:
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
修复方案:
- 使用
latesttag 的风险:建议用 Git commit SHA 作为 tag - 生产环境配置 imagePullSecrets + IRSA/RAM Role 自动刷新
- 关键镜像提前
skopeo copy到集群同 region 的私有仓库 - 配置 VPC Endpoint 消除 NAT Gateway 的跨 AZ 流量费和延迟
题目 3:Pod CrashLoopBackOff — 启动即崩溃排查#
故障现象: Pod 反复启动后立即崩溃,状态在 Running 和 CrashLoopBackOff 之间循环。describe 显示多次重启计数。
排查路径(决策树):
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关键命令:
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>常见原因与修复:
- 配置文件缺失:ConfigMap/Secret 未挂载或 key 拼写错误 → 检查 volumeMount 和 volume 匹配
- 依赖服务未就绪:启动时需要连接数据库/Redis/Kafka → 添加 initContainer 等待依赖 / 业务层加 retry
- 权限不足:容器以非 root 运行但挂载了 root 拥有的目录 → 配置 fsGroup 或 initContainer 修改权限
- 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 v2memory.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字段确认。
排查路径(决策树):
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关键命令:
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 + 大操作拆分 + 限流 |
| 启动即 OOM | limit 过低 | 参考 VPA 推荐值调整 limits |
预防措施:
- 启用 VPA Recommender 模式(不自动更新,只给推荐)
- Java 应用用
-XX:+UseContainerSupport(JDK 11+) - Java 应用
limit.memory>-Xmx* 1.3(留 30% 给 Native Memory + Metaspace) - 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.stat的nr_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/2 或 Init:Error,主容器未创建。describe 显示某个 initContainer 一直处于 Running 或 CrashLoopBackOff。
排查路径(决策树):
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 内容是否合法关键命令:
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常见根因与修复:
- 依赖服务未部署:initContainer 在等待尚不存在的 Service → 先部署依赖,再部署此 Pod
- 数据库连接配置错误:迁移脚本连不上 DB → 检查 Secret 中 DB_HOST/DB_PASS
- 权限问题:非 root init container 操作 root 拥有的 mount → 设置
securityContext.fsGroup - 无限等待:init 中没有设置 timeout → 加
set -e和 timeout 机制
题目 6:Pod Evicted — 驱逐排查#
故障现象:
Pod 状态变为 Evicted,或被自动驱逐后重建。describe 看到 The node was low on resource。多个 Pod 同时被驱逐,然后新 Pod 卡在 Pending。
排查路径(决策树):
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 处理 → 连接丢失 / 消息丢失关键命令:
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 状态修复方案:
- 短期:清理磁盘 →
crictl rmi --prune(containerd)→ ssh 到节点手动释放 - 中期:
- 配置 Prometheus 磁盘告警(> 80% 提前预警,不等 kubelet 的 90% 阈值)
- 配置日志轮转和自动清理
- 设置 PodDisruptionBudget 保护关键服务
- 长期:
- 升级 CNI 版本修复 IP 泄漏 bug
- 添加 ENI IP 使用率监控(terway_node_available_ip < 3)
- kubelet eviction 阈值调优 → 延迟驱逐但提前告警
子类 2:网络排障题 — Service 不通、DNS 解析失败的排查#
题目 1:Service 无法访问 — 从 Service 到 Pod 全链路排查#
故障现象:
Pod 内通过 Service 名称访问下游服务报 connection refused 或 no route to host。
排查路径(决策树):
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 流量关键命令:
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根因分析(按出现频率):
- EndpointSlice 无 ready 端点(selector 不匹配 / Pod 未 Ready)— 最常见
- targetPort 写错 — Service 的 targetPort 与容器实际监听端口不一致
- NetworkPolicy 拦截 — 默认拒绝所有时忘记放行(表现为 timeout 而非 refused)
- 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。
排查路径(决策树):
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 综合排障)关键命令:
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常见根因:
- 跨 namespace 访问没带命名空间:
my-service应写成my-service.other-namespace - CoreDNS OOMKilled:大集群缓存增长超出默认 170Mi limit → 调高到 512Mi
- 上游 DNS 故障:节点 /etc/resolv.conf 指向的 DNS 不稳定 → 在 Corefile 中直接指定可靠上游
- CoreDNS 副本不足:2 副本在高并发下无法承载 → 3-4 副本 + HPA
题目 3:DNS 解析 5 秒延迟 — conntrack 竞态条件#
故障现象:
应用间歇性出现 DNS 解析需要 5 秒才返回,日志显示 i/o timeout 或请求超时。问题复现率低,但一旦出现影响严重。
排查路径(决策树):
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)关键命令:
# 在 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。
排查路径(决策树):
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 证书是否已过期关键命令:
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>根因分析(按频率):
- Ingress class 不匹配(40%)→ 检查 ingressClassName
- 后端 Service selector 不匹配 → Endpoints 为空
- TLS secret 不存在或过期 → 检查 secret 的 namespace
- DNS wildcard 误导向 → dig 验证
- HTTPRoute 和 Gateway 引用链断裂(Gateway API 场景)
题目 5:NetworkPolicy 误拦截排查#
故障现象:
服务之间突然不通,但 Pod 和 Service 都正常。curl 超时不返回任何响应(不是 connection refused,是一直卡住)。
排查路径(决策树):
怀疑 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关键命令:
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)、连接超时、或间歇性不通。
排查路径(决策树):
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 状态。
排查路径(决策树):
节点 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。
关键命令:
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 containerdConditions 解读速查表:
| Condition | Status | 含义 | 处理 |
|---|---|---|---|
| MemoryPressure | True | 节点内存不足 | 清理内存 / 添加节点 |
| DiskPressure | True | 磁盘空间不足 | 清理磁盘 / 扩展卷 |
| PIDPressure | True | PID 耗尽 | 排查进程泄漏 / 调高 pid_max |
| NetworkUnavailable | True | CNI 未初始化 | 检查 CNI DaemonSet / 网络插件 |
| Ready | False | 节点不可调度 | 根据 Reason 具体排查 |
处置动作:确认节点有问题后先隔离,再修复/换机(cordon / drain)
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。
排查路径(决策树):
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 泄漏】修复方案:
- 短期:清磁盘 + 手动释放泄漏 IP(云厂商 API)
- 中期:Prometheus 告警 >80% 使用率提前预警;配置日志轮转
- 长期:配置 kubelet 日志最大文件数 / 镜像垃圾回收策略 / emptyDir sizeLimit
题目 3:kubelet 证书过期排查#
故障现象:
节点显示 NotReady,kubelet 日志报 certificate has expired 或 x509: certificate has expired。
排查路径:
# 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常见原因:
- kubeadm 部署的集群,kubelet 证书默认 1 年有效期
- 手动轮转只 renew 了 API Server 证书,忘了 kubelet 证书
- 时钟不同步导致证书被认为过期(NTP 问题)
修复方案:
- kubeadm 集群:
kubeadm certs renew all && systemctl restart kubelet - 配置自动轮转:开启 kubelet 的
--rotate-certificates+ controller manager 的自动批准 - 添加证书过期监控:
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 驱逐排序不完全一致。
排查路径(决策树):
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。
排查路径:
# 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常见原因与修复:
- 进程 fork 炸弹:代码 bug 导致无限 fork → 修复代码 + 设置 Pod PID limit
- 僵尸进程堆积:父进程未回收子进程 → init 进程需正确处理 SIGCHLD
- Pod PID 限制过低:kubelet 默认未限制 Pod PID → 配置
--pod-max-pids
子类 4:存储排障题 — PV/PVC/挂载相关排查#
题目 1:PVC Pending — 无法绑定 StorageClass#
故障现象:
PVC 状态为 Pending,describe 显示 waiting for a volume to be created 或 no persistent volumes available。
排查路径(决策树):
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 绑定了特定节点)关键命令:
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 volume 或 Volume is already used by pod(s),特别是使用 RWO (ReadWriteOnce) PV 时。
排查路径(决策树):
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 显示 Resizing 或 FileSystemResizePending,一直未完成。
排查路径:
# 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常见根因:
- StorageClass
allowVolumeExpansion: false→ 不支持动态扩容 - 云资源配额耗尽 → EBS/PVC 数量达到上限
- Pod 使用了 subPath 挂载 → 在线扩容不适用
- CSI Driver 不支持特定文件系统的在线扩容
题目 4:数据丢失风险 — PVC 孤儿与 StatefulSet 删除#
故障现象: 删除了 Deployment/StatefulSet 后,PVC 也被删除了(数据丢失)。或 PVC 未被删除但长期无人知晓(产生浪费)。
排查路径:
# 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(删除)修复方案:
- StatefulSet 设置
persistentVolumeClaimRetentionPolicy.whenDeleted: Retain(数据安全) - 定期跑孤儿 PVC 巡检脚本(CronJob),输出报告人工确认后删除
- 对有状态服务操作前,先确认存储后端(filesystem vs 对象存储)
kubectl delete statefulset --cascade=orphan保留 PVC(旧版本默认行为)
子类 5:安全排障题 — RBAC/权限/安全策略排查#
题目 1:ServiceAccount 权限不足排查#
故障现象:
Pod 或外部客户端调用 K8s API 报 403 Forbidden,日志显示 User "system:serviceaccount:<ns>:<sa>" cannot <verb> <resource>。
排查路径(决策树):
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)修复示例:
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 是否有某操作的权限。
排查命令:
# 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 会漏:
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:直接观测"哪条规则丢了包",别靠猜
# 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。错误信息包含 privileged、baseline、restricted 等级别提示。
排查路径:
# 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:所有部署卡住,滚动更新失败,业务不可用
逐层排查过程:
# 第一层:看到 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根因完整链路:
磁盘 >90% → kubelet DiskPressure → 强制驱逐 Pod(不等 preStop)
→ Terway preStop/删除钩子未执行 → IP 未回收(CRD 状态仍为"已分配")
→ 节点 ENI IP 耗尽 → 新 Pod 调度时无可用 IP → Pending
→ 所有业务部署挂掉修复方案:
- 立即止血:手动释放泄漏 IP(云厂商 API UnassignPrivateIpAddresses)
- 清磁盘:
crictl rmi --prune+crictl rmp -a(containerd;老 Docker 集群才用docker system prune -af) - 监控补盲:加 Terway ENI IP 使用率监控(< 3 个告警)、磁盘 >80% 告警
- 长期修复:升级 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 手动测试时表现正常。
排查路径:
# 第一步:查看 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:调低 ndots 值(推荐)yaml
spec: dnsConfig: options: - name: ndots value: "2" # 只有 1 个点的域名才走 search 域 - 方案 2:外部域名使用 FQDN(末尾加点,跳过 search 域)python
requests.get("http://api.example.com./v1/data") # 末尾加 . - 方案 3:结合 single-request-reopen 避免 conntrack 竞态
- 方案 4:CoreDNS cache 调优,缓存 NXDOMAIN 响应减少重复查询
题目 3:集群级连锁故障 — 僵尸集群导致系统级雪崩#
故障现象:
kubectl apply 或 ArgoCD sync 周期性卡顿 30-32 秒后超时。argocd app list 和 UI 响应极慢。集群中所有 reconcile 都在排队。
排查路径(决策树):
集群级响应变慢 / 周期性 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 最佳),不能与其他服务共享磁盘根因分析(按频率):
- ArgoCD 残留 cluster secret 指向已删除集群(最常见)→ controller reconcile 排队卡死
- etcd 磁盘 IO 瓶颈 → 整个控制面板响应慢
- API Server 连接数过多 → 配置 max-requests-inflight 限制
- 代理服务器过载 → 内部流量被代理拖慢
预防措施:
- 删除集群时过四层清理(物理/逻辑/IaC/GitOps 层)
- 删除集群的 checklist 必须包含清理 ArgoCD cluster secret
- etcd 独立 SSD + 定期备份 + 定期 defrag
- Prometheus etcd 监控:
etcd_disk_wal_fsync_duration_seconds_bucket、etcd_server_has_leader
题目 4:Pod 被 ApplicationSet 反复复活#
故障现象:
尝试 kubectl delete application <name> 或 argocd app delete <name> 删除 ArgoCD Application,Application 删除后立即又出现,状态和之前一样。
排查路径:
# 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 天未被评估过。
排查路径:
# 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 配置文件。
修复方案:
- 修正 PrometheusRule 的 labels 使其匹配 ruleSelector
- 建立每周巡检(CronJob)对比 git 规则数和集群实际加载规则数
- 任何 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
逐层排查过程:
# 第一层:从用户反馈倒推
# - 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 件套同时成立):
- 跨环境业务表 id 区间重叠
- 广播总线共用(同 broker 同 vhost 同 exchange)
- 消费者未按环境标签过滤
- 配置中心从老环境复制时漏改 host/vhost
修复方案:
- 清洗数据:CTAS 备份 → 临时表中转 → 分批 DELETE(每批 1000 行+0.2s 间隔)
- 基础设施隔离:
- 独立 Aurora cluster(Serverless v2, $50/月起)
- 独立 RabbitMQ broker(mq.m7g.medium, $71/月)
- 独立 Valkey 实例(cache.t4g.micro, $11/月)
- 编码层防御:
- 业务表
AUTO_INCREMENT偏移 ≥ 10,000,000 - 消费侧强制按
dispatch_env过滤
- 业务表
- 流程改进:新环境上线必须过 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 定义版本子集。
- 部署新版本 Deployment(v2),确保与旧版本(v1)有区分性的 label(如
version: v2) - 创建 DestinationRule 定义 v1 和 v2 两个 subset
- 创建 VirtualService 配置流量权重分配
- 观察 Grafana 监控指标(P99 延迟、错误率),逐步调整权重
关键 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验证与推进:
# 观察 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 分析。
解题思路:
分紧急止血和保留现场两条线并行:
- 立即回滚:使用 Deployment 的 revision history 回滚到上一个稳定版本
- 保留现场:在回滚前导出故障 Pod 的日志和事件
# 第一步:确认发布历史
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回滚后验证:
# 确认所有 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)。
推荐方案:
- ConfigMap 以 Volume 方式挂载到容器(而非环境变量)
- 应用代码内监听文件变化(使用 inotify 或配置框架的热加载能力)
- 修改 ConfigMap 后,kubelet 会在约 1 分钟内同步到挂载目录(
--sync-frequency默认 1m)
关键 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 + 版本化命名 + 触发滚动更新"模式:
# 每次配置变更创建新的 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(快速落地)
# 在集群中部署 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(企业级推荐)
# 在 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 前自动解密。
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:
- 创建 StorageClass 定义 EBS provisioner 和参数
- StatefulSet 中通过
volumeClaimTemplates声明 PVC 模板 - K8s 为每个 Pod 自动创建独立的 PVC(如
data-kafka-0、data-kafka-1)
关键 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/dataPVC 绑定流程:
- StatefulSet 创建 Pod
kafka-0,自动生成 PVCdata-kafka-0 - StorageClass provisioner 在 pod 所在 AZ 创建 EBS 卷,生成 PV
- 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):
- 先扩容一个 follower broker(影响小)
- 等待 PVC 状态从
Resizing变为Bound(由 CSI 驱动完成底层卷扩容) - 验证数据完整性后,逐个扩容其余节点
操作步骤:
# 第一步:确认 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 管理
配置结构示例:
# 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。
解题思路:
- 在目标 Namespace 创建专用 ServiceAccount
ci-deployer - 创建 Role 授予对 Deployment、Service、ConfigMap 的 CRUD 权限(限定在该 Namespace)
- 通过 RoleBinding 绑定
- CI 使用该 ServiceAccount 的 Token 连接集群
完整配置:
---
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。
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 配置:
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: {}渐进式落地策略:
- Week 1-2:先打
warn标签,观察日志中的违规 Pod - Week 3-4:整改不符合的 Pod 配置,同时开启
audit记录 - Week 5:确认无违规后,切换到
enforce强制拦截
注意事项:
readOnlyRootFilesystem: true后应用写/tmp会失败,必须挂载 emptyDir- Istio sidecar 需要
NET_ADMIN和NET_RAW能力,必要时配置豁免 - 检查基础镜像的默认用户(某些镜像默认 root 运行),Dockerfile 中指定
USER 1000 warn→audit→enforce三步走是安全推行 PSA 的最佳实践
题目 4:Secret 外部管理(Vault/ESO)#
场景描述: 公司统一使用 HashiCorp Vault 管理所有敏感凭据。需要将 Vault 中的数据库密码同步到 K8s Secret,并支持密码轮转时 Secret 自动更新。
解题思路:
部署 External Secrets Operator(ESO),通过 ClusterSecretStore 连接 Vault,用 ExternalSecret CR 定义同步规则。
完整配置:
# 第一步: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
# 安装 Reloader
helm install reloader stakater/reloader# 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。
完整配置:
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_ADMIN和NET_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 自动扩缩,防止流量峰值时服务不可用,但也不能频繁抖动。
解题思路:
- 设置资源 requests(CPU
500m),这是 HPA 计算 CPU 利用率的基础 - 配置 HPA 双指标:CPU 利用率目标 70% + 自定义 QPS 指标(每 Pod 目标 1000 QPS)
- 扩容要快、缩容要慢:behavior 中区分 scaleUp 和 scaleDown
关键 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 资源配置:
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 不支持)。
完整配置:
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 频繁起停 Podfallback设置合理的兜底副本数,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 声明容忍。
操作步骤:
# 第一步:给 GPU 节点打 taint
kubectl taint nodes gpu-node-01 nvidia.com/gpu=present:NoSchedule
# 同时打标签(亲和性使用)
kubectl label nodes gpu-node-01 node-type=gpuGPU Pod 配置:
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 配置:
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验证分布:
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。此处不再重复。