kubernetes实现spring cloud服务平滑升级的一种解决方案

又是三月天

因为过年加上疫情的原因,2020年的2月没有了对时间的概念

也因为疫情的原因,今年回到了离别多年的家乡

家乡还是以前的样子

只是许多以前的一起玩耍的小伙伴们大多都结了婚,出了嫁

曾经那些个青涩的我们就像罗大佑唱的歌里说的那样:

轻飘飘的时光就这么溜走,转头回去看看时已匆匆数年




问题描述

回到本文正题,情况是这样的:

由于公司的服务用的是springcloud框架并且服务全采用的kubernetes来进行部署的,但每次有新的服务进行升级时,新发上去的服务都会出现2分钟左右的无法服务,在开发环境自己测试时有点影响就算了,但是如果是线上环境造成某一个服务短时间的不能访问影响还是蛮大的。

在服务升级时,让服务能平滑升级达到用户无感的效果这是非常有必要的

正好今天的事不多,决定自己来研究一把,并且最后实现了想要达到服务平滑升级的效果,以此来记录一下

环境:

  • kubernetes
  • spring cloud,注册中心用的eureka

原因分析

先来分析原因:

之所以会出现上面那种服务需要2分钟左右的时间才能正常被外部访问的情况,是因为每次新的服务发布到k8s上去后,k8s会将新的服务启动,然后再会让原始服务down掉,滚动升级就是这么高大上,看起来确实是天衣无缝。

而在spring cloud的服务中,用户访问的一般都是网关(gateway)或zuul,通过gateway或zuul进行一次中转再去访问内部的服务,但是通过网关访问内部服务时需要一个过程。一般流程是这样的:服务启动好了后会先将自己(服务名->ip:端口)注册到eureka中以便其他服务能访问到它,然后其他服务会定时访问注册中心(eureka)以获取到eureka中最新的服务注册列表。

所以用户通过网关访问到其他服务的一个前提便是:所访问的服务网关已经拿到它在eureka的注册信息了。否则会报出错,提示No instances available这样的错误信息出来。

那么通过kubenetes按照滚动刷新的方式来更新服务的话,就可能出现这样的情况:

在T时刻,serverA_1(老服务)已经down了,serverA_2(新服务)已经启动好,并已注册到了eureka中,但是对于gateway中存在的仍是serverA_1(老服务)的注册信息,那么此时用户去访问serverA就会报错的,因为serverA_1所在的容器都已经stop了。

解决办法

减小从eureka获取注册列表的间隔

gateway去拉取eureka中的注册信息的间隔时间是由参数eureka.client.registry-fetch-interval-seconds来决定的,默认为30秒

所以对于gateway这种类型的服务,我们可以考虑将从eureka拉取注册列表的间隔设小一点,比如5秒。

老服务主动从注册中心里移除

同时,serverA_1(老服务)stop的时候应该主动通知eureka将自己从注册列表中主动移除,k8s中的PreStop正好也有对应的实现方法。

https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#hook-details

PreStop

This hook is called immediately before a container is terminated due to an API request or management event such as liveness probe failure, preemption, resource contention and others. A call to the preStop hook fails if the container is already in terminated or completed state. It is blocking, meaning it is synchronous, so it must complete before the call to delete the container can be sent. No parameters are passed to the handler

如在k8s的服务中添加preStop,通过手动的方式通知eurekaServer让本服务下线

          lifecycle:
            preStop:
              httpGet:
                port: 8881
                path: /spring/shutdown

依赖于retry,待新服务在所有节点中都已就绪再将老服务停止

上面两种办法都不能立即让其他服务获取到新服务的注册信息

让服务达到平滑升级另一种解决办法:依赖于ribbon和spring retry的方式来让服务实现平滑升级的办法,通过配置readiness,让新的服务启动好了分钟后再将旧服务停止

https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/

借助liveness或readiness便可以实现:

The periodSeconds field specifies that the kubelet should perform a liveness probe every 5 seconds. The initialDelaySeconds field tells the kubelet that it should wait 5 second before performing the first probe. To perform a probe, the kubelet executes the command cat /tmp/healthy in the Container. If the command succeeds, it returns 0, and the kubelet considers the Container to be alive and healthy. If the command returns a non-zero value, the kubelet kills the Container and restarts it.

根据文档可知,在探针的结果返回0就代表已就绪,返回非0就代表还未就绪。

在服务的kubernetes的deployment配置文件中添加redainessProbe或livenessProbe即可

        readinessProbe:
          exec:
            command: 
            - echo 
            - '0'
          initialDelaySeconds: 180
          periodSeconds: 10

参数initialDelaySeconds参数代表首次探针的延迟时间,这里的180就是指待pod启动好了后,再等待180秒再进行存活性检测

echo '0'一直返回0,代表服务已就绪

这样配置的话就会达到新的服务启动好了3分钟后kubenetes才会让旧服务down掉,而三分钟后基本上所有的服务都已经从eureka获取到了新服务的注册信息了,再借助于ribbon的重试机制则可实现服务的平滑升级,当然,前提是得要开启loadbalancer的retry

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章