https://github.com/RisingStack/kubernetes-graceful-shutdown-example
一直以来我对优雅地停止 Pod 这件事理解得很单纯: 不就利用是 PreStop hook 做优雅退出吗? 但这周听了组里大哥的教诲之后,发现很多场景下 PreStop hook 并不能很好地完成需求,这篇文章就简单分析一下”优雅地停止 Pod”这回事儿.
何谓优雅停止?
优雅停止(Graceful shutdown)这个说法来自于操作系统,我们执行关机之后都得 OS 先完成一些清理操作,而与之相对的就是硬中止(Hard shutdown),比如拔电源。
到了分布式系统中,优雅停止就不仅仅是单机上进程自己的事了,往往还要与系统中的其它组件打交道。比如说我们起一个微服务,网关把一部分流量分给我们,这时:
假如我们一声不吭直接把进程杀了,那这部分流量就无法得到正确处理,部分用户受到影响。不过还好,通常来说网关或者服务注册中心会和我们的服务保持一个心跳,过了心跳超时之后系统会自动摘除我们的服务,问题也就解决了;这是硬中止,虽然我们整个系统写得不错能够自愈,但还是会产生一些抖动甚至错误;
假如我们先告诉网关或服务注册中心我们要下线,等对方完成服务摘除操作再中止进程,那不会有任何流量受到影响;这是优雅停止,将单个组件的启停对整个系统影响最小化;
按照惯例,SIGKILL 是硬终止的信号,而 SIGTERM 是通知进程优雅退出的信号,因此很多微服务框架会监听 SIGTERM 信号,收到之后去做反注册等清理操作,实现优雅退出.
PreStop Hook
回到 Kubernetes(下称 k8s),当我们想干掉一个 Pod 的时候,理想状况当然是 k8s 从对应的 Service(假如有的话)把这个 Pod 摘掉,同时给 Pod 发 SIGTERM 信号让 Pod 中的各个容器优雅退出就行了。但实际上 Pod 有可能犯各种幺蛾子:
已经卡死了,处理不了优雅退出的代码逻辑或需要很久才能处理完成;
优雅退出的逻辑有 BUG,自己死循环了;
代码写得野,根本不理会 SIGTERM;
因此,k8s 的 Pod 终止流程中还有一个”最多可以容忍的时间”,即 grace period (在 pod 的 .spec.terminationGracePeriodSeconds 字段中定义),这个值默认是 30 秒,我们在执行 kubectl delete 的时候也可通过 –grace-period 参数显式指定一个优雅退出时间来覆盖 pod 中的配置。而当 grace period 超出之后,k8s 就只能选择 SIGKILL 强制干掉 Pod 了.
很多场景下,除了把 Pod 从 k8s 的 Service 上摘下来以及进程内部的优雅退出之外,我们还必须做一些额外的事情,比如说从 k8s 外部的服务注册中心上反注册。这时就要用到 PreStop hook 了,k8s 目前提供了 Exec 和 HTTP 两种 PreStop hook,实际用的时候,需要通过 Pod 的 .spec.containers[].lifecycle.preStop 字段为 Pod 中的每个容器单独配置,比如:
spec:
contaienrs:
最后我们串起来再整个表述一下 Pod 退出的流程(官方文档里更严谨哦):
1.用户删除 Pod
2.1.Pod 进入 Terminating 状态;
2.2.与此同时,k8s 会将 Pod 从对应的 service 上摘除;
2.3.与此同时,针对有 preStop hook 的容器,kubelet 会调用每个容器的 preStop hook,假如 preStop hook 的运行时间超出了 grace period,kubelet 会发送 SIGTERM 并再等 2 秒;
2.4.与此同时,针对没有 preStop hook 的容器,kubelet 发送 SIGTERM
3.grace period 超出之后,kubelet 发送 SIGKILL 干掉尚未退出的容器
这个过程很不错,但它存在一个问题就是我们无法预测 Pod 会在多久之内完成优雅退出,也无法优雅地应对”优雅退出”失败的情况。而在我们的产品 tidb-operator 中,这就是一个无法接受的事情.
有状态分布式应用的挑战
为什么说无法接受这个流程呢? 其实这个流程对无状态应用来说通常是 OK 的,但下面这个场景就稍微复杂一点:
TiDB 中有一个核心的分布式 KV 存储层 TiKV。TiKV 内部基于 Multi-Raft 做一致性存储,这个架构比较复杂,这里我们可以简化描述为一主多从的架构,Leader 写入,Follower 同步。而我们的场景是要对 TiKV 做计划性的运维操作,比如滚动升级,迁移节点.
在这个场景下,尽管系统可以接受小于半数的节点宕机,但对于预期性的停机,我们要尽量做到优雅停止。这是因为数据库场景本身就是非常严苛的,基本上都处于整个架构的核心部分,因此我们要把抖动做到越小越好。要做到这点,就得做不少清理工作,比如说我们要在停机前将当前节点上的 Leader 全部迁移到其它节点上.
得益于系统的良好设计,大多数时候这类操作都很快,然而分布式系统中异常是家常便饭,优雅退出耗时过长甚至失败的场景是我们必须要考虑的。假如类似的事情发生了,为了业务稳定和数据安全,我们就不能强制关闭 Pod,而应该停止操作过程,通知工程师介入。 这时,上面所说的 Pod 退出流程就不再适用了.
小心翼翼: 手动控制所有流程
这个问题其实 k8s 本身没有开箱即用的解决方案,于是我们在自己的 Controller 中(TiDB 对象本身就是一个 CRD) 与非常细致地控制了各种操作场景下的服务启停逻辑.
抛开细节不谈,最后的大致逻辑是在每次停服务前,由 Controller 通知集群进行节点下线前的各种迁移操作,操作完成后,才真正下线节点,并进行下一个节点的操作.
而假如集群无法正常完成迁移等操作或耗时过久,我们也能”守住底线”,不会强行把节点干掉,这就保证了诸如滚动升级,节点迁移之类操作的安全性.
但这种办法存在一个问题就是实现起来比较复杂,我们需要自己实现一个控制器,在其中实现细粒度的控制逻辑并且在 Controller 的控制循环中不断去检查能否安全停止 Pod。
另辟蹊径: 解耦 Pod 删除的控制流
复杂的逻辑总是没有简单的逻辑好维护,同时写 CRD 和 Controller 的开发量也不小,能不能有一种更简洁,更通用的逻辑,能实现”保证优雅关闭(否则不关闭)”的需求呢?
有,办法就是 ValidatingAdmissionWebhook
这里先介绍一点点背景知识,Kubernetes 的 apiserver 一开始就有 AdmissionController 的设计,这个设计和各类 Web 框架中的 Filter 或 Middleware 很像,就是一个插件化的责任链,责任链中的每个插件针对 apiserver 收到的请求做一些操作或校验。举两个插件的例子:
DefaultStorageClass,为没有声明 storageClass 的 PVC 自动设置 storageClass
ResourceQuota,校验 Pod 的资源使用是否超出了对应 Namespace 的 Quota
虽然说这是插件化的,但在 1.7 之前,所有的 plugin 都需要写到 apiserver 的代码中一起编译,很不灵活。而在 1.7 中 k8s 就引入了 Dynamic Admission Control 机制,允许用户向 apiserver 注册 webhook,而 apiserver 则通过 webhook 调用外部 server 来实现 filter 逻辑。1.9 中,这个特性进一步做了优化,把 webhook 分成了两类: MutatingAdmissionWebhook 和 ValidatingAdmissionWebhook,顾名思义,前者就是操作 api 对象的,比如上文例子中的 DefaultStroageClass,而后者是校验 api 对象的,比如 ResourceQuota。拆分之后,apiserver 就能保证在校验(Validating)之前先做完所有的修改(Mutating),下面这个示意图非常清晰:
而我们的办法就是,利用 ValidatingAdmissionWebhook,在重要的 Pod 收到删除请求时,先在 webhook server 上请求集群进行下线前的清理和准备工作,并直接返回拒绝。这时候重点来了,Control Loop 为了达到目标状态(比如说升级到新版本),会不断地进行 reconcile,尝试删除 Pod,而我们的 webhook 则会不断拒绝,除非集群已经完成了所有的清理和准备工作.
下面是这个流程的分步描述:
用户更新资源对象;
controller-manager watch 到对象变更;
controller-manager 开始同步对象状态,尝试删除第一个 Pod;
apiserver 调用外部 webhook;
webhook server 请求集群做 tikv-1 节点下线前的准备工作(这个请求是幂等的),并查询准备工作是否完成,假如准备完成,允许删除,假如没有完成,则拒绝,整个流程会因为 controller manager 的控制循环回到第 2 步;
好像一下子所有东西都清晰了,这个 webhook 的逻辑很清晰,就是要保证所有相关的 Pod 删除操作都要先完成优雅退出前的准备,完全不用关心外部的控制循环是怎么跑的,也因此,它非常容易编写和测试,非常优雅地满足了我们”保证优雅关闭(否则不关闭)”的需求,目前我们正在考虑用这种方式替换线上的旧方案.
后记
其实 Dynamic Admission Control 的应用很广,比如 Istio 就是用 MutatingAdmissionWebhook 来实现 envoy 容器的注入的。从上面的例子中我们也可以看到它的扩展能力很强,而且常常能站在一个正交的视角上,非常干净地解决问题,与其它逻辑做到很好的解耦.
当然了,Kubernetes 中还有非常多的扩展点,从 kubectl 到 apiserver,scheduler,kubelet(device plugin,flexvolume),自定义 Controller 再到集群层面的网络(CNI),存储(CSI) 可以说是处处可以做事情。以前做一些常规的微服务部署对这些并不熟悉也没用过,而现在面对 TiDB 这样复杂的分布式系统,尤其在 Kubernetes 对有状态应用和本地存储的支持还不够好的情况下,得在每一个扩展点上去悉心考量,做起来非常有意思,因此后续可能还有一些 tidb-operator 中思考过的解决方案分享.
https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks
https://kelvinji2009.github.io/blog/k8s-best-practice-s01e05/?utm_source=tuicool&utm_medium=referral
Google Developer Advocate Sandeep Dinesh的七部分视频和博客系列
对于分布式系统,处理故障是关键。 Kubernetes通过监视系统状态并重新启动已停止执行的服务的控制器来解决这个问题。 另一方面,Kubernetes通常可以强制终止您的应用程序,作为系统正常运行的一部分。
在本期“Kubernetes最佳实践”中,让我们来看看如何帮助Kubernetes更有效地完成工作并体验下如何减少应用程序停机时间。
在容器出现之前的世界中,大多数应用程序在VM或物理机器上运行。 如果应用程序崩溃,启动替换程序需要很长时间。 如果您只有一台或两台机器来运行应用程序,那么这种恢复时间是不可接受的。
相反,在崩溃时使用进程级监视来重新启动应用程序变得很常见。 如果应用程序崩溃,进程监视可以捕获退出代码并立即重新启动应用程序。
随着像Kubernetes这样的系统的出现,不再需要进程监控系统,因为Kubernetes会重启崩溃的应用程序本身。 Kubernetes使用事件循环来确保容器和节点等资源是健康的。 这意味着您不再需要手动运行这些进程监视器。 如果资源未通过运行状况检查,Kubernetes会自动轮转更换。
查看这一集视频,了解如何为您的服务设置自定义健康检查。
Kubernetes终止生命周期
Kubernetes不仅可以监控应用程序的崩溃。 它可以创建更多应用程序副本,以便在多台计算机上运行,更新应用程序,甚至可以同时运行多个版本的应用程序!
这意味着Kubernetes可以终止一个完全健康的容器有很多原因。 如果您使用滚动更新更新部署,Kubernetes会在启动新pod时慢慢终止旧pod。 如果释放节点,Kubernetes将终止该节点上的所有pod。 如果节点资源不足,Kubernetes将终止pod以释放这些资源
查看第三集,可以了解有关资源的更多信息
重要的是,您的应用程序要优雅地处理终止,以便最终用户受到的影响最小,并且恢复时间尽可能快(Time-to-recovery)!
实际上,这意味着您的应用程序需要处理SIGTERM消息并在收到它时开始关闭。 这意味着你需要保存所有需要保存的数据,关闭网络连接,完成剩下的任何工作以及其他类似任务。
一旦Kubernetes决定终止您的pod,就会发生一系列事件。 让我们看看Kubernetes终止生命周期的每一步。
1.Pod被设置为“终止”状态,并从所有服务的端点列表中删除
此时,pod停止获得新的流量。 在pod中运行的容器不会受到影响。
如果您的应用程序在接收SIGTERM时没有正常关闭,您可以使用此Hook来触发正常关闭。 接收SIGTERM时大多数程序都会正常关闭,但如果您使用的是第三方代码或管理系统则无法控制,所以preStop Hook是在不修改应用程序的情况下触发正常关闭的好方法。
您的代码应该监听此事件并在此时开始干净地关闭。 这可能包括停止任何长期连接(如数据库连接或WebSocket流),保存当前状态或类似的东西。
即使您使用preStop Hook,如果您发送SIGTERM信号,测试一下应用程序会发生什么情况也很重要,这样您在生产环境中才不会感到惊讶!
如果你的应用程序完成关闭并在terminationGracePeriod完成之前退出,Kubernetes会立即进入下一步。
如果您的Pod通常需要超过30秒才能关闭,请确保增加优雅等待期。 您可以通过在Pod的YAML中设置terminationGracePeriodSeconds选项来实现。 例如,要将其更改为60秒:
gcp-terminationGracePeriodSeconds.png
结论
Kubernetes可以出于各种原因终止pod,并确保您的应用程序优雅地处理这些终止,这是创建稳定系统和提供出色用户体验的核心。
优雅停止(Graceful shutdown)这个说法来自于操作系统,比如我们windows关机系统首先会退出软件然后一步步到达关机,而相对的就是硬终止(Hard shutdown),简单的理解就是直接拔电源
到了微服务中,网关会把流量分配给每个Pod节点上,比如我们上线更新Pod的时候
如果我们直接将Pod杀死,那这部分流量就无法得到正确处理,会影响部分用户,通常来说网关或者注册中心会将我们的服务保持一个心跳,过了心跳超时之后会自动摘除我们的服务,但是有一个问题就是超时时间可能是30秒也可能是60秒,虽然不会影响我们的系统,但是会产生用户轻微抖动。
如果我们在停止前执行一条命令,通知网关或者注册中心这台主机进行下线,那么注册中心就会标记这台主机已经下线,不进行流量转发,用户就不会有任何影响,这就是优雅停止,将滚动更新影响最小化
Pod Hook
Pod Hook是由kubelet发起的,当容器中的进程启动前或者容器中的进程终止之前运行,这是包含在容器的生命周期之中。我们可以同时为Pod中的所有容器都配置hook
在k8s中,理想的状态是pod优雅释放,并产生新的Pod。但是并不是每一个Pod都会这么顺利
Pod卡死,处理不了优雅退出的命令或者操作
优雅退出的逻辑有BUG,陷入死循环
代码问题,导致执行的命令没有效果
对于以上问题,k8s的Pod终止流程中还有一个”最多可以容忍的时间”,即grace period (在pod的.spec.terminationGracePeriodSeconds字段定义),这个值默认是30秒,当我们执行kubectl delete的时候也可以通过–grace-period参数显示指定一个优雅退出时间来覆盖Pod中的配置,如果我们配置的grace period超过时间之后,k8s就只能选择强制kill Pod
Kubernetes为我们提供了两种钩子函数:
PostStart :这个钩子在容器创建后立即执行。但是,并不能保证钩子将在容器ENTRYPOINT之前运行,因为没有参数传递给处理程序。 主要用于资源部署、环境准备等。不过需要注意的是如果钩子花费时间过长以及于不能运行或者挂起,容器将不能达到Running状态。
PreStop :钩子在容器终止前立即被调用。它是阻塞的,意味着它是同步的,所以它必须在删除容器的调用出发之前完成。主要用于优雅关闭应用程序、通知其他系统等。如果钩子在执行期间挂起,Pod阶段将停留在Running状态并且不会达到failed状态
如果PostStart或者PreStop钩子失败,它会杀死容器。所以我们应该让钩子函数尽可能的轻量。当然有些情况下,长时间运行命令是合理的,比如在停止容器之前预先保留状态。
这里稍微简单说一下Pod终止的过程
用户发送命令删除Pod,Pod进入Terminating状态
service摘除Pod节点
当kubelet看到Pod已被标记终止,开始执行preStop钩子,假如preStop hook的运行时间超过了grace period,kubelet会发送SIGTERM并等2秒
官方文档介绍
在Pod Hook钩子函数中有Exec和HTTP两种方式
Exec - 用于执行一段特定的命令,不过要注意的是该命令小号的资源会被计入容器
HTTP - 对容器上的特定端点执行HTTP请求
基于PostStart命令演示
首先我们先进行演示PostStart的两种方式
第一种Exec
我们echo一段话追加到 /tmp/message,在Pod启动前进行操作
cat »exec_test.yaml«EOF
apiVersion: v1
kind: Pod
metadata:
name: abcdocker
labels:
name: abcdocker
spec:
containers:
可以通过下面查看结果,pod的目录已经有我们在yaml文件写的测试文件
[root@abcdocker yaml]# kubectl get pod
NAME READY STATUS RESTARTS AGE
abcdocker 1/1 Running 0 37s
[root@abcdocker yaml]# kubectl exec -it -n default abcdocker /bin/bash
root@abcdocker:/# cat /tmp/message
https://i4t.com
root@abcdocker:/#
root@abcdocker:/# exit
创建容器后,Kubernetes立即发送postStart事件。但是,不能保证在调用Container的入口点之前先调用postStart处理程序。postStart处理程序相对于Container的代码异步运行,但是Kubernetes对容器的管理会阻塞,直到postStart处理程序完成。在postStart处理程序完成之前,容器的状态不会设置为RUNNING。
第二种HTTP方式
使用HttpGet配置Host、Path、Port
apiVersion: v1
kind: Pod
metadata:
name: abcdocker
labels:
name: abcdocker
spec:
containers:
基于PreStop环境演示
起因:
在生产环境中使用spring框架,由于服务更新过程中,服务容器被直接充值,部分请求仍被分发到终止的容器(没有配置钩子,熟悉默认环境),导致服务出现500错误,这部分错误请求数据占用比较少,因为Pod滚动更新都是一对一。因为部分用户会产生服务器错误的情况,考虑使用优雅的终止方式,将错误请求降到最低,直至滚动更新不影响用户
Eureka是一个基于REST的服务,作为Spring Cloud服务注册中心,用于定位服务来进行中间层服务器的负载均衡和故障转移。各服务启动时,会向Eureka Server注册自己的信息(IP、端口、服务信息等),Eureka Server会存储这些信息,微服务启动后,会周期性(默认30秒)的向Eureka Server发送心跳以续约自己的租期,并且可以从eureka中获取其他微服务的地址信息,执行相关逻辑
image_1dpi0idnqk981okaacv16l4172p9.png-61kB
由于Eureka默认的心跳检测为30秒,当K8S下线Pod时Eureka会有30秒的异常问题,所以我们需要在Pod 停止前发送一条请求,通知Eureka进行下线操作,这样进行优雅的停止对用户的影响做到最小
具体yaml如下
apiVersion: v1
kind: Pod
metadata:
name: abcdocker
labels:
name: abcdocker
spec:
containers:
####### 参数解释
127.0.0.1:8080 #代表eureka地址
service-registry #代表注册中心
DOWN #执行down请求
sleep #等待30秒
当我们删除Pod的时候就会执行上面的命令操作,并且等待30秒
[root@yzsjhl82-135 yaml]# kubectl get pod
NAME READY STATUS RESTARTS AGE
abcdocker 1/1 Running 0 2m16s
[root@yzsjhl82-135 yaml]# kubectl delete pod abcdocker
pod “abcdocker” deleted
#此刻Pod不会马上删除,而是执行Exec中的命令,并等待30秒
配置中添加了一个sleep时间,主要是作为服务停止的缓冲时间
总结: Hook调用的日志没有暴露给Pod的Event,所以只能到通过describe命令来获取,如果是正常的操作是不会有event,如果有错误可以看到FailedPostStartHook和FailedPreStopHook这种event。并且如果Hook调用出现错误,则Pod状态不会是Running
优雅停止(Graceful shutdown)这个说法来自于操作系统,比如我们windows关机系统首先会退出软件然后一步步到达关机,而相对的就是硬终止(Hard shutdown),简单的理解就是直接拔电源
到了微服务中,网关会把流量分配给每个Pod节点上,比如我们上线更新Pod的时候
如果我们直接将Pod杀死,那这部分流量就无法得到正确处理,会影响部分用户,通常来说网关或者注册中心会将我们的服务保持一个心跳,过了心跳超时之后会自动摘除我们的服务,但是有一个问题就是超时时间可能是30秒也可能是60秒,虽然不会影响我们的系统,但是会产生用户轻微抖动。
如果我们在停止前执行一条命令,通知网关或者注册中心这台主机进行下线,那么注册中心就会标记这台主机已经下线,不进行流量转发,用户就不会有任何影响,这就是优雅停止,将滚动更新影响最小化
Pod Hook
Pod Hook是由kubelet发起的,当容器中的进程启动前或者容器中的进程终止之前运行,这是包含在容器的生命周期之中。我们可以同时为Pod中的所有容器都配置hook
在k8s中,理想的状态是pod优雅释放,并产生新的Pod。但是并不是每一个Pod都会这么顺利
Pod卡死,处理不了优雅退出的命令或者操作
优雅退出的逻辑有BUG,陷入死循环
代码问题,导致执行的命令没有效果
对于以上问题,k8s的Pod终止流程中还有一个”最多可以容忍的时间”,即grace period (在pod的.spec.terminationGracePeriodSeconds字段定义),这个值默认是30秒,当我们执行kubectl delete的时候也可以通过–grace-period参数显示指定一个优雅退出时间来覆盖Pod中的配置,如果我们配置的grace period超过时间之后,k8s就只能选择强制kill Pod
Kubernetes为我们提供了两种钩子函数:
PostStart :这个钩子在容器创建后立即执行。但是,并不能保证钩子将在容器ENTRYPOINT之前运行,因为没有参数传递给处理程序。 主要用于资源部署、环境准备等。不过需要注意的是如果钩子花费时间过长以及于不能运行或者挂起,容器将不能达到Running状态。
PreStop :钩子在容器终止前立即被调用。它是阻塞的,意味着它是同步的,所以它必须在删除容器的调用出发之前完成。主要用于优雅关闭应用程序、通知其他系统等。如果钩子在执行期间挂起,Pod阶段将停留在Running状态并且不会达到failed状态
如果PostStart或者PreStop钩子失败,它会杀死容器。所以我们应该让钩子函数尽可能的轻量。当然有些情况下,长时间运行命令是合理的,比如在停止容器之前预先保留状态。
这里稍微简单说一下Pod终止的过程
用户发送命令删除Pod,Pod进入Terminating状态
service摘除Pod节点
当kubelet看到Pod已被标记终止,开始执行preStop钩子,假如preStop hook的运行时间超过了grace period,kubelet会发送SIGTERM并等2秒
官方文档介绍
在Pod Hook钩子函数中有Exec和HTTP两种方式
Exec - 用于执行一段特定的命令,不过要注意的是该命令小号的资源会被计入容器
HTTP - 对容器上的特定端点执行HTTP请求
基于PostStart命令演示
首先我们先进行演示PostStart的两种方式
第一种Exec
我们echo一段话追加到 /tmp/message,在Pod启动前进行操作
cat »exec_test.yaml«EOF
apiVersion: v1
kind: Pod
metadata:
name: abcdocker
labels:
name: abcdocker
spec:
containers:
可以通过下面查看结果,pod的目录已经有我们在yaml文件写的测试文件
[root@abcdocker yaml]# kubectl get pod
NAME READY STATUS RESTARTS AGE
abcdocker 1/1 Running 0 37s
[root@abcdocker yaml]# kubectl exec -it -n default abcdocker /bin/bash
root@abcdocker:/# cat /tmp/message
https://i4t.com
root@abcdocker:/#
root@abcdocker:/# exit
创建容器后,Kubernetes立即发送postStart事件。但是,不能保证在调用Container的入口点之前先调用postStart处理程序。postStart处理程序相对于Container的代码异步运行,但是Kubernetes对容器的管理会阻塞,直到postStart处理程序完成。在postStart处理程序完成之前,容器的状态不会设置为RUNNING。
第二种HTTP方式
使用HttpGet配置Host、Path、Port
apiVersion: v1
kind: Pod
metadata:
name: abcdocker
labels:
name: abcdocker
spec:
containers:
基于PreStop环境演示
起因:
在生产环境中使用spring框架,由于服务更新过程中,服务容器被直接充值,部分请求仍被分发到终止的容器(没有配置钩子,熟悉默认环境),导致服务出现500错误,这部分错误请求数据占用比较少,因为Pod滚动更新都是一对一。因为部分用户会产生服务器错误的情况,考虑使用优雅的终止方式,将错误请求降到最低,直至滚动更新不影响用户
Eureka是一个基于REST的服务,作为Spring Cloud服务注册中心,用于定位服务来进行中间层服务器的负载均衡和故障转移。各服务启动时,会向Eureka Server注册自己的信息(IP、端口、服务信息等),Eureka Server会存储这些信息,微服务启动后,会周期性(默认30秒)的向Eureka Server发送心跳以续约自己的租期,并且可以从eureka中获取其他微服务的地址信息,执行相关逻辑
image_1dpi0idnqk981okaacv16l4172p9.png-61kB
由于Eureka默认的心跳检测为30秒,当K8S下线Pod时Eureka会有30秒的异常问题,所以我们需要在Pod 停止前发送一条请求,通知Eureka进行下线操作,这样进行优雅的停止对用户的影响做到最小
具体yaml如下
apiVersion: v1
kind: Pod
metadata:
name: abcdocker
labels:
name: abcdocker
spec:
containers:
####### 参数解释
127.0.0.1:8080 #代表eureka地址
service-registry #代表注册中心
DOWN #执行down请求
sleep #等待30秒
当我们删除Pod的时候就会执行上面的命令操作,并且等待30秒
[root@yzsjhl82-135 yaml]# kubectl get pod
NAME READY STATUS RESTARTS AGE
abcdocker 1/1 Running 0 2m16s
[root@yzsjhl82-135 yaml]# kubectl delete pod abcdocker
pod “abcdocker” deleted
#此刻Pod不会马上删除,而是执行Exec中的命令,并等待30秒
配置中添加了一个sleep时间,主要是作为服务停止的缓冲时间
总结: Hook调用的日志没有暴露给Pod的Event,所以只能到通过describe命令来获取,如果是正常的操作是不会有event,如果有错误可以看到FailedPostStartHook和FailedPreStopHook这种event。并且如果Hook调用出现错误,则Pod状态不会是Running