k8s

优雅终止

概述

在删除pod时,容器中的应用进程可能还在处理请求,需要保证已存在的连接不断开,新的连接不过来,最后再停止进程。这个过程就叫做优雅终止。

删除pod的过程

  1. 用户提交删除pod请求,将pod状态设置为Terminating。
  2. kube-proxy更新iptables,将pod从service的endpoint列表中摘除掉,新的流量不再转发到该pod。
  3. 如果pod配置preStop Hook,执行该Hook。
  4. kubelet对pod中所有container发送SIGTERM信号(kill -15/kill)停止应用进程。
  5. 等待容器进程完全停止,如果在terminationGracePeriodSeconds内 (默认 30s) 还未完全停止,就发送SIGKILL信号 (kill -9) 强制杀死进程。
  6. 所有容器进程终止,清理pod资源。

SIGTERM信号收不到

应用必须要有处理SIGTERM信号的能力,这样才能实现优雅终止。在实现的过程中可能会有一个坑:容器进程收不到SIGTERM信号。

如果容器的EntryPoint使用了脚本,业务进程就成了shell的子进程,在pod停止时业务进程可能收不到SIGTERM信号,因为 shell 不会自动传递信号给子进程。

栗子

1.直接定义命令

这个栗子是Dockerfile中定义了ping localhost,但是实际上容器内该pid为1的是一个shell进程,ping命令成为了shell的子进程。这样就会导致外部发送任何信号也无法传递到ping命令,ctrl+c也不行。

cat Dockerfile
FROM centos
CMD ping localhost

docker exec 26ad ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 20:14 ? 00:00:00 /bin/sh -c ping localhost
root 7 1 0 20:14 ? 00:00:00 ping localhost
root 20 0 0 20:15 ? 00:00:00 ps -f 

2.使用脚本

cat Dockerfile
FROM centos
ENTRYPOINT ["/start.sh"] 
cat start.sh 
#! /bin/bash
ping localhost

解决

上面的栗子1是可以修改Dockerfile,使用Exec表示法。

cat Dockerfile
FROM centos
CMD ["/bin/ping localhost"]

但是栗子2已经是Exec表示法了,这时可以有两种方法:

  1. 尽量不使用shell启动业务进程。
  2. 如果一定要通过shell启动进程,那么需要在shell中添加处理SIGTERM信号的能力。

shell处理SIGTERM信号

使用exec启动

在shell中启动二进制的命令前加一个exec即可让该二进制启动的进程代替当前shell进程,即让新启动的进程成为主进程:

#! /bin/bash
...
exec /bin/yourapp # 脚本中执行二进制

使用trap

如果容器内需要启动多个进程,那么就不能用exec了,因为exec只能让一个进程成为主进程。这时可以使用trap来捕获信号,当收到信号后触发回调函数来将信号通过kill传递给业务进程,脚本示例:

#! /bin/bash

/bin/app1 & pid1="$!" # 启动第一个业务进程并记录 pid
echo "app1 started with pid $pid1"

/bin/app2 & pid2="$!" # 启动第二个业务进程并记录 pid
echo "app2 started with pid $pid2"

handle_sigterm() {
  echo "[INFO] Received SIGTERM"
  kill -SIGTERM $pid1 $pid2 # 传递 SIGTERM 给业务进程
  wait $pid1 $pid2 # 等待所有业务进程完全终止
}
trap handle_sigterm SIGTERM # 捕获 SIGTERM 信号并回调 handle_sigterm 函数

wait # 等待回调执行完,主进程再退出

使用适当的init进程

tini:https://github.com/krallin/tini

tini作为主进程 (PID 1) 在容器中启动,然后它再运行shell启动脚本,shell作为子进程,shell中启动的业务进程也成为它的子进程,当它收到信号时会将其传递给所有的子进程,从而也能完美解决 SHELL 无法传递信号问题,还可以负责清理异常退出的子进程。Dockerfile栗子如下:

FROM centos
# Add Tini
ENV TINI_VERSION v0.19.0
ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini
RUN chmod +x /tini
ENTRYPOINT ["/tini", "--"]

# Run your program under Tini
CMD ["/your/program", "-and", "-its", "arguments"]
# 或者
ADD start.sh /
ADD app1 /bin/app1
ADD app2 /bin/app2
CMD ["/start.sh"]

cat start.sh
#! /bin/bash
/bin/app1 &
/bin/app2 &
wait

preStop Hook

k8s调用hook有2种方式:exec,http,tcpSocket(暂不支持)。

k explain deployment.spec.template.spec.containers.lifecycle.preStop

file

其中httpGet和tcpSocket在kubelet进程执行,而exec则由容器内执行 。

如果业务代码中没有处理SIGTERM信号的能力,也无法使用第三方库或系统来增加优雅终止的逻辑,那么可以在pod中使用preStop Hook来实现优雅终止

k explain deployment.spec.template.spec.containers.lifecycle

file

示例:

 lifecycle:
          preStop:
            exec:
              command:
              - /clean.sh

如果PreStop失败了,会产生一个FailedPreStopHook 事件,在describe pod的Events中可以看到。

Events:
  Type     Reason               Age              From               Message
  ----     ------               ----             ----               -------
...
  Warning  FailedPostStartHook  4s (x2 over 5s)  kubelet            Exec lifecycle hook ([badcommand]) for Container "lifecycle-demo-container" in Pod "lifecycle-demo_default(30229739-9651-4e5a-9a32-a8f1688862db)" failed - error: command 'badcommand' exited with 126: , message: "OCI runtime exec failed: exec failed: container_linux.go:380: starting container process caused: exec: \"badcommand\": executable file not found in $PATH: unknown\r\n"
  Normal   Killing              4s (x2 over 5s)  kubelet            FailedPostStartHook
...

在某些极端情况下,pod被删除的一小段时间内,仍然可能有新连接被转发过来,因为kubelet与kube-proxy同时watch到pod 被删除,kubelet有可能在kube-proxy同步完iptables前就已经停止容器了,这时可能导致一些新的连接被转发到正在删除的 pod。

而通常情况下,当应用收到SIGTERM信号后都不再接受新连接,只保持存量连接继续处理,所以就可能导致pod删除的瞬间部分请求失败。这时可以在preStop中使用sleep命令,等待kube-proxy处理完iptables。

       lifecycle:
          preStop:
            exec:
              command:
              - sleep
              - 5s
  • 调整优雅终止时长
    k explain deployment.spec.template.spec.terminationGracePeriodSeconds

    file

可以看到默认是30s,你的应用(preStop + 业务进程停止)可能超过30s,那么容器就会被kill掉。根据你的业务自定义优雅终止时长比如调整为60s。

分类: k8s
0 0 投票数
文章评分
订阅评论
提醒
guest

1 评论
最旧
最新 最多投票
内联反馈
查看所有评论
דירות דיסקרטיות במרכז

An intriguing discussion is worth comment. I do think that you should write more on this topic, it might not be a taboo subject but typically folks dont discuss these issues. To the next! All the best!!

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部
0
希望看到您的想法,请您发表评论x