3. Ingress高可用基础

Ingress Controller 是集群南北向流量的唯一入口,它一旦不可用,所有依赖 Ingress 暴露的服务都会同时中断。因此 Ingress 的高可用不是"多开几个副本"这么简单,而是要把副本数量、调度分布、驱逐保护、更新节奏四件事组合起来,才能同时覆盖"节点故障""主动运维""版本发布"三类中断来源。本节围绕这四件事展开,并以 ingress-nginx 为例给出可直接参考的配置与判断依据。

一、多副本:先解决"有没有冗余"

单副本 Ingress Controller 存在两个硬伤:一是 Pod 所在节点宕机即入口中断;二是滚动更新时旧 Pod 先终止、新 Pod 尚未 Ready,会出现秒级到十秒级的连接拒绝。因此生产环境至少 2 副本,推荐 3 副本——3 副本允许在滚动更新期间始终保持 2 个可用实例,同时容忍 1 个节点故障。

副本数不是越多越好。每个 ingress-nginx 副本都会 watch 全部 Ingress/Service/EndpointSlice 对象,副本数增加会线性放大 apiserver 的 watch 压力。经验上,中小集群 2~3 副本、大型集群 3~5 副本即可,真正需要更高吞吐时应优先横向扩展节点与调优 worker 参数,而不是无限加副本。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: ingress-nginx-controller
  namespace: ingress-nginx
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1
      maxSurge: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: ingress-nginx
  template:
    metadata:
      labels:
        app.kubernetes.io/name: ingress-nginx
    spec:
      terminationGracePeriodSeconds: 60
      containers:
      - name: controller
        image: registry.k8s.io/ingress-nginx/controller:v1.10.1
        lifecycle:
          preStop:
            exec:
              command: ["/bin/sh","-c","sleep 15 && /nginx-ingress-controller ..."]
        readinessProbe:
          httpGet:
            path: /healthz
            port: 10254
          periodSeconds: 5
          failureThreshold: 2

这里有两个容易被忽略的细节。第一,terminationGracePeriodSeconds 要大于 preStop 的 sleep 时间,否则 Pod 会在优雅退出完成前被强杀。第二,preStop 中的 sleep 是为了等待 EndpointSlice 从各节点 kube-proxy/ipvs 规则中摘除,避免"Pod 已停止但流量仍被转发过来"造成的 502。sleep 15 秒是常见起点,具体值应结合集群规模和 EndpointSlice 同步延迟实测调整。

二、Pod 反亲和性:让副本分散到不同节点

仅有 3 个副本还不够——如果调度器把它们全放到同一节点,节点故障依然会一次性带走全部实例。Pod 反亲和性(podAntiAffinity)就是用来约束"同一组 Pod 不要落在同一故障域"的调度规则。

反亲和性分软硬两种。requiredDuringSchedulingIgnoredDuringExecution 是硬约束,不满足就 Pending;preferredDuringSchedulingIgnoredDuringExecution 是软约束,尽量满足但不阻塞调度。对 Ingress Controller,推荐"硬约束按节点、软约束按可用区"的组合:节点级硬约束保证同一节点最多一个副本,可用区级软约束引导副本尽量跨 AZ 分布,避免因 AZ 级故障整体失联。

      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchLabels:
                app.kubernetes.io/name: ingress-nginx
            topologyKey: kubernetes.io/hostname
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchLabels:
                  app.kubernetes.io/name: ingress-nginx
              topologyKey: topology.kubernetes.io/zone

使用硬约束前必须确认节点数 ≥ 副本数,否则多出的副本会永久 Pending。若集群节点数少于期望副本数,应改用软约束,或引入 topologySpreadConstraints 做更平滑的分布控制。

三、PodDisruptionBudget:保护主动驱逐

反亲和性解决的是"Pod 落在哪",PDB 解决的是"Pod 能不能被主动赶走"。节点排水(kubectl drain)、集群升级、自动缩容都会触发 Eviction API,PDB 就是 Eviction API 的准入闸门:当驱逐会导致可用副本低于阈值时,驱逐请求被拒绝并重试,直到有新的 Ready 副本补位。

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: ingress-nginx-pdb
  namespace: ingress-nginx
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app.kubernetes.io/name: ingress-nginx

PDB 的阈值有两种写法:minAvailable 保证至少保留几个可用,maxUnavailable 限制最多同时少几个。3 副本场景下,minAvailable: 2 与 maxUnavailable: 1 等价,含义都是"任意时刻至少 2 个 Ready"。需要注意:PDB 只约束自愿中断,节点宕机、OOMKilled 这类非自愿中断不受 PDB 保护,所以 PDB 不能替代多副本和反亲和性,三者是互补关系。

PDB 的 selector 必须与 Ingress Controller Pod 的标签精确匹配。若 selector 写错导致匹配到 0 个 Pod,PDB 会显示 ALLOWED DISRUPTIONS 为 0 或直接不生效,drain 时可能一次性驱逐全部副本。部署后务必用 kubectl get pdb -n ingress-nginx 核对 MIN AVAILABLE、ALLOWED DISRUPTIONS 与 AGE 是否符合预期。

四、滚动更新策略:控制发布期间的中断窗口

滚动更新由 maxUnavailable 和 maxSurge 两个参数控制。maxUnavailable: 1 表示更新过程中最多允许 1 个副本不可用,maxSurge: 1 表示最多额外创建 1 个新副本。两者配合,3 副本在更新期间可用数始终维持在 2~4 之间,不会出现"全部旧 Pod 已停、新 Pod 未 Ready"的空窗。

更新能否真正无损,还取决于新 Pod 何时被判定为 Ready。readinessProbe 探测 /healthz 通过只说明进程存活,不代表已加载完 Ingress 规则、已建立到 apiserver 的 watch。因此建议在 readinessProbe 之外,让新 Pod 在启动后先完成一次配置同步再对外服务;同时把 minReadySeconds 设为 5~10 秒,避免新 Pod 刚 Ready 就被判定为可用、随即又因配置未就绪而抖动。

下面这张图把四类机制与它们各自防护的中断来源对应起来,便于在排障时快速定位"这次中断应该由哪一层兜住"。

Ingress 高可用四层防护与中断来源对应关系 中断来源 单节点宕机 副本全在同一节点 可用区故障 副本未跨 AZ 分布 节点排水/升级 驱逐过多副本 版本发布 旧停新未就绪 防护机制 多副本部署 replicas ≥ 2,推荐 3 Pod 反亲和性 硬约束按节点 + 软约束按 AZ PodDisruptionBudget minAvailable: 2 滚动更新策略 maxUnavailable:1 / maxSurge:1 可用性结果 节点故障不中断 剩余副本继续服务 AZ 故障可降级 其他 AZ 承接流量 驱逐被限速 逐台排水,不批量失联 发布零中断 始终 ≥2 个 Ready 验证要点 1. kubectl get pods -n ingress-nginx -o wide → 确认副本数、分布节点、Ready 状态 2. kubectl get pdb -n ingress-nginx → ALLOWED DISRUPTIONS 应 ≥ 1,否则 drain 会被阻塞 3. kubectl drain <node> --ignore-daemonsets → 观察驱逐是否被 PDB 限速、服务是否持续可访问

五、把四层机制串起来验证

配置完成后,建议按以下顺序做一次闭环验证,确认四层机制确实生效,而不是只停留在 YAML 层面。

  1. 副本与分布:kubectl get pods -n ingress-nginx -o wide,确认 3 个 Pod 均为 Running/Ready,且 NODE 列互不相同。若出现两个副本同节点,说明反亲和性未生效或节点数不足。
  2. PDB 状态:kubectl get pdb -n ingress-nginx,ALLOWED DISRUPTIONS 应至少为 1。若为 0,说明当前可用副本数已等于 minAvailable,此时任何 drain 都会被拒绝,需要先恢复副本。
  3. 驱逐演练:对一台承载 Ingress 的节点执行 kubectl drain,观察被驱逐的 Pod 是否被 PDB 限速、是否在另一节点重建并 Ready,同时用 curl -I 持续探测入口,确认无 5xx。
  4. 滚动更新演练:修改镜像 tag 触发更新,用 kubectl rollout status deployment/ingress-nginx-controller 观察进度,同时持续探测入口,确认更新期间无连接拒绝。
如果 drain 长时间卡在 "Evicting pod" 且 PDB 的 ALLOWED DISRUPTIONS 为 0,通常不是 PDB 配置错误,而是新副本因资源不足、镜像拉取失败或反亲和性硬约束无法调度。此时应先解决调度问题,而不是删除 PDB 绕过保护。