k8s环境下spring cloud优雅停机
目的
Spring cloud 微服务、k8s容器化部署的架构下,单个服务升级过程中,不停止对外提供服务,使得用户对整个升级过程无感知,从而实现服务的优雅升级。
负载均衡器
1. spring cloud ribbon(k8s同namespace):k8s同一命名空间之间的服务调用,采用ribbon、eureka做服务注册和负载均衡。
问题:
a、ribbon和eureka(CP)注重服务的可用性,所以存在服务实例的缓存,本地缓存:(1)ribbon本地缓存 (2)eureka client本地缓存 。远程缓存:(1)eureka server 三级缓存
b、service 提供者停止,pod 向容器发送SIGTERM信号,service 提供者会向eureka server下线服务,这个时候service消费者会存在原有service的实例信息
2. k8s service (k8s不同namespce): k8s不同命名空间之间的服务调用,采用k8s service、kube-proxy做服务注册和负载均衡。
问题:
a、k8s service 支持服务的负载均衡,只有容器探针检测成功后,才会停止旧的容器Service。采用两种探针实现RollingUpdate。但是前提要存在多个实例
基于spring cloud 负载均衡的优雅停机
针对spring cloud负载均衡存在的问题,要做到优雅停机,需要进行如下配置:
a、缩短缓存时间
1. 修改Client配置(ribbon和eureka client)
1 ribbon: 2 # ribbon刷新eureka的时间5s, 默认30s 3 ServerListRefreshInterval: 5000 4 #注册中心 5 eureka: 6 client: 7 serviceUrl: 8 defaultZone: http://eureka-server:8761/eureka 9 # eureka客户端需要多长时间发定时刷新本地缓存时间,默认30s 10 registry-fetch-interval-seconds: 5 11 instance: 12 prefer-ip-address: true 13 instance-id: ${spring.cloud.client.ipAddress}:${server.port} 14 # eureka客户端需要多长时间发送心跳给eureka服务器,表明他仍然活着,默认30秒 15 lease-renewal-interval-in-seconds: 5 16 # eureka服务器在接受到实力的最后一次发出的心跳后,需要等待多久才可以将此实例删除 默认90秒 17 lease-expiration-duration-in-seconds: 15
缩短ribbon本地缓存时间,eureka client拉取注册表、心跳间隔时间
2. 修Server配置(eureka server)
1 eureka: 2 server: 3 # 禁用readOnlyCacheMap 4 useReadOnlyResponseCache: false 5 # 主动失效检测间隔, 默认60s 6 evictionIntervalTimerInMs: 5000 7 instance: 8 # eureka服务器的标识,如果是集群就可以写成 eurekaSer1,eurekaSer2,eurekaSer3.. 9 hostname: eureka-server 10 health-check-url-path: /actuator/health 11 client: 12 # 开启客户端存活状态监测 13 healthcheck: 14 enabled: true 15 registerWithEureka: false 16 fetchRegistry: false
缩短失效检测的实现,同时禁用readOnlye缓存。 eureka server 存在三级缓存 ,均是纯内存操作,详细见下图
(1)registry(ConcurrentMap):注册表
(2)readWriteCacheMap(GuavaCache):读写缓存
(3)readOnlyCacheMap(ConcurrentMap):只读缓存
b、Service先下线,再停机
1、增加Service 下线端点
1 @ConfigurationProperties(prefix = "endpoints.offline") 2 public class OfflineEndpoint extends AbstractEndpoint<String> { 3 4 public OfflineEndpoint() { 5 super("offline", false); 6 } 7 8 9 @Override 10 public String invoke() { 11 String remoteIp = IPUtils.getIp(); 12 String localIp = IPUtils.getLocalIp(); 13 log.info("remoteIp:{} trigger service offline", remoteIp); 14 if(localIp.substring(0, localIp.lastIndexOf(".")).equals(remoteIp.substring(0, remoteIp.lastIndexOf(".")))) { 15 log.info("service start execute offline"); 16 DiscoveryManager.getInstance().shutdownComponent(); 17 try { 18 Thread.sleep(10000); 19 } catch (InterruptedException e) { 20 log.error("sleep exception",e); 21 throw new RuntimeException(e); 22 } 23 log.info("service end execute offline"); 24 return "SUCCESS"; 25 } else { 26 log.warn("remoteIp:{} localIp:{} not trigger service ll_offline", remoteIp, localIp); 27 return "NOT_ALLOW"; 28 } 29 30 } 31 }
2、利用pod preStop hook
1 lifecycle: 2 preStop: 3 httpGet: 4 port: 9494 5 scheme: HTTP 6 path: offline
c、Spring Cloud升级流程总结
针对容器化RollingUpdate,Service 旧版本A,新版本A1发布流程如下:
1、新版本容器Service A1,而在Service A1启动后就会注册到Eureka Server,
2、Service A1,只有容器探针检测成功后,才会停止旧的容器Service A。所以此时 Service A1 和 Service A都注册在 Eureka Server上,接受流量
3、Service A1,容器探测成功,下线Service A
4、Service A 触发 preStop的offline操作,将Service A 从Eureka Server中下线,直至缓存更新完成
5、Service A停止,Service A1 上限,完成单次服务滚动更新
基于k8s service 负载均衡的优雅停机
实例注册:容器启动成功并不会注册到service,只有需要容器readinessPro成功后,才会注册到service。
实例注销:容器执行preStop的同时就会将实例信息从service注销
k8s Deployment本身就支持Rolling update,所以这里优雅停机:可以先扩容到2个实例,然后再缩容到1个实例,实现单服务的滚动更新