在CCE中实现应用高可用部署
在云原生环境中,容器化应用虽然具备弹性与敏捷性,但其运行环境本身存在诸多不确定性风险。例如某个单一节点的失效、一次错误的滚动更新,都可能导致服务中断,引发核心业务受损。
- Pod拓扑分布:为了确保高可用性和容错性,建议工作负载跨可用区、跨节点均匀分布,避免单点故障。
- Pod中断预算:为了保护关键工作负载,建议使用Pod中断预算(PodDisruptionBudget, PDB)来维护应用的稳定性,限制自愿中断时最多有多少Pod不可用。
- Pod健康状态管理:为了确保Pod正常运行和服务流量,建议配置就绪、存活、启动探针,让Kubernetes能自主管理Pod健康状态。
- 优雅生命周期:通过preStop钩子和优雅终止窗口,确保Pod退出时业务不中断。
本文通过构建一个高可用Nginx示例,为您介绍部署方案。
构建高可用应用示例
Pod拓扑分布
假设集群有4个节点,分布在3个可用区。
- 方案一:使用拓扑分布约束(推荐)
使用拓扑分布约束(topologySpreadConstraints)能够精确控制Pod在不同拓扑域(可用区、节点)之间的分布偏差,支持同时设置多个拓扑维度,且不会因其他Pod的干扰导致分布不均。
配置示例如下:
当副本数为4且有三个可用区时,调度器会尝试按2,1,1形式尽可能均匀分布(如果某个可用区无节点可用也可能出现2,2,0的形式),优先避免所有Pod挤在同一可用区。
... spec: replicas: 4 template: spec: topologySpreadConstraints: # 约束1:在可用区之间均匀分布 - maxSkew: 1 # 允许的最大Pod数量差,表示任意两个可用区的Pod数差 ≤ 1 topologyKey: topology.kubernetes.io/zone whenUnsatisfiable: DoNotSchedule # 硬约束,无法满足时禁止调度(也可用ScheduleAnyway) labelSelector: matchLabels: app: nginx-ha # 约束2:在节点之间均匀分布(可选,进一步打散) - maxSkew: 1 topologyKey: kubernetes.io/hostname whenUnsatisfiable: DoNotSchedule labelSelector: matchLabels: app: nginx-ha ...参数说明:
- maxSkew: 1:任意两个拓扑域之间的Pod数量差不超过1,实现最均匀的分布。
- whenUnsatisfiable: DoNotSchedule:硬约束,若找不到满足条件的节点则Pod保持Pending。
- whenUnsatisfiable: ScheduleAnyway:软约束,调度器会优先尝试均匀分布,实在不行也会调度。
- 方案二:使用Pod反亲和性
使用Pod反亲和性更灵活,适合复杂逻辑,通过preferredDuringScheduling规则将Pod优先分散到不同可用区的节点上,需要时也可以结合requiredDuringScheduling规则实现硬约束。
配置示例如下:
... affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 80 # 高权重:优先跨可用区 podAffinityTerm: labelSelector: matchExpressions: - key: app operator: In values: - nginx-ha topologyKey: topology.kubernetes.io/zone - weight: 20 # 低权重:其次跨节点 podAffinityTerm: labelSelector: matchExpressions: - key: app operator: In values: - nginx-ha topologyKey: kubernetes.io/hostname ...
Pod中断预算
Pod中断预算(PodDisruptionBudget, PDB)是Kubernetes中用于限制自愿性中断的资源对象。其中自愿性中断是指由系统或管理员主动触发的Pod删除操作,例如Deployment的滚动更新、手动删除Pod等。
PDB通过设置 minAvailable(最小可用Pod数)或 maxUnavailable(最大不可用Pod数),确保在执行这些操作时,不会让服务容量低于安全线。
配置示例如下:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: nginx-ha-pdb
spec:
minAvailable: 2 # 至少保持2个Pod可用
selector:
matchLabels:
app: nginx-ha - minAvailable:表示在任何时候,至少要有多少个Pod保持可用(可以是绝对数字,如 2;也可以是百分比,如 50%)。
- maxUnavailable:表示最多允许多少个Pod同时不可用(可以是绝对数字或百分比)。
- selector:通过标签选择器匹配需要受此PDB约束的Pod集合。
minAvailable和maxUnavailable是互斥的,只能设置其中一个。
Pod健康状态管理
探针是Kubelet定期在容器上执行的诊断检查,根据结果判断Pod的健康状态并采取相应动作。Kubernetes提供三种探针:
- 就绪探针(readinessProbe):判断Pod是否准备好接收流量。失败时,Pod会被从Service的Endpoints中移除,但不会重启容器。常用于滚动更新时控制新版本的上线节奏。
- 存活探针(livenessProbe):判断Pod是否处于健康状态。失败时,Kubelet会重启容器。用于修复卡死、死锁等应用级故障。
- 启动探针(startupProbe):判断容器内的应用是否已经启动完成。在启动探针成功之前,存活和就绪探针会被禁用。适用于启动时间较长的应用(如Java Spring Boot),防止它们在启动期间被存活探针误杀。
配置示例如下:
...
containers:
- name: nginx
image: nginx:alpine
ports:
- containerPort: 80
# 就绪探针:准备就绪才接收流量
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5 # 延迟多少秒才开始探测
periodSeconds: 10 # 每隔多少秒执行一次探测
failureThreshold: 3 # 连续失败多少次才判定为失败
# 存活探针:异常则重启容器
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 15
periodSeconds: 20
failureThreshold: 3 # 可适当放宽(如3-5次),避免因短暂波动(如高负载、GC停顿)导致容器频繁重启
# 启动探针:保护启动慢的应用(如Java)
startupProbe:
httpGet:
path: /
port: 80
failureThreshold: 30
periodSeconds: 10
... 优雅生命周期
当Pod被删除(如滚动更新、节点排空、缩容)时,Kubernetes会向Pod中的容器发送SIGTERM信号。如果容器立即退出,正在处理中的请求可能会中断,导致客户端收到错误响应(如Connection Refused)。配置preStop与优雅终止,可以等Pod先摘除流量,等容器完成所有清理工作并退出。
配置示例如下:
...
containers:
- name: nginx
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 30"] # 等待30秒,给ELB摘除后端的时间
terminationGracePeriodSeconds: 45 # 总宽限期45秒,必须大于 preStop sleep 时间
... 完整YAML示例
此示例副本数为4,整合了拓扑分布约束(推荐方案)、PDB、探针和优雅终止,可应对单节点故障和单可用区中断。
# 1. PodDisruptionBudget: 保证自愿中断时至少有2个Pod运行
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: nginx-ha-pdb
spec:
minAvailable: 2
selector:
matchLabels:
app: nginx-ha
---
# 2. Deployment: 整合了拓扑分布约束、探针、优雅终止
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-ha
spec:
replicas: 4
selector:
matchLabels:
app: nginx-ha
template:
metadata:
labels:
app: nginx-ha
spec:
terminationGracePeriodSeconds: 45
# 使用拓扑分布约束,均匀分布到可用区和节点
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: nginx-ha
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: nginx-ha
containers:
- name: nginx
image: nginx:alpine
ports:
- containerPort: 80
# 优雅生命周期
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 30"]
# 探针配置
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 15
periodSeconds: 20
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
imagePullSecrets:
- name: default-secret 验证与测试
- 验证Pod分布:
kubectl get pod -owide -l app=nginx-ha
观察输出中的NODE字段,应分布在多个节点和可用区。
- 验证PDB:
kubectl get pdb nginx-ha-pdb
输出示例:
STATUS: Healthy, Allowed disruptions: 2
- 模拟节点故障:排空一个节点,观察Pod是否漂移,且总不可用Pod数不超过PDB限制。
- 模拟滚动更新:触发滚动更新,观察旧Pod终止前是否等待了preStop定义的时间。
- 模拟应用故障:手动关闭应用内部端口,观察就绪探针是否将Pod摘除,存活探针是否重启容器。
常见问题
PDB与滚动更新参数maxSurge和maxUnavailable冲突吗?
不冲突。Deployment的maxSurge和maxUnavailable与PDB共同作用,Kubernetes会取更严格的限制。
preStop应该设置多久?
取决于业务处理完现有请求的平均+最大时间。通常15-30秒,再配合terminationGracePeriodSeconds多留出5-10秒余量。
反亲和性使用required还是preferred?
若集群中可供调度的节点数充足,且副本数 ≤ 节点/可用区数,可以使用required强制打散。否则建议用preferred,避免Pod因无法满足硬约束而Pending。
拓扑分布约束中maxSkew应设为多少?
maxSkew设置为1可实现最严格的均匀分布。如果节点数不是副本数的整数倍,设为1仍能保证最大差为1。