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

2,446 阅读2分钟

发布服务的时候,如果没有正确的下线服务,会造成发布期间有部分请求出现异常。本文介绍k8s平台上发布服务的策略。

一、思路

1、让新的服务先启动起来,注册到注册中心,等待客户端发现新服务。

2、把待下线的服务从注册中心下线,等待客户端刷新服务列表。

3、把待下线的服务优雅停机。

二、实现方式

基于K8S滚动升级的机制,当新的POD准备就绪之后,旧的POD会被删除。在删除旧POD之前会调用容器preStop的钩子。

1、让新的服务先启动起来,注册到注册中心,等待客户端发现新服务。

K8S容器本身有一个就绪探针配置,当就绪探针返回正常,则开始删除旧POD。

//就绪探针
readinessProbe: 
  httpGet:
  	path: /actuator/health
  	port: 8080
  	scheme: HTTP
  initialDelaySeconds: 120
  timeoutSeconds: 300
  periodSeconds: 35
  successThreshold: 1
  failureThreshold: 1

我们可以使用SpringBoot的健康检查actuator作为就绪探针。 核心的配置是initialDelaySeconds,当容器创建到第一次就绪检查的延迟时间。 initialDelaySeconds=服务启动完成耗时+客户端刷新服务列表的时间(ribbon.ServerListRefreshInterval参数控制,默认是30s)

2、把将要下线的服务从注册中心下线,等待客户端刷新服务列表。

我们要实现一个preStop钩子,把服务从注册中心下线,而且要等待ribbon.ServerListRefreshInterval秒时间,等待客户端刷新服务,把当前要下线的服务剔除。 我们用的nacos作为注册中心, AbstractAutoServiceRegistration是服务注册的抽象基类,stop方法可以从服务中心下线服务,NacosAutoServiceRegistration是实现类,所以我们只需要调用NacosAutoServiceRegistration.stop()即可下线服务。

 public void stop() {
        if (this.getRunning().compareAndSet(true, false) && this.isEnabled()) {
            this.deregister();
            if (this.shouldRegisterManagement()) {
                this.deregisterManagement();
            }

            this.serviceRegistry.close();
        }

   }

我们自定义一个deregister的endpoint,让k8s的preStop去调用。

@Endpoint(id = "deregister")
public class NacosServiceDeregisterEndpoint {

    private static final Logger log = LoggerFactory.getLogger(NacosServiceDeregisterEndpoint.class);

    private NacosAutoServiceRegistration serviceRegistration;


    public NacosServiceDeregisterEndpoint(NacosAutoServiceRegistration serviceRegistration) {
        this.serviceRegistration = serviceRegistration;
    }

    @ReadOperation
    public Map<String, Object> deregister() {
        log.info("开始服务下线");
        serviceRegistration.stop();
        log.info("完成服务下线");
        int wait = 10;
        log.info("等待{}s,等待客户端刷新服务列表", wait);
        int num = 0;
        while (true) {
            try {
                //等待客户端刷新服务发现列表
                TimeUnit.SECONDS.sleep(1);
                num++;
                log.info("还需等待{}秒", wait - num);
                if (num >= wait) {
                    break;
                }
            } catch (InterruptedException e) {
                //ignore
                log.info("InterruptedException", e);
            }
        }

        log.info("等待结束");
        Map<String, Object> result = new HashMap<>();
        result.put("deregister", true);
        return result;
    }

}

别忘记了要把endpoint注册到BeanFactory中。

@Configuration(proxyBeanMethods = false)
public class NacosServiceDeregisterAutoConfig {

    @Bean
    public NacosServiceDeregisterEndpoint deregisterEndpoint(NacosAutoServiceRegistration registration) {
        return new NacosServiceDeregisterEndpoint(registration);
    }
}

最后我们需要定义K8S的preStop钩子

lifecycle:
   preStop:
     httpGet:
     path: /actuator/deregister
     port: 8080
     scheme: HTTP

3、把待下线的服务优雅停机

Spring Boot 已经支持优雅停机。

我们需要添加下面的配置,超时需要根据平时请求的耗时来定,可以稍微大一点也没关系。

server.shutdown=graceful
spring.lifecycle.timeout-per-shutdown-phase=60s

最后还有一个重要的K8S配置

terminationGracePeriodSeconds要大于preStop+spring.lifecycle.timeout-per-shutdown-phase,可以设置大一点没什么关系。

//停止超时时间
terminationGracePeriodSeconds: 300

4、总结

8fc7c3aaf6e8484eb651e56c3d6235f9.png