DockePod信号处理机制与僵尸进程优化

Docke&Pod信号处理与僵尸进程优化

容器与信号的关系

  • SIGTERM信号:程序结束(terminate)信号,这是用来终止进程的标准信号,也是 kill 、 killall 、 pkill 命令所发送的默认信号。与SIGKILL不同的是该信号可以被阻塞和处理。通常用来要求程序自己正常退出。shell命令kill缺省产生这个信号。SIGTERM is the default signal sent to a process by the kill or killall commands

  • SIGKILL信号:终止进程,杀死进程。此信号为 “必杀(sure kill)” 信号,处理器程序无法将其阻塞、忽略或者捕获,故而 “一击必杀”,总能终止程序

  • SIGHUP信号:当终端断开(挂机)时,将发送该信号给终端控制进程。SIGHUP 信号还可用于守护进程(比如,init 等)。许多守护进程会在收到 SIGHUP 信号时重新进行初始化并重读配置文件

  • SIGINT信号:当用户键入终端中断字符(通常为 Control-C ) 时,终端驱动程序将发送该信号给前台进程组。该信号的默认行为是终止进程

  • SIGQUIT信号:当用户在键盘上键入退出字符(通常为 Control-\ )时,该信号将发往前台进程组。默认情况下,该信号终止进程,并生成用于调试的核心转储文件。进程如果陷入无限循环,或者不再响应时,使用 SIGQUIT 信号就很合适

  • SIGTSTP信号:这是作业控制的停止信号,当用户在键盘上输入挂起字符(通常为 Control-Z )时,将该信号给前台进程组,使其停止运行

  • 当你在执行 Docker 容器时,主要执行程序(Process)的 PID 将会是 1,只要这个程序停止,容器就会跟着停止

  • 由于容器中一直没有像systemd 或sysvinit 这类的初始化系统(init system),少了初始化系统来管理程序,会导致当程序不稳定的时候,无法进一步有效的处理程序的状态,或是无法有效的控制Signal 处理机制

  • 我们以docker stop 为例,这个命令实质上是对容器中的PID 1 送出一个SIGTERM 讯号如果程序本身并没有处理Signal 的机制,就会直接忽略这类讯号,这就会导致docker stop 等了10秒之后还不结束,然后Docker Engine 又会对PID 1 送出另一个SIGKILL 讯号,试图强迫砍掉这个程序,这才会让容器彻底停下来。但因为 SIGKILL 是无法被捕获(trapped)地,所以没有办法干净地终止掉子进程比如主程序在被终止时正在写入文件,那么该文件就会因此损坏。这就像直接拔掉了服务器的电源线一样残酷

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 格式)

通常都是因为容器启动入口使用了 shell,比如使用了类似 /bin/sh -c my-app/docker-entrypoint.sh 这样的 ENTRYPOINTCMD,这就可能就会导致容器内的业务进程收不到 SIGTERM 信号,原因是:

  • 容器主进程是 shell,业务进程是在 shell 中启动的,成为了 shell 进程的子进程,不管你 Dockerfile 用其中哪个指令,两个指令都推荐使用 exec 格式,而不是 shell 格式。原因就是因为使用 shell 格式之后,程序会以 /bin/sh -c 的子命令启动,并且 shell 格式下不会传递任何信号给程序。这也就导致,在 docker stop 容器的时候,以这种格式运行的程序捕捉不到发送的信号,也就谈不上优雅的关闭了
  • shell进程默认会处理SIGTERM信号,自己会退出,但是不会将信号传递给子进程,所以当我们以子进程启动业务就会导致业务进程不会触发停止逻辑
# 在当前进程上启动一个sleep进程
[root@VM-0-3-centos ~]# echo $$ && sleep 1000
28347# kill 28347,这个28347为父进程,根据下面结果我们可以知道,父进程未把终止信号传递给子进程(因为sleep未被关闭)
[root@VM-0-3-centos ~]# kill 28347
[root@VM-0-3-centos ~]# ps -ef | grep slee
root      4922 28347  0 22:55 pts/1    00:00:00 sleep 1000
root      5331 29381  0 22:56 pts/2    00:00:00 grep --color=auto slee# 我们不kill 父进程了,这次我们kill sleep自身进程
[root@VM-0-3-centos ~]# kill 4922# 查看另一个窗口,我们可以发现shell进程本身可以处理终止信号
[root@VM-0-3-centos ~]# echo $$ && sleep 1000
28347
Terminated
  • 当等到 K8S 优雅停止超时时间 (terminationGracePeriodSeconds,默认 30s),发送 SIGKILL 强制杀死 shell 及其子进程

无法处理信号

案例一

# 执行简单的sleep命令
[root@VM-0-3-centos ~]# docker run -d --rm --name=test ubuntu:22.04 /bin/sh -c "sleep 10000"
ec604a00f360c6b52554455c0b95f638ed5d97ec2578ae03462c65a345bd795a# 查看容器内部进程,可以发现sleep是子进程,子进程是无法接收到中止信号的
[root@VM-0-3-centos ~]# docker exec test ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 10:17 ?        00:00:00 /bin/sh -c sleep 10000   # 父进程
root         7     1  0 10:17 ?        00:00:00 sleep 10000							 # 子进程
root         8     0  0 10:18 ?        00:00:00 ps -ef# 尝试停止这个容器,此时你会发现要等10秒,容器才会结束,其实是/bin/sh预设并不会处理(handle)讯号,所以他会把所有不认得的讯号忽略,直到作业系统把他SIGKILL为止
[root@VM-0-3-centos ~]# time docker stop test
testreal    0m10.141s
user    0m0.008s
sys     0m0.010s

案例二

  • shell 格式,默认 /bin/sh -c
FROM ubuntu:22.04
RUN apt-get update && apt-get -y install redis-server && rm -rf /var/lib/apt/lists/*
EXPOSE 6379
CMD "/usr/bin/redis-server"			# 以子进程方式启动进程# 运动redis-shell容器
[root@VM-0-3-centos ~]# docker run -d --name=redis-shell redis:shell# 查看redis日志
[root@VM-0-3-centos ~]# docker logs d5704fc16e2b27cf1fda076ca0eed80100df7f8fd4ef3d0e05487c1a6ee691fa
7:C 14 Aug 2023 10:23:21.682 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
7:C 14 Aug 2023 10:23:21.683 # Redis version=6.0.16, bits=64, commit=00000000, modified=0, pid=7, just started
7:C 14 Aug 2023 10:23:21.683 # Warning: no config file specified, using the default config. In order to specify a config file use /usr/bin/redis-server /path/to/redis.conf
7:M 14 Aug 2023 10:23:21.684 * Running mode=standalone, port=6379.
7:M 14 Aug 2023 10:23:21.684 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
7:M 14 Aug 2023 10:23:21.684 # Server initialized
7:M 14 Aug 2023 10:23:21.684 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
7:M 14 Aug 2023 10:23:21.684 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo madvise > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled (set to 'madvise' or 'never').
7:M 14 Aug 2023 10:23:21.684 * Ready to accept connections# 查看redis进程信息,可以发现redis是子进程方式启动的
[root@VM-0-3-centos ~]# docker exec redis-shell ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 10:23 ?        00:00:00 /bin/sh -c "/usr/bin/redis-server"
root         7     1  0 10:23 ?        00:00:00 /usr/bin/redis-server *:6379
root        12     0  0 10:25 ?        00:00:00 ps -ef# 手动停止redis-shell进程,等停止完成后,我们查看redis日志,我们可以发现redis并没有主动关闭服务,而是直接被干掉了
[root@VM-0-3-centos ~]# docker stop redis-shell  			# 这里会停大概10s
redis-shell
[root@VM-0-3-centos ~]# docker logs d5704fc16e2b
7:C 14 Aug 2023 10:23:21.682 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
7:C 14 Aug 2023 10:23:21.683 # Redis version=6.0.16, bits=64, commit=00000000, modified=0, pid=7, just started
7:C 14 Aug 2023 10:23:21.683 # Warning: no config file specified, using the default config. In order to specify a config file use /usr/bin/redis-server /path/to/redis.conf
7:M 14 Aug 2023 10:23:21.684 * Running mode=standalone, port=6379.
7:M 14 Aug 2023 10:23:21.684 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
7:M 14 Aug 2023 10:23:21.684 # Server initialized
7:M 14 Aug 2023 10:23:21.684 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
7:M 14 Aug 2023 10:23:21.684 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo madvise > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled (set to 'madvise' or 'never').
7:M 14 Aug 2023 10:23:21.684 * Ready to accept connections

案例三

  • 以exec方式启动进程,但是这个进程以非exec方式启动脚本或程序(脚本和程序实际是等效的)
[root@VM-0-3-centos redis-server-exec]# cat sleep.sh 
#!/bin/bash
sleep 10000 # 等效/bin/sh -c sleep 10000  [root@VM-0-3-centos redis-server-exec]# cat dockerfile 
FROM ubuntu:22.04
COPY sleep.sh /
CMD ["/sleep.sh"]# 启动容器
[root@VM-0-3-centos redis-server-exec]# docker run -d --name=test redis:son
fe59095f93e2f687c67cb43e13c37011e6032882fea6d00d928eafd968aab737# 查看进程,我们可以发现sleep 也是以子进程方式启动的,故无法对信号进行处理
[root@VM-0-3-centos redis-server-exec]# docker exec test ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 13:50 ?        00:00:00 /bin/bash /sleep.sh
root         7     1  0 13:50 ?        00:00:00 sleep 10000
root         8     0  0 13:50 ?        00:00:00 ps -ef

正常处理信号

案例一

  • exec 格式, 推荐使用这种格式
FROM ubuntu:22.04
RUN apt-get update && apt-get -y install redis-server && rm -rf /var/lib/apt/lists/*
EXPOSE 6379
CMD ["/usr/bin/redis-server"]			# 以当前进程方式启动进程# 运动redis-exec容器
[root@VM-0-3-centos ~]# docker run -d --name=redis-exec redis:exec
9bed75cfdd7d9efaf0775abb7f43d1a7c13931c1c959f65c28081e9b89f7d5e8# # 查看redis日志
[root@VM-0-3-centos ~]# docker logs 9bed75cfdd7d9efaf0775abb7f43d1a7c13931c1c959f65c28081e9b89f7d5e8
1:C 14 Aug 2023 13:39:35.582 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
1:C 14 Aug 2023 13:39:35.582 # Redis version=6.0.16, bits=64, commit=00000000, modified=0, pid=1, just started
1:C 14 Aug 2023 13:39:35.582 # Warning: no config file specified, using the default config. In order to specify a config file use /usr/bin/redis-server /path/to/redis.conf
1:M 14 Aug 2023 13:39:35.583 * Running mode=standalone, port=6379.
1:M 14 Aug 2023 13:39:35.583 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
1:M 14 Aug 2023 13:39:35.583 # Server initialized
1:M 14 Aug 2023 13:39:35.583 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
1:M 14 Aug 2023 13:39:35.583 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo madvise > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled (set to 'madvise' or 'never').
1:M 14 Aug 2023 13:39:35.584 * Ready to accept connections# 查看redis进程信息,可以发现redis是在当前进程方式启动的
root@VM-0-3-centos ~]# docker exec redis-exec ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 13:39 ?        00:00:00 /usr/bin/redis-server *:6379
root        11     0  2 13:40 ?        00:00:00 ps -ef# 手动停止redis-exec进程可以发现立刻停止了,我们查看redis日志,我们可以发现redis并有主动关闭服务,而不是直接被干掉了
[root@VM-0-3-centos ~]# docker stop redis-exec
redis-exec[root@VM-0-3-centos ~]# docker logs redis-exec
1:C 14 Aug 2023 13:39:35.582 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
1:C 14 Aug 2023 13:39:35.582 # Redis version=6.0.16, bits=64, commit=00000000, modified=0, pid=1, just started
1:C 14 Aug 2023 13:39:35.582 # Warning: no config file specified, using the default config. In order to specify a config file use /usr/bin/redis-server /path/to/redis.conf
1:M 14 Aug 2023 13:39:35.583 * Running mode=standalone, port=6379.
1:M 14 Aug 2023 13:39:35.583 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
1:M 14 Aug 2023 13:39:35.583 # Server initialized
1:M 14 Aug 2023 13:39:35.583 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
1:M 14 Aug 2023 13:39:35.583 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo madvise > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled (set to 'madvise' or 'never').
1:M 14 Aug 2023 13:39:35.584 * Ready to accept connections
1:signal-handler (1692020490) Received SIGTERM scheduling shutdown...
1:M 14 Aug 2023 13:41:30.888 # User requested shutdown...
1:M 14 Aug 2023 13:41:30.888 * Saving the final RDB snapshot before exiting.
1:M 14 Aug 2023 13:41:30.896 * DB saved on disk
1:M 14 Aug 2023 13:41:30.896 # Redis is now ready to exit, bye bye...        # 我们可以发现,redis是主动退出了

案例二

  • 以exec方式启动进程,这个进程也以exec方式启动脚本或程序(脚本和程序实际是等效的)
  • 脚本中执行二进制
[root@VM-0-3-centos redis-server-exec]# cat sleep.sh 
#!/bin/bash
exec sleep 10000			# 以非子进程启动[root@VM-0-3-centos redis-server-exec]# cat dockerfile 
FROM ubuntu:22.04
COPY sleep.sh /
CMD ["/sleep.sh"]# 启动容器
root@VM-0-3-centos redis-server-exec]# docker run -d --name=test redis:son
d9c45f8d3b0f9aa7842b0e3b83a6a716310beff24b0e8c3990820e7d8b54f71a# 查看容器进程,我们可以发现,这是以非子进程启动的,所以可以接受信号
[root@VM-0-3-centos redis-server-exec]# docker exec test ps -ef 
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 13:53 ?        00:00:00 sleep 10000
root         7     0  0 13:53 ?        00:00:00 ps -ef

案例三

通常我们一个容器只会有一个进程,也是 Kubernetes 的推荐做法。但有些时候我们不得不启动多个进程,比如从传统部署迁移到 Kubernetes 的过渡期间,使用了富容器,即单个容器中需要启动多个业务进程,这时也只能通过 shell 启动,但无法使用上面的 exec 方式来传递信号,因为 exec只能让一个进程替代当前 shell 成为主进程

这个时候我们可以在 shell 中使用 trap 来捕获信号,当收到信号后触发回调函数来将信号通过 kill 传递给业务进程。脚本示例:

[root@VM-0-3-centos redis-server-exec]# cat entrypoint.sh 
#!/bin/bash
sleep 100000 & pid1="$!"  # 启动一个进程,并记录pid
echo "sleep1 started with pid $pid1"sleep 100000 & pid2="$!"  # 启动一个进程,并记录pid
echo "sleep2 started with pid $pid2"handle_sigterm() {echo "[INFO] Received SIGTERM"kill -SIGTERM $pid1 $pid2 # 传递 SIGTERM 给业务进程wait $pid1 $pid2 # 等待所有业务进程完全终止
}
echo "[INFO] sleep1/sleep2 start ok"trap handle_sigterm SIGTERM # 捕获 SIGTERM 信号并回调 handle_sigterm 函数
wait # 等待回调执行完,主进程再退出[root@VM-0-3-centos redis-server-exec]# cat dockerfile 
FROM ubuntu:22.04
COPY entrypoint.sh  /
CMD ["/entrypoint.sh"]# 启动容器
[root@VM-0-3-centos redis-server-exec]# docker run -d --name test test:v1
5979b214264d87592c8b3515fb47714b2c51efd2af12f36a1fffa56bc60d5b88# 查看进程,这里我们可以看到,entrypoint.sh 启动了二个sleep子进程
[root@VM-0-3-centos redis-server-exec]# docker exec 5979b214264d ps -ef 
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 14:17 ?        00:00:00 /bin/bash /entrypoint.sh
root         7     1  0 14:17 ?        00:00:00 sleep 100000
root         8     1  0 14:17 ?        00:00:00 sleep 100000
root         9     0  0 14:18 ?        00:00:00 ps -ef# 停止的时候我们可以发现,立刻被中止了,说明终止信号被正常处理了
[root@VM-0-3-centos redis-server-exec]# docker stop test
test

僵尸进程

  • 当一个子进程终止后,它首先会变成一个失效(defunct)的进程,也称为僵尸zombie进程,等待父进程或系统收回(reap)。在Linux内核中维护了关于“僵尸”进程的一组信息(PID,终止状态,资源使用信息),从而允许父进程能够获取有关子进程的信息。如果不能正确回收“僵尸”进程,那么他们的进程描述符仍然保存在系统中,系统资源会缓慢泄露
  • 大多数设计良好的多进程应用可以正确的收回僵尸子进程,比如NGINX master进程可以收回已终止的worker子进程。如果需要自己实现,则可利用如下方法:
    • 利用操作系统的waitpid()函数等待子进程结束并请除它的僵死进程
    • 由于当子进程成为“defunct”进程时,父进程会收到一个SIGCHLD信号,所以我们可以在父进程中指定信号处理的函数来忽略SIGCHLD信号,或者自定义收回处理逻辑
  • 如果父进程已经结束了,那些依然在运行中的子进程会成为孤儿orphaned进程。在Linux中Init进程(PID1)作为所有进程的父进程,会维护进程树的状态,一旦有某个子进程成为了“孤儿”进程后,init就会负责接管这个子进程。当一个子进程成为“僵尸”进程之后,如果其父进程已经结束,init会收割这些“僵尸”,释放PID资源
  • Linux 中,若子进程缺失父进程,其残留资源会由 init 进程回收。但在 Docker 中,容器并非一个完整的操作系统,不会初始化 init 进程,容器中的第一个进程只是一个普通进程,所以并不会回收僵尸进程

下面我们做几个试验来验证不同的PID1进程对僵尸进程不同的处理能力

FROM ubuntu:22.04
RUN apt-get update && apt-get -y install redis-server && rm -rf /var/lib/apt/lists/*
EXPOSE 6379
CMD ["/usr/bin/redis-server"]			# 以当前进程方式启动进程# 启动容器
[root@VM-0-3-centos redis-server-exec]# docker run -d --name=redis redis:v1
daca6972da659b99922df3fe227120b473bc22f4b28e6436e2337979c50ab706# 在redis容器中启动一个bash进程,并创建子进程“sleep 10000”
[root@VM-0-3-centos redis-server-exec]# docker exec -it redis bash
root@daca6972da65:/# sleep 10000# 查看进程,我们可以发现一个sleep进程是bash进程的子进程
[root@VM-0-3-centos ~]# docker exec redis ps -ef 
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 14:38 ?        00:00:00 /usr/bin/redis-server *:6379
root        11     0  0 14:38 pts/0    00:00:00 bash
root        19    11  0 14:38 pts/0    00:00:00 sleep 10000
root        20     0  0 14:39 ?        00:00:00 ps -ef# 我们杀死bash进程之后查看进程列表,这时候bash进程已经被杀死。这时候sleep进程(PID为19),虽然已经结束,而且被PID1进程(redis-server)接管,但是其没有被父进程回收,成为僵尸状态。这是因为PID1进程“redis-server”没有考虑过作为init对僵尸子进程的回收的场景
[root@VM-0-3-centos ~]# docker exec redis ps -ef 
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 14:38 ?        00:00:00 /usr/bin/redis-server *:6379
root        19    11  0 14:38 pts/0    00:00:00 [sleep] <defunct>
root        20     0  0 14:39 ?        00:00:00 ps -ef
FROM ubuntu:22.04
RUN apt-get update && apt-get -y install redis-server && rm -rf /var/lib/apt/lists/*
EXPOSE 6379
CMD "/usr/bin/redis-server"			# 以子进程方式启动进程# 启动容器
[root@VM-0-3-centos redis-server-exec]# docker run -d --name=redis redis:v1
8325ff6ad59350249b3f0bdeb24e356b53a897906ae739a603fe594f518c0c38# 在redis容器中启动一个bash进程,并创建子进程“sleep 10000”
[root@VM-0-3-centos redis-server-exec]# docker exec -it redis bash
root@8325ff6ad593:/# sleep 10000# 查看进程,我们可以发现一个sleep进程是bash进程的子进程
[root@VM-0-3-centos ~]# docker exec redis ps -ef 
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 14:44 ?        00:00:00 /bin/sh -c "/usr/bin/redis-server"...# ??????????????????????????????
root         7     1  0 14:44 ?        00:00:00 /usr/bin/redis-server *:6379
root        12     0  0 14:45 pts/0    00:00:00 bash
root        20    12  0 14:45 pts/0    00:00:00 sleep 10000
root        21     0  0 14:45 ?        00:00:00 ps -ef# 我们杀死bash进程之后查看进程列表,发现“bash”和“sleep 1000”进程都已经被杀死和回收
[root@VM-0-3-centos ~]# docker exec redis kill -9 12
[root@VM-0-3-centos ~]# docker exec redis ps -ef 
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 14:44 ?        00:00:00 /bin/sh -c "/usr/bin/redis-server"...# ??????????????????????????????
root         7     1  0 14:44 ?        00:00:00 /usr/bin/redis-server *:6379
root        33     0  2 14:46 ?        00:00:00 ps -ef

这是因为sh/bash等应用可以自动清理僵尸进程。简单而言,如果在容器中运行多个进程,PID1进程需要有能力接管“孤儿”进程并回收“僵尸”进程。我们可以

  • 利用自定义的init进程来进行进程管理,比如S6,phusion myinit,dumb-init,tini等

  • Bash/sh等缺省提供了进程管理能力,如果需要可以作为PID1进程来实现正确的进程回收

  • 如果我们父进程以bash/sh启动,能提供进程收割能力,防止容器出现僵尸进程,但是缺无法将终止信号传递给子进程,为此我们一般采用:dumb-init、tini作为父进程(提供进程接管能力、信号传递能力)

tini&dumb-init进程

  • tini 是一套更简单的init 系统,专门用来执行一个子程序(spawn a single child),并等待子程序结束,即便子程序已经变成僵尸程序(zombie process)也能捕捉到,同时也能转送Signal 给子程序
  • Tini一般在容器中运行,用于生成子进程,等待它推出,reap僵尸进程,并执行信号转发
  • dumb-init 和 tini 都可以作为 init 进程,作为主进程 (PID 1) 在容器中启动,然后它再运行 shell 来执行我们指定的脚本 (shell 作为子进程),shell 中启动的业务进程也成为它的子进程,当它收到信号时会将其传递给所有的子进程,从而也能完美解决 SHELL 无法传递信号问题,并且还有回收僵尸进程的能力。
  • 如果你使用Docker 来跑容器,可以非常简便的在docker run 的时候用–init 参数,就会自动注入tini 程式(/sbin/docker-init) 到容器中,并且自动取代ENTRYPOINT 设定,让原本的程式直接跑在tini 程序底下。注意:Docker 1.13 以后的版本才开始支援 --init 参数,并内建 tini 在内
  • init系统有以下几个特点
    • 它是系统的第一个进程,负责产生其他所有用户进程
    • init 以守护进程方式存在,是所有其他进程的祖先
    • 它主要负责:启动守护进程、回收孤儿进程、将操作系统信号转发给子进程

Tini

  • 当我们docker版本>=1.13时,docker默认集成了tini
# 以init方式启动容器
[root@VM-0-3-centos ~]# docker run -d --init --name=test ubuntu:22.04 sleep 10000
203e03c6ac46f2806e648d4b3dce9efe3b70f9d239474136e6874f35885a642d# 查看容器进程
[root@VM-0-3-centos ~]# docker exec test ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 01:37 ?        00:00:00 /sbin/docker-init -- sleep 10000
root         7     1  0 01:37 ?        00:00:00 sleep 10000
root         8     0  0 01:38 ?        00:00:00 ps -ef# 停止容器,我们可以发现,正常情况下,sleep是作为子进程,无法正常处理信号的,需要等待10s,这里立马就停止了,说明docker-init将信号传递给子进程了
[root@VM-0-3-centos ~]# docker stop test
test
  • Dockerfile集成tini
FROM ubuntu:22.04
ENV TINI_VERSION v0.19.0
ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini
RUN chmod +x /tini
# [-vvv/-vv/-v] 设置日志级别,可选参数
ENTRYPOINT ["/tini", "-vvv","--"]# 启动程序,这里仅测试,启动命令由docker run传递进来
# CMD ["/your/program", "-and", "-its", "arguments"]# 启动容器
[root@VM-0-3-centos ~]# docker run -d --name=test test:tini sleep 10000
ca4f0d14c341223d530bbac9a5beb0907e633e23b095eb2168ade9b6df3e6d70# 查看容器进程
[root@VM-0-3-centos ~]# docker exec test ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 01:45 ?        00:00:00 /tini -vvv -- sleep 10000
root         7     1  0 01:45 ?        00:00:00 sleep 10000
root         8     0  0 01:46 ?        00:00:00 ps -ef# 测试僵尸进程回收,我们另起一个窗口,启动sleep,然后将其父进程bash kill掉
[root@VM-0-3-centos ~]# docker exec -it test /bin/bash
root@ca4f0d14c341:/# sleep 10000# 查看此时进程信息,sleep的父进程IP为14
[root@VM-0-3-centos ~]# docker exec test ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 01:45 ?        00:00:00 /tini -vvv -- sleep 10000
root         7     1  0 01:45 ?        00:00:00 sleep 10000
root        14     0  0 01:47 pts/0    00:00:00 /bin/bash
root        22    14  0 01:47 pts/0    00:00:00 sleep 10000
root        23     0  0 01:48 ?        00:00:00 ps -ef# 我们可以发现tini进程将子进程回收了,并没有产生僵尸进程
[root@VM-0-3-centos ~]# docker exec test kill -9 14
[root@VM-0-3-centos ~]# docker exec test ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 01:45 ?        00:00:00 /tini -vvv -- sleep 10000
root         7     1  0 01:45 ?        00:00:00 sleep 10000
root        35     0  0 01:48 ?        00:00:00 ps -ef

参考链接:https://blog.miniasp.com/post/2021/07/09/Use-dumb-init-in-Docker-Container
参考链接:http://www.oschina.net/translate/docker-and-the-pid-1-zombie-reaping-problem

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/37426.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

k8s和docker简单介绍

当涉及到容器技术和容器编排时&#xff0c;Docker和Kubernetes是两个重要的概念。我将更详细地介绍它们以及它们之间的关系。 Docker&#xff1a; Docker是一种容器化技术&#xff0c;它允许你将应用程序及其依赖项打包到一个称为"容器"的封闭环境中。每个容器都包…

rust包跨平台编译,macbook ,linux

在 MacBook 上编译 Rust 项目并生成 Linux 包需要一些步骤。以下是一般的步骤概述&#xff1a; 1. **安装所需工具&#xff1a;** 首先&#xff0c;确保您的 MacBook 上已经安装了所需的工具。您需要 Rust 编程语言的工具链以及一些用于交叉编译到 Linux 的工具。 - 安装 R…

【BASH】回顾与知识点梳理(二十一)

【BASH】回顾与知识点梳理 二十一 二十一. Linux 的文件权限与目录配置21.1 使用者与群组属主(文件拥有者)属组(群组概念)其他人的概念root(万能的天神)Linux 用户身份与群组记录的文件 21.2 Linux 文件权限概念Linux 文件属性Linux 文件权限的重要性 21.3 如何改变文件属性与权…

组合模式(C++)

定义 将对象组合成树形结构以表示部分-整体’的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性(稳定)。 应用场景 在软件在某些情况下&#xff0c;客户代码过多地依赖于对象容器复杂的内部实现结构&#xff0c;对象容器内部实现结构(而非抽象接口)的变化…

Redis数据结构——链表list

链表是一种常用的数据结构&#xff0c;提供了顺序访问的方式&#xff0c;而且高效地增删操作。 Redis中广泛使用了链表&#xff0c;例如&#xff1a;列表的底层实现之一就是链表。 在Redis中&#xff0c;链表分为两部分&#xff1a;链表信息 链表节点。 链表节点用来表示链表…

PyTorch深度学习实践---笔记

PyTorch深度学习实践---笔记 2.线性模型&#xff08;Linear Model&#xff09;2.exercise 3. 梯度下降算法&#xff08;Gradient Descent&#xff09;3.1梯度下降&#xff08;Gradient Descent&#xff09;3.2 随机梯度下降&#xff08;Stochastic Gradient Descent&#xff09…

亚马逊测评工作室怎么做?

亚马逊是全球最大的电商平台之一&#xff0c;任何一个卖家想要提升自己店铺的知名度和销量&#xff0c;都需要关注自己Listing的Review数量和星级评价&#xff0c;而测评对于卖家账号的评定和产品曝光量有着重要影响&#xff0c;可以用于店铺提升销量&#xff0c;留评等 在进行…

【GC日志分析】-对象晋升到老年代分析

[GC (Allocation Failure) [PSYoungGen: 123456K->65432K(262144K)] 123456K->765432K(1048576K), 0.1234567 secs] [Times: user0.10 sys0.02, real0.13 secs] 从这段GC日志中&#xff0c;可以看到两个年轻代和老年代的堆内存容量变化情况。 首先&#xff0c;我们来看…

百度坐标(BD09)、国测局坐标(火星坐标,GCJ02)、和WGS84坐标系之间的转换

<!DOCTYPE html> <html><head><meta charset"UTF-8"><title></title></head><body><script>/*** * 百度坐标&#xff08;BD09&#xff09;、国测局坐标&#xff08;火星坐标&#xff0c;GCJ02&#xff09;、和…

c51单片机串口通信(中断方式接收数据)(单片机--单片机通信)示例代码 附proteus图

单片机一般采用中断方式接受数据&#xff0c;这样便于及时处理 #include "reg51.h" #include "myheader.h" #define uchar unsigned char int szc[10]{0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90}; int bufferc[6]{0}; int sza[6]{0x01,0x02,0x0…

HOT96-只出现一次的数字

leetcode原题链接&#xff1a;只出现一次的数字 题目描述 给你一个 非空 整数数组 nums &#xff0c;除了某个元素只出现一次以外&#xff0c;其余每个元素均出现两次。找出那个只出现了一次的元素。你必须设计并实现线性时间复杂度的算法来解决此问题&#xff0c;且该算法只使…

TEXTure环境配置,跑通inference的demo

TEXTure 环境配置安装kaolin这个包,这里可能会遇到各种问题配置huggingface的访问令牌 运行Text Conditioned Texture Generation指令报错1报错2成功运行 查看结果查看贴图后的三维网格模型 环境配置 # 创建一个名为texture的环境 conda create -n texture python3.9 -y# 激活…

EasyLLM:简化语言模型处理,实现 OpenAI 和 Hugging Face 客户端的无缝切换

前言 在这短短不到一年的时间里&#xff0c;国内外涌现的大型语言模型&#xff08;LLM&#xff09;可谓是百花齐放&#xff0c;不管是开源还是闭源都出现了一些非常优秀的模型&#xff0c;然而在利用LLM进行应用开发的时候&#xff0c;会发现每个模型从部署、到训练、微调、AP…

人工智能任务1-【NLP系列】句子嵌入的应用与多模型实现方式

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下人工智能任务1-【NLP系列】句子嵌入的应用与多模型实现方式。句子嵌入是将句子映射到一个固定维度的向量表示形式&#xff0c;它在自然语言处理&#xff08;NLP&#xff09;中有着广泛的应用。通过将句子转化为向量…

ASP.NET Core - 缓存之分布式缓存

分布式缓存是由多个应用服务器共享的缓存&#xff0c;通常作为访问它的应用服务器的外部服务进行维护。 分布式缓存可以提高 ASP.NET Core 应用的性能和可伸缩性&#xff0c;尤其是当应用由云服务或服务器场托管时。 与其他将缓存数据存储在单个应用服务器上的缓存方案相比&am…

沁恒ch32V208处理器开发(三)GPIO控制

目录 GPIO功能概述 CH32V2x 微控制器的GPIO 口可以配置成多种输入或输出模式&#xff0c;内置可关闭的上拉或下拉电阻&#xff0c;可以配置成推挽或开漏功能。GPIO 口还可以复用成其他功能。端口的每个引脚都可以配置成以下的多种模式之一&#xff1a; 1 浮空输入 2 上拉输入…

AMEYA360:DNB1101大唐恩智浦工规级电池管理芯片

大唐恩智浦作为全球领先的半导体供应商&#xff0c;一直致力于为全球客户提供高质量的解决方案。在电池管理芯片领域&#xff0c;大唐恩智浦推出的DNB1101可谓是一款工规级的电池管理芯片&#xff0c;其卓越的性能和可靠性成为市场上备受全球领先的半导体供应商&#xff0c;一直…

c#编码技巧(十四):全面总结delegate、Func委托的写法演变

delegate委托对于初学者来说不太好理解。 按笔者的经验&#xff0c;delegate本质是函数指针&#xff0c;可以把它理解为某一类方法的入口&#xff0c;把他翻译为&#xff1a;“长得像XXX的函数方法&#xff08;入参是什么、返回值是什么&#xff09;”可能更容易理解。 以下示例…

2023杭电多校第7场M题-M. Minimal and Maximal XOR Sum

题目链接&#xff1a;csoj | M. Minimal and Maximal XOR Sum (scnu.edu.cn) 解题思路&#xff1a; 最小值&#xff1a;每次操作的区间长度为2&#xff0c;即交换两个相邻数&#xff0c;每次异或2(10)&#xff0c;故最小值肯定为2(10)或0(00)&#xff0c;如果是偶排序最小值是…

Java接口压力测试—如何应对并优化Java接口的压力测试

导言 在如今的互联网时代&#xff0c;Java接口压力测试是评估系统性能和可靠性的关键一环。一旦接口不能承受高并发量&#xff0c;用户体验将受到严重影响&#xff0c;甚至可能导致系统崩溃。因此&#xff0c;了解如何进行有效的Java接口压力测试以及如何优化接口性能至关重要…