首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >一条漏掉的 PodDisruptionBudget,一场可用区级别的惊魂

一条漏掉的 PodDisruptionBudget,一场可用区级别的惊魂

原创
作者头像
编程小妖女
修改2025-09-26 23:57:57
修改2025-09-26 23:57:57
4030
举报
文章被收录于专栏:后端开发后端开发

本文记录我处理过的一个 Kubernetes 生产集群故障,目的是梳理问题分析的思路和排查策略,希望下次再遇到类似问题时,能更有针对性的解决问题,尽量少熬夜。

背景与技术环境

  • 集群:Kubernetes v1.27(托管版,三可用区),Containerd 运行时
  • 网关:NGINX Ingress Controller,上游为若干 DeploymentClusterIP Service
  • 核心服务:web(无状态),常态副本数 replicas=5,跨 AZ 均衡调度
  • 弹性:HPACPU 指标自动扩缩,minReplicas 当时未设置(疏忽)
  • 发布策略:Deployment 滚动更新,默认 maxUnavailable=25%maxSurge=25%
  • 保护:未配置 PodDisruptionBudget(PDB)

KubernetesPDB 用来约束自愿性中断期间允许离线的副本上限,在节点维护、滚动升级、集群缩容等动作里扮演保险丝的角色。


事故经过与用户侧症状

我司的运维在某一可用区对一批节点执行 kubectl drain 做内核补丁,配合集群自动修复能力,这在我们的标准变更流程里被认为是低风险动作。

因为 没有 PDB 的上限保护,这一 AZweb 的 5 个副本在短时间内被逐出到 0,再新调度上来的副本尚未全部 ReadyEndpoints 还未恢复时,对外流量已打到 Ingress,用户侧开始出现 502/503

对外暴露的典型错误:

  • nginx 错误日志:no live upstreams while connecting to upstream(下图为类似报错的日志截屏,标红处为关键字) 错误截图
  • 在使用 Istio/Envoy 的团队里,同类现象常体现为 no healthy upstream。这类报错的本质含义是网关找不到任何健康的后端。

这些错误并非 Ingress 本身坏了,而是后端副本在该 AZ 被清空或未就绪,导致上游列表一度为空。社区与厂商文章都将其归因于后端不可达或健康检查失败。


控制面与命令行层面的观测

事后复盘时,我们在另一套演练环境复现了当时的关键观测点,以确定结论具有普遍性。

  • kubectl get endpoints web -n prod 在故障窗口返回 subsets: [](空列表)
  • kubectl drain <node> --ignore-daemonsets --delete-emptydir-data 在没有 PDB 的前提下不会API Server 阻止,副本可以被快速驱逐
  • 日志侧的典型文字证据(取自已公开的同类报错,便于读者比对):
  • nginxno live upstreams while connecting to upstream(反向代理层无法找到任何上游)(Server Fault)
  • Istio/Envoyno healthy upstream(服务网格侧同义错误)

如果 存在 PDB 且设置合理,在 drain 期间常见的命令行报错会变成阻断式的:

代码语言:sh
复制
error when evicting pods/<pod-name>: Cannot evict pod as it would violate the pod's disruption budget.

这是一个真实而经典的错误字符串,出现在试图驱逐会突破 PDB 下限时。


复现与修复:给 web 配置 PDB + 滚动策略 + HPA 兜底

为了让读者可以亲手跑一遍,我们整理了一份可运行的 YAML 套件。在一个干净的命名空间下部署 DeploymentServiceHPAPDB,并演示 drain 前后行为差异。

目标:副本数为 5 的服务,任何自愿性中断期间 至少保留 4 个可用副本PDBDeployment 的滚动策略、HPAminReplicas 共同构成三道防线。PDB 的写法与行为可参考官方任务文档。

1)完整可运行清单

代码语言:yaml
复制
apiVersion: v1
kind: Namespace
metadata:
  name: prod
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
  namespace: prod
spec:
  replicas: 5
  revisionHistoryLimit: 2
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 0        # 关键:更新时不允许任何不可用
      maxSurge: 1
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: web
          image: ghcr.io/nginxinc/nginx-unprivileged:1.27-alpine
          ports:
            - containerPort: 8080
          readinessProbe:
            httpGet:
              path: /
              port: 8080
            periodSeconds: 3
            failureThreshold: 2
          livenessProbe:
            httpGet:
              path: /
              port: 8080
            periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
  name: web
  namespace: prod
spec:
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 8080
      protocol: TCP
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: web-hpa
  namespace: prod
spec:
  minReplicas: 5              # 兜底:即使缩容也不跌破 5
  maxReplicas: 10
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: web
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 60
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: web-pdb
  namespace: prod
spec:
  minAvailable: 4             # 关键:自愿性中断时至少保留 4 个可用副本
  selector:
    matchLabels:
      app: web

关于 minAvailablemaxUnavailable 的选择,社区文章分享过将 maxUnavailable 用于更直观地表达允许中断的上限,在小副本场景下尤其清晰;两者的取整规则也有说明。

2)验证步骤

  1. 部署:kubectl apply -f suite.yaml,待 Deployment 全部 Ready
  2. 演练 drain:针对某一节点执行 kubectl drain <node> --ignore-daemonsets --delete-emptydir-data
  3. 观测:
  • kubectl get pdb -n prodweb-pdbALLOWED DISRUPTIONS 会随 Ready 副本动态变化。
  • drain 试图把某个 AZReady 数压到 3 时,驱逐会被 API Server 拒绝,并在 kubectl 端可见错误 Cannot evict pod as it would violate the pod's disruption budget.(真实错误串)
  • Endpoints 在任何时刻都不会变成空集合,Ingress 不再出现 no live upstreams/no healthy upstream

为什么单靠滚动策略不够

Deployment 的滚动参数只对发布生效;而 drain、节点替换、集群缩容这类动作属于自愿性中断,需要通过 Eviction APIPDB 检查。官方 PDB 文档与 OpenShiftPDB API 说明都明确了驱逐判定基于 minAvailable/maxUnavailable

再补一层 HPA minReplicas 的原因也很现实:当业务低谷时 HPAreplicas 自动降到很小的值,如果没有 minReplicas 的硬阈,夜间维护配合 drain 仍然可能把可用副本压到临界乃至 0。关于 HPA 行为与建议,官方文档与最佳实践文章都有覆盖。


真实错误信息与排查路径备忘

这类事故里,最有辨识度的字符串与现象如下,可作为 SRE 值班排查的优先断面:

  • nginxno live upstreams while connecting to upstream,说明反向代理拿不到任何上游。常见于后端全部不可用或健康检查未通过。
  • Istio/Envoyno healthy upstream,含义相同。
  • kubectl drain PDB 时的阻断提示:Cannot evict pod as it would violate the pod's disruption budget.(我们在演练环境复刻,命令端确实会返回这一错误)

若要进一步从实现层面理解 nginxno live upstreams,一些解析文章会直接引用源码路径,点出该错误是在上游选择器返回 NGX_BUSY 时写入日志的,这能帮助你将问题迅速归因到上游列表为空或不可用


避坑清单(结合这次事故做成的标准化配置)

  1. 每个需要持续可用的 Deployment/StatefulSet 必须有 PDB
  • 对于副本 >=5web/api 类服务,建议 minAvailable: 4maxUnavailable: 1
  • 对于副本较少的关键组件,用 maxUnavailable: 0 更直观。maxUnavailable 的语义见文档与实践文。
  1. 滚动发布策略
  • maxUnavailable: 0,避免发布过程中叠加削减可用副本。配合 PDB 才覆盖到更多场景(发布以外的自愿性中断)。
  1. HPA 兜底
  • 为关键服务设置 minReplicas 不低于 PDBminAvailable。社区经验也强调 minReplicas > minAvailable 的直觉规则。
  1. drain 流程的预检
  • kubectl get pdb -A 检查目标命名空间是否缺少 PDB
  • 使用 --disable-eviction=false(默认)执行 drain,避免跳过 PDB 审查。
  • 若出现 Cannot evict pod...,说明 PDB 生效,这是安全的阻断信号,不要强制 --force
  1. 网关侧的告警与止血
  • 针对 nginxno live upstreams5xx 比例做细粒度告警,扩充到 Istio/Envoyno healthy upstream。根因多在后端不可用而非网关配置本身。

进阶:minAvailablemaxUnavailable 何时选谁

两者都能表达可用性下限,但在小副本时 maxUnavailable: 1 往往更贴合直觉(比如 replicas=2 的控制面 Deployment),运维更容易推算可被中断的上限;社区的经验贴也给出了做法与取整细节。


结语

Kubernetes 给了我们强大的自动化与弹性,但可用性这道题从不自动及格。一个简短的 PDB 清单,配上稳妥的滚动窗口与 HPA 的最小副本,就能在夜间维护时把风险从整面墙倒降到最多倒一块砖。事故之后,我们把 PDB 纳入了上架检查清单,让 drain 在没有保险丝时根本发不出去,也把 HPA minReplicas 调整到和业务 SLO 一致的安全下限。


附:PDB 与相关资料索引

  • Kubernetes 官方:为你的应用指定 PodDisruptionBudget(含 minAvailable/maxUnavailable 行为与示例)
  • OpenShift 文档:PDB policy/v1 的判定语义与字段说明(适用于理解取整与判定过程)
  • HPA 官方文档与演练(建议结合 minReplicas 使用)
  • 实战文章:PDBmaxUnavailable 的思考与经验分享(在小副本时更直观)
  • 常见用户侧报错的权威解释:Istio/Envoyno healthy upstreamnginxno live upstreams(本质都是后端不可用)

实战小抄(可直接贴进变更模板)

  • web 设置 PDB
代码语言:yaml
复制
  apiVersion: policy/v1
  kind: PodDisruptionBudget
  metadata:
    name: web-pdb
    namespace: prod
  spec:
    minAvailable: 4
    selector:
      matchLabels:
        app: web
  • Deployment 滚动策略设为 maxUnavailable: 0,将 HPA minReplicas 设为 >= PDB.minAvailable
  • drain 前先看 kubectl get pdb -n prod;在缺失 PDB 的命名空间禁止执行变更。
  • 演练 drain 时若看到 Cannot evict pod as it would violate the pod's disruption budget. 说明保护起效,不要--force 绕过。

只要还在 Kubernetes 上跑关键业务,用一张 PDB小纸条把门拴上,值回票价。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景与技术环境
  • 事故经过与用户侧症状
  • 控制面与命令行层面的观测
  • 复现与修复:给 web 配置 PDB + 滚动策略 + HPA 兜底
    • 1)完整可运行清单
    • 2)验证步骤
  • 为什么单靠滚动策略不够
  • 真实错误信息与排查路径备忘
  • 避坑清单(结合这次事故做成的标准化配置)
  • 进阶:minAvailable 与 maxUnavailable 何时选谁
  • 结语
  • 附:PDB 与相关资料索引
    • 实战小抄(可直接贴进变更模板)
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档