跳转至

Kubernetes、IaC 与云实操

在 45 分钟内定位“部分 Pod Pending、部分 Pod CrashLoopBackOff”

任务

给定 namespace1 checkout-prod 的只读访问权限、最近一次发布 commit、节点和监控只读权限。要求在不删除 Pod2、不重启节点的前提下:分类所有异常 Pod,给出至少两个可证伪假设,定位主因与促成因素,提出可回退止血方案,并提交取证记录。题目预置:部分 Pod 无法调度,另一些已调度但反复退出。

相关技术说明

Pending3 必须先按 spec.nodeName 分叉:未绑定节点属于 scheduler4/约束/资源/卷问题,已绑定但 Pending 属于 kubelet、镜像、CNI5、CSI6 或 sandbox。CrashLoopBackOff7 则应读取上一容器终止状态、退出码和 --previous 日志。二者可能有共同变更,但不能因为同时发生就假设同根因。

现场排障的核心是保存时间线和原始证据,先读后写。events 会聚合/过期,日志可能因重启轮转;任何重启、强删或放松调度约束都会改变现场。结论要说明哪些证据支持、哪些证据排除,以及止血会牺牲的容量、安全或拓扑保证。

实践经验

复合生产场景的标准结论是:新版本把 memory request 从 512Mi 提高到 2Gi,导致新 Pod 在现有节点无可行容量;少数调度成功的 Pod 又因 Secret 键名变更,入口脚本退出 1。两者同源于一次发布,但属于独立故障阶段。稳妥止血是回滚 workload 模板到已验证版本,保留一个失败 Pod 供取证;若业务容量危急,先扩已验证旧版本副本,不通过删 request 强塞节点。

参考实现思路/命令

  1. 固化版本、时间和对象,不先修改:
kubectl -n checkout-prod get deploy,rs,pod -o wide
kubectl -n checkout-prod get events --sort-by=.metadata.creationTimestamp
kubectl -n checkout-prod rollout history deploy/checkout
  1. 对 Pending 分组并确认是否已绑定。先把第一步记录的对象名赋给 PENDING_PODCANDIDATE_NODE,后续命令均引用同一对象,避免取证中途选错:
kubectl -n checkout-prod get pod -o custom-columns='NAME:.metadata.name,NODE:.spec.nodeName,PHASE:.status.phase,REASON:.status.conditions[?(@.type=="PodScheduled")].reason'
kubectl -n checkout-prod describe pod "$PENDING_POD"
kubectl describe node "$CANDIDATE_NODE"
kubectl -n checkout-prod get resourcequota,limitrange,pvc

期望证据为 FailedScheduling: Insufficient memory,节点 allocatable 与已分配 request 证明并非实时 CPU 问题;同时检查 affinity、taint 和 PVC,避免首因偏误。

  1. 对 CrashLoop 保存当前与上一实例;CRASH_POD 同样取自第一步的固定记录:
kubectl -n checkout-prod get pod "$CRASH_POD" -o jsonpath='{range .status.containerStatuses[*]}{.name}{"\t"}{.restartCount}{"\t"}{.lastState.terminated.reason}{"\t"}{.lastState.terminated.exitCode}{"\n"}{end}'
kubectl -n checkout-prod logs "$CRASH_POD" -c app --previous --timestamps
kubectl -n checkout-prod get deploy checkout -o yaml

日志应显示缺少 PAYMENT_ENDPOINT,再比对新旧 ReplicaSet 的 Secret keyRef。禁止输出 Secret 值,只验证键是否存在与引用是否一致。

  1. 写出决策:暂停发布;保留一个失败副本;回滚到上一 ReplicaSet,观察 Ready endpoint、业务错误率和内存调度;在非生产验证修正后的 request 与 Secret 契约后重新 canary。

验收

  • 10 分钟内按“未调度 / 已调度未启动 / 进程重启”分类,命令与时间戳可复现。
  • 用 scheduler event + request/allocatable 证明 Pending 主因;用 lastState + previous log + 新旧配置差异证明 CrashLoop 主因。
  • 止血不会修改 Secret 明文、强删 Pod 或破坏故障域;包含回退、观察指标和停止条件。
  • 长期方案包括配置契约测试、生产规格压测、发布前调度模拟与异常原因告警。

评分(100 分)

  • 20 分:分层与时间线;25 分:Pending 证据链;20 分:CrashLoop 证据链;15 分:安全止血;10 分:长期改进;10 分:表达不确定性和副作用。
  • 若直接重启/删 Pod 破坏证据,最高 60 分;若读取并展示 Secret 明文,按严重安全错误最高 40 分;只给命令清单无结论,最高 50 分。

常见错误

  • kubectl top 的低利用率否定 request 不足。
  • 把 CrashLoopBackOff 当根因,不看上一退出状态。
  • 所有异常强行解释成单一根因,忽略一次发布的两个独立缺陷。
  • 直接降低 request/取消 limit,没有说明节点过载和邻居影响。

设计并验证一个可排空、可扩缩、受安全基线保护的 Deployment

任务

为一个启动 70 秒、p99 请求 2 秒、可处理 SIGTERM 的 HTTP API 编写核心 Kubernetes 配置:Deployment、Service、PDB8、HPA9 与 topology spread。要求滚动发布时无计划内 5xx,单 AZ10 故障仍保留服务;使用非 root、只读根文件系统,明确临时写目录。给出本地/测试集群的验证命令和失败注入方案。

相关技术说明

可用性由 rollout、探针、终止、拓扑、弹性和容量共同决定。startup probe 应覆盖最坏启动时间;readiness 决定接流,liveness 只查本地不可恢复故障。终止时 kubelet 的 grace period 已经开始计时,preStop 必须在这个总预算内完成。/drain 的契约应是:先把应用置为 draining、使 readiness 失败并拒绝新长任务,再等待 EndpointSlice/LB 的实测传播时间和在途请求完成,最后才返回;若它只改一个标志后立即返回,随后的 TERM 仍可能截断请求。

题目只给 p99=2 秒,并未给最长请求/deadline,因而不能仅凭 p99 证明“零计划内 5xx”。生产配置必须以实测端点传播上界、最长允许请求、连接排空方式和安全余量计算 terminationGracePeriodSeconds,同时给排空过程设置有界超时,避免无限等待。验收目标应绑定指定负载模型和错误预算,而不是作无条件承诺。

PDB 只约束自愿中断,topology spread 才减少副本同域;HPA 的 CPU utilization 依赖 request。maxSurge 会增加连接/IP/CPU 峰值,maxUnavailable、PDB 与最小副本需在故障和升级两种场景求解。安全上下文若只读,必须给 /tmp 或应用缓存挂受限 emptyDir。

实践经验

复合生产落地可选择最小 6 副本、三 AZ 近似均匀,maxUnavailable: 0maxSurge: 1,PDB minAvailable: 4。压测若测得端点/LB 最坏传播 10 秒、业务最大 deadline 20 秒,可把示例 grace 设为 45 秒并要求 /drain 在 35 秒内完成;数值必须随环境复测。这个配置优先可用与平滑连接,发布较慢;若集群缺 surge 容量会卡住,因此上线前验证至少一个额外 Pod 的 CPU、内存与 IP。HPA 用请求率更理想;题目只给 CPU 时设置合理 request 并明确数据库连接的全局上限。

参考实现思路/命令

核心配置片段如下,示例 digest 仅展示语法;真实答案必须替换为流水线批准并可在 registry 验证的 digest:

apiVersion: apps/v1
kind: Deployment
metadata: {name: api}
spec:
  replicas: 6
  strategy:
    rollingUpdate: {maxSurge: 1, maxUnavailable: 0}
  minReadySeconds: 20
  progressDeadlineSeconds: 900
  selector: {matchLabels: {app: api}}
  template:
    metadata: {labels: {app: api}}
    spec:
      terminationGracePeriodSeconds: 45
      topologySpreadConstraints:
      - maxSkew: 1
        topologyKey: topology.kubernetes.io/zone
        whenUnsatisfiable: DoNotSchedule
        labelSelector: {matchLabels: {app: api}}
      containers:
      - name: api
        image: registry.example/api@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
        ports: [{containerPort: 8080}]
        resources:
          requests: {cpu: 500m, memory: 512Mi}
          limits: {memory: 1Gi}
        startupProbe:
          httpGet: {path: /startup, port: 8080}
          periodSeconds: 5
          failureThreshold: 24
        readinessProbe:
          httpGet: {path: /ready, port: 8080}
          periodSeconds: 5
          failureThreshold: 2
        livenessProbe:
          httpGet: {path: /live, port: 8080}
          periodSeconds: 10
          failureThreshold: 3
        lifecycle:
          preStop: {httpGet: {path: /drain, port: 8080}}
        securityContext:
          runAsNonRoot: true
          allowPrivilegeEscalation: false
          readOnlyRootFilesystem: true
          capabilities: {drop: ["ALL"]}
          seccompProfile: {type: RuntimeDefault}
        volumeMounts: [{name: tmp, mountPath: /tmp}]
      volumes: [{name: tmp, emptyDir: {sizeLimit: 256Mi}}]

Service selector 指向 app: api;PDB 用 minAvailable: 4;HPA 设置 minReplicas: 6、有界 maxReplicas、CPU 目标并配置慢缩容稳定窗口。示例 /drain 必须实现上文的“置为 NotReady→等待传播→等待在途请求→有界返回”契约,而不是立即返回的普通 HTTP 接口;45 秒只是基于示例测量值。实际生产还应给 namespace 启用 Restricted PSS,并验证目标集群确有三个 zone。

验证命令/步骤:kubectl diff -f 后 server-side dry-run;发布时观察 kubectl rollout status、EndpointSlice 条件与每 AZ Ready 数;持续压测中删除一个 Pod、drain 一台节点、隔离一个 zone(演练环境)、让启动延迟至 100 秒、向进程发送 TERM。分别记录 /drain 开始、端点停止接收新流量、最后在途请求完成和进程退出的时间,采集 5xx、p99、连接中断、desired/Ready、probe failure、PDB allowed disruptions 和下游连接。

验收

  • startup probe 提供约 120 秒窗口,注入 100 秒启动延迟时不会被 liveness 杀死,startup 成功前不接流;在给定最长请求和传播上界下,TERM 后不接新流且 45 秒总预算内完成在途请求。
  • 指定压测模型下正常 rollout 与单节点 drain 无计划内 5xx;单 AZ 故障至少 4 个 Ready,或明确指出缺少 N-1 容量时拒绝测试。
  • 配置通过 Restricted 基线,容器无法写根文件系统但 /tmp 可用且有上限。
  • HPA 不与手工副本持续争写,scale-out 不突破数据库/IP 容量预算。

评分(100 分)

  • 20 分:三类探针语义;20 分:终止/排空;15 分:rollout/PDB;15 分:拓扑与 N-1;15 分:资源/HPA;10 分:安全;5 分:验证质量。
  • liveness 检查数据库或只有固定 sleep 排空,相关项不得满分;忽略目标集群实际 zone/容量,最高 75 分;把 PDB 当节点故障保护,扣 15 分。

常见错误

  • startup failureThreshold 只覆盖平均启动时间。
  • /drain 做成立即返回的“改标志”接口,或把 preStop 等待与 grace period 误认为相加。
  • PDB 与 maxUnavailable: 0 配置后声称任何故障都不中断。
  • 设置 CPU limit 却不观察 throttling,或 HPA CPU 目标没有 request。

将 30 个生产资源从单体 state 安全拆到网络与应用两个 state

任务

给定一个含 VPC、子网、路由、负载均衡器和应用实例的生产 state,要求把 12 个网络资源迁入独立 network-prod state,业务不能中断,任何资源不得重建。提交迁移 manifest、只读预检、逐步命令/配置方案、回退边界、验收证据;并解释为何不能把两条流水线并行执行。

相关技术说明

state 拆分是在两个状态容器间转移远端对象的管理权,不是复制 HCL。源 state 与目标 state 有各自锁,无法形成跨后端事务;因此不能同时保证“始终有映射”和“绝不出现双重映射”。正确不变量是同一远端对象不同时受两个资源地址管理,并用全局 writer 冻结保护源端释放与目标接管之间的短暂无映射窗口。资源地址、对象 ID、provider alias/账号/区域和依赖输出必须逐项稳定,网络消费者不能在迁移中读到不存在的 remote state 输出。

最安全路径取决于工具和后端能力,但共同原则是:冻结全部 writer、备份并校验 lineage/serial、在克隆环境预演、以 manifest 驱动、每批后做完整 plan。跨 state 通常先以无销毁方式让源 state 忘记对象,再由目标 import;工具明确支持时也可直接执行受控 state 移动。moved block 适合同一 state 内的地址重构,不能把它当成跨后端事务;-target 只用于受控恢复/验证,不作为最终完整性证明。

实践经验

复合生产方案采用 12 行 manifest(旧地址、新地址、云对象 ID、依赖者、风险级别),先迁无流量副作用的子网/安全规则,最后迁路由和网关。整个窗口内源/目标 apply 队列都冻结,只有迁移身份可写 state。为避免消费者读取半成品,先在目标配置发布完整 outputs,但应用 state 仍固定使用迁移前的稳定参数存储;迁移验收后再切 remote-state 契约。

参考实现思路/命令

  1. 预检:确认工作区、账号与后端,下载/保存两个 state 的受控版本,列清单并生成只读 plan:
tofu -chdir=monolith state list
tofu -chdir=network state list
tofu -chdir=monolith plan -refresh-only -out=pre-move.refresh.tfplan
tofu -chdir=monolith show -json pre-move.refresh.tfplan

真实环境中 state 备份由后端版本功能和受控命令完成,文件作为敏感数据保存,不附到工单。验证每个 ID 的账号、区域、类型和当前 owner;两条 CI 队列进入全局维护锁。

  1. 在目标代码声明相同资源地址与完全匹配配置,固定 provider/lock file;先只在 state 副本/沙箱验证 import block(示意),生产目标 state 此时尚不接管:
import {
  to = aws_route.private_to_shared
  id = "rtb-0abc1234def567890_10.20.0.0/16"
}

在副本中重放并确认 import 后目标 plan 不要求 update/replace,同时校验 provider 身份与对象 ID。不要为了“先验证生产目标”而提前 import 真实目标 state;那会与源 state 形成双重映射。

  1. 在全局维护锁内按小批迁移:先把源资源块替换为工具支持的无销毁 removed 声明并 apply,或在备份后执行受控 state remove 再同步移除源配置;立刻查询云 API,证明对象仍存在且 ID 未变。随后才在生产目标 state import 同一 ID,并检查 state show。若后端和工具支持直接跨 state 的受控移动,也必须遵守相同清单、备份和锁原则。整个短暂无映射窗口内,源、目标普通 plan/apply 队列都保持冻结。

  2. 每批执行两个完整 plan:目标应 no-op,源不得 destroy;云 API 查询确认对象 ID、路由和关联未变化。最终解除全局锁前做端到端探针,并把 import 临时块按工具推荐方式清理/保留审计记录。

  3. 回退边界:源已释放而目标尚未 import 时,先保持 writer 冻结,可按清单把对象重新 import 回源地址,或在确认真实对象从未变化后恢复受控 state 备份;目标已接管但尚未改真实对象时,则先移除目标映射再恢复源,避免双重所有权。一旦任一 state 已对真实资源执行更新,不能只回滚 state 文件,必须先对账真实对象。任何异常先停止 writer,而不是运行无人审核的反向脚本。

验收

  • manifest 12/12 对象 ID 与地址一致;迁移前后云资源 ID 未变、无 create/delete/replace 审计事件。
  • 两个最终完整 plan 均无未解释变化;应用连接、路由双向探针和 LB 健康连续通过。
  • 源 state 不再含网络对象,目标 state 完整拥有;无双重管理、无悬空资源。
  • state 备份受加密和最小权限保护,日志不泄露敏感属性;有明确异常停机与人工决策点。

评分(100 分)

  • 20 分:所有权/非事务边界;20 分:manifest 与预检;25 分:迁移顺序和锁;15 分:完整 plan/业务验证;10 分:回退;10 分:state 安全。
  • 并行 apply 两个 state 或直接删真实资源,判不合格;直接手改 state JSON,最高 40 分;只使用 -target 作为最终验证,最高 70 分。

常见错误

  • 以为两个 backend lock 能互相排斥。
  • 只从源删除资源配置并正常 apply,却没有无销毁 removed/state remove,导致真实对象被计划删除。
  • import 成功就结束,不检查配置差异导致下一次替换。
  • 恢复旧 state 作为万能回滚,忽略真实 API 已部分成功。

演练 GitOps 控制面与主区域同时丢失后的恢复

任务

设计并执行桌面演练:Git 主站、主区域 GitOps controller 和生产集群控制面同时不可用,但备用 Git 镜像、制品 registry、etcd snapshot、Terraform state、外部 secret manager、备用区域集群,以及可查询复制位点/恢复时间的备用区域业务数据库副本均可用。目标是按 RTO 60 分钟、订单数据与关键配置 RPO 15 分钟恢复订单 API;要求恢复过程中不允许陈旧 controller 自动覆盖 live state,并能证明部署的是事故前最后批准 digest。

相关技术说明

Git 只保存声明配置,不包含 Kubernetes live data、Terraform 资源映射、PVC 数据、secret 明文与云外部资源状态。灾备恢复需要一致检查点:Git commit、镜像 digest、state 版本、etcd snapshot revision 和业务数据库复制/恢复位点。题设显式提供可核验的备用数据库副本,才有证据验证订单数据 RPO;若该前提缺失,候选人必须指出 Git 或 etcd snapshot 无法证明业务数据 RPO 15 分钟。先启动 controller 再确认期望新鲜度,会让陈旧缓存或旧分支立刻 reconcile,造成二次事故。

恢复顺序遵循身份/KMS→只读证据→数据/state→目标基础设施→工作负载→流量→writer 自动化。etcd 全库恢复和在备用集群从 Git 重建是不同路径;题设有备用集群,优先用 Git+制品重建无状态资源,用数据库备份/复制恢复数据,而不是把主集群 etcd snapshot 强灌到不同基础设施。

实践经验

复合生产取舍是把备用 controller 以暂停同步/只读 diff 模式启动,先验证 Git mirror 最后 commit 的签名与提交时间,再从发布账本取得订单 API digest;secret 由备用区域 workload identity 获取。基础网络由最后可信 Terraform state 只读 plan 对账。只有 canary 健康、数据库复制点满足 RPO 后才逐批启用 sync 和 DNS 权重。这样可能牺牲几分钟 RTO,却降低陈旧期望写入的爆炸半径。

参考实现思路/命令

  1. 宣告事故并冻结 writer:禁用主/备用区域自动 apply、auto-sync 与 prune,记录 freeze 时间;验证 break-glass 身份、KMS 与审计可用。建立恢复表:组件、权威副本、时间戳/hash、RPO、owner。

  2. 验证 Git 与制品,不立即同步。先从恢复表把已核验路径、commit 和发布号写入 VERIFIED_MIRRORAPPROVED_COMMITAPPROVED_RELEASE

git -C "$VERIFIED_MIRROR" log -1 --show-signature --format='%H %cI'
git -C "$VERIFIED_MIRROR" show "$APPROVED_COMMIT":environments/prod/orders.yaml
crane digest "registry.example/orders:$APPROVED_RELEASE"

实际可使用组织批准的 registry 工具;验收是 manifest 中 digest 与发布账本完全一致,而不是 tag 名相同。对渲染结果做离线对象数、目标 namespace、高风险删除和策略检查。

  1. 基础设施只读对账:恢复/读取最后可信 backend 版本,在备用区域执行完整 plan;任何 delete/replace 先停。验证 VPC 路由、私有 DNS、KMS、secret identity、数据库复制位点和集群 API。Terraform plan artifact 绑定 commit/state serial,不在演练中随意 apply 未审批差异。

  2. 先恢复数据/依赖,再应用 canary:数据库备用提升前 fence 主写路径并记录实际 RPO;GitOps controller 保持 manual sync、prune off。只同步订单 API 的 namespace 级资源,先 1 个 canary,通过配置/Secret 引用、readiness、关键交易和审计后扩副本。

  3. DNS/LB 按 1%→10%→50%→100% 权重切流,每阶段观察错误、p99、数据库写入、队列积压和跨区域费用。旧长连接排空;任一门禁失败,权重回到上一步且不改变数据权威。

  4. 全量稳定后才逐 Application 恢复 self-heal,最后单独审批 prune;主区域恢复时作为新候选副本重新同步数据,不直接抢回主角色。

验收

  • 60 分钟内关键交易恢复,数据库实际 RPO 不超过 15 分钟;时间线可分解检测、身份、数据、部署和切流耗时。
  • 订单镜像 digest、Git commit、state serial 和数据位点均有签名/hash/时间证据;无 tag 漂移。
  • controller 在期望确认前未执行写入,prune 最后恢复;审计中无未授权删除/替换。
  • 演练环境不会发真实支付/通知,恢复后完成业务校验、积压处理、成本与安全检查。

评分(100 分)

  • 20 分:权威数据与一致检查点;20 分:恢复顺序;15 分:GitOps 防二次事故;15 分:数据 fencing/RPO;15 分:渐进切流;10 分:安全与审计;5 分:复盘指标。
  • 一启动 controller 就 auto-sync/prune,最高 40 分;按 tag 而非 digest 声称制品一致,扣 15 分;把 etcd snapshot 当跨区域业务数据备份,扣 20 分。

常见错误

  • 只恢复 Git,不恢复 state、密钥、数据与外部资源契约。
  • 没有可用且可量化的业务数据库恢复点,却用 Git 时间或 etcd revision 声称满足订单数据 RPO。
  • 把“API 可访问”当成订单业务恢复。
  • 主区域一恢复就自动回切,未处理分叉与旧连接。
  • 演练连接真实第三方副作用系统,造成测试订单或通知。

  1. Kubernetes namespace(命名空间)是在同一集群内组织和隔离对象名称、权限及配额的逻辑边界。 

  2. Pod 是 Kubernetes 中最小可调度单元,可包含一个或多个共享网络与部分存储的容器。 

  3. Pending 表示 Pod 已被 Kubernetes 接受,但仍未完成调度或容器启动;它是阶段而非根因。 

  4. Kubernetes scheduler(调度器)根据资源、约束和策略为尚未绑定节点的 Pod 选择节点。 

  5. CNI 是 Container Network Interface(容器网络接口),规定插件如何为容器配置网络。 

  6. CSI 是 Container Storage Interface(容器存储接口),规定编排系统如何调用存储插件完成供给、挂载和快照等操作。 

  7. CrashLoopBackOff 表示容器反复退出,kubelet 正按递增退避等待下次重启;必须继续检查退出状态和日志。 

  8. PDB 是 Pod Disruption Budget(Pod 中断预算),限制自愿中断期间可同时不可用的 Pod 数量。 

  9. HPA 是 Horizontal Pod Autoscaler(Pod 水平自动扩缩器),根据指标调整工作负载副本数。 

  10. AZ 是 Availability Zone(可用区),是云区域内具有相对独立供电、网络和故障边界的基础设施单元。