作者:yangxinhui2602905795 | 来源:互联网 | 2023-09-25 14:08
在生产环境中,如何保证在服务升级的时候,不影响用户的体验,这个是一个非常重要的问题。如果在我们升级服务的时候,会造成一段时间内的服务不可用,这就是不够优雅的。
那什么是优雅的呢?主要就是指在服务升级的时候,不中断整个服务,让用户无感知,进而不会影响用户的体验,这就是优雅的。
微服务发布方式
说到优雅升级,就不得不了解一下常见的微服务的发布方式,选择不同的发布方式,就会达到不同的效果,在产线实践中最为常用的发布方式有:
蓝绿发布
蓝绿部署( Blue Green Deployment)是一种可以保证系统在不间断提供服务的情况下上线的部署方式,主要的流程为:不停老版本,部署新版本然后进行测试。确认OK后将流量切到新版本,然后老版本同时也升级到新版本。
其大致步骤为:
首先把B组从负载均衡中摘除,进行新版本的部署。A组仍然继续提供服务;
当B组升级完毕,负载均衡重新接入B组,再把A组从负载列表中摘除,进行新版本的部署,B组重新提供服务;
A组也升级完成,负载均衡重新接入A组,此时,AB组版本都已经升级完成,并且都对外提供服务。
通过LB来实现流量控制,比如Nginx;
如果出问题,影响范围较大;
发布策略简单;
用户无感知,平滑过渡;
升级/回滚速度快。
需要准备正常业务使用资源的两倍以上服务器,防止升级期间单组无法承载业务突发;
短时间内浪费一定资源成本。
在Kubernetes中,可以使用Service中的Selector标签结合Pod中的Labels标签进行流量的切换处理,从而达到蓝绿部署的效果。
灰度发布
灰度发布(Canary Deployment)又叫金丝雀发布,以前,旷工在下矿洞是面临的一个重要危险是矿井中的毒气,他们想到一个办法来辨别矿井中是否有毒气,矿工们随身携带一只金丝雀下矿井,金丝雀对毒气的抵抗能力比人类要弱,在毒气环境下会先挂掉起到预警的作用。
其大致步骤为:
准备好部署各个阶段的工件,包括:构建工件,测试脚本,配置文件和部署清单文件;
从LB摘掉灰度服务器;
升级“金丝雀”应用(切断原有流量并进行部署);
对灰度服务进行自动化测试或者少量用户流量到新版本进行测试;
如果灰度服务器测试成功,升级剩余服务器(否则就回滚)。
保证整体系统稳定性,在初始灰度的时候就可以发现、调整问题,影响范围可控;
新功能逐步评估性能,稳定性和健康状况,如果出问题影响范围很小,相对用户体验也少;
用户无感知,平滑过渡。
自动化要求高,如果发布自动化程度不够,发布期间可引发服务中断。
在Kubernetes中,有很多的方案可以实现灰度发布,比如Nginx Ingress Controller、Kong Ingress Controller、Istio等,这里案例展示的是最常用的Nginx Ingress Controller的Canary实现。
基于权重的金丝雀发布
基于请求的金丝雀发布(A/B TEST)
滚动发布
滚动发布(Rolling Update),一种高级的发布策略,发布过程中,应用不中断,用户体验平滑,也是Kubernetes应用更新默认的发布方式。
先升级1个副本,主要做部署验证;
每次升级副本,自动从LB上摘掉,升级成功后自动加入集群;
事先需要有自动更新策略,分为若干次,每次数量/百分比可配置;
回滚是发布的逆过程,先从LB摘掉新版本,再升级老版本,这个过程一般时间比较长;
自动化要求高。
应用不中断,用户体验平滑;
节约资源。
没有一个确定 OK 的环境。使用蓝绿部署,我们能够清晰地知道老版本是 OK 的,而使用滚动发布,我们无法确定;
修改了现有的环境;
部署时间慢,取决于每阶段更新时间;
发布策略较复杂;
如果需要回滚,很困难。举个例子,在某一次发布中,我们需要更新 100 个实例,每次更新 10 个实例,每次部署需要 5 分钟。当滚动发布到第 80 个实例时,发现了问题,需要回滚,这个时候就会很难回滚;
有的时候,我们还可能对系统进行动态伸缩,如果部署期间,系统自动扩容/缩容了,我们还需判断到底哪个节点使用的是哪个代码。尽管有一些自动化的运维工具,但是依然令人心惊胆战。
滚动发布虽然发布策略复杂,回滚难度高,但是却是K8S默认的服务更新方式,K8S中,Deployment资源本质上是使用RS+Rollout Update组成的。
这样完全将服务版本与滚动更新机制结合,自动完成滚动更新的一系列操作,增强用户体验、节约资源、易于回滚。Kubernetes中的滚动更新机制如下:
如何优雅下线应用
在生产环境中,要保证服务不中断,服务的上下线是不可避免的,我们希望能够优雅地下线微服务,在此总结一下Spring Boot中的应用的几种下线方式。
该方式借助的是 Spring Boot 应用的 Shutdown hook,应用本身的下线也是优雅的,但如果你的服务发现组件使用的是 Eureka,那么默认最长会有 90 秒的延迟,其他应用才会感知到该服务下线,这意味着:该实例下线后的 90 秒内,其他服务仍然可能调用到这个已下线的实例。
因此,该方式是不够优雅的。
Spring Boot 提供了/shutdown端点,可以借助它实现优雅停机。
使用方式:在想下线应用的Application.yml中添加如下配置,从而启用并暴露/shutdown端点:
management:
endpoint:
shutdown:
enabled: true
endpoints:
web:
exposure:
include: shutdown
发送 POST 请求到 /shutdown端点Curl -X Http://ip:port/actuator/shutdown
该方式本质和方式一样也是借助 Spring Boot 应用的 Shutdown Hook 去实现的,所以不建议使用。
Spring Boot 应用提供了/pause端点,利用该端点可实现优雅下线。
使用方式:在想下线应用的Application.yml中添加如下配置,从而启用并暴露/pause端点:
management:
endpoint:
# 启用pause端点
pause:
enabled: true
# 启用restart端点
restart:
enabled: true
endpoints:
web:
exposure:
include: pause,restart
发送 POST 请求到/actuator/pause端点Curl -X http://ip:port/actuator/pause
该应用在Eureka Server 上的状已被标记为DOWN,但是应用本身其实依然是可以正常对外服务的。
在想下线应用的Application.yml中添加配置,从而暴露/service-registry端点。
management:
endpoint
web:
exposure:
include: service-registry
发送 POST 请求到/actuator/service-registry端点:
curl -X "POST" "http://localhost:8000/actuator/service-registry?status=DOWN" \
-H "Content-Type: application/vnd.spring-boot.actuator.v2+json;charset=UTF-8"
这个接口会将服务的Eureka的状态标记为Down,等服务流量变为0,可以进行删除或者升级操作,算是比较优雅的一种方式。
- EurekaAutoServiceRegistration
除了上述的下线方式之外,还有一种利用EurekaAutoServiceRegistration对象达到优雅下线的目标。
执行EurekaAutoServiceRegistration.start()方法时,当前服务向 Eureka 注册中心注册服务;
执行EurekaAutoServiceRegistration.stop()方法时,当前服务会向 Eureka 注册中心进行反注册,注册中心收到请求后,会将此服务从注册列表中删除。
@RestController
@RequestMapping(value = "/graceful/registry-service")
public class GracefulOffline {
@Autowired
private EurekaAutoServiceRegistration eurekaAutoServiceRegistration;
@RequestMapping("/online")
public String online() {
this.eurekaAutoServiceRegistration.start();
return "execute online method, online success.";
}
@RequestMapping("/offline")
public String offline() {
this.eurekaAutoServiceRegistration.stop();
return "execute offline method, offline success.";
}
}
可以根据此API去自定义接口去进行服务的下线
如何优雅关闭容器
服务在容器里正常跑着,前面讲了服务的优雅下线方式,下来就该容器的优雅关闭内容了,要不然,光服务进行优雅关闭了,容器一整个直接被Kill了,也是没有用的。
提到容器关闭,就不得不了解一个基础的东西,就是信号,信号是事件发生时对进程的通知机制,有时也称之为软件中断,信号有不同的类型,可以通过 Kill -l 获取信号名称:
9) SIGKILL 此信号为 “必杀(Sure Kill)” 信号,处理器程序无法将其阻塞、忽略或者捕获,故而 “一击必杀”,总能终止程序;
15) SIGTERM 这是用来终止进程的标准信号,也是 Kill 、 Killall 、 Pkill 命令所发送的默认信号。精心设计的应用程序应当为 SIGTERM 信号设置处理器程序,以便其能够预先清除临时文件和释放其它资源,从而全身而退。因此,总是应该先尝试使用 SIGTERM 信号来终止进程,而把 SIGKILL 作为最后手段,去对付那些不响应 SIGTERM 信号的失控进程。
- Dockerfile中的ENTRYPOINT 和 CMD 指令
接着说 Dockerfile 中的 ENTRYPOINT 和 CMD 指令,它们的主要功能是指定容器启动时执行的程序
CMD 有三种格式:
CMD["Executable","Param1","Param2"] (Exec 格式, 推荐使用这种格式)
CMD ["Param1","Param2"] (作为 ENTRYPOINT 指令参数)
CMD Command Param1 Param2 (Shell 格式,默认 /bin/sh -c )
ENTRYPOINT 有两种格式:
ENTRYPOINT ["Executable", "Param1", "Param2"] (Exec 格式,推荐优先使用这种格式)
ENTRYPOINT Command Param1 Param2 (Shell 格式)
Docker Stop 停掉容器的时候,默认会发送一个 SIGTERM 的信号,默认 10s 后容器没有停止的话,就 SIGKILL 强制停止容器。通过 -t 选项可以设置等待时间。
在停止容器的时候系统底层默认会向主进程发送 SIGTERM 信号,而对剩余子进程发送 SIGKILL 信号。系统这样做的大概原因是因为大家在设计主进程脚本的时候都不会进行信号的捕获和传递,这会导致容器关闭时,多个子进程无法被正常终止,所以系统使用 SIGKILL 这个不可屏蔽信号,而是为了能够在没有任何前提条件的情况下,能够把容器中所有的进程关掉。
不管你 Dockerfile 用其中哪个指令,两个指令都推荐使用 Exec 格式,而不是 Shell 格式。原因就是因为使用 Shell 格式之后,程序会以 /bin/sh -c 的子命令启动,Shell会作为主进程,并且 Shell 格式下不会传递任何信号给程序。这也就导致,在 Docker Stop 容器的时候,以这种格式运行的程序捕捉不到发送的信号,也就谈不上优雅的关闭了。
关于微服务发布、应用优雅下线以及容器优雅关闭的知识点就先讲解到这里,下篇我们将着重介绍hzero服务如何在Kubernetes中优雅滚动。
下期预告
K8S优雅升级系列(下) | 如何优雅滚动发布
往期推荐
1、汉得企业级数字化PaaS平台 HZERO 1.9.0 版本正式发布!
2、汉得aPaaS低代码平台-飞搭 2.3.0 RELEASE正式发布!
3、汉得集成平台 集星獭 1.4.0 版本正式发布!
签约客户
*部分统计,持续更新
▲ 更多精彩内容,扫码关注“四海汉得”公众号