docker 容器内运行多进程踩坑 您所在的位置:网站首页 docker中的进程会在系统中体现吗为什么 docker 容器内运行多进程踩坑

docker 容器内运行多进程踩坑

2024-07-01 14:30| 来源: 网络整理| 查看: 265

为什么在一个Docker中运行多个程序进程?

Docker在进程管理上有一些特殊之处,如果不注意这些细节中就会带来一些隐患。另外Docker鼓励“一个容器一个进程(one process per container)”的方式。这种方式非常适合以单进程为主的微服务架构的应用。然而由于一些传统的应用是由若干紧耦合的多个进程构成的,这些进程难以拆分到不同的容器中,所以在单个容器内运行多个进程便成了一种折衷方案;此外在一些场景中,用户期望利用Docker容器来作为轻量级的虚拟化方案,动态的安装配置应用,这也需要在容器中运行多个进程。而在Docker容器中的正确运行多进程应用将给开发者带来更多的挑战。

如何在一个Docker中运行多个程序进程?

基本思路是在Dockerfile 的CMD 或者 ENTRYPOINT 运行一个”东西”,然后再让这个”东西”运行多个其他进程简单说来是用Bash Shell脚本或者三方进程守护 (Monit,Skaware S6,Supervisor),其他没讲到的三方进程守护工具同理。

docker内运行多进程问题

一 孤儿进程与僵尸进程管理

当一个子进程终止后,它首先会变成一个“失效(defunct)”的进程,也称为“僵尸(zombie)”进程,等待父进程或系统收回(reap)。在Linux内核中维护了关于“僵尸”进程的一组信息(PID,终止状态,资源使用信息),从而允许父进程能够获取有关子进程的信息。如果不能正确回收“僵尸”进程,那么他们的进程描述符仍然保存在系统中,系统资源会缓慢泄露。

大多数设计良好的多进程应用可以正确的收回僵尸子进程,比如NGINX master进程可以收回已终止的worker子进程。如果需要自己实现,则可利用如下方法:

利用操作系统的waitpid()函数等待子进程结束并请除它的僵死进程, 由于当子进程成为“defunct”进程时,父进程会收到一个SIGCHLD信号,所以我们可以在父进程中指定信号处理的函数来忽略SIGCHLD信号,或者自定义收回处理逻辑。

如果父进程已经结束了,那些依然在运行中的子进程会成为“孤儿(orphaned)”进程。在Linux中Init进程(PID1)作为所有进程的父进程,会维护进程树的状态,一旦有某个子进程成为了“孤儿”进程后,init就会负责接管这个子进程。当一个子进程成为“僵尸”进程之后,如果其父进程已经结束,init会收割这些“僵尸”,释放PID资源。

然而由于Docker容器的PID1进程是容器启动进程,它们会如何处理那些“孤儿”进程和“僵尸”进程?

二 进程的高可用,进程异常结束后如何恢复。

单进程的容器进程挂掉后整个容器也会停止。但多进程的如果遇见这样的情况 :第一个进程负责正常的对外工作,第二个进程是一个被第一个进程调用的常驻程序(或者为第一个进程提供些库的更新),不能停止,停止后会影响第一个进程的正常工作。

1、用/bin/sh 或者/bin/bash作为PID1进程,这是因为sh/bash等应用可以自动清理僵尸进程。Bash/sh等缺省提供了进程管理能力,如果需要可以作为PID1进程来实现正确的进程回收。

例子:

1234567891011121314151619:33 $ sudo docker exec -it ditto_cron bash[root@02f08adf3cd6 s]# ps -efUID PID PPID C STIME TTY TIME CMDroot 1 0 0 19:33 ? 00:00:00 /bin/bash /home/s/script/start.shroot 16 1 0 19:33 ? 00:00:00 php /home/s/dconf_reload/bin/../src/dconf_main.php ditto 1 3root 43 1 10 19:33 ? 00:00:01 /home/s/scanService/ditto worker --config /home/s/scanService/conf/config.yamlroot 50 0 0 19:33 ? 00:00:00 bashroot 73 50 0 19:33 ? 00:00:00 ps -ef[root@02f08adf3cd6 s]# kill 41[root@02f08adf3cd6 s]# ps -ef UID PID PPID C STIME TTY TIME CMDroot 1 0 0 19:33 ? 00:00:00 /bin/bash /home/s/script/start.shroot 16 1 0 19:33 ? 00:00:00 php /home/s/dconf_reload/bin/../src/dconf_main.php ditto 1 3root 43 1 6 19:33 ? 00:00:01 /home/s/scanService/ditto worker --config /home/s/scanService/conf/config.yamlroot 50 0 0 19:33 ? 00:00:00 bashroot 83 50 0 19:33 ? 00:00:00 ps -ef

但是这种需要CMD或者ENTRYPOINT采用exec形式:

1CMD ["可执行文件", "参数1", "参数2"...]

另一种格式是shell格式

1CMD

exec 格式会让/bin/bash 成为1号进程,而shell格式会让后面的命令行成为1号进程。

这种方法可以解决掉僵尸进程的问题,但是进程的高可用需要增加脚本实现。

2、使用Supervisor

Supervisor是一个C/S架构进程管理工具,通过它可以监控和控制其他的进程。可以处理僵尸进程的问题及SIGTERM信号。在Linux系统启动之后,第一个启动的用户态进程是/sbin/init ,它的PID是1,其余用户态的进程都是init进程的子进程。Supervisor在Docker容器里面充当的就类似init进程的角色,其它的应用进程都是Supervisor进程的子进程。通过这种方法就可以实现在一个容器中启动运行多个应用,。

123456789[root@1e7babdbf192 s]# ps -efUID PID PPID C STIME TTY TIME CMDroot 1 0 0 20:21 ? 00:00:00 /usr/bin/python /usr/bin/supervisord -c /etc/supervisord.confroot 7 1 1 20:21 ? 00:00:01 /home/s/scanService/ditto worker --config /home/s/scanService/conf/config.yamlroot 8 1 0 20:21 ? 00:00:00 /bin/bash /home/s/script/check_dconf.shroot 30 1 0 20:21 ? 00:00:00 php /home/s/dconf_reload/bin/../src/dconf_main.php ditto 1 3root 214 0 0 20:22 ? 00:00:00 bashroot 247 8 0 20:22 ? 00:00:00 sleep 10root 248 214 0 20:22 ? 00:00:00 ps -ef

但要注意一点:supervisor只能管理到前台进程,对于一般的服务,没有终端的进程supervisor无法管理。除非是把这种进程放入一个脚本中,让这个脚本前台运行并且检测该进程的状态。

1234567891011121314151617181920212223242526272829303132333435363738394041#!/bin/bashEXEC="/home/s/dconf_reload/src/dconf_main.php"PROG=`basename $EXEC`LogPath="/home/s/dconf_reload/log"Log="${LogPath}/check_dconf.log.`date +%F`"check(){ #判断指定进程是否存在 result=`ps -ef | grep -w $PROG | grep -v grep | wc -l` if [ $result -le 0 ]; then #不存在, 启动 /bin/bash /home/s/dconf_reload/bin/dctl check ditto >/dev/null 2>&1 sleep 2 echo "`date +'%Y-%m-%d %H:%M:%S'` restart dconf" >> $Log #ps axuwwww | grep scan_unit | grep avast | grep -v grep | awk '{print $2}' | xargs kill -9 else #存在,判断状态 #取进程状态,用来判断是否僵死 val=`ps aux | grep $PROG | grep -v grep | awk '{print $8}'` if [ "$val" == "Zs" ];then # 取进程ID,用来kill掉进程 pid = `ps -aux | grep $PROG | grep -v grep | awk '{print $2}'` kill -9 $pid echo "`date +'%Y-%m-%d %H:%M:%S'` process ..." >> $Log exit 1 else sleep 10 echo "`date +'%Y-%m-%d %H:%M:%S'` sleep 10" >> $Log fi fi }while truedo check done

supervisor.conf 配置如下

12345678910111213141516171819202122232425262728293031323334353637383940414243[unix_http_server]file=/var/run/supervisor/supervisor.sock ; (the path to the socket file)chmod=0700 ; sockef file mode (default 0700)[inet_http_server]port:127.0.0.1:9001[supervisord]pidfile=/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid)logfile=/var/log/supervisor/supervisord.log ; (main log file;default $CWD/supervisord.log)nodaemon=truestdout_logfile=/dev/stdoutstdout_logfile_maxbytes=0loglevel=debug[supervisorctl]serverurl=http://127.0.0.1:9001[program:check_dconf]user=rootcommand=/home/s/script/check_dconf.shautostart=trueautorestart=truestartsecs=1stopsignal=INT[program:check_ditto]user=rootcommand=/home/s/scanService/ditto worker --config /home/s/scanService/conf/config.yamlautostart=trueautorestart=truestartsecs=1stdout_logfile=/home/s/scanService/log/stdout.logstdout_logfile_maxbytes=10MBstdout_logfile_backups=10stdout_capture_maxbytes=1MBstderr_logfile=/home/s/scanService/log/stderr.logstderr_logfile_maxbytes=10MBstderr_logfile_backups=10stderr_capture_maxbytes=1MBstopsignal=INT

supervisor 在多进程的情况如果都是前台进程会很好用,因为它解决了僵尸进程和高可用两个问题。但如果有后台程序的话处理就要配合脚本实现。

3、使用monit

  Monit是一个轻量级(500KB)跨平台的用来监控Unix/linux系统的开源工具。部署简单,并且不依赖任何第三方程序、插件或者库。

  Monit可以监控服务器进程、文件、文件系统、网络状态(HTTP/SMTP等协议)、远程主机、服务器资源变化等等。 并且可以设定资源变化后需要做的动作,比如服务失败后自动重启,邮件告警等等。  相对于supervisor而言,monit的功能更为强大,不仅可以管理前台、后台进程,而且还能监控文件系统,网络的资源。这里不详细讲解monit的安装使用。只贴下monit的配置

 /etc/monit.conf 主配置文件

  /etc/monit.d/ 各项服务单独配置文件路径,在主配置文件中将其include进来。

monit.conf monit卓配置

1234567set daemon 30 # check services at 30 seconds intervalsset log syslogset httpd port 2812 and use address localhost # only accept connection from localhost allow localhost # allow localhost to connect to the server and allow admin:monit # require user 'admin' with password 'monit'include /etc/monit.d/*

dconf.conf 配置,需提供dconf的启动脚本和停止脚本

123pheck process dconf with MATCHING dconf_main.php start "/bin/bash -c /home/s/script/start_dconf.sh" stop "/bin/bash -c /home/s/script/stop_dconf.sh"

ditto.conf 配置,,需提供ditto的启动脚本和停止脚本

123456check process ditto with MATCHING scanService start "/bin/bash -c /home/s/script/start_ditto.sh" stop "/bin/bash -c /home/s/script/stop_ditto.sh" if failed port 9234 3 cycles then restart

monit 提供了前台运行方式,解决了多进程不管是前台运行还是后台运行,还有进程高可用的的问题。然而不幸的是,monit没有提供管理僵尸进程(回收子进程)问题的方法。

123456789101112131415161710:36 $ sudo docker exec -it ditto_monit bash[root@152b5b9b6423 s]# ps -efUID PID PPID C STIME TTY TIME CMDroot 1 0 0 10:43 ? 00:00:00 /usr/bin/monit -Iroot 14 1 10 10:43 ? 00:00:01 /home/s/scanService/ditto worker --config /home/s/scanService/conf/config.yamlroot 35 1 0 10:43 ? 00:00:00 php /home/s/dconf_reload/bin/../src/dconf_main.php ditto 1 3root 85 0 1 10:43 ? 00:00:00 bashroot 97 85 0 10:43 ? 00:00:00 ps -ef[root@152b5b9b6423 s]# kill 14[root@152b5b9b6423 s]# ps -ef UID PID PPID C STIME TTY TIME CMDroot 1 0 0 10:43 ? 00:00:00 /usr/bin/monit -Iroot 14 1 5 10:43 ? 00:00:01 [ditto] root 35 1 0 10:43 ? 00:00:00 php /home/s/dconf_reload/bin/../src/dconf_main.php ditto 1 3root 85 0 0 10:43 ? 00:00:00 bashroot 108 35 68 10:44 ? 00:00:00 php /home/s/dconf_reload/bin/../src/dconf_main.php ditto 1 3root 109 85 0 10:44 ? 00:00:00 ps -ef

所以需要加入一个脚本,这个脚本运行为pid为1的进程,负责回收处理。my_init

1234567root 1 0 0 21:37 ? 00:00:00 /usr/bin/python2.6 /home/s/script/my_init -- /usr/bin/monit -Iroot 8 1 0 21:37 ? 00:00:00 /usr/bin/monit -Iroot 16 1 4 21:37 ? 00:00:01 /home/s/scanService/ditto worker --config /home/s/scanService/conf/config.yamlroot 32 1 0 21:37 ? 00:00:00 php /home/s/dconf_reload/bin/../src/dconf_main.php ditto 1 3root 97 0 0 21:38 ? 00:00:00 bashroot 118 32 85 21:38 ? 00:00:00 php /home/s/dconf_reload/bin/../src/dconf_main.php ditto 1 3root 119 97 0 21:38 ? 00:00:00 ps -ef

不采用my_init 这种第三方的程序,自己实现子进程的回收处理及信号处理也可以。

docker 高版本在提供了解决方案 在run时加入–init参数可以在容器内部启动一个init 进程作为1号进程,但是低版本的docker无此功能。

123[jinri@23v update]$ docker run --help|grep init --health-start-period duration Start period for the container to initialize before starting health-retries countdown (ms|s|m|h) (default 0s) --init Run an init inside the container that forwards signals and reaps processes

另外如果使用的是centos7的镜像还可以使用系统自带的systemd作为容器中的1号进程。它提供进程的自启和信号处理等工作。

最终采用方案:

使用 /bin/bash + crond 的方式

/bin/bash 实现子进程的回收,crond实现对 dconf的高可用监控重启

12345678UID PID PPID C STIME TTY TIME CMDroot 1 0 0 15:13 pts/0 00:00:00 /bin/bash /home/s/script/start.shroot 21 1 0 15:13 pts/0 00:00:01 php /home/s/dconf_reload/bin/../src/dconf_main.php ditto 1 3root 46 1 0 15:13 ? 00:00:00 crondroot 48 1 0 15:13 pts/0 00:00:02 /home/s/scanService/ditto worker --config /home/s/scanService/conf/config.yamlroot 1401 0 0 15:31 pts/1 00:00:00 bashroot 4438 0 2 16:11 pts/2 00:00:00 bashroot 4450 4438 0 16:11 pts/2 00:00:00 ps -ef

参考链接

理解Docker容器的进程管理

Monit 简介

docker 和pid 1 僵尸进程问题

一个容器多个进程



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有