作者:罗健文 李来 云服务开发工程师
在生产场景中部署的服务提供者常因业务降级或其余场景须要下线和上线的部署操作,本文总结了利用在高低线过程中会遇到的典型问题,并思考在虚机和容器场景该如何解决这些问题,躲避该过程中可能呈现的服务消费者的申请失败,实现利用的优雅高低线。
一、微服务利用上 / 下线公布过程中存在的问题
在利用高低线公布过程中,如何做到流量的无损上 / 下线,是一个零碎能保障 SLA 的要害。如果利用高低线不平滑,就会呈现短时间的服务调用报错,比方连贯被回绝、申请超时、没有实例和申请异样等问题。
1.1 上线过程中的问题
在利用上线公布过程中,因为过早裸露服务,实例可能仍处在 JVM JIT 编译或者应用的中间件还在加载,若此时大量流量进入,可能会霎时压垮新起的服务实例。咱们在理论场景中,已经遇到 provider 服务启动后,然而数据库连贯出现异常,未做好启动前的资源筹备,导致该 provider 服务在注册核心裸露后 DB 异样还未修复,无奈失常提供被 consumer 调用的能力,导致大量申请异样返回。如下图日志所示,利用初始化时,DB 连贯失败(该服务对 DB 是弱依赖)。
1.2 下线过程中的问题
在利用下线过程中,服务消费者感知服务提供者下线有提早,在一段时间内,被路由到已下线服务提供者实例的申请都抛连贯被回绝异样。其次服务实例在接管到 SIGKILL 信号时,会立刻敞开,然而这时候可能在申请队列中存在一部分申请还在解决,如果立刻敞开这些申请都会损失掉。理论利用中,咱们在环境上部署了 provider 的惟一一个实例,该服务被 consumer 调用,而后再执行 kill -9 强杀利用 provider 的惟一实例后,服务过程实际上曾经被终止,然而服务的注册信息还会在注册核心(该场景应用的是 ServiceComb)保留一段时间,未及时革除,如下图所示。若此时消费者服务 consumer 调到该实例会报连贯回绝谬误。因为消费者 consumer 服务还能发现该实例,获取其 IP 和端口尝试去调用,然而该 provider 服务实例其实曾经被销毁了。
二、如何解决利用上 / 下线问题
那么有哪些优化措施,能够缩小利用上 / 下线中流量的损失?
2.1 解决利用上线问题
利用上线公布次要问题是:其中一个起因是注册太早,过早的裸露了服务;另一个起因是一些利用初始化迟缓,若遇到大量流量,利用容易宕机。能够采取以下优化措施:
- 提早注册:微服务利用能够采纳提早注册的形式,即在利用启动之后肯定工夫再进行注册。这样能够确保利用齐全就绪后再注册,防止了服务未就绪就被内部拜访的状况。
- 健康检查:微服务利用能够实现健康检查接口,通过该接口能够查看服务是否就绪。注册核心能够通过定期调用该接口来判断服务是否能够对外提供服务,从而防止了服务未就绪就被内部拜访的状况。
- 预热:对新实例进行预热,而不是忽然将所有流量转移到新实例上,从而防止新实例遇到大量流量,利用容易宕机的状况。
- 启动优化:对于整个服务启动的过程,能够进行一些优化措施,比方缩小不必要的依赖、调整启动程序等,从而放慢服务启动速度。
2.2 利用正当的上线过程
正当的利用上线大抵分为这样一个过程:当利用启动后,通过设置提早注册工夫(服务对外裸露的工夫)确保利用多久后可提供服务,其次可依赖平台查看服务的就绪状态(比方 K8S 的就绪探针)确保服务对外提供服务为就绪状态,而后通过预热对刚启动利用进行爱护,确保流量缓缓进入刚启动的利用,最初流量逐步增到失常状况。
2.3 解决利用下线问题
利用下线过程最次要问题是:消费者利用无奈及时感知到注册核心列表的刷新,导致可能还有新流量拜访下线利用。能够采取以下优化措施:
- 缩小注册核心缓存工夫:将注册核心中服务列表的缓存工夫缩短,能够使消费者利用更快地获取到服务列表的最新信息。这样能够缩小因服务列表缓存而导致的拜访下线利用的流量。
- 实时性优化:在服务消费者和注册核心之间应用长连贯、实时告诉等机制,从而可能实时获取注册核心中服务列表的变动。
- 实现熔断机制:在消费者利用中实现熔断机制,当某个服务实例呈现故障或不可用时,能够疾速切换到其余可用的服务实例。这样能够防止将流量发送到已下线的应用程序上,并确保消费者利用的可用性。
2.4 利用正当的下线过程
正当的利用下线大抵分为这样一个过程:当利用承受到内部的敞开(进行服务)申请后,不能在接管新的业务申请,然而会存在一些正在解决的业务申请,需等这些申请解决完后再销毁利用应用的资源,最初就能够告诉主过程退出。
三、利用下线留神点
针对利用下线在虚机场景和容器场景须要关注一些留神点。
3.1 虚机场景
当咱们要敞开虚拟机利用时,咱们个别会应用 ps -ef | grep xxx 查找到过程 ID,而后再执行 kill -9 PID 操作。
kill 命令应用科普:
- kill -9,零碎会收回 SIGKILL(9)信号,由操作系统内核实现杀过程操作,该信号不容许疏忽和阻塞,应用程序会立刻终止(强制杀死)。
- kill -15,默认应用信号,零碎向利用发送 SIGTERM(15)信号,给指标过程一个清理善后工作的机会是一种优雅终止过程的形式,通知过程须要进行运行并开始清理资源。
因为 kill -9 PID 会强制杀死利用,以正当的利用下线流程看,应需解决完相干旧业务申请,清理相干资源后再退出过程,所以当要敞开虚拟机利用时,请执行 kill PID——以优雅的形式进行运行。
3.2 容器场景
Kubernetes 目前是业界容器编排畛域的事实标准,业界个别默认都是用 K8S 来治理容器。K8S 提供了 Pod 优雅退出机制,容许 Pod 在退出前实现一些清理工作。preStop 会先执行完,而后 K8S 才会给 Pod 发送 TERM 信号。在容器场景利用 K8S 提供的 preStop 机制,配合提早下线 API 应用,这样就能保障流量的无损下线。
...
spec:
containers:
- name: lifecycle-demo-container
image: nginx
lifecycle:
preStop:
exec:
command: ["/bin/sh","-c","to do xxx; do sleep 30; done"]
...
(1)为什么容器利用(K8S 环境)要配置 preStop?首先要介绍一下 Pod 的终止过程。
参考:https://kubernetes.renkeju.com/chapter_4/4.5.5.pod_terminatio…
- 用户发送删除 Pod 对象的命令。
- API 服务器中的 Pod 对象会随着工夫的推移而更新,在宽限期内(默认为 30 秒),Pod 被视为“dead”。
- 将 Pod 标记为“Terminating”状态。
- (与第 3 步同时运行)kubelet 在监控到 Pod 对象转为“Terminating”状态的同时启动 Pod 关闭程序。
- (与第 3 步同时运行)端点控制器监控到 Pod 对象的敞开行为时将其从所有匹配到此端点的 Service 资源的端点列表中移除。
- Pod 对象中的容器过程收到 TERM 信号。
- 如果以后以后 Pod 对象定义了 preStop 钩子处理器,则在其标记为“Terminating”后即会以同步的形式启动执行;如若宽限期完结后,preStop 仍未执行完结,则第 2 步会被从新执行并额定获取一个时长为 2 秒的小宽限期。
- 宽限期完结后,若存在任何一个仍在运行的过程,那么 Pod 对象即会收到 SIGKILL 信号。
- kubelet 申请 API Server 将此 Pod 资源的宽限期设置为 0 从而实现删除操作,它变得对用户不在可见。
默认状况下,所有删除操作的宽限期都是 30 秒,不过,kubectl delete 命令能够应用“–grace-period=”选项自定义其时长,若应用 0 值则示意间接强制删除指定的资源,不过,此时须要同时为命令应用“–force”选项。
从上述 Pod 终止过程的时序图可知,敞开 Pod 流程(关注红色框),给 Pod 内的过程发送 TERM 信号 (即 kill, kill -15),如果配置了 preStop 钩子也会同时解决,最初宽限期完结后,若存在任何一个仍在运行的过程,那么 Pod 对象即会收到 SIGKILL(kill-9)信号。
(2)存在这样一种状况 Pod 中的业务过程承受不到 SIGTERM 信号
存在这样一种状况 Pod 中的业务过程承受不到 SIGTERM 信号(而且没有配置 preStop 钩子),期待一段时间业务过程间接被 SIGKILL 强制杀死了。
为什么业务过程承受不到 SIGTERM 信号?
通常都是因为容器启动入口应用了 shell,比方应用了相似 /bin/sh -c my-app 或 /docker-entrypoint.sh 这样的 ENTRYPOINT 或 CMD,这就可能就会导致容器内的业务过程收不到 SIGTERM 信号,起因是:
- 容器主过程是 shell,业务过程是在 shell 中启动的,成为了 shell 过程的子过程。
- shell 过程默认不会解决 SIGTERM 信号,本人不会退出,也不会将信号传递给子过程,导致业务过程不会触发进行逻辑。
- 当等到 K8S 优雅进行超时工夫 (terminationGracePeriodSeconds,默认 30s),发送 SIGKILL 强制杀死 shell 及其子过程。
(3)如何解决上述 Pod 中的业务过程接管不到 SIGTERM 信号问题
- 配置 preStop 钩子(K8S 场景),解决退出前实现一些清理工作,比方应用无损高低线插件的应用服务需在进行前告诉实例进行下线。
- 如果能够的话,尽量不应用 shell 启动业务过程。
- 如果肯定要通过 shell 启动,比方在启动前须要用 shell 过程一些判断和解决,或者须要启动多个过程,那么就须要在 shell 中传递下 SIGTERM 信号了。
参考:https://imroc.cc/k8s/faq/why-cannot-receive-sigterm/
所以容器利用(K8S 环境)要配置 preStop,在进行前告诉实例进行下线,加了一层防护,保障 Pod 中的业务能优雅的完结。
四、Sermant 如何解决利用上 / 下线问题
针对利用高低线公布过程中的问题,Sermant 插件提供预热和提早下线机制,为利用提供无损高低线的能力。预热是无损上线的外围机制,提早下线是无损下线的外围机制,而且为了无损上线,还做了提早注册机制。
4.1 上线问题的解决形式
- 提早注册:若服务还未齐全初始化就曾经注册到注册核心提供给消费者调用,很有可能因资源为加载实现导致申请报错。能够通过设置提早注册,让服务充沛初始化后再注册到注册核心对外提供服务。
- 预热:是基于客户端实现的,当流量进入时,Sermant 会动静调整流量,依据服务的预热配置,对流量进行动态分配。对于开启服务预热的实例,在刚启动时,绝对于其余已启动的实例,调配的流量会更少,流量将以曲线形式随时间推移减少直至与其余实例近乎持平。目标是采纳少流量对服务实例进行初始化,避免服务解体。4.2 下线问题的解决形式
上图形容了 Sermant 是如何解决服务下线问题的:0. 微服务利用 consumerA、providerA、consumerB、providerB 携带 Sermant 启动,并将相干 ip:port 等信息注册到注册核心;
- 微服务利用 consumerA 能够失常调用 providerA 和 providerB;
- 若要重启 providerA,providerA 会标记本身将下线(告诉注册核心将下线),并开始统计申请确保以后申请已全副解决实现;
- providerA 会告诉其上游利用其本身的下线信息;
- consumerA 承受到 providerA 下线信息后,将其从缓存实例列表移除;
- providerA 在解决完以后的所有申请后,即可重启。
总的来说,Sermant 对于服务下线的机制概括为:
- 提早下线:即对下线的实例提供爱护,插件基于下线实时告诉 + 刷新缓存的机制疾速更新上游的实例缓存,同时基于流量统计的形式,确保行将下线的实例尽可能的将流量解决实现,最大水平防止流量失落。提供了提早下线 API,不便在 K8S 环境中配置 preStop。http://127.0.0.1:16688/$$sermant$$/shutdown
- 流量统计:为确保以后申请已全副解决实现,在服务下线时,Sermant 会尝试期待 30s(可配置),定时统计和判断以后实例申请是否均解决实现,解决实现后最终下线。
Sermant 优雅高低线能力的具体介绍和应用见:https://sermant.io/zh/document/plugin/graceful.html
五、总结
Sermant 插件为微服务利用提供无损高低线的能力,若要下线利用,针对虚构场景,请应用 kill PID;针对容器场景(K8S 环境),请配置 preStop 钩子。
结束语
Sermant 作为专一于服务治理畛域的字节码加强框架,致力于提供高性能、可扩大、易接入、功能丰富的服务治理体验,并会在每个版本中做好性能、性能、体验的看护,宽泛欢送大家的退出。
Sermant 官网:https://sermant.io
GitHub 仓库地址:https://github.com/huaweicloud/Sermant
扫码退出 Sermant 社区交换群