Spring Boot Nacos 实现不停服发布过程详解
作者:迹_Jason
引言
最近,由于业务属性比较重要,对服务发布提出了更高的要求,希望能实现不停服发布。目前,团队所有项目已经完成基于K8s容器化部署,服务注册发现基于Nacos,故本文基于该两前提下进行讨论。
基于该架构下,需要解决如下几个问题:
- K8s Java 应用实现滚动发布,如果新服务不正常的情况下,不将新服务发布上去,且旧服务不下线
- 服务从Nacos上主动下线,让流量不再流入
K8s 滚动发布
K8s 已天然支持滚动发布的机制,只需要简单的配置就可以实现我们的要求,如下是具体配置摘要,我将从上到下进行说明。
apiVersion: apps/v1 kind: Deployment metadata: labels: app: example-service name: example-service spec: #副本数量 replicas: {{.pod_replicas}} selector: matchLabels: app: example-service minReadySeconds: 30 #设置升级延迟时间15秒,等待15秒后升级 strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 #升级过程中最多可以比原先设置多出的POD数量 maxUnavailable: 0 template: metadata: labels: app: example-service monitortype: backend spec: #以下内容为可选,容器调度策略,保证同一deployment的多个副本位于不同的机器上,防止单节点挂掉导致服务不可用,由于涉及到要与运维沟通资源情况,无法直接给予固定配置。 affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: app operator: In values: - example-service topologyKey: kubernetes.io/hostname containers: - image: example-service:latest imagePullPolicy: IfNotPresent name: example-service lifecycle: preStop: exec: command: - curl - '-XPOST' - '127.0.0.1:8080/actuator/shutdown' readinessProbe: # 就绪探针 httpGet: path: /actuator/health/readiness port: 8080 initialDelaySeconds: 30 # 延迟加载时间 periodSeconds: 10 # 重试时间间隔 timeoutSeconds: 1 # 超时时间设置 successThreshold: 1 # 健康阈值 failureThreshold: 3 # 不健康阈值 livenessProbe: # 存活探针 httpGet: path: /actuator/health/liveness port: 8080 initialDelaySeconds: 30 # 延迟加载时间 periodSeconds: 10 # 重试时间间隔 timeoutSeconds: 1 # 超时时间设置 successThreshold: 1 # 健康阈值 failureThreshold: 3 # 不健康阈值 ports: - containerPort: 8080 name: backend protocol: TCP envFrom: - configMapRef: #存放公共环境变量,比如:数据库,redis,nacos等连接信息,每个项目的各个微服务基本都是一样的。 name: pub-cm - configMapRef: #存放个性化配置,对于个别的服务,除了公共变量外,会涉及其他引用信息。 name: example-service-cm env: - name: POD_NAME valueFrom: fieldRef: apiVersion: v1 fieldPath: metadata.name resources: #设定以下是设定服务资源根据你的项目实际情况来 limits: # limits是代表的资源上限,服务能消耗的资源上限 cpu: {{.pod_cpu_limit}} #{limits_cpu}(必须),目前默认单位为m,如果申请一核则为1024m,以此类推,默认则为500m memory: {{.pod_memory_limit}} #{limits_mem}(必须),目前默认单位为Mi,如果申请1G内存则为1024Mi,以此类推,默认则为2048Mi requests: # requests是服务所需最小的启动资源,设置后如果node达不到这个资源要求就会部署失败 cpu: {{.pod_cpu_request}} #{requests_cpu}(必须),目前默认单位为m,如果申请一核则为1024m,以此类推,默认则为250m memory: {{.pod_memory_request}} #{requests_mem}(必须),目前默认单位为Mi,如果申请1G内存则为1024Mi,以此类推,默认则为500Mi # 以下为必须选项,项目做日志采集 volumeMounts: - name: host-time readOnly: true mountPath: /etc/localtime - name: example-service-log mountPath: /home/logs # 如果接入日志必须存在容器内/home/logs文件夹下存放日志文件 subPathExpr: $(POD_NAME) volumes: - name: example-service-log hostPath: path: /home/logs type: DirectoryOrCreate - name: host-time hostPath: path: /etc/localtime type: '' imagePullSecrets: # 写死,前提是要执行这个凭证创建命令 - name: harborha-secret001fe --- apiVersion: policy/v1beta1 kind: PodDisruptionBudget #设置pod最小可用数量 metadata: name: example-service-pdb spec: minAvailable: 50% selector: matchLabels: app: example-service
RollingUpdate 用于配置服务滚动升级的策略,maxSurge设置升级过程中最多可以比原先设置多出的POD数量。
preStop 在容器下线前执行的操作,我这边是希望他先调用 Spring Boot Actuator提供的下线接口,让服务正常业务处理完后,下线掉。
readinessProbe 与 livenessProbe 都是探针,不同的地方是 readinessProbe 在容器启动时,会检查服务是否启动完全和正常,正常后Pod才会被显示正常,这种在使用Service或者Ingress的时候非常有用,livenessProbe 则是周期性检查服务的健康性,如果服务不健康将下线掉服务。
需要注意 readinessProbe 的 initialDelaySeconds 是在服务启动时开始计时,基于服务本身启动时间设置一个相对合理的时间,以提高成功率。
PodDisruptionBudget 该控制器主要是通过设置应用 Pod 处于正常状态的最低个数或最低百分比,这样可以保证在主动销毁 Pod 的时候,不会销毁太多的 Pod 导致业务异常中断,从而提高业务的可用性。
PodDisruptionBudget与Deployment中 RollingUpdate 配置说明
在滚动更新的时候,会根据RollngUpdate 配置来,在Eviction(主动驱逐保护,e.g.存在不健康的节点,下线服务)会根据PDB策略来。
Nacos主动下线
我们想象一种场景,A服务通过Nacos服务注册发现LoadBalance方式直接调用B服务,即使B服务容器已经被销毁,但如果A服务中还存在旧B服务的地址,那么就会调用异常,所以我们希望B服务下线的时候,A服务是有感知的,故我们选择在主动通知Nacos服务下线服务,同时,由Nacos去通知其他服务下线通知。
新建一个Endpoint,该方式是基于Spring Boot 2.7.X 版本的,其他版本可能有所区别。
@WebEndpoint(id = "deregister") @Slf4j @ConditionalOnClass(value = WebEndpoint.class) public class NacosServiceDeregisterEndpoint { private final NacosDiscoveryProperties nacosDiscoveryProperties; private final NacosRegistration nacosRegistration; private final NacosServiceRegistry nacosServiceRegistry; public NacosServiceDeregisterEndpoint(NacosDiscoveryProperties nacosDiscoveryProperties, NacosRegistration nacosRegistration, NacosServiceRegistry nacosServiceRegistry) { this.nacosDiscoveryProperties = nacosDiscoveryProperties; this.nacosRegistration = nacosRegistration; this.nacosServiceRegistry = nacosServiceRegistry; } @ReadOperation public String deregisterEndPoint() { String serviceName = nacosDiscoveryProperties.getService(); String groupName = nacosDiscoveryProperties.getGroup(); String clusterName = nacosDiscoveryProperties.getClusterName(); String ip = nacosDiscoveryProperties.getIp(); int port = nacosDiscoveryProperties.getPort(); log.info("Deregister from the Nacos, ServiceName:{}, GroupName:{}, ClusterName:{}, IP:{}, Port:{}", serviceName, groupName, clusterName, ip, port); // 设置服务下线 nacosServiceRegistry.setStatus(nacosRegistration, "DOWN"); return "success"; } }
增加和修改bootstrap.yaml
配置
management: endpoint: health: show-details: always probes: enabled: true metrics: enabled: true shutdown: enabled: true endpoints: web: exposure: include: "health,shutdown,metrics,deregister" server: port: 8080 metrics: tags: application: ${spring.application.name}
后续
其实上方我们还遗漏一个问题——“服务更新时,服务已经成功注册到 Nacos,但容器还没有被检测为健康,此时流量打到新节点上。”这种情况应该怎么解决。其实不用苦恼,
以上就是Spring Boot Nacos 实现不停服发布的详细内容,更多关于Spring Boot Nacos不停服发布的资料请关注脚本之家其它相关文章!