Spring Cloud + Nacos + K8S 零影响发布方案

打印 上一主题 下一主题

主题 971|帖子 971|积分 2913

题目描述

在生产环境中利用 springcloud 框架,由于服务更新过程中,容器服务会被直接停止,部分请求仍被分发到停止的容器,导致服务出现500错误,这部分错误请求数据占用比力少,因为Pod滚动更新都是一对一。因为部分用户会产生服务器错误的环境,考虑利用优雅的停止方式,将错误请求降到最低,直至滚动更新不影响用户。这里结合nacos利用来分析。
在 K8s 的滚动升级中,好比 5 个 Pod 服务在升级过程中,会先启动一半左右(好比:3 个新的启动),然后下线一部分服务……直到所有的旧服务被新服务完全替代,简单粗暴的明确滚动升级。如果我们不涉及 Nacos 还好,因为 K8s 会包管在升级过程中,因为负载的环境很有可能在升级过程中会一部分请求打到旧服务里,但是如果在旧服务准备关闭服务时,旧情求还没返回回去的话就会出现 HTTP 连接关闭环境等一些不可预测的意外发生,导致本次请求的业务失败,这是在生产上绝不能出现的事故。针对 K8s 的优雅关闭题目,我们可以继续往下看,下面会介绍 Nacos & K8s 一个结合优雅关闭的方案。
我们来再谈谈 Nacos 在这里如果无优雅关闭的话会出现的环境,实在和 K8s 的本质很类似,如果我们已经解决了 K8s 的优雅关闭题目,那和 Nacos 之间又有什么接洽呢?
我们可以想象下,照旧举例上面的 5 个 Pod 的情形,在一个 Pod 启动时,服务的也天然会注册到 Nacos 上去,同理可得,在服务关闭时,Nacos 注册列表里服务也天然会被下线。那么类似的环境也会出现,如果说此时的情求打到旧服务上面,但是由于 Nacos 有监听时间(默认 30s)拉取一次最新环境,以及在每个 Pod 服务里当地也有一份缓存映射表(也有一个窗口时间更新),所以很有可能在这个窗口期之内,还有一些的旧的请求访问负载到旧服务里,但是这里会出现两种环境
K8s Pod 服务已下线,但是 Nacos 在窗口时间之内注册列表未更新,导致请求达到一个根本不存在的旧服务里 旧请求已经打到旧服务里,但是高峰期时,程序处置惩罚较慢,还没来及返反响应体,服务就被关闭了 以上这两种环境都会导致本次请求出现失败,生产上更是无语~ 所以我们针对 Nacos 的优雅关闭环境也会有一个解决方案,见“Nacos & K8s 优雅关闭方案”
一、思路

为相识决这个题目, 我们将利用 springboot 的 graceful shutdown 功能和 nacos 的自动下线功能来解决这个题目. 详细思路如下:
1、让新的服务先启动起来,注册到注册中央,等待客户端发现新服务。
2、把待下线的服务从注册中央下线,等待客户端革新服务列表。
3、把待下线的服务优雅停机。
二、实现方式

1.创建从 nacos 中下线副本的API

我们通过创建自定义名为 deregister 的 endpoint 来通知 nacos 下线副本
  1. import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
  2. import com.alibaba.cloud.nacos.registry.NacosRegistration;
  3. import com.alibaba.cloud.nacos.registry.NacosServiceRegistry;
  4. import lombok.extern.log4j.Log4j2;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
  7. import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
  8. import org.springframework.stereotype.Component;
  9. @Component
  10. @Endpoint(id = "deregister")
  11. @Log4j2
  12. public class LemesNacosServiceDeregisterEndpoint {
  13.     @Autowired
  14.     private NacosDiscoveryProperties nacosDiscoveryProperties;
  15.     @Autowired
  16.     private NacosRegistration nacosRegistration;
  17.     @Autowired
  18.     private NacosServiceRegistry nacosServiceRegistry;
  19.     /**
  20.      * 从 nacos 中主动下线,用于 k8s 滚动更新时,提前下线分流流量
  21.      *
  22.      * @param
  23.      * @return com.lenovo.lemes.framework.core.util.ResultData<java.lang.String>
  24.      * @author Yujie Yang
  25.      * @date 4/6/22 2:57 PM
  26.      */
  27.     @ReadOperation
  28.     public String endpoint() {
  29.         String serviceName = nacosDiscoveryProperties.getService();
  30.         String groupName = nacosDiscoveryProperties.getGroup();
  31.         String clusterName = nacosDiscoveryProperties.getClusterName();
  32.         String ip = nacosDiscoveryProperties.getIp();
  33.         int port = nacosDiscoveryProperties.getPort();
  34.         log.info("deregister from nacos, serviceName:{}, groupName:{}, clusterName:{}, ip:{}, port:{}", serviceName, groupName, clusterName, ip, port);
  35.         // 设置服务下线
  36.         nacosServiceRegistry.setStatus(nacosRegistration, "DOWN");
  37.         return "success";
  38.     }
  39. }
复制代码
2.支持 Graceful Shutdown

Spring Boot 原生支持已经支持优雅停机。
我们需要添加下面的设置,超时需要根据平常请求的耗时来定,可以轻微大一点也没关系。我们只需要在 bootstrap.yaml 中添加一下设置即可。
  1. server:
  2.   # 开启优雅下线
  3.   shutdown: graceful
  4. spring:
  5.   lifecycle:
  6.     # 优雅下线超时时间
  7.     timeout-per-shutdown-phase: 5m
  8. # 暴露 shutdown 接口
  9. management:
  10.   endpoint:
  11.     shutdown:
  12.       enabled: true
  13.   endpoints:
  14.     web:
  15.       exposure:
  16.         include: shutdown
复制代码
3.设置K8s 

有了上面两个 API, 接下来就设置到 k8s 上
1.K8S容器本身有一个停当探针设置,当停当探针返回正常,则开始删除旧POD。
2.terminationGracePeriodSeconds 如果关闭应用的时间凌驾 10 分钟, 则向容器发送 kill 信号, 防止应用长时间下线不了
terminationGracePeriodSeconds要大于preStop+spring.lifecycle.timeout-per-shutdown-phase,可以设置大一点没什么关系。
3.preStop 先执行下线操作, 等待30s, 留够通知到其他应用的时间, 然后执行 graceful shutdown 关闭应用。
  1. ---
  2. apiVersion: apps/v1
  3. kind: Deployment
  4. metadata:
  5.   name: lemes-service-common
  6.   labels:
  7.     app: lemes-service-common
  8. spec:
  9.   replicas: 2
  10.   selector:
  11.     matchLabels:
  12.       app: lemes-service-common
  13. #  strategy:
  14. #    type: RollingUpdate
  15. #    rollingUpdate:
  16. ##     replicas - maxUnavailable < running num  < replicas + maxSurge
  17. #      maxUnavailable: 1
  18. #      maxSurge: 1
  19.   template:
  20.     metadata:
  21.       labels:
  22.         app: lemes-service-common
  23.     spec:
  24. #      容器重启策略 Never Always OnFailure
  25. #      restartPolicy: Never
  26. #     在pod优雅终止时,定义延迟发送kill信号的时间,此时间可用于pod处理完未处理的请求等状况。默认单位是秒
  27. #     如果关闭时间超过10分钟, 则向容器发送kill信号
  28.       terminationGracePeriodSeconds: 600
  29.       affinity:
  30.         podAntiAffinity:
  31.           preferredDuringSchedulingIgnoredDuringExecution:
  32.             - podAffinityTerm:
  33.                 topologyKey: "kubernetes.io/hostname"
  34.                 labelSelector:
  35.                   matchExpressions:
  36.                     - key: app
  37.                       operator: In
  38.                       values:
  39.                         - lemes-service-common
  40.               weight: 100
  41. #          requiredDuringSchedulingIgnoredDuringExecution:
  42. #            - labelSelector:
  43. #                matchExpressions:
  44. #                  - key: app
  45. #                    operator: In
  46. #                    values:
  47. #                      - lemes-service-common
  48. #              topologyKey: "kubernetes.io/hostname"
  49.       volumes:
  50.         - name: lemes-host-path
  51.           hostPath:
  52.             path: /data/logs
  53.             type: DirectoryOrCreate
  54.         - name: sidecar
  55.           emptyDir: { }
  56.       containers:
  57.         - name: lemes-service-common
  58.           image: 10.176.66.20:5000/lemes-cloud/lemes-service-common-server:v0.1
  59.           imagePullPolicy: Always
  60.           volumeMounts:
  61.             - name: lemes-host-path
  62.               mountPath: /data/logs
  63.             - name: sidecar
  64.               mountPath: /sidecar
  65.           ports:
  66.             - containerPort: 80
  67.           resources:
  68. #           资源通常情况下的占用
  69.             requests:
  70.               memory: '2048Mi'
  71. #           资源占用上限
  72.             limits:
  73.               memory: '4096Mi'
  74.           livenessProbe:
  75.             httpGet:
  76. #             指定 Kubernetes 访问的健康检查 URL 路径
  77.               path: /actuator/health/liveness
  78.               port: 80
  79.             initialDelaySeconds: 5
  80. #           探针可以连续失败的次数
  81.             failureThreshold: 10
  82. #           探针超时时间
  83.             timeoutSeconds: 10
  84. #           多久执行一次探针查询
  85.             periodSeconds: 10
  86.           startupProbe:
  87.             httpGet:
  88.               path: /actuator/health/liveness
  89.               port: 80
  90.             failureThreshold: 30
  91.             timeoutSeconds: 10
  92.             periodSeconds: 10
  93.           readinessProbe:
  94.             httpGet:
  95.               path: /actuator/health/readiness
  96.               port: 80
  97.             initialDelaySeconds: 5
  98.             timeoutSeconds: 10
  99.             periodSeconds: 10
  100.           lifecycle:
  101.             preStop:
  102.               exec:
  103. #应用关闭操作:1. 从 nacos 下线,2. 等待30s, 保证 nacos 通知到其他应用 2.触发 springboot 的 graceful shutdown
  104.                 command:
  105.                   - sh
  106.                   - -c
  107.                   - curl http://127.0.0.1/actuator/deregister;sleep 30;curl -X POST http://127.0.0.1/actuator/shutdown;
复制代码


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

半亩花草

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表