路线图

面试 · 系统串联题

星辉 2026-07-02 阅读 13 min 2,658 字 路线图
面试 · 系统串联题 封面

这份是什么:这里收录的是「锚点题」——真实生产反复用、K8s 面试跑不掉、且能一题炸开成一棵知识树的综合题。考官抛出一个高频问题后,会顺着你的回答一层层往深追(现象 → 机制 → 边界 → 定位 → 治理),一道题就能把调度、网络、存储、探针、QoS、RBAC 等横向串起来。

与 1~5 类的关系

  • 1-概念解释型 / 2-对比辨析型 / 3-原理追问型单点知识(一个概念、一对对比、一个机制)。
  • 4-场景排查型 / 5-方案设计型单场景的排查树或方案。
  • 本档凌驾于 5 类之上:每道锚点题都会串出上述多个单点,并给出「考官追问链」,帮你把散点连成网。考前重点刷这一份——顺着追问链自己讲一遍,卡壳的地方回对应类补齐。

怎么用:先盖住「参考答题骨架」,自己顺着「考官追问链」从第 1 层讲到最后一层;讲不下去的层,就是你的知识断点。「踩坑/加分点」是区分「背过」和「真做过」的分水岭,务必内化成自己的话。

技术基线:本档结论对齐 2026 年年中主流版本(K8s 1.33+,原生 sidecar 与 nftables kube-proxy 模式均已在 1.33 GA;iptables 仍是 kube-proxy 上游默认模式)。


锚点题 1:Pod 起不来,给我一套完整的排查树#

为什么是锚点题:这是 K8s 面试出现频率最高的问题,没有之一。它同时是运维日常最高频的动作。答得好不好,直接暴露你是「跑过 demo」还是「扛过线上」。四大异常状态(Pending / ImagePullBackOff / CrashLoopBackOff / OOMKilled)背后是调度、镜像、探针、资源、cgroup 五套完全不同的机制,一题就能验区分度。

会串出的知识点地图

  • 通用起手式get pod(STATUS/READY/RESTARTS)→ describe pod(看 Events,八成答案在这)→ logs --previous(看崩溃前输出)→ get events --sort-by(全局时间线)
  • Pending 分支:kube-scheduler 预选/优选、resources.requests、Taint/Toleration、nodeSelector/nodeAffinity、topologySpreadConstraints、PVC 未绑定(WaitForFirstConsumer)、节点全 cordon
  • ImagePullBackOff / ErrImagePull 分支:镜像名/tag、imagePullSecret(kubernetes.io/dockerconfigjson)、私有仓库网络/证书、Docker Hub 限流、CPU 架构不匹配(arm64/amd64)、imagePullPolicy: Never 导致的 ErrImageNeverPull
  • CrashLoopBackOff 分支:应用启动即退、命令/ENTRYPOINT 写错、探针误杀(liveness vs startupProbe)、配置/依赖缺失、退避算法、exit code 语义
  • OOMKilled 分支:cgroup OOM(容器超 memory limit)vs 内核全局 OOM(节点内存耗尽)、可压缩 vs 不可压缩资源、runtime 对 cgroup 的感知(GOMEMLIMIT / -XX:MaxRAMPercentage

考官追问链

  1. 现象层kubectl get pod 里这四种状态你怎么一眼区分?分别对应生命周期的哪个阶段?
  2. 起手式层:拿到一个起不来的 Pod,前三条命令敲什么?为什么 logs 要加 --previous
  3. 机制层:CrashLoopBackOff 的重启退避是怎么算的?exit code 137 / 143 / 127 分别代表什么?OOMKilled 的 137 和 liveness 探针杀容器的 137 怎么区分?
  4. 边界层:liveness、readiness、startup 三个探针分工是什么?为什么「应用启动慢」这种场景配 liveness 会把自己搞成 CrashLoop,正确做法是什么?
  5. 定位/治理层:describe 的 Events 空了、logs 也没东西(比如容器根本没起来),你下一步去哪查?(→ 节点级 journalctl -u kubelet / dmesg / 运行时日志)如何从根上减少这类问题?

参考答题骨架

先说通用四步,再分四支。

通用起手式

bash
kubectl get pod <pod> -n <ns> -o wide      # 看 STATUS / READY / RESTARTS / 所在节点
kubectl describe pod <pod> -n <ns>          # 看 Events + 每个容器的 State/Last State/Exit Code
kubectl logs <pod> -c <container> --previous # 看"上一次"崩溃前的输出(关键)
kubectl get events -n <ns> --sort-by='.lastTimestamp'  # 全局时间线,找连锁线索

分支一:Pending —— 还没被调度/绑定到节点

  • 本质:调度失败,describe 的 Events 里是 FailedScheduling
  • 常见原因:
    • Insufficient cpu/memory:节点剩余资源装不下 Pod 的 requests(不是 limits)→ kubectl describe node 看 Allocated,调低 requests / 扩节点 / 开 Autoscaler。
    • node(s) had taint {...} that the pod didn't tolerate:污点不容忍 → 加 toleration 或移除 taint。
    • node(s) didn't match node selector/affinity:标签不匹配。
    • didn't match pod topology spread constraints:拓扑约束过严。
    • PVC 没绑定:WaitForFirstConsumer 模式下要等调度、或根本没有可用 PV → 转存储排查。
  • 区分:Pending 是「没落到节点」;如果已绑定节点但容器没起,状态是 ContainerCreating(多为拉镜像/挂卷/CNI 分配 IP 阶段),别混。

分支二:ImagePullBackOff / ErrImagePull —— 镜像拿不到

  • ErrImagePull 是首次失败,ImagePullBackOff 是退避重试中。
  • 排查:describe 看 Events 里的具体报错。
    • not found / manifest unknown:镜像名或 tag 写错、tag 不存在。
    • unauthorized / pull access denied:私有仓库缺 imagePullSecret,或 secret 类型不对(必须是 kubernetes.io/dockerconfigjson),或 SA 没引用该 secret。
    • dial tcp ... timeout:节点到仓库网络不通 / 私有 registry 证书问题 / Docker Hub 匿名拉取限流。
    • no match for platform:镜像架构与节点不符(arm64 vs amd64)。
    • ErrImageNeverPullimagePullPolicy: Never 但节点本地没这个镜像。

分支三:CrashLoopBackOff —— 起来又挂,反复重启

  • 本质:容器进程反复退出,kubelet 按退避重启,间隔 10s→20s→40s…封顶 5 分钟
  • describe 看容器 Last State: TerminatedExit Code + Reasonlogs --previous 看崩溃前输出:
    • Exit 0:正常退出(可能是 command 跑完就结束,本不该是长驻进程)。
    • 1/2:应用自身报错(配置缺失、依赖不可达、端口占用)。
    • 137 = 128+9(SIGKILL,多为 OOM 或 liveness 探针杀)。
    • 143 = 128+15(SIGTERM,被优雅终止)。
    • 139 = SIGSEGV(段错误)。
    • 126 命令不可执行 / 127 命令未找到(ENTRYPOINT、command 写错)。
  • 高频误配:应用启动要 30s,却配了 livenessProbe initialDelaySeconds 太短 → 还在启动就被判失败杀掉 → 无限 CrashLoop。正解:用 startupProbe 兜住慢启动,startup 通过后 liveness 才开始生效。

分支四:OOMKilled —— 内存超限被杀

  • describeLast State: Terminated, Reason: OOMKilled, Exit Code: 137
  • 两种 OOM 要分清:
    1. cgroup OOM:容器用量超 limits.memory,被该容器 cgroup 的 OOM killer 杀,只杀这一个容器,Reason 明确写 OOMKilled。
    2. 内核全局 OOM:整个节点内存被打满,Linux 内核 OOM killer 按 oom_score_adj 挑进程杀,可能殃及邻居
  • 定位:kubectl top pod 看真实用量;看 limit 是不是设太小;查内存泄漏;查 runtime 是否感知 cgroup——老 JVM 不加 -XX:+UseContainerSupport/MaxRAMPercentage 会按宿主机内存算堆,Go 不设 GOMEMLIMIT 也可能自杀式 OOM。

踩坑 / 加分点

  • memory 是不可压缩资源,超 limit 直接杀;CPU 是可压缩资源,超 limit 只 throttle(限流)不杀——很多人把「CPU 打满导致 Pod 挂」归错因,其实 CPU 从不会因超限被杀,只会变慢。
  • Events 有保留期(默认约 1 小时),排查慢了事件就没了,要养成第一时间 describe + 存现场的习惯。
  • describelogs 都空、容器压根没进入运行的情况(比如 CNI 分不到 IP、挂卷 Multi-Attach、镜像层解压失败),必须上节点看 journalctl -u kubeletcrictl ps -a / crictl logsdmesg。这一步能不能想到,是「只会 kubectl」和「懂底层」的分水岭。
  • 原生 sidecar(1.33 GA,initContainers + restartPolicy: Always)挂了也会拖垮主容器启动,排查时别漏了 init/sidecar 容器的独立日志。
  • 治理侧一句话收口:合理 requests/limits + 探针分工(startup 兜启动、readiness 控流量、liveness 保活)+ 镜像预热/多架构清单 + PDB,能把这四类问题的发生率压到最低。

锚点题 2:集群里 A 服务访问 B 服务不通,你怎么分层排查?#

为什么是锚点题:微服务落 K8s 后,「服务间调不通」是最高频的线上故障,也是面试网络能力的照妖镜。它天然是一条自上而下的分层链,每一层都是一个独立知识点,答的过程就是在画 K8s 的整张网络拓扑。

会串出的知识点地图

  • DNS 层:CoreDNS、/etc/resolv.confnameserver(kube-dns ClusterIP,kubeadm 默认 10.96.0.10)、search 域、ndots:5、FQDN <svc>.<ns>.svc.cluster.local、跨 ns 要带 namespace
  • Service / Endpoints 层:selector 与 Pod label 匹配、EndpointSlice、readiness 没过的 Pod 不进 Endpoints、targetPort
  • 直连 Pod IP:二分法定位「问题在 Service 层还是 Pod/网络层」
  • kube-proxy 层:iptables / IPVS / nftables 模式、ClusterIP 是虚 IP 靠 DNAT、kube-proxy DaemonSet 健康
  • CNI / 跨节点层:overlay(VXLAN) vs BGP vs eBPF、MTU、节点安全组
  • NetworkPolicy 层:默认全通 → 一旦被 policy 选中即默认拒绝、别忘放行 DNS egress、Calico/Cilium
  • Ingress 层:仅当经外部入口时

考官追问链

  1. 现象层:先别急着查,你怎么定位是「解析失败」「连接被拒」还是「连上了但超时」?在哪个 Pod 里、用什么命令测?
  2. DNS 层nslookup b-svc 返回什么算正常?为什么有时候跨 namespace 调用要写全 b-svc.other-nsndots:5 是什么、会带来什么副作用?
  3. Service 层kubectl get endpoints b-svc 是空的,你会怀疑哪两件事?为什么「Pod 明明 Running 却不在 Endpoints 里」?
  4. 二分定位:直连 Pod IP 能通、走 Service 不通,说明问题在哪层?反过来呢?
  5. 策略/底层层:确认到了 kube-proxy / NetworkPolicy 层,分别怎么验证?为什么加了 NetworkPolicy 后「偶发解析失败」是经典坑?

参考答题骨架

我会在 A 的 Pod 内从上往下逐层排除(没有工具就 kubectl debug 起 netshoot ephemeral container)。

第 0 步 · 定位现象

bash
kubectl exec -it <A-pod> -- sh
nslookup b-svc            # 先分清:是解析不了?还是解析了连不上?
curl -v b-svc:8080        # 看是 DNS 失败 / connection refused / timeout

第 1 层 · DNS

  • /etc/resolv.confnameserver 应指向 kube-dns 的 ClusterIP(如 10.96.0.10),search 应含 <ns>.svc.cluster.local svc.cluster.local cluster.localoptions ndots:5
  • CoreDNS 是否健康:kubectl get pod -n kube-system -l k8s-app=kube-dns,看有没有崩、有没有被限流。
  • 跨 namespace 调用只写短名(b-svc)会先在本 ns 拼后缀,解析不到 → 要写 b-svc.<b-ns> 或全 FQDN。
  • 上游解析(外部域名)走 CoreDNS Corefile 的 forward

第 2 层 · Service / Endpoints(最高频命中)

bash
kubectl get svc b-svc -o wide          # 有没有 ClusterIP,targetPort 对不对
kubectl get endpointslices -l kubernetes.io/service-name=b-svc   # 后端有没有 Pod IP
  • Endpoints 为空 → 两大嫌疑:① Service 的 selector 和 B 的 Pod label 不匹配;② B 的 Pod 没通过 readinessProbe——未就绪的 Pod 不会被加进 Endpoints(这是「Running 却收不到流量」的头号原因)。
  • targetPort 与容器实际监听端口不一致也会「连上但拒」。

第 3 层 · 直连 Pod IP(二分法)

bash
curl <B-pod-ip>:8080
  • 直连通、走 Service 不通 → 问题在 Service / kube-proxy 层。
  • 直连也不通 → 问题在 Pod 应用本身或 CNI 网络层。

第 4 层 · kube-proxy

  • 每个节点的 kube-proxy DaemonSet 是否健康(挂了则新 Service 规则不再下发)。
  • 看模式并核对规则:iptables 模式 iptables-save | grep <clusterIP>;IPVS 模式 ipvsadm -Ln;nftables 模式(1.33 GA)nft list ruleset。ClusterIP 是虚拟 IP,靠 kube-proxy 下发 DNAT 规则转到真实 Pod IP。

第 5 层 · CNI / 跨节点

  • A、B 在不同节点却不通:overlay(VXLAN)路由 / BGP(Calico)/ eBPF(Cilium)问题;MTU 不匹配(VXLAN 封装吃掉约 50 字节,大包被丢、小包正常,表现为「curl 小接口好使、传大 body 卡死」);节点间安全组/防火墙没放 CNI 端口。

第 6 层 · NetworkPolicy

bash
kubectl get netpol -n <ns>
  • 默认没有 policy 时全通;一旦有 policy 选中某 Pod,该 Pod 就默认拒绝所有未显式放行的流量。检查 B 的 ingress 是否允许来自 A(按 label/namespace)、A 的 egress 是否允许到 B。Calico/Cilium 的策略比原生更强(可按 FQDN、L7)。

第 7 层 · Ingress(仅当从集群外或经 ingress 进来):Ingress Controller、rules 的 host/path、backend service。

踩坑 / 加分点

  • NetworkPolicy 忘了放行 DNS egress(到 kube-dns 的 UDP/TCP 53)是最隐蔽的坑:表现是「服务时通时不通/偶发解析超时」,因为业务连接靠 IP 直连能过,但每次域名解析被策略拦。上了 default-deny egress,第一条必须放 DNS。
  • readiness 探针没过导致 Endpoints 空——排查时先看这个,能省一半时间。
  • conntrack 表打满(nf_conntrack: table full)会随机丢连接,高并发短连接场景要查 net.netfilter.nf_conntrack_max
  • 历史遗留的 CoreDNS + ndots:5 + UDP conntrack 竞态会造成「DNS 偶发 5 秒延迟」,可用 NodeLocal DNSCache 或 dnsConfig 调 ndots 缓解(细节见 4-场景排查型 的 DNS 专题)。
  • Service.spec.externalTrafficPolicy: Local 会让没有本地 endpoint 的节点直接丢包,也能表现成「部分不通」。

锚点题 3:如何让一次滚动更新做到真正零停机?#

为什么是锚点题:发布是每天都在做的高危动作,「零停机」几乎是所有面试官都会追的点。它能一路串出 Deployment 策略、三种探针、Pod 优雅终止的完整时序、信号处理、PDB——而且绝大多数人只会答 maxUnavailable,答不全 preStop 与 Endpoints 摘除的时序竞态,区分度极高。

会串出的知识点地图

  • Deployment 策略RollingUpdatemaxSurge / maxUnavailable(默认各 25%)、先扩后缩
  • readinessProbe:决定何时把新 Pod 加入 Endpoints
  • Pod 优雅终止时序:SIGTERM + preStop hook 与 Endpoints 摘除是并行异步的,中间有流量窗口
  • preStop hook + terminationGracePeriodSeconds(默认 30s):用 sleep 补上摘流量的时间差
  • 应用信号处理:捕获 SIGTERM、PID 1 问题、exec 形式 CMD、tini
  • PDB(PodDisruptionBudget):保护自愿驱逐(drain/升级)时的最小可用数
  • 长连接(gRPC/WebSocket)额外 drain

考官追问链

  1. 现象层:只把 maxUnavailable 设成 0 就零停机了吗?为什么还会有几秒 5xx / connection reset?
  2. 机制层:删一个 Pod 的瞬间,K8s 内部并行发生了哪几件事?为什么「Endpoints 摘除」和「容器收 SIGTERM」的先后没有保证,会产生什么窗口?
  3. 补丁层:业界为什么普遍在 preStopsleep 几秒?这几秒到底在等谁?和 terminationGracePeriodSeconds 是什么关系?
  4. 应用层:应用没捕获 SIGTERM 会怎样?为什么 CMD ["sh","-c","java ..."] 这种写法会让优雅关闭失效?
  5. 运维边界层:滚动更新受 PDB 约束吗?那 PDB 到底在什么场景救你?长连接服务怎么额外处理?

参考答题骨架

零停机是**「保容量 + 控流量进出时机 + 优雅退出」三件事的合力**,缺一不可。

1)Deployment 策略——保容量

yaml
strategy:
  type: RollingUpdate
  rollingUpdate:
    maxUnavailable: 0    # 更新期间可用副本数不掉(先拉起新的再停旧的)
    maxSurge: 1          # 允许临时多 1 个副本

maxUnavailable: 0 保证任意时刻健康副本数 ≥ 期望值。

2)readinessProbe——控流量「进」的时机

  • 没有 readiness,新 Pod 一进 Running 就被加进 Endpoints 开始收流量,但应用可能还没热身好(连接池、缓存、JIT)→ 打过去就报错。
  • readiness 通过后才进 Endpoints,这是新 Pod 平滑接流量的前提。

3)优雅终止时序——控流量「出」的时机(核心难点) 删 Pod 瞬间,K8s 并行做两件事,二者没有先后保证:

  • A:kubelet 对容器发 SIGTERM + 执行 preStop hook。
  • B:EndpointSlice controller 把该 Pod 从 Endpoints 摘除 → 各节点 kube-proxy / ingress 异步更新转发规则。

竞态窗口:如果 A 比 B 快,容器已经开始关闭,但规则还没更新完,仍有新连接被路由到正在关闭的 Pod → connection refused / reset。 补丁preStopsleep,把关闭动作往后拖,给 B 的传播留时间:

yaml
lifecycle:
  preStop:
    exec:
      command: ["sh", "-c", "sleep 10"]   # 等 Endpoints/iptables 摘干净再让进程收尾
terminationGracePeriodSeconds: 45          # 必须 > preStop sleep + 应用 drain 时间

时序:SIGTERM 发出/preStop 开始 → sleep 10s(期间流量被摘除)→ 应用优雅关闭 → 若超过 grace period 仍未退 → SIGKILL 强杀preStop + 应用关闭时间 必须小于 terminationGracePeriodSeconds(默认 30s),否则被强杀。

4)应用信号处理

  • 进程要捕获 SIGTERM:停止接新请求、drain 在途请求、关连接池,然后退出。
  • PID 1 陷阱CMD ["sh","-c","java -jar app.jar"] 里真正的进程不是 PID 1(是 sh),信号不透传给 java → 收不到 SIGTERM → 只能等 grace period 到点被 SIGKILL 硬杀,在途请求全断。用 exec 形式(CMD ["java","-jar","app.jar"])或加 tini 做 init。

5)PDB——保护运维场景

yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
spec:
  minAvailable: 2          # 或 maxUnavailable
  selector: { matchLabels: { app: my-svc } }
  • 注意:滚动更新本身由 Deployment 控制器负责,不受 PDB 约束;PDB 约束的是自愿驱逐kubectl drain、节点升级、Cluster Autoscaler 缩容)。它保证「运维操作不会一次干掉太多副本打穿容量」。零停机的完整答案必须把这块也讲到,否则「发布零停机但升级节点时被打穿」。

踩坑 / 加分点

  • 只配 maxUnavailable 不配 readiness = 假零停机:新 Pod 没热身就接流量,照样 5xx。
  • preStop sleep 是业界公认的补丁,根因是 Endpoints 摘除的传播延迟无法与 SIGTERM 同步;不加就是「发布必抖 3~5 秒」。
  • PID 1 信号不透传是最常见的「优雅关闭没生效」根因,且极隐蔽(本地跑好好的)。
  • 长连接(gRPC、WebSocket、DB 连接池):SIGTERM 后已建立的长连接不会自动断,要应用主动发 GOAWAY / 关连接,并配合 LB 层的 connection draining;只靠 K8s 机制不够。
  • 加分总结一句:maxUnavailable=0 保容量 → readiness 控进流 → Endpoints 摘除 + preStop sleep 控出流 → SIGTERM 优雅关闭在 grace period 内完成 → PDB 兜运维场景,五环相扣才是真零停机。

锚点题 4:一个节点变成 NotReady,集群里会发生什么?你怎么处理?#

为什么是锚点题:节点故障是生产必然遇到的事,这题既考底层(kubelet/运行时/PLEG 心跳机制),又考「故障后集群的自动行为」(污点驱逐 + 宽限期),还考运维操作(cordon/drain)和数据安全边界(StatefulSet 不自动重建)。能把「NotReady ≠ Pod 立即停」和「5 分钟驱逐宽限期」讲清楚的人,才是真扛过节点故障的。

会串出的知识点地图

  • 判定机制:kubelet 上报 node lease(默认 10s 一次)+ node status;kube-controller-manager 的 node-monitor-grace-period(默认约 40s)判 NotReady
  • NotReady 根因:kubelet 挂/失联、容器运行时(containerd/CRI)挂、PLEG is not healthy、CNI 未就绪(NetworkUnavailable)、资源压力(Memory/Disk/PID Pressure)
  • 自动驱逐:node controller 打 node.kubernetes.io/not-ready:NoExecute / unreachable:NoExecute 污点;Pod 默认带 tolerationSeconds=300(5 分钟宽限)后才被驱逐重建
  • 数据面影响:Pod 可能仍在跑(僵尸);readiness 无法上报导致流量黑洞;StatefulSet Pod 卡 Terminating 不自动重建(防脑裂)
  • 运维动作cordon(只停调度)→ drain --ignore-daemonsets(驱逐,受 PDB 约束)→ 维修 → uncordon / delete node

考官追问链

  1. 机制层:apiserver 是怎么知道一个节点「掉线」的?多久判定?为什么不是立刻?
  2. 现象层:节点变 NotReady 的常见根因有哪些?「PLEG is not healthy」是什么意思,通常指向哪里?
  3. 自动行为层:判定 NotReady 后,上面的 Pod 会立刻被迁走吗?为什么默认要等 5 分钟?这个 5 分钟怎么来的、能不能调?
  4. 数据面边界层:节点失联但 Pod 其实还在跑,会不会仍有流量打过去(黑洞)?为什么 StatefulSet 的 Pod 会卡在 Terminating 不重建,强删有什么风险?
  5. 处理层:确认节点要维修,cordondrain 分别干什么?drain 卡住不动通常是什么原因?

参考答题骨架

判定机制

  • kubelet 每 ~10s 续约 node lease,并周期上报 node status。
  • kube-controller-manager 的 node lifecycle controller 在 node-monitor-grace-period(默认约 40s)内收不到更新 → 把 Node 标为 NotReady

NotReady 常见根因

  • kubelet 进程挂了 / 与 apiserver 网络断(unreachable)。
  • 容器运行时挂(containerd/CRI 不响应)。
  • PLEG is not healthy:Pod Lifecycle Event Generator 定期 relist 容器状态超时,通常是 runtime 卡住、磁盘 IO 慢、单节点容器数过多。
  • CNI 没就绪 → node condition NetworkUnavailable
  • 资源压力:MemoryPressure / DiskPressure / PIDPressure

判定后的自动行为(关键)

  1. node controller 给节点打 NoExecute 污点:node.kubernetes.io/not-ready.../unreachable
  2. Pod 若不容忍该污点会被驱逐——但准入控制器默认给所有 Pod 注入 tolerationSeconds=300 的容忍。所以节点抖动的头 5 分钟,Pod 不会被立刻迁走,避免短暂抖动引发大规模重调度。超过 5 分钟才在别的节点重建。
  3. 这个宽限期可通过 Pod 的 toleration tolerationSeconds 调小(加快故障转移),但会更敏感于抖动。

数据面影响(区分度所在)

  • NotReady ≠ Pod 立即停止:kubelet 失联时容器可能还在宿主机上跑(僵尸),只是 apiserver 视角认为不可用。
  • 流量黑洞:kubelet 失联无法上报 readiness,Pod 在 Endpoints 里的状态可能停在旧值,kube-proxy 仍把流量转过去 → 打到一个可能已经半死的实例。真正靠谱的摘除依赖健康探测与 Endpoints 更新。
  • StatefulSet 的特殊性:节点 unreachable 时,mysql-0 会进入 Terminating不会自动重建——因为 K8s 无法确认老 Pod 真的死了,贸然在别处拉起同标识 Pod 可能双写导致数据脑裂。需要人工确认节点确实死了再 --force --grace-period=0 删除(有风险,要先做 fencing)。

处理流程

bash
kubectl describe node <node>                 # 看 Conditions / Events 定位根因
# 上节点(或带外)确认:
systemctl status kubelet && journalctl -u kubelet -n 200
systemctl status containerd
df -h; free -m                               # 磁盘/内存压力

kubectl cordon <node>                        # 标记不可调度(不影响现有 Pod)
kubectl drain <node> --ignore-daemonsets --delete-emptydir-data  # 驱逐 Pod(受 PDB 约束)
# 维修 …
kubectl uncordon <node>                      # 恢复调度
# 若节点报废,确认 Pod 已在别处重建后:
kubectl delete node <node>

踩坑 / 加分点

  • cordon 只停「新调度」,不动现有 Pod;drain 才真正把 Pod 赶走——两者常被混。
  • drain 卡住不动的三大原因:① PDB 不允许(会打穿 minAvailable);② 有 emptyDir 数据(需 --delete-emptydir-data);③ 有非控制器管理的裸 Pod(需 --force)。
  • StatefulSet Pod 卡 Terminating 时无脑 --force 删除是数据事故高发点(主库双写);正确姿势是先确认节点物理下线/断电(fencing)再删。
  • 5 分钟驱逐宽限(tolerationSeconds=300)是「快速故障转移」和「抗抖动」的权衡旋钮,金融级快切场景会调小,但要配合更稳的网络。
  • DaemonSet Pod 不会被 drain 驱逐(--ignore-daemonsets 是常态),别指望 drain 能清空节点上的 CNI/日志 agent。

锚点题 5:一个请求从集群外进来,到 Pod 里被处理,完整路径是什么?#

为什么是锚点题:这是「南北向流量」的全景题,一口气串起 DNS、云 LB、Ingress、Service、kube-proxy、CNI、容器网络。能把每一跳讲清楚,等于把 K8s 网络的骨架画了一遍;而且它是排查「外部访问异常」「源 IP 丢失」「TLS 在哪终止」等一类问题的地图。

会串出的知识点地图

  • DNS → 云 LB:域名解析到公网 LB IP;L4/L7 LB
  • Ingress Controller:本身是集群内 Pod(nginx/Envoy/Gateway API 实现),经 LoadBalancer/NodePort Service 暴露;做 host/path 路由 + TLS 终止
  • Service / Endpoints:Ingress 引用 Service 拿后端 Pod IP(数据面常直连 Pod,绕过 kube-proxy)
  • kube-proxy:若走 ClusterIP,做 ClusterIP→PodIP 的 DNAT(iptables/IPVS/nftables)
  • CNI:跨节点 overlay/BGP/eBPF,同节点 veth/bridge,把包送进 Pod netns
  • externalTrafficPolicy Local vs Cluster:源 IP 保留 vs 多一跳
  • Gateway API(2026 现代方向)、Cilium 无 kube-proxy(eBPF 直接做 Service LB)

考官追问链

  1. 入口层:用户敲域名后,流量第一站到哪?Ingress Controller 自己是怎么被外部访问到的(它不也是个 Pod 吗)?
  2. L7 层:Ingress 拿到请求后按什么路由?TLS 通常在哪一跳终止?
  3. 转发层:Ingress 到后端,是走 Service 的 ClusterIP,还是直连 Pod IP?两者有什么区别?kube-proxy 在这条路里扮演什么角色?
  4. 底层网络层:包到了目标节点后,CNI 怎么把它送进 Pod 的网络命名空间?同节点和跨节点有何不同?
  5. 边界/坑层externalTrafficPolicy: LocalCluster 对源 IP 和转发路径有什么影响?各有什么坑?

参考答题骨架

完整路径(自外向内)

text
客户端
  │ ① DNS:域名 → Ingress 的公网 LB IP
云负载均衡器(L4/L7)
  │ ② 分发到各节点的 NodePort(externalTrafficPolicy 影响源 IP 与是否二次转发)
Ingress Controller Pod(nginx / Envoy / Gateway API 实现)
  │ ③ L7 路由:按 host/path 匹配 Ingress 规则;通常在此终止 TLS
  │ ④ 引用后端 Service 拿 Endpoints,数据面多为"直连 Pod IP"做 LB
(若走 ClusterIP) kube-proxy
  │ ⑤ DNAT:ClusterIP → 某个健康 Pod IP(iptables/IPVS/nftables 规则)
CNI 网络
  │ ⑥ 跨节点:overlay(VXLAN)/BGP/eBPF 路由到目标 Pod 所在节点
  │    同节点:走 bridge + veth pair
目标 Pod 的 network namespace
  │ ⑦ 包进入 Pod netns,容器进程 accept 监听端口
容器处理请求

逐跳要点

  • ② 云 LB → 节点:LoadBalancer 类型 Service 会在每个节点开 NodePort,云 LB 把流量分到各节点。externalTrafficPolicy 决定行为(见踩坑)。
  • ③ Ingress Controller:它自己就是跑在集群里的 Pod,通过一个 type: LoadBalancer 的 Service 对外暴露。它做 L7(host/path/重写/TLS 终止/限流)。
  • ④~⑤ Service 抽象 vs 数据面:Ingress 逻辑上「转发给 Service」,但主流 Ingress Controller(nginx/Envoy)会 watch Endpoints 直接把流量打到 Pod IP,绕过 kube-proxy 以减少一跳;只有走 ClusterIP 的路径才由 kube-proxy 做 DNAT。
  • ⑥ CNI:同节点走 veth/bridge;跨节点 overlay 封装(VXLAN)或纯路由(Calico BGP)或 eBPF(Cilium)。

踩坑 / 加分点

  • externalTrafficPolicy: Cluster(默认):任意节点收到流量后可再转发到别的节点上的 Pod,负载更均衡,但会 SNAT 丢失客户端真实源 IP,且多一跳。
  • externalTrafficPolicy: Local:只转发到本节点的 Pod,保留源 IP、少一跳,但如果该节点上没有对应 Pod 就直接丢包——必须配 healthCheckNodePort 让云 LB 只把流量发给有端点的节点。「源 IP 拿不到」和「部分节点 503」这两个经典问题都出在这里。
  • 双层 LB 叠加(云 LB + Ingress)会叠加延迟,高性能场景考虑 LB 直连或 Gateway API。
  • TLS 终止位置决定后端是否明文、能否做 mTLS:终止在 Ingress(后端明文)vs 透传到 Pod(端到端加密)要按合规要求选。
  • 现代加分点:Gateway API 正逐步取代 Ingress(角色分离、更强的路由表达);Cilium 无 kube-proxy 模式用 eBPF 在内核直接完成 Service 负载均衡,路径里就没有 iptables/kube-proxy 这一跳了。

锚点题 6:requests / limits、QoS、OOM、驱逐这几者是怎么联动的?#

为什么是锚点题:资源模型是 K8s 稳定性的地基,也是「Pod 莫名被杀/被驱逐/性能毛刺」几乎所有资源类故障的总根源。这题把调度、cgroup、QoS 分级、kubelet 软驱逐、内核硬 OOM 一条线串起来,能不能讲清「CPU 和内存的本质区别」是硬核区分点。

会串出的知识点地图

  • requests:调度依据(scheduler 按 requests 找节点)+ cgroup 保障下限(cpu 权重)
  • limits:cgroup 硬上限
  • QoS 三级:Guaranteed(requests==limits)/ Burstable / BestEffort(啥都没设)
  • 可压缩 vs 不可压缩:CPU 超限只 throttle(CFS quota 限流)不杀;Memory 超限直接 OOMKill
  • kubelet 软驱逐:节点资源到阈值主动驱逐,顺序 BestEffort → Burstable(超 requests 越多越先)→ Guaranteed
  • 内核硬 OOM:按 oom_score_adj 杀(Guaranteed 约 -998,BestEffort 约 1000)
  • runtime 感知 cgroupGOMEMLIMIT / -XX:MaxRAMPercentage;LimitRange 兜底

考官追问链

  1. 基础层:requests 和 limits 各自在什么时候起作用?调度看哪个,运行时限哪个?
  2. QoS 层:三个 QoS 等级是怎么被判定出来的(不是手动设的)?分别在什么场景下有意义?
  3. 本质区别层:CPU 和内存超过 limit 时,行为为什么完全不同?「CPU 打满 Pod 被杀」这句话对吗?
  4. 驱逐层:节点内存快满了,kubelet 先驱逐谁?和内核 OOM killer 有什么区别、谁先动手?
  5. 治理层:怎么设 requests/limits 才既不浪费又不容易被杀?为什么 JVM/Go 应用要单独关照?

参考答题骨架

requests vs limits

  • requests:① 调度依据——scheduler 按 requests 判断节点装不装得下;② cgroup 保障——CPU 转成 cpu.shares/weight 保证竞争时的最小份额。
  • limits:cgroup 硬上限——CPU 转成 CFS quota,Memory 转成 cgroup memory limit。

QoS 三级(由 requests/limits 关系自动判定)

QoS判定条件被驱逐/OOM 顺序
Guaranteed每个容器 cpu、memory 的 requests 等于 limits最后被动,最安全
Burstable设了 requests/limits 但不满足 Guaranteed居中(超 requests 越多越先)
BestEffortrequests、limits 都没设最先被杀

CPU vs 内存的本质区别(核心)

  • CPU 可压缩:超过 limit 只会被 CFS throttle(限流变慢),绝不会因此被杀。所以「CPU 打满导致 Pod 被杀」是错误归因——真相往往是被 throttle 导致响应变慢、探针超时才被重启。
  • 内存不可压缩:超过 limits.memory → 该容器 cgroup 的 OOM killer 直接杀容器(OOMKilled,exit 137)。

两种「因内存被杀」的路径

  1. 容器超 limit → cgroup OOM:只杀超限那个容器,Reason 明确 OOMKilled。
  2. 节点内存告急 → kubelet 软驱逐memory.available 低于 eviction 阈值时,kubelet 主动驱逐 Pod 腾内存,顺序:先 BestEffort → 再 Burstable(且用量超出 requests 越多、priority 越低者越先)→ 最后 Guaranteed。被驱逐的 Pod 状态是 Evicted
  3. 内存瞬间打满、来不及软驱逐 → 内核全局 OOM:Linux 内核 OOM killer 按 oom_score_adj 挑进程杀。kubelet 按 QoS 设不同分值:Guaranteed 约 -998(最不易被杀),BestEffort 约 1000(最易),Burstable 居中。

联动链一句话requests 定调度落点 + QoS 分级 + 驱逐保护程度;limits 定 cgroup 硬顶 + 触发 OOM 的线;QoS 决定内存紧张时「谁先死」(软驱逐顺序 + 内核 oom_score)。

踩坑 / 加分点

  • 生产别用 BestEffort:一有资源压力它第一个被驱逐/被 OOM,等于把自己放在祭天名单第一位。
  • CPU limit 设太低导致 throttle 抖动:P99 毛刺、偶发超时,kubectl top 看 CPU 用量还没打满 limit 却在抖——查 container_cpu_cfs_throttled_periods_total。很多团队干脆不设 CPU limit(只设 requests)来避免 throttle。
  • **memory requests ≠ limits(Burstable)**在超卖节点上有被驱逐/OOM 风险;对内存敏感的关键服务可设成 Guaranteed 换稳定。
  • JVM/Go 要感知 cgroup:老 JVM 不加 -XX:+UseContainerSupport/-XX:MaxRAMPercentage 会按宿主机内存算堆 → 必 OOM;Go 用 GOMEMLIMIT 提示 GC 上限,避免自杀式 OOM。
  • LimitRange 给 namespace 设默认 requests/limits,防止有人漏配变 BestEffort;用 ResourceQuota 防止 namespace 整体超卖。

锚点题 7:一个有状态应用(数据库/中间件)上 K8s,用 StatefulSet + PVC 要考虑哪些完整问题?#

为什么是锚点题:无状态谁都会,有状态才见功力。这题串起 StatefulSet 的三大保证、Headless Service、PVC 与 volumeClaimTemplates、存储拓扑(AZ 绑定)、扩缩容与数据保留策略、Multi-Attach、节点故障下的脑裂防护——是「敢不敢把数据库放 K8s」这类真实决策的知识底座。

会串出的知识点地图

  • StatefulSet 三保证:稳定网络标识(pod-0 固定名 + 固定 DNS)、稳定存储(每 Pod 专属 PVC)、有序部署/扩缩/滚动
  • Headless ServiceclusterIP: None):每 Pod DNS pod-0.svc.ns.svc.cluster.local
  • volumeClaimTemplates:每副本一个 PVC,与序号绑定,重建不删
  • 存储拓扑:云盘 zonal 绑 AZ、volumeBindingMode: WaitForFirstConsumer 避免盘与 Pod 不同区
  • 数据保留:缩容/删除不删 PVC、persistentVolumeClaimRetentionPolicy、reclaimPolicy(Retain/Delete)
  • 更新策略:RollingUpdate + partition(灰度)、OnDelete
  • Multi-Attach:RWO 卷同一时刻单节点挂载
  • 节点故障:Pod 卡 Terminating 不自动重建(防脑裂)

考官追问链

  1. 选型层:为什么这个应用不能用 Deployment?StatefulSet 到底多给了你哪三样东西?
  2. 网络/存储层pod-0 的固定 DNS 怎么来的(Headless Service 的作用)?每个副本的盘是怎么和它绑定、且重建后还挂原盘的?
  3. 调度边界层:云盘是绑 AZ 的,如果 Pod 被调度到别的可用区会怎样?volumeBindingMode 的两个取值有什么区别,Immediate 会踩什么坑?
  4. 数据安全层kubectl scale --replicas=1 缩容后,被缩掉副本的 PVC 会被删吗?删 StatefulSet 会删数据吗?这样设计的用意?
  5. 故障层:节点宕机时 mysql-0 为什么卡在 Terminating 不在别处重建?强删有什么风险?Multi-Attach error 什么时候出现?

参考答题骨架

为什么用 StatefulSet(三大保证)

  1. 稳定网络标识:Pod 名固定有序(mysql-0mysql-1),重建后名字不变;配合 Headless Service 每个 Pod 有稳定 DNS mysql-0.mysql.ns.svc.cluster.local——集群成员互相寻址(如主从、Raft peer)靠这个。
  2. 稳定存储volumeClaimTemplates 为每个副本生成专属 PVC(data-mysql-0…),PVC 与序号绑定,Pod 重建/迁移后仍挂回原来那块盘
  3. 有序:默认按 0→N-1 顺序启动、N-1→0 顺序终止、滚动更新也有序(可用 podManagementPolicy: Parallel 改并行)。

关键配置与考量

  • Headless ServiceclusterIP: None):DNS 直接返回各 Pod IP,提供每 Pod 稳定域名。
  • 存储拓扑(高频坑):云盘通常是 zonal(绑定单可用区)。若 Pod 被调度到与其 PV 不同的 AZ,就挂不上。用 StorageClass 的 volumeBindingMode: WaitForFirstConsumer——先由调度器选好节点,再在同 AZ 创建/绑定盘;用默认的 Immediate 会先建盘再调度,容易盘和 Pod 不同区导致 Pending。
  • 扩缩容与数据保留:缩容 StatefulSet 默认不删 PVC(防误删数据),要手动清理孤儿 PVC;persistentVolumeClaimRetentionPolicywhenScaled / whenDeleted 可配 Retain/Delete。PV 的 reclaimPolicy 生产用 Retain,删 PVC 也不真删底层盘。
  • 更新策略RollingUpdate + partition: N 做灰度(只更新序号 ≥ N 的 Pod,验证无误再降 partition);OnDelete 则完全手动。
  • Multi-AttachReadWriteOnce 卷同一时刻只能被一个节点挂载。Pod 跨节点迁移时,若旧节点还没释放卷,新 Pod 报 Multi-Attach error——常见于节点硬宕机。
  • 节点故障下的脑裂防护:节点 unreachablemysql-0Terminating不自动重建,因为 K8s 无法确认老实例真死了,贸然在别处拉起同标识 Pod 挂同一逻辑数据可能双写脑裂。需人工确认(fencing)后再 --force 删除。

踩坑 / 加分点

  • 缩容留下的孤儿 PVC 是隐性成本泄漏,也可能在扩容回来时挂到脏数据;要么配 whenScaled: Delete,要么纳入巡检。
  • volumeBindingMode: Immediate 跨 AZ 踩坑极常见:多 AZ 集群里先建盘后调度,盘在 A 区、Pod 被排到 B 区 → 永久 Pending。
  • 节点宕机时无脑 kubectl delete pod --force --grace-period=0数据脑裂高发操作(主库双写);正确做法先物理断电/fencing 再删。
  • 备份不能只备 etcd:etcd 只有元数据,业务数据在 PV 里,要针对 PVC 做卷快照 + 应用层逻辑备份(如 mysqldump/物理备份)。
  • RWO vs RWX 选型:需要多 Pod 同时读写(如共享文件)要用 RWX(NFS/CephFS/云文件存储),块存储只能 RWO。
  • 现实建议加分:数据库能用云托管(RDS)就别自己上 StatefulSet;确要自建,用成熟 Operator(如 mysql-operator、CNPG)比裸 StatefulSet 稳得多,它们把主从切换/备份/fencing 都封装了。

锚点题 8:一个操作报「Forbidden」权限错误,你怎么定位到根因?#

为什么是锚点题:RBAC 是生产权限的地基,CI 机器人、Operator、SA 权限报错是运维日常。这题好在报错信息本身就是完整线索,考的是你懂不懂 RBAC 的主体→绑定→角色→规则四段式,以及那些「apiGroup 写错」「子资源要单独授权」的细节坑。

会串出的知识点地图

  • 报错解析User "system:serviceaccount:ns:sa" cannot get resource "pods" in API group "" in namespace "ns"——who / verb / resource / apiGroup / namespace 全在里面
  • 定位工具kubectl auth can-i <verb> <resource> --as=... -n nsauth can-i --list
  • RBAC 四段式:主体(User/Group/ServiceAccount)→(Cluster)RoleBinding →(Cluster)Role → rules(apiGroups + resources + verbs)
  • 命名空间 vs 集群级:Role/RoleBinding vs ClusterRole/ClusterRoleBinding;RoleBinding 引用 ClusterRole 只在该 ns 生效
  • 常见坑:apiGroup 空串(core)、子资源(pods/log、pods/exec、deployments/scale)需单独授权、verb 粒度(get/list/watch 分开)、SA 没挂到 Pod

考官追问链

  1. 读错误层:这条 Forbidden 报错里,能直接读出哪几个关键信息?(who/verb/resource/apiGroup/ns)
  2. 验证层:不改任何东西,怎么快速验证「这个 SA 到底有没有这个权限」、以及「它一共有哪些权限」?
  3. 模型层:RBAC 从主体到最终权限是怎么串起来的?Role 和 ClusterRole、RoleBinding 和 ClusterRoleBinding 的区别?
  4. 细节坑层:为什么「明明给了 deployments 权限还是报错」?(apiGroup 写成 core / 子资源没授权 / verb 缺 list)
  5. 主体层:Pod 里的进程默认用哪个 ServiceAccount?为什么不写 serviceAccountName 常常直接没权限?

参考答题骨架

第一步 · 读懂报错

text
Error from server (Forbidden): pods is forbidden:
User "system:serviceaccount:app:ci-bot" cannot list resource "pods"
in API group "" in namespace "app"

一句话拆出五要素:主体 ci-bot(app 命名空间的 SA)、verb listresource podsapiGroup ""(core 组)、namespace app

第二步 · 用 can-i 复现 + 列权限

bash
kubectl auth can-i list pods --as=system:serviceaccount:app:ci-bot -n app     # 复现:no
kubectl auth can-i --list --as=system:serviceaccount:app:ci-bot -n app        # 看它到底有哪些权限

第三步 · 顺着四段式找断点 主体 → 绑定 → 角色 → 规则:

bash
kubectl get rolebindings,clusterrolebindings -A -o wide | grep ci-bot        # 找绑定
kubectl get role <role> -n app -o yaml                                       # 看引用的 Role 的 rules

逐项核对 rules 里的 apiGroups / resources / verbs 是否覆盖了报错要的组合。

RBAC 模型速记

  • 主体:User / Group / ServiceAccount。
  • Role/RoleBinding:namespace 级;ClusterRole/ClusterRoleBinding:集群级。
  • RoleBinding 可以引用 ClusterRole——复用通用角色定义,但权限只在该 RoleBinding 所在的 namespace 生效(高频误解点)。

踩坑 / 加分点

  • apiGroup 写错是头号坑:core 资源(pods、services、configmaps)的 apiGroup 是空串 "";deployments 在 apps;ingress 在 networking.k8s.io。把 deployments 写进 core 组,规则就是不生效还不报语法错。
  • 子资源要单独授权pods/logpods/execpods/portforwarddeployments/scale 都是独立 resource——给了 pods 不等于能 kubectl logs/exec
  • verb 粒度getlistwatch 是分开的;控制器常还需要 create/update/patch/delete。「能看单个但 kubectl get pods 报错」往往是缺 list
  • SA 没挂到 Pod:Pod 不写 spec.serviceAccountName 就用 namespace 的 default SA,而 default 通常没任何权限——「代码里调 API 报 Forbidden」经常是这个。
  • RoleBinding 引 ClusterRole 只在本 ns 生效的误解会导致「明明绑了 cluster-admin 还是跨不了 ns」。跨 ns/集群级操作要用 ClusterRoleBinding。
  • 加分:优先复用内置 ClusterRole(view/edit/admin/cluster-admin)+ 用 aggregationRule 聚合;生产审计要抓通配符 *cluster-admin 的滥用;给 CI 机器人按最小权限精确到具体 resource/verb(见 5-方案设计型 的 CI 机器人 RBAC 设计)。

复习节奏建议:考前把这 8 道锚点题的「考官追问链」当口播稿,每题限时 58 分钟一口气讲完;讲不顺的层回到对应的 15 类精修。面试时被问到任一相关点,都可以主动把答案「升维」成这里的一棵树——展现体系感,正是高级岗和普通岗的分水岭。