最近,因为业务属性比拟重要,对服务公布提出了更高的要求,心愿能实现不停服公布。目前,团队所有我的项目曾经实现基于K8s容器化部署,服务注册发现基于Nacos,故本文基于该两前提下进行探讨。

基于该架构下,须要解决如下几个问题:

  1. K8s Java 利用实现滚动公布,如果新服务不失常的状况下,不将新服务公布下来,且旧服务不下线
  2. 服务从Nacos上被动下线,让流量不再流入

K8s 滚动公布

K8s 已人造反对滚动公布的机制,只须要简略的配置就能够实现咱们的要求,如下是具体配置摘要,我将从上到下进行阐明。

apiVersion: apps/v1kind: Deploymentmetadata:  labels:    app: example-service  name: example-servicespec:  #正本数量  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/v1beta1kind: PodDisruptionBudget #设置pod最小可用数量metadata:  name: example-service-pdbspec:  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,但容器还没有被检测为衰弱,此时流量打到新节点上。”这种状况应该怎么解决。其实不必苦恼,Nacos 本身曾经能保障在服务启动齐全之后,才会注册到 Nacos 服务列表上。