K8s Pod优雅关闭,没你想象的那么简单!

更新部署服务时,旧的 Pod 会终止,新 Pod 上位。如果在这个部署过程中老 Pod 有一个很长的操作,我们想在这个操作成功完成后杀死这个 pod(优雅关闭),如果无法做到的话,被杀死的 pod 可能会丢失一定的流量,或者外界无法感知到该 Pod 被杀死。特别是,如果我们有一个接收大量流量的 API,错误率在部署过程中会显著增加。

其实这也挺简单的,添加一个优雅关闭就行了,之前写过优雅关闭的最佳实践K8S Pod流量的优雅无损切换实践

d5286a81ba447a5b1f5d61d16290d955.png

当 Kubernetes 杀死一个 pod 时,会发生以下 5 个步骤:

1、 Pod 切换到终止状态并停止接收任何新流量,容器仍在 pod 内运行。

2、 preStop 钩子是一个特殊的命令或 HTTP 请求被执行,并被发送到 pod 内的容器。

3、 SIGTERM 信号被发送到 pod,容器意识到它将很快关闭。

4、 Kubernetes 等待宽限期 (terminationGracePeriodSeconds)。此等待与 preStop hook 和 SIGTERM 信号执行并行(默认 30 秒)。因此,Kubernetes 不会等待这些完成。如果这段时间结束,则直接进入下一步。正确设置宽限期的值非常重要。

5、向 pod 发送 SIGKILL 信号,然后移除 pod。如果容器在宽限期后仍在运行,则 Pod 被 SIGKILL 强行移除,终止完成。

总结下大致分为两步,第一步定义 preStop,一般情况下可以休眠 30s,用于处理残余流量;第二步发送 SIGTERM 信号,服务收到信号后进行服务的收尾工作处理。比如:关闭连接、通知第三方注册中心服务关闭.....

有同学疑问,既然 pod 已经终止了,同时 K8s 的网络 endpoint 也摘除了,为什么还会进来流量呢?

因为这个网络接口的摘除是异步的,这也是为什么会首先执行 preStop,然后发送 SIGTERM 信号的原因所在。

这样做基本上能够保证流量无损,但是这样做的前提是服务能够收到 SIGTERM 信号。

理想情况下,一个容器只有一个进程,但是在现实场景下很难做到,比如,我会用一个 shell 脚本去管理和启动 Java 进程,除了 shell 脚本主进程之外,还要运行监控、日志收集等子进程,这样一个容器里面就运行了多个进程。

022812aea85606612cad2c589c39cd5b.png

系统底层默认会向主进程发送 SIGTERM 信号,而对剩余子进程发送 SIGKILL 信号。系统这样做的大概原因是因为大家在设计主进程脚本的时候都不会进行信号的捕获和传递,这会导致容器关闭时,多个子进程无法被正常终止,所以系统使用 SIGKILL 这个不可屏蔽信号,而是为了能够在没有任何前提条件的情况下,能够把容器中所有的进程关掉。

具体可以使用strace -p pid去跟踪服务调用情况。

也就是说如果主进程自身不是服务本身,可能会导致是被强制Kill的,解决的方法也很简单,也就是在主进程中对收到的信号做个转发,发送到容器中的其他子进程,这样容器中的所有进程在停止时,都会收到 SIGTERM,而不是 SIGKILL 信号了。

具体如何实现呢?比如下面的trap信号,就是一种实现方式,这里有一篇最佳实践http://veithen.io/2014/11/16/sigterm-propagation.html

#startup.sh
...
trap 'kill -TERM $child' TERM
nohup java $JAVA_OPTS -jar ./xxx.jar --server.port=8080 &

child=$!
wait $child
wait $child

当然很多成熟的框架都实现了优雅关闭功能,比如spring的CustomHealthCheck类扩展了AbstractHealthIndicator类,并允许我们通过覆盖doHealthCheck()方法来构建自定义健康检查结构。根据我们从HealthService收到的标志,我们将系统的健康状态设置为up或down。

这样的话,我们可以通过preStop调用该接口实现另外一种方式的优雅关闭。

lifecycle:
        preStop:
          httpGet:
            path: /unhealthy
            port: http

最后服务端收到优雅关闭信号后可以进行一些善后处理工作。

这就是K8s,自身很简单,但是它的低层牵涉了Linux内核、进程、网络、存储等方方面面的知识,但并不会在Kubernetes的文档中交代清楚。可偏偏就是它们,才是容器技术的精髓所在。

Linux命令查询工具

cc677b07eea96cba0f14c333ec1af10e.png有收获,点个在看 5729f2a1862356cbbacc2980e2d2b0b9.png

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当一个Kubernetes Pod处于Pending状态时,意味着该Pod正在等待被调度到一个可用的节点上运行。这可能是由于以下几个原因导致的: 1. 资源不足:集群中有足够的资源(例如CPU、内存)来满足Pod的需求。这可能是因为节点资源已经被其他Pod占用完毕,或者有足够的节点可供调度。 2. 调度限制:Pod的调度可能受到了一些限制条件的约束,例如节点选择器、亲和性或反亲和性规则、污点和容忍等。如果有满足这些条件的节点可用,Pod将会一直处于Pending状态。 3. 存储问题:如果Pod需要特定的存储卷,并且该存储卷无法成功挂载到Pod所在的节点上,那么Pod将无法调度并一直处于Pending状态。 4. 网络问题:如果Pod需要特定的网络配置,并且该网络配置无法成功应用到Pod所在的节点上,那么Pod将无法调度并一直处于Pending状态。 要解决Pod Pending的问题,可以采取以下几个步骤: 1. 检查集群资源:确保集群中有足够的资源可供调度新的Pod。可以通过kubectl describe nodes命令查看节点资源使用情况。 2. 检查调度限制:检查Pod的调度限制条件是否满足,例如节点选择器、亲和性或反亲和性规则等。可以通过kubectl describe pod <pod-name>命令查看Pod的详细信息。 3. 检查存储配置:确保Pod所需的存储卷可以成功挂载到Pod所在的节点上。可以通过kubectl describe pod <pod-name>命令查看Pod的详细信息,并检查存储卷相关的错误信息。 4. 检查网络配置:确保Pod所需的网络配置可以成功应用到Pod所在的节点上。可以通过kubectl describe pod <pod-name>命令查看Pod的详细信息,并检查网络相关的错误信息。 如果以上步骤都有解决问题,可以考虑增加集群资源、修改调度限制条件、修复存储配置或网络配置等来解决Pod Pending的问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值