一般應用(比如mariadb)都會有一個退出命令,用戶使用類似systemctl stop ****.service方法,停止其服務時,systemd會調用其配置文件注冊的退出命令,該命令執行清理資源、退出集群、輸出必要日志等操作后才殺死自己的進程;在系統shutdown的時候也會有類似的流程,最大程度的保證應用正常退出,下面我們稱之為“進程優雅退出”。
將應用Docker化后,一個突出的問題是,如何讓進程優雅的退出,而不是強行殺死進程。Docker stop和Docker kill分別實現了優雅退出和強行退出兩個操作:
Docker stop:向容器內1號進程,發送SIGTERM信號,在10S之后(可通過參數指定)再發送SIGKILL信號。
Docker kill:直接發送SIGKILL信號。
顯然Docker已經考慮到應用優雅退出的問題,但在實際使用中,會遇到下面2個困難:
1. 只有1號進程才收到SIGTERM信號,但Docker中有很多1號進程為monitor或者為初始化腳本的進程,並不是工作進程,且1號進程不能處理SIGTERM信號,以mariadb為例,容器內的進程關系如下:
1 /bin/sh /usr/bin/mysqld_safe --wsrep-cluster-address=gcomm:// 2 /usr/sbin/mysqld --basedir=/usr --datadir=/var/lib/mysql/ ...
其中2號進程為1號進程的子進程,雖然2號進程可以處理SIGTERM信號但其收不到該信號,而1號進程雖然能收到SIGTERM但並不能處理此信號。
2. 即使1號進程能處理SIGTERM信號,但若其有子進程為外部命令(非build in命令),且子進程為前台阻塞狀態,那么1號進程在直到子進程退出前仍然不能收到SIGTERM信號。如下bash是不會處理SIGTERM信號的:
#!/bin/bash trap 'exit 0' SIGTERM sleep 10000
關於這一點需要了解進程處理信號的限制:只有當進程阻塞在內建命令時才可以響應SIG信號,否則會一直等待子進程退出后再處理,如上面的bash,要等到10000秒之后才能處理SIGTERM。關於內建命令和外部命令,描述如下:
內部命令實際上是shell程序的一部分,shell不需要創建子進程,比如:exit,history,cd,echo,wait,trap等,linux系統加載運行時shell就被加載並駐留在系統內存中,一般不會fork子進程。 外部命令是linux系統中的實用程序部分,需要時才將其調用內存。一般會fork出子進程。 用type命令可以分辨內部命令與外部命令。
綜上所述,對於多進程Docker,我建議在容器中使用自定義bash腳本作為容器入口,腳本中使用后台方式執行具體應用的命令,然后使用內建wait阻塞,並通過trap指令監聽SIGTERM,執行應用退出操作,下面以容器化mariadb為例,描述其腳本的大概實現:
#!/bin/bash
trap 'mysqladmin -uroot -p123456 shutdown' SIGTERM mysqld_safe --wsrep-cluster-address=gcomm://10.158.113.207,10.158.113.80,10.158.113.79 & wait $!
以上述腳本為入口的maraidb容器內進程關系如下:
mysql 1 0.0 0.0 11628 1352 ? Ss+ 07:59 0:00 /bin/bash /usr/bin/test.sh mysql 9 0.0 0.0 11764 1636 ? S+ 07:59 0:00 /bin/sh /usr/bin/mysqld_safe --wsrep-cluster-address=gcomm://10.158.113.207,10.158.113.80,10 mysql 188 1.0 3.7 1087368 300168 ? Sl+ 07:59 0:16 \_ /usr/sbin/mysqld --basedir=/usr --datadir=/var/lib/mysql --plugin-dir=/usr/lib64/mysql/p
當執行docker stop ***的時候,該容器會自動調用mysqladmin shutdown優雅退出。
以上為個人原創,歡迎轉發,並保留出處。
個人能力有限,錯誤之處請留言指出。