【kubernetes】从僵尸进程探讨容器的多进程管理
在kubernetes的架构设计中,同一个pod内有多个服务的情况下,每个服务作为一个容器是最佳实践,各容器通过pause容器共享网络命令空间。如果违背这一原则,在一个容器内运行了多个进程,则可能引发一些意料之外的问题。僵尸进行无法回收示例情景如下:一个pod内只有一个容器容器内运行两个进程(一个后台,一个前台)该pod容器镜像的entrypoint脚本如下:#!/bin/bash/usr/sbi
在kubernetes的架构设计中,同一个pod内有多个服务的情况下,每个服务作为一个容器是最佳实践,各容器通过pause容器共享网络命令空间。如果违背这一原则,在一个容器内运行了多个进程,则可能引发一些意料之外的问题。
僵尸进行无法回收
示例情景如下:
- 一个pod内只有一个容器
- 容器内运行两个进程(一个后台,一个前台)
该pod容器镜像的entrypoint脚本如下:
#!/bin/bash
/usr/sbin/sshd &
exec "$@"
传入的command参数为:
args:
- '/usr/bin/gobgpd'
- '-f'
- '/etc/gobgpd/gobgpd.conf'
容器启动后,其中会有两个进程:
- sshd: 后台运行(启动会detach当前的父进程,并fork一个新的进程,其父进程为init进程)
- gobgpd: 前台运行(由于前面有exec,所以会替换当前entrypoint脚本的进程)
进入容器查看进程:
root@rs0-57c568fd64-6b8cs:/$ ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 14:17 ? 00:00:00 /usr/bin/gobgpd -f /etc/gobgpd/gobgpd.conf
root 7 1 0 14:17 ? 00:00:00 [sshd] <defunct>
root 12 1 0 14:17 ? 00:00:00 /usr/sbin/sshd
sshd 19 1 0 14:17 ? 00:00:00 [sshd] <defunct>
sshd 70 1 0 14:17 ? 00:00:00 [sshd] <defunct>
root 73 0 0 14:17 pts/0 00:00:00 bash
sshd 88 1 0 14:17 ? 00:00:00 [sshd] <defunct>
root 89 73 0 14:17 pts/0 00:00:00 ps -ef
可见gobgpd的进程号为1, 表明其成为了容器的init进程, sshd出现了多个进程,其中还有若干显示为defunct的僵尸进程,由于gobgpd为init进程,因此是其他所有进程的父进程。 sshd进程结束时,本应由其父进程进行回收处理,但其父进程(gobgpd)并不具备真正的init进程的功能,因此无法对僵尸进程进行回收。这样就会不断的产生新的僵尸进程,导致进程空间占满,直接影响pod所在的node。
最佳实践
最佳实践一定是将上述pod分为两个独立的容器, 一个运行gobgpd, 另一个运行sshd。每个容器中都只运行一个单一的前台的进程。
如果一定要在一个容器中运行多个进程,且这些进程之间逻辑上没有父子进程关系。那么需要避免这些进程成为init进程(因为后台进程会成为init进程的子进程)。方法有以下几种:
使用pause容器并共享进程空间
一个pod内有多个容器时,kubernetes会启动一个pause基础容器,用于共享各容器的网络空间,但各容器的进程空间仍是隔离的,可在yaml中开启共享进程空间:
shareProcessNamespace: true
开启后,在pod内的任何容器内,都可以看到init进程总是为/pause
root@rs0-78b495fddf-mt57t:/$ ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 14:35 ? 00:00:00 /pause
root 6 0 0 14:35 ? 00:00:00 /usr/bin/gobgpd -f /etc/gobgpd/gobgpd.conf
root 12 6 0 14:35 ? 00:00:00 [sshd] <defunct>
root 17 1 0 14:35 ? 00:00:00 /usr/sbin/sshd
root 92 0 0 14:35 pts/0 00:00:00 bash
root 106 92 0 14:35 pts/0 00:00:00 ps -ef
如上,pause成为了容器内的init进程(因为共享了进程空间,所有容器的init都是这个pause进程), 可以看到, sshd后台进程被init进程接管(其中还有一个defunct的sshd进程的原因是:sshd后台进程是从entrypoint脚本中启动的, 后台进程启动时,会先在init进程下fork一个sshd进程,然后终止entrypoint脚本进程下的sshd, 但由于启动脚本最后的exec命令将entrypoint进程替换为了gobgpd进程,成为了sshd的父进程,而gobgpd不是一个shell进程,没有回收子进程的能力,因此这个sshd进程成为了僵尸进程),虽然还有一个sshd僵尸进程,但却不会持续产生新的僵尸进程了。
这种方法的前提是pod内必须有多个容器,如果只有一个容器的话,kubernetes将不会创建pause容器,也就没有pause进程。
启动脚本中不用exec
通常,在entrypoint脚本中完成所有预备工作后,会使用exec将entrypoint进程替换为用户传入的命令行参数指定的服务进程,以便在服务退出后直接退出容器, 也方便在容器内部通过ps命令查看容器内实际运行的命令。
如果不采用第一种方法(使用pause容器),可以通过不使用exec命令来避免服务进程成为init进程,entrypoint脚本修改如下:
#!/bin/bash
/usr/sbin/sshd &
$@
如此一来, gobgpd进程便不会替代entrypiont脚本进程成为主进程, gobgpd和sshd都会成为entrypoint脚本进程的子进程, entrypoint是一个shell脚本,作为主进程,其具有回收终止的子进程的能力,因此不会有僵尸进程存留。
root@rs0-57c568fd64-bx2kh:/$ ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 15:05 ? 00:00:00 /bin/bash /usr/local/bin/docker-entrypoint.sh
root 8 1 0 15:05 ? 00:00:00 /usr/bin/gobgpd -f /etc/gobgpd/gobgpd.conf
root 13 1 0 15:05 ? 00:00:00 /usr/sbin/sshd
root 83 0 0 15:06 pts/0 00:00:00 bash
root 143 83 0 15:07 pts/0 00:00:00 ps -ef
总结
总之,容器的设计原本就是单一的前台进程,应尽量将不同的服务放在不同的容器中,以避免产生问题。
更多推荐
所有评论(0)