Skip to content

第6章 Kubernetes 核心与生产实践(本章最重要) ​

对应简历:技能第5条「熟练掌握 Kubernetes:kubeadm 单 / 多 master 高可用集群搭建(ARM 国产化与 x86 云环境双实践),熟悉 Flannel / Calico、PV / PVC 与故障排查」/ 项目一职责「负责 Kubernetes 集群部署与高可用改造:在统信 UOS(ARM)环境完成 Docker 与 kubeadm 组件部署,先搭建单 master 集群,再通过 Nginx + Keepalived(VIP 漂移 + 健康检查脚本)升级为多 master 高可用架构并完成故障转移测试」/ 项目二七条职责全部(3 master + CLB 架构、containerd + kubeadm v1.28.2 + Flannel + 9 项避坑、ImagePullSecret 镜像双轨、Prometheus 监控、Kuboard 管控、云服务配套、故障切换演练与 etcd 备份证书续期规范)

术语约定:Kubernetes(以下简称 K8s,仅正文简写)。内网 IP 一律脱敏为 <VIP>、<master01-ip> 等占位符。

6.1 架构:两大节点七组件,apiserver 唯一入口 【必背】 ​

标准回答(可口述 60–90 秒): K8s 集群分两大节点角色,共七个核心组件。Master 控制面四个:apiserver 是集群唯一入口,所有读写请求必经它,负责认证、授权、准入三道关卡,本身无状态所以可以多实例横向扩;etcd 是分布式 KV 存储,保存全集群的状态数据,靠 Raft 协议保证强一致——整个集群的"事实"都在这里;controller-manager 是各种控制器的集合,靠"调谐循环"(期望态与实际态不断对比修正)实现故障自愈、副本数维持、滚动更新;scheduler 监听还没绑定节点的 Pod,经过预选和优选两阶段给它挑一个最合适的节点并写 Binding。

Node 工作节点三个:kubelet 是节点代理,注册节点、上报状态、按 Pod 清单调用容器运行时创建容器、执行探针,还管理静态 Pod(/etc/kubernetes/manifests 目录下的清单);kube-proxy 维护 Service 的 iptables/ipvs 转发规则,让 ClusterIP 能落到 Pod;容器运行时(containerd 或 docker)通过 CRI 接口被 kubelet 调用。

最重要的架构原则是:所有组件不直连 etcd、组件之间也不互相直连,全部通过 apiserver 的 list-watch 机制交互——所以 apiserver 挂了表现为 kubectl 全部超时、集群"失联",但已经运行的业务 Pod 完全不受影响。 追问链:

  • 追问1:各组件挂了分别什么现象? → 答:这个我能一组件一现象地说全:apiserver 挂——kubectl 全超时但业务 Pod 不受影响;etcd 不可用——apiserver 无处读写,集群变只读甚至不可用;scheduler 挂——新 Pod 全卡 Pending 没人调度;controller-manager 挂——副本数不自动调、节点挂了 Pod 不重建、Endpoint 不刷新;kubelet 挂——节点 NotReady,但节点上已有容器还在跑(只是失管);kube-proxy 挂——ClusterIP Service 访问不通,但 Pod IP 直连正常。这套"现象到组件"的映射是生产排障的定位地图,我把它贴在工位上背熟的。
  • 追问2:master 上的组件为什么是静态 Pod? → 答:静态 Pod 由节点上的 kubelet 直接监视 manifests 目录创建,不经过 apiserver 调度。这个设计的巧妙之处在于解决自举问题:apiserver、etcd 这些组件本身就是集群的一部分,如果它们要等 apiserver 调度才能起,就陷入"鸡生蛋"死锁;改成 kubelet 直管后,即使 apiserver 挂了,kubelet 还能把它拉起来。我验证过:删掉 apiserver 容器,几秒内 kubelet 自动重建——这也是我更新 apiserver 证书的方法(杀容器让它带新证书重建)。
  • 追问3(深挖):apiserver 端口? → 答:一组必背端口:apiserver 6443(HTTPS)、etcd 2379 客户端/2380 节点间、kubelet 10250、NodePort 范围 30000-32767、kube-scheduler 和 kube-controller-manager 的 10259/10257。还有一个常被挖的细节:集群内部的 kubernetes Service 是 443 端口,经 Endpoints 转到 apiserver 的 6443——所以集群内组件访问的是服务名 443,不是直连 6443。高可用场景下客户端连的是 VIP 入口:银行项目是 VIP:16443(Nginx 代理),移动云是 CLB 的 VIP:6443。

6.2 四大控制器区别与使用场景 【必背】 ​

标准回答: K8s 常用四大控制器,我的选型口诀是"无状态 Deployment、有状态 StatefulSet、每节点一份 DaemonSet、一次或定时 Job/CronJob"。Deployment 管无状态应用(Web、API、前端),保证副本数并提供滚动更新和回滚——它不直接管 Pod 而是管 ReplicaSet:每次更新新建一个 RS 逐步扩新缩旧,旧 RS 缩到 0 但保留,这就是回滚的资本,revisionHistoryLimit 默认保留 10 个版本。

StatefulSet 管有状态应用(数据库主从、Kafka、ZK),三大特性:稳定的网络标识(Pod 名固定为 mysql-0、mysql-1 而不是随机后缀)、独立存储(volumeClaimTemplates 给每个 Pod 独立 PVC,Pod 重建还挂原来的盘)、有序启停(按序号顺序起停,主从场景必须)。

DaemonSet 保证每个节点恰好运行一个 Pod——日志采集 filebeat、监控 node-exporter、CNI 组件都是它,新节点加入集群自动补一份,这是 Deployment 做不到的"按节点维度保证"。Job 跑完即止(数据迁移、批处理),CronJob 定时跑(定时备份、报表)。 追问链:

  • 追问1:为什么 Deployment 不直接管 Pod? → 答:因为中间隔了 ReplicaSet 这层历史版本抽象:Deployment 每次更新模板变化时新建一个 RS,新 RS 扩、旧 RS 缩,全程 Deployment 只是两个 RS 副本数的操纵者。旧 RS 缩到 0 但不删除——kubectl rollout history 里的每个版本对应一个 RS,回滚就是把旧 RS 副本拉回来,秒级完成不用重建镜像。这个设计把"版本"具象成了 RS 对象,是 K8s 滚动更新和回滚的机制基础,我讲更新策略时都会先讲这层结构。
  • 追问2:数据库为什么尽量不上 K8s? → 答:我的三个理由:一是滚动更新与数据一致性冲突——数据库升级要控制切换时机,不能像无状态应用那样直接换实例;二是 Pod 漂移带来主从拓扑风险——调度器不感知数据库主从角色,可能把主从调度到同节点或任意搬动;三是运维操作粒度不匹配——数据库的备份、扩容、参数调整有自己的一套流程,塞进容器编排反而增加复杂度。所以我两个项目数据库都在 K8s 外:银行是 Vastbase 虚拟机集群、移动云用云数据库主备——"有状态核心数据外置"是我能讲清的架构取舍。
  • 追问3:DaemonSet 为什么不能被 Deployment 替代? → 答:因为两者的"保证"语义不同:Deployment 保证总数——replicas 3 就是全集群 3 个副本,调度器自由决定放哪,三个还可能挤在同一节点;DaemonSet 保证分布——每个满足条件的节点恰好 1 个,节点增减自动跟随。日志采集和监控代理必须每台机器都有,用 Deployment 就会出现"某些节点没有采集器"的监控盲区。我集群里的 node-exporter 用 DaemonSet(容器化场景)、Flannel 也是 DaemonSet,这就是活例子。

6.3 Pod 生命周期:创建、销毁与状态机 【必背】 ​

标准回答: Pod 创建主链路我按三步讲(简化必背版)。第一步:kubectl 提交请求,apiserver 经过认证、授权、准入三关校验后把 Pod 对象写入 etcd——此时状态 Pending、NODE 为空,还没人管它。第二步:scheduler 通过 watch 机制发现有未绑定的 Pod,进入两阶段调度:预选(Filter)过滤掉资源不足、污点不容忍、亲和性不满足的节点;优选(Score)给剩下的节点打分(资源均衡、镜像本地化等策略),选出最优者写 Binding 把 Pod 和节点绑定。

第三步:目标节点的 kubelet watch 到属于自己的 Pod,开始创建——先调容器运行时建 pause 沙箱容器、CNI 给 Pod 配网络和 IP、CSI 挂载存储卷、拉取镜像、启动业务容器,探针通过后变 Ready 进入 Service 的 Endpoints。销毁流程:删除 Pod 后,先执行 preStop 钩子并向主进程发 SIGTERM 给它优雅退出的机会,宽限期 terminationGracePeriodSeconds(默认 30 秒)内没退出就 SIGKILL 强杀——这就是为什么 CMD 要用 exec 格式(能收到 SIGTERM)。

状态机主线:Pending(未调度或等镜像)→ ContainerCreating → Running →(探针通过 = Ready)→ Succeeded/Failed/Terminating;Unknown 表示 apiserver 与节点失联。 追问链:

  • 追问1:Pending 和 ContainerCreating 区别? → 答:一眼判断方法:kubectl get pods -o wide 看 NODE 列。NODE 列为空的 Pending 是还没调度上——describe 看 Events 通常是资源不足、污点不容忍、PVC 没绑定;NODE 有值但状态 ContainerCreating 是已经绑定到节点、卡在 kubelet 侧——常见原因拉镜像慢/失败、CNI 配网、挂卷。两个状态对应完全不同的责任层(调度器 vs 节点 kubelet),判断错了排查方向就全错。
  • 追问2:CrashLoopBackOff 怎么查? → 答:固定四步:第一步 describe 看 Last State 的 exitCode——137 查内存 limit(OOMKilled)、1 看应用异常、143 是被终止;第二步 kubectl logs --previous 看上一次崩溃的最后日志,配置错误、连不上依赖都在这;第三步检查探针配置——initialDelaySeconds 太短,应用没起完就被 liveness 杀掉形成循环;第四步检查 command 是否覆盖了镜像的 ENTRYPOINT 导致启动命令不对。CrashLoopBackOff 的退避时间会指数拉长(10s、20s、40s...),所以抓紧在前几次崩溃时把日志抓下来。
  • 追问3(深挖):init 容器? → 答:init 容器在主容器之前按声明顺序依次执行,全部成功退出主容器才会启动——本质是"启动前置条件"的表达。典型用途:等待依赖服务就绪(循环探测数据库端口)、初始化配置文件、准备挂载目录。我部署过的真实例子是 Flannel:它的 DaemonSet 里 install-cni 就是 init 容器,负责先把 CNI 配置文件和可执行文件放进宿主目录,主容器才起——理解了这个例子,init 容器的价值就不用背了。

6.4 Deployment 滚动更新与回滚 【必背】 ​

标准回答: Deployment 的更新机制一句话:新建 RS 逐个扩新、旧 RS 逐个缩旧,靠两个参数控节奏——maxSurge 允许最多超出期望副本数多少(默认 25%),决定"新 Pod 先起几个再停旧的";maxUnavailable 允许最多不可用多少(默认 25%),决定"可以忍多少容量缺口"。

滚动更新不中断业务的两根支柱:一是这个节奏控制,二是 readinessProbe——新 Pod 必须通过就绪探针才被视为可用、才进入 Endpoints 接流量,然后才摘掉旧 Pod。回滚机制:kubectl rollout undo deploy/<name> --to-revision=N——因为旧 RS 一直保留着(副本 0),回滚就是把旧 RS 副本拉回来、新 RS 缩回去,不涉及镜像重建,秒级完成。

命令组四条背熟:set image 触发更新、rollout status 观察进度、rollout history 看版本列表、rollout undo 回滚。

我在移动云集群上对业务发布就是这套流程,配合 Kuboard 页面操作底层逻辑相同。 实操模板(更新回滚命令组):

bash
kubectl set image deploy/web web=<registry>/web:v2   # 触发更新
kubectl rollout status deploy/web                      # 观察进度
kubectl rollout history deploy/web                     # 版本列表
kubectl rollout undo deploy/web --to-revision=2        # 回滚

追问链:

  • 追问1:滚动更新卡住怎么查? → 答:分三步:rollout status 一直不返回,先 kubectl get rs 看新旧两个 RS 的副本数分布——新 RS 副本上不去说明新 Pod 起不来;describe 新 Pod 看 Events(镜像拉取失败、探针不过、资源不足);特别要警惕一种死锁:配了 maxUnavailable: 0(零中断要求)而新 Pod 永远不 Ready——旧 Pod 不能摘、新 Pod 进不来,更新卡死。处理是先修新 Pod 问题或 rollout undo 恢复,再排查根因,别让生产一直挂着半成品版本。
  • 追问2:ConfigMap 更新后怎么生效? → 答:分三种挂载方式答全:volume 挂载的——kubelet 周期同步(约 1 分钟)自动更新挂载文件内容,但应用要自己支持配置热加载才会生效;环境变量注入的——不会更新,环境变量在 Pod 创建时就固化了,必须重启 Pod(kubectl rollout restart 是标准做法);subPath 挂载的——不会自动更新(kubelet 的同步机制对 subPath 路径无效),这是经典坑。所以我的配置管理习惯:需要热更新的配置用 volume 挂载,环境变量只放启动时确定的内容。

6.5 Service 四类与 kube-proxy 【必背】 ​

标准回答: 先讲 Service 的工作原理一条线:Service 通过 label selector 关联一组 Pod,Endpoints 控制器实时维护"所有 Ready 状态的 Pod IP"列表,kube-proxy 在每个节点上把 Service 的规则写成 iptables 或 ipvs 规则——访问 ClusterIP 的流量被 DNAT 到某个后端 Pod IP。这个设计解决了根本问题:Pod IP 易变(重建就换),Service 提供稳定的访问入口,Pod 生灭由 Endpoints 自动跟踪。

四种类型按场景记:ClusterIP(默认)分配集群内虚拟 IP,用于内部服务互调;NodePort 在每个节点开一个 30000-32767 的端口对外,适合测试和无 LB 环境——我项目的 Kuboard 用 30088、kube-state-metrics 用 30080;LoadBalancer 调用云厂商自动创建外部负载均衡,公有云生产的标准对外方式;ExternalName 把 Service 名 CNAME 到外部域名,让集群内用服务名访问外部服务。

特例 Headless Service(clusterIP: None)不分配 VIP,DNS 查询直接返回 Pod IP 列表,配合 StatefulSet 用——每个 Pod 有独立 DNS 名。 追问链:

  • 追问1:Pod 重启 IP 变了 Service 为什么不受影响? → 答:因为客户端从来不直接记 Pod IP,访问的是稳定的 ClusterIP(或 Service 名);Pod 重建后新的 IP 由 Endpoints 控制器自动更新进 Endpoints 对象,kube-proxy 的转发规则跟着刷新。整个链路上"稳定入口 + 动态后端"由 selector 和 Endpoints 机制粘合——这也是微服务注册发现思想在 K8s 里的原生实现。反过来的坑也要知道:如果客户端绕过 Service 直连 Pod IP,Pod 一重建连接就断——我在现场规范里明确"跨组件访问必须走 Service 名"。
  • 追问2:Service 访问不通怎么查? → 答:我的四步链:第一步 kubectl get ep <svc> 看 Endpoints 列表——空的话说明 selector 没匹配上 Pod(标签写错)或 Pod 没 Ready(未通过就绪探针的 Pod 不进 Endpoints),这是最高频原因;第二步进 Pod 侧验证——kubectl exec 进去 ss -lntp 确认应用真的在监听、targetPort 没写错;第三步查节点转发层——iptables-save 或 ipvsadm 看 kube-proxy 规则有没有生成、kube-proxy 日志有无异常;第四步查 DNS——nslookup 服务名看 CoreDNS 解析是否正常。四层逐层收窄,基本无漏网。

6.6 Ingress 与访问链路 【理解】 ​

标准回答(一条链路背下来): Ingress 的完整访问链路我背一条主线:浏览器发起请求 → DNS 解析到入口地址 → LB 或 NodePort 做四层入口 → Ingress Controller(七层)接收 → 按 Ingress 规则匹配域名和路径 → 转给对应 Service → Endpoints 里选一个 Pod IP → 到达业务容器。

这里必须分清两个对象:Ingress 资源本身只是"路由规则的声明"(哪个域名哪个路径转给哪个 Service),真正干活的是 Ingress Controller——它本质是个 Nginx,watch 所有 Ingress 对象动态生成 Nginx 配置并 reload。所以 Ingress 能力取决于 Controller:TLS 卸载(证书挂在 Controller 上)、rewrite、限流、基于 header/cookie 的灰度发布都是 Controller 层实现的。

部署时三个易错点:规则要被 Controller 接管(get ingress 的 ADDRESS 列非空才算生效);ingressClassName 要和 Controller 匹配;TLS Secret 必须和 Ingress 同 namespace。

我的项目对外入口走了云 CLB 加 NodePort 兜底,Ingress 属于我完整理解并能配置的知识点。 追问链:

  • 追问1:为什么有了 Service 还要 Ingress? → 答:因为 Service 是四层概念——ClusterIP 只在集群内、NodePort 一个服务占一个端口且没有 TLS 卸载和域名路由。假设有 20 个微服务对外,用 NodePort 就要暴露 20 个端口、申请 20 个防火墙放行,用户还要记端口号。Ingress 在四层之上加了七层统一入口:一个入口地址(80/443)按域名和路径路由到几十个 Service,TLS 证书统一挂载,还顺手获得 rewrite、限流、灰度这些 Nginx 级能力——"对外一个门,对内千条路",这就是 Ingress 的价值。

6.7 存储五种卷与 PV/PVC/StorageClass 【必背】 ​

标准回答: 先过常用五种卷:emptyDir 是 Pod 内临时共享空间,Pod 删除就没,适合容器间交换临时文件;hostPath 把宿主机目录挂进 Pod——我部署 Kuboard 就用它(类型 DirectoryOrCreate 自动建目录)持久化 /data/kuboard;configMap 和 secret 把配置注入成文件;NFS 和 PV 类网络存储做跨节点持久化。

然后是三层抽象,这是核心:PV 是集群级的真实存储资源,由管理员或存储驱动创建;PVC 是用户的"存储申请单"——声明需要多大容量、什么访问模式,不用关心底层在哪;StorageClass 是动态供给模板——绑定 provisioner(存储驱动)和参数,PVC 指定 SC 后自动创建 PV 并绑定,实现"申请即供给"。绑定链一句话:Pod 用 PVC 名 → PVC 按 capacity 和 accessModes 匹配(或经 SC 动态创建)PV → PV 背后对接真实存储。

PV 生命周期:Available → Bound → Released →(Retain 保留等待手工处理 / Delete 连底层一起删)。访问模式三种:ReadWriteOnce 单节点读写、ReadOnlyMany 多节点只读、ReadWriteMany 多节点读写(需要 NFS 这类共享存储才支持)。 追问链:

  • 追问1:PV 一直 Pending? → 答:describe pvc 看 Events 逐条对:静态供给场景——PVC 的容量和访问模式与任何现存 PV 匹配不上(比如要 20G 只有 10G 的 PV、要 RWX 只有 RWO 的本地盘);动态供给场景——StorageClass 名字写错或不存在、provisioner 没部署好、底层存储池配额满了导致创建失败。另外 Released 状态的 PV 不会自动回到 Available(数据还在),静态模式要手工清理并删 claimRef——这个细节踩过一次就记住了。
  • 追问2:Kuboard 为什么用 hostPath 而不是 PV? → 答:一个"存储选型跟着调度策略走"的典型例子。Kuboard 是单副本应用,我用 nodeSelector 把它固定调度到 k8s-master-01,容器数据天然只在这一个节点——hostPath 加 DirectoryOrCreate 就是最简单可靠的持久化方案,不引入 NFS 或云盘的额外依赖。我做过实测验证:手动删 Pod 触发重建(还在同节点),重新登录后配置和审计日志都在。反过来说,如果哪天它要多副本或允许漂移,就必须换 ReadWriteMany 的网络存储——选型不是背"生产必须 PV",而是看调度拓扑和数据需求。

6.8 ConfigMap 与 Secret 【理解】 ​

标准回答: ConfigMap 存非敏感配置(应用配置文件、环境变量),Secret 存敏感数据(密码、token、证书),两者的使用方式完全一样、三种用法:注入环境变量(key 变成变量名)、volume 挂载成文件(每个 key 一个文件)、Secret 专属的第三种 imagePullSecrets 用于拉私有镜像。

创建命令背两条:kubectl create configmap app-conf --from-file=application.yml、kubectl create secret generic db-secret --from-literal=password='xxx';docker-registry 类型专门存仓库凭据。必须主动讲的关键认知:Secret 的 base64 只是编码不是加密——echo 'xxx' | base64 谁都能解,它的安全性靠的是 RBAC 权限控制(谁能 get secret)而不是编码本身;生产的强化手段是 etcd 静态加密(apiserver 配 encryption-provider)加最小化 RBAC。

更新行为差异是高频追问:volume 挂载约 1 分钟热更新、环境变量方式不更新需重启 Pod、subPath 挂载不更新——三种情况必须张口就说。 追问链:

  • 追问1:两者更新行为差异? → 答:三种方式逐个说:volume 挂载的 ConfigMap/Secret,kubelet 周期性(约 1 分钟)重新同步挂载内容,文件内容会更新,但应用是否重载要看它支不支持热加载;环境变量注入的,Pod 创建时就固化,后续修改源对象对已运行的 Pod 完全无效,要 kubectl rollout restart 滚动重启;subPath 挂载的文件不会同步更新——kubelet 的更新机制是替换整个挂载目录,subPath 指向的单个文件不在替换范围。我处理"改了配置不生效"的工单,第一步就是问"哪种挂载方式"。

6.9 三种探针(kubelet 发起) 【必背】 ​

标准回答: 先纠正最高频的错误认知:探针由 kubelet 发起,不是 apiserver 也不是 master——kubelet 直接在本节点对 Pod 执行探测。三种探针各管一件事:livenessProbe 存活探针,失败的动作是重启容器,检测"进程是否活着"——发现卡死、死锁时拉起来恢复;readinessProbe 就绪探针,失败的动作是从 Service 的 Endpoints 摘除,检测"是否可以接流量"——启动慢、预热中、依赖故障时先不接客,但容器不重启;startupProbe 启动探针,在它通过之前另外两种探针都被禁用,专治慢启动应用——Java 应用冷启动可能要一两分钟,没有它 liveness 会在启动期误杀形成重启循环。

探测方式三种:httpGet 请求路径按状态码判断(2xx/3xx 成功)、tcpSocket 能建立 TCP 连接即健康、exec 执行命令按退出码判断。

参数四件套:initialDelaySeconds 首次延迟、periodSeconds 间隔、timeoutSeconds 超时、failureThreshold 连续失败阈值。

我的配置习惯:Java 服务 startup 加 readiness 加宽松的 liveness,三层保护。 追问链:

  • 追问1:Running 但 0/1 Ready 什么原因? → 答:Ready 计数就绪探针,0/1 说明容器活着但 readiness 没通过、不在接流量状态。四个高频原因:探针配置错——路径不对、端口写错(应用监听 8080 探针查 9090);initialDelaySeconds 太短,应用还没起完探针就开始失败;应用 bind 127.0.0.1,探针从节点网络访问不到;依赖故障导致健康检查接口返回 5xx。排查组合拳:describe 里 grep "readiness probe failed" 看具体报错、logs 看应用启动进度、exec 进容器 ss -lntp 确认监听地址端口。
  • 追问2:liveness 和 readiness 配反会怎样? → 答:真实配置事故的形态:把本该做 readiness 的检查(比如依赖服务连通性)配成了 liveness——应用依赖抖动一下,liveness 失败重启容器;但重启解决不了依赖问题,起来又失败,形成重启循环,反而把"部分功能不可用"放大成"整个服务雪崩"。正确的语义分工:liveness 只检查进程自身健康(死循环、卡死才重启它),外部依赖类检查放 readiness(依赖故障就先摘流量,恢复了自动回来)。我给人讲探针就用这个反面案例,一听就懂。

6.10 requests/limits 与 QoS 【必背】 ​

标准回答: 一句话先行:request 决定"能不能调度上来",limit 决定"跑起来最多用多少"。requests 是调度依据——调度器保证节点上所有 Pod 的 requests 总和不超过节点 Allocatable,注意这是"承诺值"不是实际用量;limits 是运行时 cgroup 上限——CPU 超限会被限流(throttle,变慢但不死),内存超限直接 OOMKilled(退出码 137),两种资源超限后果完全不同,这个差异必须讲。

由此推出 QoS 三级,决定节点资源不足时的驱逐顺序:Guaranteed——所有容器的 request 等于 limit,资源最有保障,最后被驱逐;Burstable——设置了 requests/limits 但不相等,居中;BestEffort——什么都没设,最先被驱逐。

配置示例里 500m 就是 0.5 核。配套两个对象:LimitRange 给命名空间设默认 requests/limits(防止有人不设变成 BestEffort),ResourceQuota 限制命名空间的资源总量(多团队配额管理)。 实操模板:

yaml
resources:
  requests: { cpu: "500m", memory: "512Mi" }   # 500m=0.5核
  limits:   { cpu: "1",    memory: "1Gi" }

追问链:

  • 追问1:request 设远超实际用量会怎样? → 答:资源"虚占":调度器按 requests 算账,节点明明实际很闲,但 requests 池子满了,新 Pod 全 Pending——这就是资源碎片,集群实际利用率低的常见根因。反过来 request 设太低,高峰期节点超卖严重、Pod 互相挤压。我的实践值:request 按日常 P90 用量设(保证调度后有稳定供给),limit 按峰值加余量设——request 贴实际、limit 留弹性,两头的坑都避开。
  • 追问2:CPU limit 给太低对 Java 应用? → 答:Java 应用启动期要做 JIT 编译和大量类加载,CPU 需求是平时的好几倍;limit 给低了启动期被 throttle,启动时间从 30 秒拖到几分钟,探针超时被杀、重启、再 throttle——启动死循环。我的经验配置:核心 Java 服务内存 request 等于 limit(保 Guaranteed 级驱逐优先级),CPU 的 limit 给 request 的 2 到 3 倍留启动突发空间。这类"参数背后是应用行为"的知识点,是我在课程实验和平台部署里反复验证过的。

6.11 调度:污点容忍、亲和性与调度器(Kuboard 真实用例) 【必背】 ​

标准回答: 调度器两阶段模型:预选(Filter)先淘汰不合格节点——资源不足、污点不容忍、亲和性不满足的直接出局;优选(Score)对幸存节点打分——资源均衡度、镜像是否已在本地等,选最高分。控制调度的手段四组,按精细度递进:nodeSelector 最简单——按节点标签硬匹配,匹配不上就 Pending;nodeAffinity 更强——requiredDuringScheduling 是硬策略(不满足 Pending)、preferred 加权重(尽量满足),还支持丰富的运算符;podAffinity 和反亲和——按"已有 Pod 的分布"决策,典型用法是多副本 podAntiAffinity 打散到不同节点实现高可用(节点挂了不至于全军覆没);污点 taint 加容忍 toleration——给节点打污点"排斥"普通 Pod(NoSchedule 不调度、NoExecute 还要驱逐已有),Pod 声明 toleration 才能上——master 默认带 node-role.kubernetes.io/control-plane:NoSchedule,这就是普通 Pod 默认不上 master 的原因。

本人真实用例(Kuboard 部署四件套,全部是我配的):nodeSelector 固定调度到 k8s-master-01(管控链路最短)、tolerations 容忍 control-plane 污点(否则调度不上 master)、imagePullSecrets 挂私仓凭据、hostPath DirectoryOrCreate 持久化——四件套齐了才是一个完整可用的"固定节点 + 私有镜像 + 本地持久化"部署。 追问链:

  • 追问1:Pod 一直 Pending describe 报 taint 没容忍? → 答:说明目标节点(往往是 master)带 NoSchedule 污点而这个 Pod 没有对应 toleration——预选阶段直接被淘汰,没有节点可选就 Pending。两条路:给 Pod 加 tolerations 容忍该污点(我的 Kuboard 就是这么上 master 的);或者把 Pod 调度到普通 worker(master 本来就不该跑业务负载)。要删污点也是一条命令 kubectl taint node 节点 key-,但删 master 污点我不推荐——那会让业务 Pod 挤占控制面资源,这是生产纪律。
  • 追问2:怎么让 Pod 固定在某节点(多方法)? → 答:按场景四选:nodeSelector 按标签——最简单,我的默认选择;nodeAffinity required 硬策略——条件复杂时用(比如"在这些节点之一");nodeName 直填——跳过调度器直接指定, emergencies 才用,不推荐因为它绕过了调度器的资源检查;污点加容忍双保险——把某节点打成专用节点(唯一污点),只有带容忍的 Pod 能上,专用性最强。我的选型顺序:一般固定用 nodeSelector,要独占节点用 taint+toleration。

6.12 HPA 弹性伸缩 【理解】 ​

标准回答: HPA(Horizontal Pod Autoscaler)按指标自动调 Deployment 副本数,工作闭环:HPA 控制器周期性(默认 15 秒)从 Metrics Server 取各 Pod 指标 → 计算当前使用率与目标值的比值 → 换算出期望副本数并通知 Deployment 扩缩。指标支持 CPU/内存使用率和自定义指标(QPS、队列长度)。

命令一条:kubectl autoscale deploy web --cpu-percent=60 --min=2 --max=10——目标 CPU 60%,副本在 2 到 10 之间自动调节。

两个前提缺一不可:集群要部署 metrics-server(kubectl top 的数据源,没有它 HPA 拿不到指标,报 "unknown" ); Pod 必须设置 requests——使用率的定义是"实际用量 / request",没设 request 的 Pod 无法计算百分比。扩容有防抖机制(容忍度和稳定窗口)避免指标波动导致副本数抖动。 追问链:

  • 迭问1:开了 HPA 还能手动 scale 吗? → 答:不能,两个动作会打架:手动 kubectl scale 改了副本数,HPA 下一个周期按指标又把它改回去;同理 GitOps 或 CI 里 apply 带 replicas 字段的清单,也会被 HPA 覆盖。正确姿势:副本数完全交给 HPA 管理,清单里不写 replicas(或用 kustomize 注解忽略),需要临时扩容就临时调 HPA 的 min/max 而不是直接动 Deployment。这种"控制器打架"的坑,是声明式系统里很典型的一类问题。
  • 迭问2:驱逐的两种机制? → 答:两种触发路径:一是资源不足驱逐——kubelet 发现节点内存/磁盘压力时按 QoS 等级挑牺牲者(BestEffort 最先、Guaranteed 最后),提前把 Pod 驱逐避免整机 OOM;二是节点压力驱逐——磁盘可用空间不足(imagefs/nodefs 阈值)、PID 耗尽等节点级条件触发。被驱逐的 Pod 会在别处重建(前提是控制器管理的)。我的预防经验:磁盘使用率 85% 告警加日志轮转(第 1 章 1.13 的规范),就是为了让节点永远不进磁盘驱逐状态。

6.13 RBAC 与 roleRef 不可变坑(项目二真实) 【必背】 ​

标准回答: RBAC 的对象模型四件:Role 定义命名空间内的权限规则、ClusterRole 定义全集群范围的权限规则;RoleBinding 把角色绑定到主体(User、Group、ServiceAccount),ClusterRoleBinding 是集群级的绑定。一条请求的授权链路:认证(证书或 Token 确认身份)→ 授权(RBAC 判断能不能做这个动作)→ 准入(额外校验和修改)。

我的实践原则三条:最小权限——只给必要的资源加动词;为每个应用建独立的 ServiceAccount 而不是用 default;集群管理员 cluster-admin 只留给自己,绝不给应用。本人真实坑(避坑清单第 4 条):给 kube-state-metrics 配权限时,apply 修改 ClusterRoleBinding 报"字段不可更新"——roleRef 是不可变字段,K8s 设计上就禁止原地改绑定关系(防止权限被悄悄调包)。解决:kubectl delete clusterrolebinding xxx --ignore-not-found 删除重建;我们最终直接复用系统内置的 view 只读 ClusterRole 做绑定——只读权限恰好满足 kube-state-metrics 的需要,还天然符合最小权限原则。 实操模板(删除重建 + 验证命令):

bash
kubectl delete clusterrolebinding kube-state-metrics --ignore-not-found
kubectl create clusterrolebinding kube-state-metrics \
  --clusterrole=view --serviceaccount=monitoring:kube-state-metrics
# 验证某 SA 的权限
kubectl auth can-i list pods --as=system:serviceaccount:monitoring:kube-state-metrics

追问链:

  • 迭问1:Kuboard 的权限怎么做的? → 答:分两层。K8s 层:Kuboard 用一个 ServiceAccount 访问集群,权限范围决定它能展示和操作什么;平台层:Kuboard 自带用户体系,把平台用户映射到 K8s RBAC 的不同角色——管理员、只读、命名空间级,加上操作审计日志记录谁在什么时候做了什么。这就是我简历写的"RBAC 多级权限 + 操作审计"——它解决的是"团队里不同人给不同权限"的管理问题,让不熟悉 kubectl 的同事也能在页面上安全地做日常运维。
  • 迭问2:怎么验证某 SA 有什么权限? → 答:用 kubectl auth can-i 模拟授权检查:kubectl auth can-i list pods --as=system:serviceaccount:ns名:sa名 返回 yes/no;加 --list 列出该主体的全部权限。这个命令在配完 RBAC 后验证"最小权限是否生效"特别有用——我配完 kube-state-metrics 的绑定后就用它验证过只能 get/list/watch,不能改任何资源。

6.14 证书体系五类与 SAN:证书更新六步(本人真实,必背全文) 【必背】 ​

标准回答: kubeadm 集群的证书分五类要能数出来:① CA 根证书(ca.crt/ca.key),其他证书都由它签发,默认 10 年;② apiserver 证书(还有 etcd 的、front-proxy 的),默认 1 年有效期——这是到期事故的主力;③ etcd 证书(server/peer/healthcheck-client);④ kubelet 客户端证书;⑤ ServiceAccount 令牌的签名密钥对(sa.key/sa.pub)。运维规范:到期前 30 天 kubeadm certs check-expiration 检查、kubeadm certs renew all 续期,然后重启控制面静态 Pod 和 kubelet——这套流程写进了移动云项目的运维规范。再讲 SAN(Subject Alternative Name):apiserver 证书只对 certSANs 列表里的名字和 IP 有效,kubeadm 的证书只包含 init 时 certSANs 里的内容。

本人真实故障:银行项目单 master 升级多 master 后,用 VIP 连 apiserver 报 x509 证书错误——curl -k 能通但 kubectl 报错,说明不是网络是证书校验;openssl x509 -text 查证书发现 SAN 里没有新 VIP 和 master02/03 的 IP。

证书更新六步(完整背):1 导出集群配置成 kubeadm.yaml;2 在 apiServer 段追加 certSANs(新 VIP 加三台 master 的主机名和 IP);3 移走旧证书(mv /etc/kubernetes/pki/apiserver.{crt,key} ~,不移 kubeadm 不会重建)再 kubeadm init phase certs apiserver --config kubeadm.yaml 重签;4 杀掉 apiserver 容器让 kubelet 自动拉起新证书实例(docker kill 或 crictl);5 openssl x509 -in apiserver.crt -text 验证 SAN 列表;6 回写 kubectl -n kube-system edit configmap kubeadm-config 同步 certSANs。教训一句话:改集群入口地址必须先想证书。 追问链:

  • 迭问1:证书全过期了集群还能用吗? → 答:业务面还行——已运行的 Pod 靠 iptables 规则转发,不依赖 apiserver;但管理面瘫痪:kubectl 与 apiserver 的认证失败、kubelet 与 apiserver 的心跳失败导致节点 NotReady、新的调度和自愈全部停止。等于"业务还在飞,机场塔台断电"。所以必须提前 30 天续期,我把"证书有效期检查"做成了巡检固定项——kubeadm certs check-expiration 的输出按到期时间排序,最早到期的盯着。

6.15 etcd 与 Raft 多数派 【必背】 ​

标准回答: etcd 是 Go 实现的分布式 KV 存储,K8s 集群全部状态存在这里,三大特点:接口简单(HTTP 加 JSON 的 API)、安全(HTTPS 通信)、可靠——靠 Raft 协议保证一致性。Raft 多数派是必须讲透的概念:写请求要获得过半节点确认才算提交——3 副本容忍 1 台故障(2/3 仍过半),4 副本同样只容忍 1 台(3/4 过半)却多花一台机器,5 副本才容忍 2 台——所以 etcd 副本数必须奇数,生产标配 3 或 5,偶数副本是纯浪费。

两个项目我都是 3 master 堆叠式 etcd(etcd 与控制面组件同节点)跨可用区部署,兼顾成本和机房级容灾。故障表现要会推演:挂 1 台(3 副本里)集群正常工作只是失去冗余;挂 2 台失去 quorum,写请求全部失败,集群只读甚至不可用——3 是"容忍 1 台"的最小可用配置。运维命令背两条:endpoint health 健康检查(三副本全 healthy 才算过)和 snapshot save 快照备份——快照定期落到监控节点异地保存,恢复流程是停 apiserver → snapshot restore 到新 dataDir → 起 etcd。 实操模板(健康检查与快照,背):

bash
ETCDCTL_API=3 etcdctl --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/healthcheck-client.crt \
  --key=/etc/kubernetes/pki/etcd/healthcheck-client.key \
  endpoint health              # 健康检查(三副本全 healthy 才算过)
ETCDCTL_API=3 etcdctl ... snapshot save /backup/etcd-$(date +%F).db   # 快照备份

追问链:

  • 迭问1:etcd Pod 名查不到? → 答:这是我真实踩过的(避坑清单第 3 条):按网上文档敲 kubectl get pods -n kube-system | grep etcd 明明看到 etcd 在跑,再执行 etcdctl 健康检查却报 Pod NotFound。根因是堆叠式 etcd 的 Pod 命名规则是 etcd-<节点主机名>(如 etcd-k8s-master-01),不带 kube- 前缀——文档示例里的名字和实际资源对不上。解决:先 get pods 确认真实 Pod 名,校验命令按真实命名来;etcdctl 直接带三证书(--cacert --cert --key)连 127.0.0.1:2379 查 endpoint health。这个坑教会我"文档命令要先对齐自己环境的真实命名"。
  • 迭问2:快照怎么恢复? → 答:恢复流程五步:停止所有节点的 apiserver(静态 Pod 清单移走或直接停 kubelet)→ 在目标节点 etcdctl snapshot restore 快照文件 --data-dir 新目录(快照恢复会重建集群成员信息)→ 修改 etcd static pod 的 dataDir 指向新目录 → 恢复 apiserver 启动 → 验证集群数据完整。我们规范是快照定期保存到监控节点(异地于集群节点),防止节点级故障连带快照一起丢——备份和源不放同一辆车上,这是通用备份原则。

6.16 kubeadm 部署完整流程(含 kubeadm-config.yaml) 【必背】 ​

标准回答(六阶段顺序,面试常问"你们怎么部署的"): 我的部署流程六个阶段按序讲。阶段〇全节点初始化:关防火墙和 swap → 加载 overlay、br_netfilter 模块 → sysctl 配 ip_forward、bridge-nf-call-iptables、swappiness=0 → NTP 时间同步(chrony,证书校验对时间敏感,这是避坑清单第 9 条的教训)→ 装 containerd(三个关键配置:SystemdCgroup=true、pause 镜像换国内源、/etc/crictl.yaml 显式 socket)→ 装 kubelet/kubeadm/kubectl 指定版本 v1.28.2。

阶段一控制平面:master01 写 kubeadm-config.yaml(controlPlaneEndpoint 指向统一入口、certSANs 包含 VIP 和全部 master)→ kubeadm init --config --upload-certs → 配 kubectl → master02/03 带 --control-plane 加入。阶段二 CNI(顺序铁律:必须在 worker 之前):apply Flannel,DaemonSet 的 init 和主容器镜像全局替换成内网仓库地址——不先装 CNI,worker 加入即 NotReady,九成 NotReady 是 CNI 镜像拉取问题。

阶段三 worker 加入:kubeadm token create --print-join-command 生成命令批量执行。阶段四健康验证:get nodes 全 Ready、kube-system Pod 全 Running、etcd 三副本 healthy。阶段五监控和管控:Prometheus 加 Kuboard。 实操模板(kubeadm-config.yaml,移动云项目真实结构脱敏):

yaml
apiVersion: kubeadm.k8s.io/v1beta3
kind: InitConfiguration
localAPIEndpoint:
  advertiseAddress: <master01-ip>
  bindPort: 6443
nodeRegistration:
  criSocket: unix:///var/run/containerd/containerd.sock
  name: k8s-master-01
  taints:
  - effect: NoSchedule
    key: node-role.kubernetes.io/control-plane
---
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
kubernetesVersion: v1.28.2
controlPlaneEndpoint: "<VIP>:6443"          # 统一入口(CLB 或 LB VIP)
imageRepository: registry.aliyuncs.com/google_containers
networking:
  serviceSubnet: 10.96.0.0/12
  podSubnet: 10.244.0.0/16
  dnsDomain: cluster.local
apiServer:
  certSANs:                                 # 证书必须含 VIP + 全部 master IP/主机名
    - "<VIP>"
    - "<master01-ip>"
    - "<master02-ip>"
    - "<master03-ip>"
etcd:
  local:
    dataDir: /var/lib/etcd
    extraArgs:
      auto-compaction-retention: "1"
      quota-backend-bytes: "8589934592"
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: systemd                       # 与 containerd 对齐
maxPods: 110

追问链:

  • 迭问1:token 过期了怎么加节点? → 答:分两种节点。加 worker:kubeadm token create --print-join-command 生成新命令直接执行;如果 discovery-token-ca-cert-hash 也丢了,用 openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der | openssl dgst -sha256 -hex 算出 CA 哈希自己拼命令。加 master 多一步:master01 执行 kubeadm init phase upload-certs --upload-certs 拿一次性 certificate-key,join 命令加 --control-plane --certificate-key。我把这两组命令存在部署文档里,加节点时直接复制——版本配套和命令准确性都靠文档兜底。
  • 迭问2:Flannel 为什么选 VXLAN? → 答:VXLAN 是 UDP 封装方案,把 Pod 的二层包封装在宿主机三层网络里传输——好处是跨网段、跨可用区都能通(我们 3 个可用区必须靠它),对底层网络零侵入;代价是封装开销约 10% 到 20% 的吞吐。host-gw 模式性能更好(纯路由改写无封装)但要求所有节点二层直通,跨可用区不满足。另外我们没有 NetworkPolicy 需求(网络策略要 Calico 这类支持的网络插件),Flannel 够用且排障直观——flannel.1 接口、路由表都是标准材料。这个选型逻辑我能完整讲,而不是"教程用什么我用什么"。

6.17 单 master → 多 master 高可用改造(银行项目核心题,必背全文) 【必背】 ​

标准回答(六步 + 入口两层结构): 这是我在银行项目完整做过并验证的改造,六步背全。第一步入口层:所有 master 装负载均衡——haproxy(frontend bind 16443 加 backend roundrobin 挂三台 6443 带 check 加 1080 stats 页面)或 Nginx(stream 模块 upstream 加 listen 16443),监听 16443 是因为负载均衡和 master 复用一台机器、6443 已被本地 apiserver 占用;配 Keepalived——priority 100/90,vrrp_script 检测 16443 端口监听(ss -antp | grep 16443),失败 weight -20 触发 VIP 漂移(配置细节在第 4 章 4.17)。

第二步证书:按 6.14 的六步更新 certSANs——加新 VIP 和三台 master 的主机名 IP,否则用 VIP 连集群报 x509(我真实踩过:curl -k 通而 kubectl 报错,openssl 查 SAN 缺失)。第三步组件配置切入口:kubelet.conf、controller-manager.conf、scheduler.conf、kube-proxy 的 ConfigMap、~/.kube/config 全部改指 VIP:16443,逐个 kill 组件容器重启生效。第四步传证书:master01 向 02/03 scp pki 目录下的 ca、sa、front-proxy-ca、etcd 的 CA 证书和 admin.conf——新 master 要用同一套 CA 签发身份。

第五步加入:master02/03 执行 join 命令带 --control-plane 参数以控制面身份加入。第六步验证:kubectl get nodes 三 master Ready → 主节点关机实测 VIP 漂移 → curl -k https://<VIP>:16443/version 正常返回 → 全部组件 Pod Running。 追问链:

  • 迭问1:为什么集群版本 1.17.3 这么老? → 答:这是华为 BCS 边缘自治版 1.2.6 的配套版本约束——BCS 对纳管的 Kubernetes 版本有严格兼容矩阵,生产环境必须严格按版本配套表执行,不能自己随意升级。这个经历让我养成了"版本配套意识":生产选版本先查配套关系再谈新旧。加分句是顺势对比:这也解释了移动云项目为什么用 v1.28.2——新集群没有 BCS 约束,按当时的稳定版自主选型。一个约束、一个自主,两个项目放在一起讲就是我的差异化标签。
  • 迭问2:移动云为什么不用自建 Nginx+Keepalived? → 答:因为云上有更好的托管选项。CLB 是云厂商托管的四层负载均衡:无单点(不需要自己搞 VIP 漂移)、免运维(健康检查自动摘除故障节点)、配置即生效(控制台点几下)。自建 Nginx+Keepalived 在云上要自己维护 VIP 漂移脚本、心跳检测、两台代理节点的可用性——把托管服务的能力自己重新实现一遍。我的答案是"两个方案都实操过,云上首选托管;自建方案在我手里是完备的备选"——这个对比本身就是亮点,说明我选型是靠经验不是靠习惯。

6.18 ARM 国产化环境差异(本人真实) 【理解】 ​

标准回答(四点全是实测): ARM 环境部署 K8s 与 x86 的差异我总结四条,全部在银行项目实测过。第一软件源:必须用 aarch64 专用源——yum 的 kubernetes-el7-aarch64 源,默认源里是 x86 包装不上或装错。第二 Docker:当时没有 ARM 仓库版,用的是 aarch64 静态二进制包 docker-18.09.8.tgz 解压安装——解压、拷贝 systemd unit、enable 就能跑,这也是我"静态二进制优先"装机思路的来源。

第三镜像:所有容器镜像必须 arm64 版本——海外镜像内网拉不到就 docker save/load 离线导入,我写了批量加载脚本(第 5 章 5.11),交付前用 docker manifest inspect 或 inspect 的 Architecture 字段逐个核对——这在我这里是流程检查项不是口头注意。

第四编译类软件:源码装 Redis、Nginx 要确认 ARM 依赖库齐全,缺的 devel 包要从 ARM 源补。不变的方面也要说清:内核参数(ip_forward、bridge-nf-call-iptables)、防火墙、systemd 这些系统层操作与 x86 完全一致——差异集中在"二进制和镜像的架构"这一层。 追问链:

  • 迭问1:ARM 上报 manifest 不匹配? → 答:报错是 no matching manifest for linux/arm64/v8,意思是这个镜像 tag 没有构建 arm64 版本。处理三选一:找官方 multi-arch 版本(manifest inspect 看 architectures 列表确认);基于 arm64 基础镜像自己构建;用手头已验证的 arm64 离线包直接分发。我的流程化做法是交付前做"镜像架构清单"——一个表格列镜像名、tag、architecture、来源,逐项确认后才进分发环节。这套流程把偶发事故变成了清单检查,是我从踩坑里提炼的资产。

6.19 集群升级流程(项目二规范) 【理解】 ​

标准回答(五步): 集群升级我的五步规范(移动云项目写进运维文档的版本)。第一步先备份:etcd 快照 snapshot save,落到监控节点——升级失败的最终退路就是快照恢复整个集群状态。第二步 kubeadm upgrade plan 看当前版本和可升级的目标版本。

第三步先控制面后 worker 逐台滚动:kubeadm upgrade apply v1.28.x 升控制面 → 节点 drain(cordon 加驱逐)→ 升级该节点的 kubelet/kubectl → 重启 kubelet → uncordon 恢复调度 → 下一台。第四步每台升级完验证:节点 Ready、该节点业务 Pod 正常再动下一台——把故障半径控制在单节点。

第五步升级前确认兼容性:跨版本的废弃 API 变更(比如某些资源从 beta 版 API 移除,旧清单 apply 会失败)、镜像版本与运行时兼容。铁律是版本跨度:小版本逐级升(1.27→1.28→1.29),绝不跳级——每级之间都有 API 和行为的破坏性变更缓冲。 追问链:

  • 迭问1:节点维护重启怎么操作? → 答:标准三步走,顺序不能乱:kubectl cordon 节点 先打成不可调度(新的 Pod 不再上来)→ kubectl drain 节点 --ignore-daemonsets --delete-emptydir-data 驱逐存量 Pod(DaemonSet 的 Pod 忽略、emptyDir 数据丢弃要确认)→ 维护操作完成后验证节点 Ready → kubectl uncordon 节点 恢复调度。核心思想是"先摘流量再动刀":cordon 和 drain 保证这台机器上不再有业务,重启维护就不影响用户。我升级 kubelet、换内核参数、重启节点都用这套动作。

6.20 kubectl 排查命令树 【必背】 ​

标准回答(按树背诵:一看二描三日志四进五上节点): 我的 K8s 排查是一棵五层命令树,从上往下钻。第一层看:kubectl get pods -o wide 看状态和所在节点(NODE 空=没调度上)、kubectl get events -A --sort-by=.lastTimestamp | tail -20 看最近事件流、kubectl top pod/node 看资源(需要 metrics-server)。第二层描:kubectl describe pod <pod> 看详情——九成原因在 Events 里(调度失败、镜像拉取、探针失败、退出码);kubectl get ep <svc> 看 Endpoints 是否为空。第三层日志:kubectl logs <pod> --tail=200 -f 看当前日志,CrashLoop 必用 --previous 看上次崩溃的遗言。第四层进容器:kubectl exec -it <pod> -- sh 进去 ps/ss/env/curl 自证环境。第五层上节点:journalctl -u kubelet -f 看 kubelet 日志(CRI、CNI、挂载错误都在这)、crictl ps -a 和 crictl logs 看运行时层、df -h/free -m/dmesg | tail 看节点资源。方法论一句话总结:describe 看"K8s 认为发生了什么",logs 看"应用自己说了什么",journalctl 看"节点上发生了什么"——三层信息互为印证,从内到外逐层排除。 实操模板(排查命令树,背熟):

bash
# ① 看
kubectl get pods -o wide                  # 状态+所在节点(NODE 空=没调度上)
kubectl get events -A --sort-by=.lastTimestamp | tail -20
kubectl top pod / node                    # 资源(需 metrics-server)
# ② 描(90% 原因在 Events)
kubectl describe pod <pod> -n <ns>        # 调度失败/拉镜像/探针/退出码
kubectl get ep <svc>                      # Endpoints 空=没关联上
# ③ 日志
kubectl logs <pod> --tail=200 -f
kubectl logs <pod> --previous             # 上次崩溃(CrashLoop 必用)
# ④ 进容器
kubectl exec -it <pod> -- sh              # ps/ss/env/curl 自证
# ⑤ 上节点
journalctl -u kubelet -f                  # CRI/CNI/挂载错误在这
crictl ps -a && crictl logs <id>
df -h && free -m && dmesg | tail

追问链:

  • 迭问1:describe 看不出来怎么办? → 答:说明问题在 K8s 对象层之下,继续下钻的顺序:kubectl get pod -o yaml 看 status 段的 exitCode 和 reason(比 describe 更原始)→ logs --previous 拿崩溃遗言 → kubectl debug 或 exec 临时容器进环境复现 → 上节点 journalctl -u kubelet 加 crictl 看运行时层 → 最后网络层 tcpdump 抓包验证。整套思路是"每层拿到该层的事实,再决定往哪层去"——我没有玄学排障,只有分层下钻。

6.21 CLB 统一接入与故障切换演练(项目二真实) 【理解】 ​

标准回答: 移动云集群控制平面的统一入口是云负载均衡 CLB 四层 TCP 转发:所有客户端连 CLB 的 VIP:6443,CLB 把流量分发给三台 master 的 apiserver,并对后端做健康检查,异常节点自动摘除——等价于银行项目的"Nginx + Keepalived"在云上的托管版。

故障切换演练我实测做过,完整过程:停掉一台 master 的 kubelet(模拟节点故障)→ CLB 健康检查失败把该后端摘除 → 通过 VIP 执行 kubectl get nodes 正常返回,全程无感 → 恢复 kubelet,控制面组件自动重启、etcd 自动同步重新加入集群,CLB 重新纳入该节点。

配合"三副本 etcd 加秒级摘除"的设计,控制平面按 99.99% 可用性目标设计——注意口径:这是设计目标,讲保障机制不讲实测 SLA。worker 层的高可用靠多副本加 podAntiAffinity:节点故障时 Pod 被重新调度到健康节点,分钟级恢复。 追问链:

  • 迭问1:3 master 挂 1 台为什么变慢? → 答:三重延迟叠加:etcd 的成员列表里还有故障节点,每次共识交互要等它超时重试;CLB 的健康检查有探测周期,摘除有延迟;controller-manager 和 scheduler 要做 leader 重选(约 15 秒无主窗口)。如果故障节点短期修不好,要主动 etcdctl member remove 把它从 etcd 成员里摘掉,让集群回到健康的 2/3 多数派状态,重试开销立刻消失——这是我演练时观察到的现象和对应处理。

6.22 九项生产避坑 = 九个故障问答(项目二真实,逐条收录) 【必背】 ​

每条按"现象→根因→解决→预防"讲;面试问"印象深刻的故障"任选 1–2 条展开。这九条全部是我在移动云集群部署过程实际踩出并验证过解法的,每条都有完整的故事线。

追问链(内嵌于九条问答):每条的追问形态就是"为什么会出现这个现象?→ 根因是什么?→ 你怎么解决和预防的?",按序号自问自答即可。

  1. 节点 NotReady(最高频):现象是 worker join 后一直 NotReady。定位:describe 节点和 kubectl get pods -n kube-flannel 发现 Flannel Pod ImagePullBackOff——海外镜像内网拉不到、CNI 起不来、节点网络没配好自然 NotReady,九成 NotReady 是镜像问题。解决:镜像分层策略——首选内网仓库(Flannel 的 init 和主容器镜像全局替换成内网地址)、备选阿里云公共仓库、兜底二进制;预防:部署顺序强制"先 CNI 后 worker"、镜像清单提前入内网仓库。
  2. crictl 报 socket 错:现象是 crictl ps 报连接 socket 失败。根因:crictl 默认按 docker/containerd/crio 顺序遍历端点,环境里多运行时配置残留就遍历错。解决:/etc/crictl.yaml 显式写死 containerd.sock;预防:初始化脚本预置该文件——"把组件的默认行为固定成显式配置"是我从这个坑提炼的通用方法。
  3. etcd 健康检查 Pod NotFound:现象是 etcd 明明在跑,按文档敲命令说 Pod 不存在。根因:堆叠式 etcd 的 Pod 名是 etcd-<节点主机名>,不带 kube- 前缀,文档示例与真实资源名不符。解决:校验命令匹配真实命名,etcdctl 带三证书连 127.0.0.1:2379 查 endpoint health,三副本全 healthy 才过。
  4. ClusterRoleBinding 更新报错:现象是 apply 报字段不可更新。根因:roleRef 是不可变字段,设计上禁止原地改绑定关系。解决:delete --ignore-not-found 删除重建;并且复用内置 view 只读角色满足最小权限(见 6.13)。
  5. Grafana 装不上(GPG 404):现象是 apt update 报 Grafana 源 GPG 密钥 404。根因:镜像站的签名公钥 URL 失效。解决:源配置加 [trusted=yes] 跳过签名校验(内网可控环境的取舍)或直接下 DEB 包本地安装。
  6. promtool 找不到包:现象是 apt install promtool 报 Unable to locate package。根因:Ubuntu 源里 promtool 内置在 prometheus 主包,没有独立包。解决:apt install prometheus 即可。这条的价值是"报找不到包时先怀疑包的归属关系,再怀疑源"。
  7. node-exporter 无容器镜像:现象是内网仓库找不到 node-exporter 镜像。解决:全节点 apt install prometheus-node-exporter,systemd 托管二进制运行,默认 9100 端口,指标输出与容器版完全兼容,Prometheus 配置零修改——"容器不可得时二进制等价替代"的思路。
  8. 命名空间删后重建部署失败:现象是删了 monitoring 重建后各种残留冲突。根因:ServiceAccount、权限绑定、应用资源删除有时序和 Finalizer 残留。解决:严格按"命名空间 → ServiceAccount → 权限绑定 → 应用资源"顺序完整重建,删除时等 Finalizer 清完再建——顺序化重建链路写进部署文档。
  9. 认证频繁失败/时间戳乱跳:现象是节点间认证失败、日志时间戳跳变。根因:节点时间不同步,TLS 证书校验对时间敏感。解决:全节点 chrony 指向内网 NTP,偏差毫秒级;预防:"时间同步检查"写进初始化基线和巡检项——最不像坑的坑,伤害最大。

6.25 namespace 多环境隔离与资源配额 【必背】 ​

标准回答: namespace 是 K8s 里"一集群多环境/多团队"的隔离单元:资源对象(Deployment、Service、ConfigMap 等)都归属某个 ns,RBAC 可以按 ns 授权,网络策略也能按 ns 限定。

我的多环境实践模式:按环境分 ns(dev、test、prod 各一个),同名应用在三个 ns 里各自一套——发布顺序 dev → test → prod,回滚互不影响;配合 RBAC 做权限隔离:开发组只对 dev 有写权限、test 只读、prod 只有运维能操作。两个配套对象让隔离有"牙齿":LimitRange 给 ns 设默认 requests/limits——不设资源的应用自动获得默认值,防止有人漏配变成 BestEffort;ResourceQuota 限 ns 的总配额——CPU/内存总量、对象数量(Pod 数、Service 数),多团队共用集群时防止一方吃光资源。踩坑提醒(避坑清单第 8 条):ns 删除是异步的,Finalizer 没清完就重建会资源冲突——删除后要确认 Terminating 状态消失再重建,并且按"ns → SA → 权限 → 应用"的顺序完整创建。 追问链:

  • 迭问1:跨 ns 能访问吗? → 答:默认网络层是通的(K8s 的 ns 不提供网络隔离,那是 NetworkPolicy 的职责)——跨 ns 访问 Service 用全限定名 服务名.命名空间.svc.cluster.local。RBAC 层面 Role + RoleBinding 只在本 ns 有效,要跨 ns 授权得用 ClusterRole 配 RoleBinding(角色全集群、绑定限本 ns)——这是权限设计的常用组合。需要网络隔离时上 NetworkPolicy 按 nsLabel 限定——我们没上 Calico 是因为内网环境不需要这层策略,Flannel 不支持 NP 是明确的选型取舍(6.16)。
  • 迭问2:生产环境你建议 ns 按什么维度分? → 答:小团队按环境分(dev/test/prod)最直观;规模大了按"环境 × 业务线"两级分(prod-order、prod-pay)——业务线自治、爆炸半径受控、权限和配额都能按业务线管理。反模式是按技术分层分(全部前端一个 ns)——故障和权限边界与业务不对齐。我的实践:移动云按环境分,Kuboard 的多级权限正好按 ns 映射给不同角色——ns 的划分方式要和"谁是使用者、谁能操作"对齐,这是设计原则。

6.26 label 与 selector:K8s 的关联机制 【理解】 ​

标准回答: label 是打在对象上的键值对标签(app: web、tier: frontend、release: v2),selector 是按标签筛选的表达式——这两者构成 K8s 里几乎所有"关联关系"的实现机制:Service 靠 selector 找到自己的后端 Pod;Deployment 靠 template 里的 label 认领自己管理的 Pod;网络策略、亲和性调度也按 label 圈定范围。

等值 selector(app=web 且 tier=frontend)和集合 selector(environment in (prod, uat)、tier notin (db))两种语法。

工程上我积累的规范:label 是"运维界面"——命名统一小写、用公司级约定(app 应用名、version 版本、env 环境),因为标签打乱的最大代价是 Service 关联错 Pod、监控统计聚合不了;改 label 等于换身份——给运行中 Pod 改标签它会被原 Deployment"认不出"(变成孤儿被新 RS 回收),排查"服务一半实例流量异常"时先 diff Pod 的 label 和 Service 的 selector。 追问链:

  • 迭问1:Service 关联不到 Pod,label 这边怎么查? → 答:三步对账:kubectl describe svc 看 Selector 字段的键值;kubectl get pods --show-labels 看目标 Pod 的实际标签——逐字符对比(多了空格、大小写不同、env 写成 environs 都是真实事故);再确认 Pod 是否 Ready——未通过就绪探针的 Pod 即使标签匹配也不进 Endpoints(6.5 的另一类原因)。对账用命令验证:kubectl get pods -l 'app=web' 直接用 Service 的 selector 查——查出来几个就进 Endpoints 几个,一条命令完成归因。

6.27 CoreDNS 与集群内域名解析 【理解】 ​

标准回答: 集群内的服务发现靠 DNS:CoreDNS 以 Deployment 跑在 kube-system,kubelet 给每个 Pod 的 resolv.conf 配上集群 DNS。解析规则(FQDN 格式)必须会写:同 ns 短名 http://web 即可;跨 ns 用 web.other-ns;全限定名 web.default.svc.cluster.local。

Headless Service(clusterIP: None)的解析特殊——返回的是后端 Pod IP 列表而不是 VIP,这是 StatefulSet 稳定网络标识(mysql-0.mysql-hs)的实现基础。Pod 的 DNS 策略:默认 ClusterFirst(集群域名先查 CoreDNS,未命中转发外部上游)、hostNetwork 的 Pod 要显式配 dnsPolicy: ClusterFirstWithHostNet 才能用集群 DNS——这是用主机网络的 Pod"解析不了服务名"的经典坑。

排障三板斧:Pod 里 nslookup 服务名、查 CoreDNS Pod 状态和日志、看 resolv.conf 配置——"服务名不通先分是网络不通还是解析不通",tcp 服务用 telnet 服务名 80 直判。 追问链:

  • 迭问1:CoreDNS 挂了什么现象? → 答:所有"按名字访问"的流量失败(Service 名解析不了),但直接用 ClusterIP 或 Pod IP 的访问完全正常——因为转发规则在 iptables/ipvs 里,不依赖 DNS。所以判断口径清晰:服务名 nslookup 失败而 IP 直连正常 = DNS 层问题,查 CoreDNS(kube-system 里 coredns Pod 状态、Corefile 配置的上游、节点到 DNS 的网络);两边都不通才是网络层问题。CoreDNS 是 Deployment,挂了会被自动重建,风险主要是配置错误(改 Corefile 转发上游写错)——这也是我改集群组件配置前必备份的原因。

6.28 K8s 网络模型:Pod 通信与 Service 转发深入 【理解】 ​

标准回答: K8s 的网络模型三句话:每个 Pod 一个独立 IP;所有 Pod 不经 NAT 直接互通(跨节点也一样);节点上的代理(kube-proxy)实现 Service 抽象。跨节点 Pod 通信靠 CNI:我们用的 Flannel VXLAN——源节点把 Pod 的 IP 包封装进 UDP(VXLAN,默认 4789 端口)包里,外层写两个节点 IP,对端解封后交给目标 Pod——效果是"用三层网络承载二层连通",跨可用区也能跑(6.16 的选型原因);代价是封装开销约 10-20% 吞吐。

Service 的转发由 kube-proxy 落规则:iptables 模式——每个 Service 生成一组链,概率随机选 Pod DNAT,规则线性匹配、Service 数量大了性能下降;ipvs 模式——内核哈希表存转发关系,支持 rr/lc/sh 等调度算法,大规模集群首选。

一次 Service 访问的完整链路:客户端 Pod 发包到 ClusterIP → 本节点 iptables/ipvs 规则 DNAT 成某个 Endpoints 里的 Pod IP → 按 Pod 网络路由(同节点走 cni0、跨节点走 flannel 隧道)到达。 追问链:

  • 迭问1:iptables 和 ipvs 模式怎么选? → 答:小规模(几百 Service 以内)iptables 够用且是默认,排障材料多(iptables-save 直接看);Service 上千后 iptables 的线性匹配和规则更新抖动(每条规则全量刷)会成为问题,切 ipvs——需要内核模块 ip_vs 等加载、kube-proxy 配 mode: ipvs。我们 7 节点规模用默认 iptables 没有任何压力;判断标准是 Service 规模和 kube-proxy 的 sync 延迟指标,不是盲目追新——这又是一个"按规模选架构"的例子。
  • 迭问2:Pod 到 Pod 同节点怎么走? → 答:同节点不走隧道:两个 Pod 挂在同一个 cni0 网桥上(Flannel 场景),内核按网桥直接二层转发,没有封装开销——所以同节点 Pod 间吞吐明显高于跨节点,这也是"把互相调用频繁的服务用 podAffinity 聚一聚"的性能依据。跨节点才进 VXLAN 封装路径。理解"同节点走桥、跨节点走隧道"这个分界,抓包时才知道在哪等包:同节点在 cni0 抓,跨节点在 flannel.1 或物理网卡抓——我的抓包定位顺序就是按这个拓扑走的。

6.29 节点 NotReady 全链路排障 【必背】 ​

标准回答: NotReady 是 K8s 最高频的节点故障,我的全链路排障按"心跳链"走。先懂原理:节点状态是 kubelet 定期(默认 10 秒)向 apiserver 上报的,NodeLost/NotReady 的本质是 apiserver 超过宽限期(默认 40 秒)没收到心跳——原因要么 kubelet 没在跑,要么 kubelet 到 apiserver 的链路断了,要么 kubelet 自己卡死。

排障五步:① kubectl describe node 看 Conditions 和 Events——NotReady 加 "kubelet stopped posting node status" 就是心跳断;② 上节点 systemctl status kubelet——inactive/failed 就查 journalctl -u kubelet(我遇到的高频:容器运行时没起来、cgroup 驱动不匹配);③ kubelet 活着就看 CNI——kubectl get pods -n kube-flannel,Flannel Pod ImagePullBackOff 起不来、节点网络没配好,九成 NotReady 的根因(避坑清单第 1 条:内网拉不到海外镜像);④ 网络链路——节点到 apiserver(VIP:6443)连通性、证书是否过期(证书失效 kubelet 无法认证,节点也会 NotReady,6.14 的关联);⑤ 资源层——节点磁盘满、内存耗尽导致 kubelet 失联。处理原则:NotReady 节点上的 Pod 会被驱逐重建(有容忍的除外),先保业务再修节点。 追问链:

  • 迭问1:NotReady 期间上面跑的 Pod 会立刻死吗? → 答:不会立刻死——已经运行的容器和本地转发规则还在,业务短暂可用;但这个节点上的 Pod 会被标记 Unknown、不再接收新调度,超过 Pod evict 超时(默认 5 分钟)后进入驱逐队列,控制器会在其他节点重建它们。所以故障窗口内的行为是"存量先跑、增量绕行、超时迁走"。运维含义:修 NotReady 有个黄金窗口,但别指望它无限期扛着——节点修复失败就主动 drain 把业务迁干净再处理。

6.30 kubectl 高频命令速查与效率习惯 【理解】 ​

标准回答: 我的 kubectl 使用按场景分组背。对象管理:get/describe/delete 基础三件套,get 记住 -o wide(多看节点和 IP)、-o yaml(看完整定义)、--show-labels;apply -f 清单声明式管理是标准姿势(比 edit 可版本化);edit 快速改运行中对象。排障组(6.20 的命令树):logs/exec/describe/get events。发布运维组:rollout status/history/undo(6.4)、scale 手动扩缩、set image 改镜像;cordon/uncordon/drain 节点维护三连(6.19)。

调试便捷组:kubectl cp 容器和本机互拷文件(排障取日志)、kubectl port-forward svc/web 8080:80 本地端口转发(不上 NodePort 也能本地调试服务)、kubectl top 资源查看。

效率习惯三个:-n 指定 ns 或 kubectl config set-context --current --namespace=xxx 切默认 ns,避免全 ns 扫;alias k=kubectl;危险命令(delete)先 --dry-run=client -o yaml 预览。日常 80% 操作我在 Kuboard 页面做(简历口径),kubectl 是我的底层能力和应急通道——两层都用得熟。 追问链:

  • 迭问1:apply 和 edit 和 patch 怎么选? → 答:apply -f 是声明式——期望状态在文件里,可版本化、可重复执行(幂等),是管理标准资源的正道;edit 是交互式改运行中对象——快但改动不留文件、下次 apply 会覆盖回去,适合应急和临时验证;patch 按类型打局部补丁(strategic merge / json merge)——脚本里改单个字段用,比如给节点打标签 kubectl patch 或 label。我的纪律:生产变更一律走 apply(文件进 git),edit 只用于排障期的临时调整且当天补回文件——"运行状态和声明文件必须最终一致",这是声明式运维的底线。

6.31 Helm 与 Operator:包管理进阶概念 【了解】 ​

标准回答: 这两个是 K8s 生态的进阶话题,按"解决什么"讲,口径为了解层。Helm 是 K8s 的包管理器:把一套应用的全部 YAML(Deployment、Service、ConfigMap 等)模板化打包成 chart,用 values.yaml 按环境填参数,一条 helm install 部署整套——解决"几十个 YAML 手工 apply、改一个参数到处改"的复杂应用交付问题;helm upgrade/rollback 提供版本化发布。

我的实际状态:我们的部署用自定义脚本加清单文件管理(规模没到必须 Helm 的程度),Helm 我理解 chart 结构和常用命令,属于"会用但生产没用过"——如实答。Operator 是"运维知识代码化"的模式:用 CRD 自定义资源(比如定义一个 MySQLCluster 对象)加控制器(operator 程序)把某个软件的运维动作(部署、备份、故障切换、升级)编码成自动化逻辑——数据库、消息队列这类复杂有状态软件的专家经验变成软件。

我们集群里的 kube-state-metrics 这类组件是标准对象管理,没自研 Operator——答题按"理解模式和价值 + 举例 etcd operator、redis operator"的口径。 追问链:

  • 迭问1:什么时候值得引入 Helm? → 答:三个信号:应用数量多到手工 YAML 难维护(十几套以上)、多环境差异靠复制 YAML 改参数容易漏、团队需要统一的发布版本管理。引入前也要评估代价:模板语法的学习成本、chart 的维护责任、和 CI/CD 的集成方式。我的判断:"Helm 解决的是规模化交付问题,我们当前的脚本能覆盖就不过度引入"——但新公司如果有多套集群多环境,我上手 Helm 没有障碍,这套"何时引入"的分析本身就是我的答案。

6.32 Flannel vs Calico:CNI 选型对比 【理解】 ​

标准回答: 两大主流 CNI 我按"模型、能力、复杂度"三个维度对比。Flannel:定位简单可靠的三层网络——VXLAN 模式封装 Pod 包跨节点互通(6.28 讲过),host-gw 模式纯路由性能好但要求二层直通;能力边界清晰:只管连通,不支持 NetworkPolicy(网络策略)、没有网络流量加密。Calico:纯三层的路由方案(BGP 完成节点间路由分发,IP-in-IP 或 VXLAN 跨网段)——性能优秀(接近原生路由),最大优势是全功能:NetworkPolicy 的实施层(按标签定义 Pod 间访问控制)、BGP 组网能力。复杂度对比:Flannel 概念少、排障直观(flannel.1 接口、路由表就这几样)、几乎零运维;Calico 功能强但组件多(felix、bird、控制器)、概念和排障面大。我的选型逻辑(移动云真实决策):内网可信环境、没有 NetworkPolicy 需求、7 节点小规模——Flannel 够用且好运维,为用不上的能力引入复杂度不值;如果环境要多租户网络隔离或安全策略,就上 Calico。"按需求选组件"这个决策过程本身,比记住对比表更值钱。

追问链:

  • 迭问1:NetworkPolicy 是什么、没有它会怎样? → 答:NetworkPolicy 是 K8s 的网络隔离资源:按 label 选择 Pod、定义入向/出向规则(允许哪些源访问、允许访问哪些目标端口),由支持它的 CNI(Calico、Cilium)落地为 iptables/eBPF 规则。没有它(比如 Flannel),集群内任何 Pod 可以访问任何 Pod——内网可信环境这可以接受,但多团队共用或多租户场景就是横向移动的自由通道。我们环境的补偿措施:ns 按环境隔离加 RBAC 管操作面、云安全组管节点边界——讲清"我知道缺什么、为什么当前可接受、上量后怎么补",这是负责任的选型表达。

6.33 kubelet 深入:静态 Pod、cgroup 驱动与证书轮换 【理解】 ​

标准回答: kubelet 是节点上的"大管家",三个进阶知识点。静态 Pod:kubelet 直接监视 /etc/kubernetes/manifests/ 目录下的清单文件创建 Pod,不经过 apiserver——控制面组件(apiserver、etcd、controller-manager、scheduler)都以静态 Pod 存在,实现集群自举(6.1 追问 2);运维应用:改静态 Pod 清单文件保存即生效(kubelet 检测到文件变化自动重建容器)——我更新 apiserver 证书后"杀容器让 kubelet 用新配置拉起"用的就是这个机制。cgroup 驱动:kubelet 管理容器资源有两种 cgroup 层级方式——cgroupfs(直接操作文件系统)和 systemd(交给 systemd 管理单元),kubelet 与容器运行时的驱动必须一致,不一致会导致资源统计漂移甚至节点异常——我们 containerd 配 SystemdCgroup=true、KubeletConfiguration 配 cgroupDriver: systemd,两端对齐是 kubeadm-config 模板里的固定项(6.16)。证书轮换:kubelet 的客户端证书默认 1 年,--rotate-certificates 开启后 kubelet 临近到期自动向 apiserver 申请续期(配 CSR 自动批准的 RBAC),免掉手工续期的运维负担——这也是我们证书巡检规范覆盖的一项。

追问链:

  • 迭问1:静态 Pod 和普通 Pod 排障时有什么不同? → 答:两个差异要记住:静态 Pod 不受 apiserver 调度——kubectl delete pod 它会"删了又回来"(kubelet 立即重建),要真正改它必须改节点上的 manifest 文件;它们的日志和事件还是能在 apiserver 层看到(kubelet 会做镜像代理),但 kubectl edit 对它无效——edit 改的是 apiserver 里的对象,静态 Pod 的"真身"在文件里。我处理控制面组件异常时的顺序:describe 静态 Pod 看事件 → 上节点看 manifest 文件和 kubelet 日志——忘了"文件才是真身"这个事实,就会出现"怎么删都删不掉"的困惑。

6.34 K8s 版本演进要点(1.24 dockershim 移除等)【了解】 ​

标准回答: K8s 版本演进里与运维最相关的几件事,也是我"两个项目两个版本"叙事的技术注脚。1.24(2022):移除 dockershim——kubelet 不再内置 Docker 适配层,直接 CRI 对接 containerd(5.13 详述);存量集群升级到 1.24 前必须完成运行时迁移。1.25-1.27:PodSecurity 准入替代 PSP(Pod 安全策略)、一批老 API 版本清理——升级前必须检查工作负载用的 API 版本是否被移除,kubectl-convert 或 kubeadm upgrade plan 会提示。1.28(我们移动云的版本):侧车容器原生支持(init 容器声明 restartPolicy: Always 变成常驻 sidecar)、CSI 扩容等增强,社区进入稳定成熟期。我的两个版本对照着讲很自然:银行项目 v1.17.3(2023 年部署,受华为 BCS 1.2.6 配套约束——BCS 对纳管集群版本有兼容矩阵,生产严格按配套表执行);移动云 v1.28.2(2025 年自主选型的当时稳定版)。这一对比是我"版本配套意识"的最好证明——同一套技术在不同约束下的决策我都做过。

追问链:

  • 迭问1:升级前怎么检查 API 兼容性? → 答:三条路:kubeadm upgrade plan 的输出会列出即将废弃/移除的 API;kubectl get --raw /metrics 或专门工具(pluto、kubent)扫描集群里正在使用旧 API 版本的对象;逐个核对第三方组件(CNI、存储、监控)的版本兼容矩阵——它们经常是被忽略的升级阻塞项。我在升级规范里写的是"三个清单":API 废弃清单、组件兼容清单、etcd 快照回退预案——检查完才允许动第一台节点。

6.35 etcd 深入运维:压缩、配额与碎片整理 【理解】 ​

标准回答: etcd 是集群的心脏,它的运维参数围绕"数据膨胀"这个天敌。三个机制讲清楚:压缩 compaction——etcd 保留所有历史版本,watch 和读旧版本靠它们,但无限增长会撑爆空间;auto-compaction-retention: "1" 表示只保留 1 小时历史(我们 kubeadm-config 里就配了这项),定期压缩旧 revision。配额 quota-backend-bytes——默认 2G 后端空间上限,超过默认配额 etcd 进入告警然后拒绝写入(整个集群只读!)——我们配到 8G 留余量;触发告警后的恢复:压缩历史加 defrag 释放空间,然后撤销告警。碎片整理 defrag——压缩只是"逻辑标记可覆盖",实际空间要 defrag 重整 btree 才还给文件系统;defrag 有短暂阻塞,要逐节点滚动做(不能三台一起,会同时不可用)。监控上必须盯 etcd 的 db_size 指标和告警标志——etcd 磁盘满是"集群级瘫痪"级故障,我们把它列在慢故障巡检清单里(7.12 的三件事之一)。

追问链:

  • 迭问1:etcd 磁盘满告警了怎么应急? → 答:四步:确认状态——endpoint status 看 DB SIZE 是否到配额、是否有 NOSPACE 报错;止血——执行 compaction 压到安全 revision(保留近期)、逐台 defrag 释放空间(一台完成再做下一台);解除——空间降下来后 alarm disarm 清除告警、集群恢复写;根治——检查 auto-compaction 配置是否生效、评估配额要不要上调、排查是否有异常的 watch/大对象(比如巨幅 ConfigMap)在撑大数据量。全过程注意 defrag 逐台滚动——这个纪律我在讲"滚动操作"时和 Vastbase license 滚动重启是并列的例子。

6.36 K8s 安全进阶:PodSecurity 与最小化面 【了解】 ​

标准回答: K8s 安全我在 RBAC(6.13)之外再讲三层。PodSecurity 准入(PSA,1.25+ 替代废弃的 PSP):给 namespace 打三种安全级别标签——privileged(不限制)、baseline(挡最危险的:特权容器、hostNetwork 挂宿主路径、宿主 PID)、restricted(最严:必须非 root、删全部危险能力、seccomp 必须);给应用 namespace 落 baseline 或 restricted,把"危险配置"在准入时拒绝。网络隔离:NetworkPolicy 按 label 控制 Pod 间流量(6.32 讲过依赖 Calico/Cilium),没有它时靠 ns 边界和安全组补偿。供应链安全:镜像扫描(Trivy 卡点,5.31)、镜像来源可信(私有仓库加认证,5.12)、镜像签名校验。我的实际口径:内网可信环境我们做了 RBAC 分权、私有仓库认证、Kuboard 操作审计这层;PSA 和 NetworkPolicy 属于我知道怎么配、按环境风险等级启用的项——安全是分层按需,不是一次全开。

追问链:

  • 迭问1:如果让你给现有集群补一层安全,先做什么? → 答:按"性价比排序"做三件事:先收紧 RBAC——审计 cluster-admin 绑定的使用情况,把日常操作收敛到命名空间级权限(这一步零风险高收益);再开镜像准入——私有仓库强制认证加 Trivy 扫描卡点,挡住带高危漏洞的镜像(流程已在 CI 里,启用即可);最后按 namespace 逐步落 baseline 级 PSA——先在 test 环境跑两周看有没有应用被拒,再推生产。顺序的原则是"先管理面后数据面、先零风险后有影响"——安全改造最忌讳一步到位把业务全挡了。

6.38 集群健康巡检清单(kubectl 侧)【理解】 ​

标准回答: 我给移动云集群定的日常巡检清单,kubectl 一组命令五分钟出全貌:① kubectl get nodes——全 Ready 是底线,谁的 AGE 异常短(频繁重启)要追;② kubectl get pods -A --field-selector=status.phase!=Running | head——过滤出所有非 Running 的 Pod,一眼看到异常堆积;③ kubectl get events -A --sort-by=.lastTimestamp | tail -20——最近事件流,Warning 类要逐条看;④ kubectl top nodes——节点资源分布,谁长期 80% 以上就是容量风险;⑤ etcd 三副本 health(6.15 的命令)加证书有效期(kubeadm certs check-expiration,6.14 的规范)——两个"慢故障";⑥ kubectl get componentstatuses(老版本)或控制面静态 Pod 状态——控制面组件齐活。这份清单和 Prometheus 告警是互补关系:告警是被动的"越线才响",巡检是主动的"扫一遍找苗头"——比如 Pending 的 Pod 还没触发告警但巡检能提前看到。巡检结果我记进固定表格留趋势,这也是"人肉监控"到"体系监控"过渡期最扎实的动作。

追问链:

  • 迭问1:巡检发现某节点 CPU 长期 85%,怎么决策? → 答:先归因再动作:top pod 看是谁吃的——单个 Pod 异常(比如它自己的限流缺失或业务高峰)就处理那个应用(加 limit、优化、迁移);整体负载就是容量问题——按 6.10 的方式评估:能调度走的(改反亲和打散)、该扩节点的(提前申请)、该限流的(保护优先级高的)。决策框架是"先看结构再看总量"——85% 这个数字本身不说明问题,谁占的、什么时段、能不能挪,才是答案。这类巡检驱动的容量决策,是我们"资源交付分钟级"之外另一个体现主动运维价值的场景。

6.39 K8s 故障自愈的边界:什么交给系统、什么留给人 【理解】 ​

标准回答: 这是我做运维体系设计时的一个高层认知题。K8s 能自动自愈的:容器崩溃重启(重启策略)、副本数维持(Deployment)、节点失联后的 Pod 重调度、探针失败的处理(liveness 重启/readiness 摘流量)、CLB/LB 健康检查摘除——这些"规则明确、判定简单、动作可逆"的交给系统,我放心。必须留给人的:数据类故障——数据库损坏、主从数据分叉,自动切换可能放大损伤,要人确认拓扑再动;配置类故障——错误的配置发布导致的异常,系统的"自愈"会把错误配置反复拉起(CrashLoop 就是系统在忠实执行错误声明),必须人介入改声明;级联风险场景——资源不足时大规模重启、磁盘满时的驱逐风暴,自动机制的叠加反而雪上加霜(6.23 的重启风暴案例),需要人做全局权衡。我的设计原则一句话:"让机器处理'可逆且规则明确'的,让人处理'需要判断责任和取舍'的——自愈不是无人化,是把人从重复劳动里解放出来处理真正需要人的问题。"这也是我聊 AIOps 话题的立场底稿。

追问链:

  • 迭问1:既然系统会自动重调度,为什么还要半夜被告警叫醒? → 答:因为"重调度成功"和"业务恢复"不是一回事:节点挂了 Pod 迁走了,但如果是资源不够迁不动(全 Pending)、或者 Pod 起来了探针一直不过、或者故障是级联的(存储/网络问题跟着节点一起出),业务就是没恢复——这些"系统尝试过但失败"的状态必须人接手。告警叫醒人的不是"节点挂了"这个事件,而是"自愈链路没走通"这个结果。我设计告警时的一个原则就是:告"系统自愈失败",别告"系统正在自愈"——前者才是需要人的时刻,这是降噪和保命之间的正确分界线。

6.41 部署方式对比:kubeadm / 二进制 / K3s / 托管 【理解】 ​

标准回答: K8s 的部署形态我按"控制力与成本的交换"来对比。kubeadm:官方推荐——把集群生命周期管理(init/join/upgrade/renew)标准化,组件以静态 Pod 跑,我的两个项目都是它;优点是流程标准、文档全、升级路径清晰,缺点是要自己管高可用入口和证书细节(6.14/6.17 的内容就是它的代价)。二进制手工部署:每个组件自己下载二进制写 systemd——教学价值大(能真正理解每个组件),生产几乎不选:工作量大、易错、升级全手工,课程实验用它来"打开引擎盖"看组件。K3s:单二进制的轻量发行版——内置 SQLite datastore(也可外接 etcd)、内置 ingress 和本地存储,一条命令起集群;适合边缘、开发、资源受限场景(我课程实验的听书平台就是 K3s)。托管(EKS/GKE/移动云 ACK 类):控制面由云厂商管,免运维、SLA 有保障,代价是失去控制面的控制权和一定的锁定。我的选型结论:自建生产用 kubeadm(我的主实践)、边缘和实验用 K3s、上云优先托管——"按控制需求和维护预算选形态",这四种我都答得出定位。

追问链:

  • 迭问1:kubeadm 部署的集群什么部分还是要自己运维? → 答:清单很具体:高可用入口(我们的 CLB 或 Nginx+Keepalived——kubeadm 不管 LB);证书生命周期(1 年有效期,续期规范自己定);etcd 的备份和健康(快照、压缩、defrag);CNI 和组件的版本配套(镜像供给、版本兼容矩阵);节点系统的初始化基线(第 1 章 1.20 的脚本)。所以"kubeadm 半小时建好集群"和"生产可用"之间,差的正是这些——我的 9 项避坑清单和运维规范基本就是在填这些缝,这也是我在面试里证明"真在生产养过集群"的弹药。

6.42 本章案例 ​

  • 【真实项目·银行多 master 改造(STAR)】背景:数币平台 K8s 集群初为单 master(v1.17.3,受华为 BCS 1.2.6 配套约束),业务方要求控制面高可用,单点不可接受。任务:在统信 UOS ARM 环境完成单 master 到三 master 的改造并验证故障转移。行动:入口层用 haproxy 和 Nginx 双方案做 16443 四层代理(先后实施,验证等价)加 Keepalived VIP 漂移(check 脚本检测 16443 端口监听、priority 100/90、weight -20);证书层踩了 x509 报错的坑后按六步更新 certSANs;scp CA 类证书到 master02/03 后以 --control-plane 加入;组件配置统一切到 VIP:16443 并逐个重启生效。结果:3 master + 10 worker 稳定运行,主节点关机实测 VIP 秒级漂移、kubectl 经 VIP 正常返回,支撑数币试点业务;完整方案沉淀为公司标准实施文档,也成了我面试里最完整的"高可用实战"故事。
  • 【真实项目·移动云三主集群(STAR)】背景:公司外包业务上线在即,需要生产级云原生底座。任务:资源评估、方案编制、部署实施、验证全流程由我负责。行动:按通用高可用架构规划 7 台 ECS——3 台 2C4G master 跨 3 可用区、3 台 8C16G worker、1 台 4C8G 独立监控节点;CLB 四层 VIP:6443 统一接入加健康检查自动摘除;containerd 1.7.8(SystemdCgroup)加 kubeadm v1.28.2 加 Flannel v0.22.0 VXLAN;部署顺序严格"初始化→控制面→CNI→worker→验证→监控";过程中踩出九项避坑清单(NotReady 镜像、crictl socket、etcd 命名、roleRef 不可变、GPG 404、promtool、node-exporter、命名空间重建、时间同步)并逐项验证解法。结果:控制平面按 99.99% 设计目标交付(三副本 + CLB 秒级摘除,口径为设计目标);资源交付从天级到分钟级;日常 80% 运维操作在 Kuboard 页面完成;方案成为公司后续外包项目的标准基础设施底座。
  • 【真实项目·控制面故障切换演练】背景:集群宣称高可用必须有实测支撑。任务:验证单 master 故障时业务和管理面无感。行动:停一台 master 的 kubelet 模拟故障 → 观察 CLB 健康检查摘除该后端 → 经 VIP 的 kubectl get nodes 正常返回 → 恢复 kubelet 观察控制面组件自动重启、etcd 自动同步重新加入。结果:切换全程 kubectl 无失败请求,恢复后集群回到三副本健康状态;演练记录和"故障切换操作手册"写进运维规范——"高可用是设计出来的,更是演练出来的"是我从这次得到的信条。
  • 【经典故障·Secret 空值致通信失败】背景:安全整改要求把初始化密码改用 Secret 卷挂载。现象:某环境前端正常但调鉴权接口必报错,Pod 全 Running,表面无异常。定位:逐层排除后 cat 挂载的 Secret 文件,发现内容为空——随机密码生成脚本异常被封装吞掉,且密码含反斜杠被转义少了一位;另一环境恰好没生成反斜杠所以"一个环境好一个环境坏",极具迷惑性。解决:全量特殊字符测试用例加剔除易错字符。方法论:Pod Running 不代表配置正确,挂载内容要 cat 验证——这条我写进了部署自检清单。
  • 【经典故障·Pod 全量重启资源不足(话术案例)】背景:某平台改一个证书 Secret 触发 200 多个 Pod 同时重启,标准版私有云资源有限,全部 Pending 卡死。处理:先全停释放资源,再按模块分批——每次拉 20 个等 3 分钟观察稳定,分 10 批全部拉起,历时约半小时恢复。根治:后续版本改为滚动更新(每批 10 个)避免重启风暴。方法论:公共配置变更等于全量重启风暴,资源有限的环境必须有分批拉起预案——这个案例讲"变更风险预判"特别有说服力。
  • 【课程项目】云原生听书平台实验:K3s 三 master 集群加 Jenkins CI/CD 加 HPA 弹性伸缩,做了多分支流水线、Trivy 镜像扫描卡点、命名空间三环境隔离。口径是课程实验;被问 K3s 与 K8s 区别时答:单二进制轻量发行版、API 兼容、内置 datastore 和 ingress,适合边缘和中小规模——把它定位为"K8s 的轻量形态"而不是替代品。

6.43 章末自测清单(快问快答) ​

  1. 七组件和两大节点? → Master:apiserver/etcd/controller-manager/scheduler;Node:kubelet/kube-proxy/容器运行时。
  2. 为什么说 apiserver 是唯一入口? → 所有组件经它读写 etcd(list-watch),互不直连。
  3. 四大控制器口诀? → 无状态 Deployment、有状态 StatefulSet、每节点一份 DaemonSet、一次/定时 Job。
  4. Pod 创建三步? → apiserver 认证准入写 etcd → scheduler 预选优选绑定 → kubelet 建 pause+CNI/CSI+起容器。
  5. 滚动更新不中断靠什么? → readinessProbe(新 Pod Ready 才接流量)+ maxSurge/maxUnavailable 控节奏。
  6. Service 四类? → ClusterIP(内)/ NodePort(节点端口)/ LoadBalancer(云 LB)/ ExternalName(映射外部)。
  7. Ingress 链路? → 浏览器→DNS→LB→Ingress Controller→Service→Endpoints→Pod。
  8. 探针由谁发起、三种? → kubelet;liveness 重启容器、readiness 摘 Endpoints、startup 保护慢启动。
  9. request/limit 一句话? → request 决定调度,limit 决定运行上限(内存超限 OOMKilled=137)。
  10. QoS 三级与驱逐顺序? → Guaranteed→Burstable→BestEffort,BestEffort 最先被驱逐。
  11. 为什么 3 个 master? → etcd Raft 多数派:3 容忍 1 台,偶数无意义。
  12. 改入口地址第一件事? → 想证书:certSANs 加 VIP/主机名,移走旧证书重新 init phase certs apiserver。
  13. 多 master 加入参数? → --control-plane --certificate-key <密钥>,先 scp ca/sa/front-proxy/etcd 证书。
  14. worker NotReady 先查什么? → CNI Pod 镜像拉取(90% 是镜像),先 CNI 后 worker 的部署顺序。
  15. 堆叠 etcd 的 Pod 名? → etcd-<节点主机名>,不带 kube- 前缀。