
# 升级实例过程中实现业务不中断
#### 应用场景
在Kubernetes集群中，应用通常采用Deployment + LoadBalancer类型Service的方式对外提供访问。应用更新或升级时，Deployment会创建新的Pod并逐步替换旧的Pod，这个过程中可能会导致服务中断。
#### 解决方案
避免服务中断可以从Deployment和Service两类资源入手：
- Deployment可以采用**滚动升级** 的升级方式，为对各个实例逐个进行更新，而不是同时对所有实例进行全部更新，可以控制Pod的更新速度和并发数，从而确保了升级过程中业务不中断。例如，可以设置maxSurge和maxUnavailable参数，控制同时创建的新Pod数量和同时删除的旧Pod数量。确保升级过程中始终有工作负载能够提供服务。
  此外，还需要配合以下机制确保升级全过程的流量平滑：
  - **启动探针（startupProbe）**：为启动时间较长的应用提供独立的启动检测窗口，避免因存活探针或就绪探针过早检测导致Pod反复重启。
  
  - **就绪探针（readinessProbe）**：控制Pod何时可以接收流量，未就绪的Pod会从Service Endpoints中摘除，确保只有真正就绪的Pod才会被分配请求。
  
  - **readinessGates**：在LoadBalancer类型Service场景下，Pod就绪不代表ELB后端已完成注册。readinessGates引入自定义就绪条件，确保Pod在ELB侧也完成注册后才开始接收流量。
  
  - **优雅终止（preStop Hook + terminationGracePeriodSeconds）**：Pod删除时，通过preStop Hook留出时间处理已建立的连接（尤其是长连接），配合terminationGracePeriodSeconds确保优雅关闭流程不会被SIGKILL强制中断。
    
- LoadBalancer类型的Service存在两种服务亲和模式：
  - **集群级别**的服务亲和（externalTrafficPolicy: Cluster）：Cluster模式下，如果当前节点没有业务Pod，会将请求转发给其他节点上的Pod，在跨节点转发会丢失源IP。
  
  - **节点级别**的服务亲和（externalTrafficPolicy: Local）：Local模式下，请求会直接转发给Pod所在的节点，不存在跨节点转发，因此可以保留源IP。但是在Local模式下，如果实例滚动升级时Pod所在节点发生变化，导致ELB侧后端服务器会同步变化，可能会出现服务中断。这种情况下可以通过实例原地升级的方式避免服务中断，即保证ELB后端的节点上存在一个正常状态的Pod。
   
综上，实现升级实例过程中的业务不中断的方案可参考下表：
| 场景       | Service           | Deployment                                                                         |
|:---|:---|:---|
| 不需要保留源IP | 选用**集群级别**的服务亲和模式 | 滚动升级 + 启动探针/存活探针/就绪探针 + readinessGates + 优雅终止                                      |
| 需要保留源IP  | 选用**节点级别**的服务亲和模式 | 滚动升级 + 启动探针/存活探针/就绪探针 + readinessGates + 优雅终止 + 节点亲和（保证更新过程中每个节点上至少有一个Running Pod） |
   
#### 滚动升级过程中流量处理流程
- **新Pod上线**：启动探针通过后，就绪探针开始检测，同时readinessGates检查ELB后端是否注册完成，全部通过后Pod才被标记为Ready并开始接收流量。
- **旧Pod下线**：新Pod就绪且满足maxUnavailable约束后，Endpoints会摘除旧Pod，同时执行preStop Hook等待流量规则生效，preStop结束后发送SIGTERM信号通知应用优雅关闭，若超过terminationGracePeriodSeconds仍未退出则强制终止。
- **循环更新**：旧Pod退出后，若仍有旧Pod待更新，则继续创建新Pod，直至全部替换完成。
图1滚动升级过程   
![](https://support.huaweicloud.com/bestpractice-cce/zh-cn_image_0000002664644295.png "点击放大")
#### 操作步骤
本示例中，工作负载副本个数为2，并通过LoadBalance类型Service对外暴露服务。关联有Loadbalance类型Service/Ingress的工作负载滚动升级由于涉及跨服务调用，因此需要格外注意滚动升级参数的配置。
#### 配置1：配置启动探针（startupProbe）
启动探针用于判断应用容器是否已经启动完成。对于启动时间较长的应用，如果直接配置存活探针或就绪探针，可能会在应用尚未启动完成时就被判定为不健康而反复重启。**startupProbe成功之前，存活探针和就绪探针都不会生效**，因此可以为应用提供足够的启动时间。
示例中为HTTP端口检查，请根据应用实际情况进行设置。
```
startupProbe:
  httpGet:
    path: /
    port: 80
  failureThreshold: 30   # 最多尝试30次，最长等待300s启动
  periodSeconds: 10
```
![](https://support.huaweicloud.com/bestpractice-cce/public_sys-resources/note_3.0-zh-cn.png)
failureThreshold × periodSeconds决定了应用的最大允许启动时间。
本示例中为 30 × 10s = 300s，适用于启动时间较长的应用。请根据实际应用启动时间调整参数，避免设置过短导致Pod反复重启。
#### 配置2：配置就绪探针（readinessProbe）
就绪探针用于判断Pod是否已经准备好接收流量。**当就绪探针检测失败时，Pod会从Service的Endpoints中摘除，不再接收新请求，但Pod不会被删除或重启**。这确保了滚动升级过程中，只有真正就绪的Pod才会被分配流量。
示例中为HTTP端口检查，请根据应用实际情况进行设置。
```
readinessProbe:
  httpGet:
    path: /
    port: 80
  initialDelaySeconds: 5  # 容器启动后5秒开始探测
  periodSeconds: 5        # 每5秒探测一次
  timeoutSeconds: 2       # 探测超时时间
  successThreshold: 1     # 1次成功即标记为就绪
  failureThreshold: 3     # 连续3次失败即标记为不就绪
```
![](https://support.huaweicloud.com/bestpractice-cce/public_sys-resources/note_3.0-zh-cn.png)
检测周期、延时时间、超时时间等数据需要合理设置，部分应用启动时间较长，如果设置的时间过短，会导致Pod尚未就绪就接收流量，引发请求失败。
#### 配置3：配置readinessGates
在LoadBalancer类型Service（尤其是externalTrafficPolicy: Local模式）的场景下，Pod就绪并不等同于ELB后端已经完成注册。Kubernetes默认只根据readinessProbe判断Pod是否就绪，但Pod就绪后，ELB侧仍需要时间完成健康检查和后端服务器注册。如果此时就将流量转发到该Pod，可能会出现连接失败。
**readinessGates** 允许引入自定义的就绪条件，使Pod只有在readinessProbe通过**且**所有readinessGate条件都满足时，才会被标记为Ready并加入Service Endpoints。CCE中可以通过设置readinessGates配合ELB健康检查，确保Pod在ELB侧也完成注册后才开始接收流量。
```
readinessGates:
  - conditionType: target-health.elb.k8s.cce/<Service名称>
```
![](https://support.huaweicloud.com/bestpractice-cce/public_sys-resources/note_3.0-zh-cn.png)
- 设置readinessGates后，Pod需要同时满足readinessProbe和readinessGates中的所有条件才会被标记为Ready。
- 这可以有效避免Pod已就绪但ELB尚未完成注册导致的流量丢失问题，在Local模式下尤为重要。
- conditionType格式为target-health.elb.k8s.cce/\<Service名称\>，其中target-health.elb.k8s.cce 为固定前缀，\<Service名称\> 需替换为实际关联的Service名称。
- Service上需通过 kubernetes.io/elb.health-check-flag: 'on' 开启ELB健康检查，readinessGates才能正常生效。
 
#### 配置4：配置滚动升级策略
升级方式设置为"滚动升级"，逐步用新版本实例替换旧版本实例。
```
strategy:
  type: RollingUpdate
  rollingUpdate:
    maxUnavailable: "25%"  # 更新过程中最多允许25%的Pod副本处于不可用状态
    maxSurge: "25%"        # 更新过程中允许创建的Pod总数最多可超出期望副本数的25%
```
![](https://support.huaweicloud.com/bestpractice-cce/public_sys-resources/note_3.0-zh-cn.png)
maxUnavailable和maxSurge用于控制工作负载的滚动步长，配合就绪探针控制升级节奏，确保升级过程中始终有工作负载能够提供服务。
#### 配置5：配置preStop Hook处理已建立的连接
当Pod被删除时，Kubernetes会先向容器发送SIGTERM信号，同时执行preStop Hook。**preStop Hook的关键作用是在Pod从Service Endpoints摘除后，仍留出时间处理已经建立的连接（尤其是长连接），确保这些连接上的请求能够正常完成。**
推荐的preStop Hook配置如下：
```
lifecycle:
  preStop:
    exec:
      # 建议根据业务自行设置钩子方法，等待存量连接处理完成然后关闭应用
      command: ["sh", "-c", "sleep 30 && /usr/sbin/nginx -s quit"]
```
由于当Kubernetes删除Pod时，会同时执行两个动作：
1. 从Service Endpoints中移除该Pod
2. 向容器发送SIGTERM信号。
但由于kube-proxy刷新iptables规则和ELB侧摘除后端都存在延迟，可能仍有少量在途请求或已建立的连接被路由到即将终止的Pod。sleep 30s可以为这些请求提供处理时间窗口。
**长连接场景**：
如果应用存在WebSocket等长连接，preStop Hook中应主动通知应用关闭长连接，或等待长连接自然结束。例如：
```
lifecycle:
  preStop:
    exec:
      command:
        - /bin/sh
        - -c
        - |
          # 向应用发送优雅关闭信号（如调用管理接口）
          curl -X POST http://localhost:8080/admin/shutdown 2>/dev/null || true
          # 等待已建立连接处理完成
          sleep 30
```
#### 配置6：配置terminationGracePeriodSeconds
设置缩容时间窗（terminationGracePeriodSeconds），指定Pod终止前的宽限期。
```
terminationGracePeriodSeconds: 60
```
terminationGracePeriodSeconds必须大于preStop Hook的执行时间。因为Kubernetes在删除Pod时的执行顺序为：
1. 执行preStop Hook
2. 发送SIGTERM信号
3. 等待terminationGracePeriodSeconds超时
4. 发送SIGKILL强制终止
**推荐配置**：terminationGracePeriodSeconds = preStop Hook执行时间 + 应用优雅关闭时间 + 缓冲时间。
- **如果设置过小**：preStop Hook可能尚未执行完就被SIGKILL强制终止，导致已建立的连接被中断，请求处理失败。
- **如果设置过大**：会导致Pod删除速度变慢，影响滚动升级的整体效率。
本示例中preStop Hook为30s，应用优雅关闭通常需要10-20s，因此设置terminationGracePeriodSeconds为60s。
#### 配置7：配置节点亲和（Local模式下需要）
Service为**节点级别** 的服务亲和模式时建议设置。在添加调度策略时，指定工作负载需要亲和的节点。详情请参见[设置节点亲和调度（nodeAffinity）](https://support.huaweicloud.com/usermanual-cce/cce_10_0892.html)。
#### 配置示例
本文通过两个配置示例进行说明。
 #### 已配置零中断的工作负载示例
以下为包含Deployment和Service的完整YAML示例，涵盖滚动升级、健康检查探针、readinessGates、优雅终止等全部优化配置：
```
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-demo
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx-demo
  # 滚动更新策略：保证更新期间服务不中断
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: "25%"  # 更新过程中最多允许25%的Pod副本处于不可用状态
      maxSurge: "25%"        # 更新过程中允许创建的Pod总数最多可超出期望副本数的25%
  template:
    metadata:
      labels:
        app: nginx-demo
      # Pod就绪状态检查，使用了ELB的功能
      # target-health.elb.k8s.cce为固定前缀，后缀值与关联的Service名称相同
      readinessGates:
        - conditionType: target-health.elb.k8s.cce/nginx-demo-service
    spec:
      # 优雅下线的最大时长，超过设定的秒数会强制关闭Pod
      # 需大于preStop Hook处理已有连接的时长
      terminationGracePeriodSeconds: 60
      containers:
      - name: nginx
        image: nginx:perl
        ports:
        - containerPort: 80
        resources:
          requests:
            cpu: 500m
            memory: 1Gi
          limits:
            cpu: 500m
        # 启动探针 (Startup Probe)：确保容器内应用已完成启动
        # startupProbe成功之前，存活探针和就绪探针不会生效
        startupProbe:
          httpGet:
            path: /
            port: 80
          failureThreshold: 30   # 最多尝试30次，最长等待300s启动
          periodSeconds: 10
        # 就绪探针 (Readiness Probe)：判断容器是否准备好接收流量
        # 未就绪的Pod会从Service Endpoints中摘除，不会被分配请求
        readinessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 5  # 容器启动后5秒开始探测
          periodSeconds: 5        # 每5秒探测一次
          timeoutSeconds: 2       # 探测超时时间
          successThreshold: 1     # 1次成功即标记为就绪
          failureThreshold: 3     # 连续3次失败即标记为不就绪
        # 服务优雅下线配置
        lifecycle:
          preStop:
            exec:
              # 建议根据业务自行设置钩子方法，等待存量连接处理完成然后关闭应用
              command: ["sh", "-c", "sleep 30 && /usr/sbin/nginx -s quit"]
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-demo-service
  labels:
    app: nginx-demo
  namespace: default
  annotations:
    kubernetes.io/elb.class: performance  # 表示使用独享型ELB
    kubernetes.io/elb.id: xxxxxxxxxx  # 替换为实际ELB的id
    kubernetes.io/elb.health-check-flag: 'on'
    kubernetes.io/elb.health-check-option: '{"protocol":"TCP","delay":"5","timeout":"10","max_retries":"3"}'
spec:
  selector:
    app: nginx-demo
  externalTrafficPolicy: Cluster
  ports:
    - name: cce-service-0
      targetPort: 80
      nodePort: 0
      port: 8080
      protocol: TCP
  type: LoadBalancer
```
![](https://support.huaweicloud.com/bestpractice-cce/public_sys-resources/note_3.0-zh-cn.png)
- readinessGates中的conditionType格式为target-health.elb.k8s.cce/\<Service名称\>，其中\<Service名称\>需替换为实际关联的Service名称。Service上需通过 kubernetes.io/elb.health-check-flag: 'on' 开启ELB健康检查。
- preStop Hook中**sleep 30** 用于等待ELB侧摘除后端和iptables规则刷新完成，**nginx -s quit**用于优雅关闭Nginx并处理已建立的连接。请根据实际应用替换关闭命令。
- terminationGracePeriodSeconds 需大于preStop Hook执行时间（本示例中sleep 30s + nginx quit），建议预留30s缓冲，设置为60s。
 
 #### 未配置零中断的工作负载示例
以下为未配置任何零中断优化参数的Deployment和Service示例，仅包含基础配置，用于后续压测对比：
```
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-demo2
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx-demo2
  template:
    metadata:
      labels:
        app: nginx-demo2
    spec:
      containers:
      - name: nginx
        image: nginx:perl
        ports:
        - containerPort: 80
        resources:
          requests:
            cpu: 500m
            memory: 1Gi
          limits:
            cpu: 500m
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-demo2-service
  labels:
    app: nginx-demo2
  namespace: default
  annotations:
    kubernetes.io/elb.class: performance  # 表示使用独享型ELB
    kubernetes.io/elb.id: xxxxxxxxxx  # 替换为实际ELB的id
    kubernetes.io/elb.health-check-flag: 'on'
    kubernetes.io/elb.health-check-option: '{"protocol":"TCP","delay":"5","timeout":"10","max_retries":"3"}'
spec:
  selector:
    app: nginx-demo2
  externalTrafficPolicy: Cluster
  ports:
    - name: cce-service-0
      targetPort: 80
      nodePort: 0
      port: 8082
      protocol: TCP
  type: LoadBalancer
```
#### 验证零中断更新
完成上述配置后，可通过压测工具在滚动升级期间持续发送请求，分别对对照组和实验组进行压测对比，验证零中断配置的效果。
#### 准备工作
- 已配置好kubeconfig的节点，可正常执行kubectl命令
- 节点上已安装压测工具[hey](https://github.com/rakyll/hey)，可通过**hey -h**验证是否可用。
- 已分别部署[已配置零中断的工作负载示例]和[未配置零中断的工作负载示例]的工作负载。
 
#### 对已配置零中断的工作负载压测验证
1. 获取已配置零中断工作负载的Service访问地址： 
   ```
   export NGINX_ENDPOINT=$(kubectl get service nginx-demo-service -o jsonpath='{.status.loadBalancer.ingress[0].ip}{":"}{.spec.ports[0].port}')
   echo $NGINX_ENDPOINT
   ```
   
   
2. 使用hey进行压力测试，以200并发执行100000次请求： 
   ```
   hey -c 200 -n 100000 -disable-keepalive http://$NGINX_ENDPOINT
   ```
   
   
3. 在压测执行的同时，立即开启新终端触发滚动升级： 
   ```
   kubectl rollout restart deployment nginx-demo
   ```
   
   
4. 观察滚动升级状态： 
   ```
   kubectl rollout status deployment nginx-demo
   ```
   
   
5. 等待压测完成后，查看hey输出结果。 
   预期结果，流量没有中断：
   ![](https://support.huaweicloud.com/bestpractice-cce/zh-cn_image_0000002664409791.png "点击放大")
   
   
 
#### 对未配置零中断的工作负载压测验证
1. 获取未配置零中断工作负载的Service访问地址： 
   ```
   export NGINX_ENDPOINT2=$(kubectl get service nginx-demo2-service -o jsonpath='{.status.loadBalancer.ingress[0].ip}{":"}{.spec.ports[0].port}')
   echo $NGINX_ENDPOINT2
   ```
   
   
2. 使用hey进行压力测试，以200并发执行100000次请求： 
   ```
   hey -c 200 -n 100000 -disable-keepalive http://$NGINX_ENDPOINT2
   ```
   
   
3. 在压测执行的同时，立即开启新终端触发滚动升级： 
   ```
   kubectl rollout restart deployment nginx-demo2
   ```
   
   
4. 观察滚动升级状态： 
   ```
   kubectl rollout status deployment nginx-demo2
   ```
   
   
5. 等待压测完成后，查看hey输出结果。 
   预期结果，流量产生中断：
   ![](https://support.huaweicloud.com/bestpractice-cce/zh-cn_image_0000002634250584.png "点击放大")
   
   
 
