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 不能替代多副本和反亲和性,三者是互补关系。
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 就被判定为可用、随即又因配置未就绪而抖动。
下面这张图把四类机制与它们各自防护的中断来源对应起来,便于在排障时快速定位"这次中断应该由哪一层兜住"。
五、把四层机制串起来验证
配置完成后,建议按以下顺序做一次闭环验证,确认四层机制确实生效,而不是只停留在 YAML 层面。
- 副本与分布:
kubectl get pods -n ingress-nginx -o wide,确认 3 个 Pod 均为 Running/Ready,且NODE列互不相同。若出现两个副本同节点,说明反亲和性未生效或节点数不足。 - PDB 状态:
kubectl get pdb -n ingress-nginx,ALLOWED DISRUPTIONS应至少为 1。若为 0,说明当前可用副本数已等于 minAvailable,此时任何 drain 都会被拒绝,需要先恢复副本。 - 驱逐演练:对一台承载 Ingress 的节点执行
kubectl drain,观察被驱逐的 Pod 是否被 PDB 限速、是否在另一节点重建并 Ready,同时用curl -I持续探测入口,确认无 5xx。 - 滚动更新演练:修改镜像 tag 触发更新,用
kubectl rollout status deployment/ingress-nginx-controller观察进度,同时持续探测入口,确认更新期间无连接拒绝。