理解Docker容器的進程管理

摘要: Docker在進程管理上有一些特殊之處,若是不注意這些細節中的魔鬼就會帶來一些隱患。另外Docker鼓勵「一個容器一個進程(one process per container)」的方式。這種方式很是適合以單進程爲主的微服務架構的應用。然而因爲一些傳統的應用是由若干緊耦合的多個進程構成的,這些進程難以html

Docker在進程管理上有一些特殊之處,若是不注意這些細節中的魔鬼就會帶來一些隱患。另外Docker鼓勵「一個容器一個進程(one process per container)」的方式。這種方式很是適合以單進程爲主的微服務架構的應用。然而因爲一些傳統的應用是由若干緊耦合的多個進程構成的,這些進程難以拆分到不一樣的容器中,因此在單個容器內運行多個進程便成了一種折衷方案;此外在一些場景中,用戶指望利用Docker容器來做爲輕量級的虛擬化方案,動態的安裝配置應用,這也須要在容器中運行多個進程。而在Docker容器中的正確運行多進程應用將給開發者帶來更多的挑戰。git

14553279344763

今天咱們會分析Docker中進程管理的一些細節,並介紹一些常見問題的解決方法和注意事項。github

容器的PID namespace(名空間)

在Docker中,進程管理的基礎就是Linux內核中的PID名空間技術。在不一樣PID名空間中,進程ID是獨立的;即在兩個不一樣名空間下的進程能夠有相同的PID。redis

Linux內核爲全部的PID名空間維護了一個樹狀結構:最頂層的是系統初始化時建立的root namespace(根名空間),再建立的新PID namespace就稱之爲child namespace(子名空間),而原先的PID名空間就是新建立的PID名空間的parent namespace(父名空間)。經過這種方式,系統中的PID名空間會造成一個層級體系。父節點能夠看到子節點中的進程,並能夠經過信號等方式對子節點中的進程產生影響。反過來,子節點不能看到父節點名空間中的任何內容,也不可能經過kill或ptrace影響父節點或其餘名空間中的進程。sql

在Docker中,每一個Container都是Docker Daemon的子進程,每一個Container進程缺省都具備不一樣的PID名空間。經過名空間技術,Docker實現容器間的進程隔離。另外Docker Daemon也會利用PID名空間的樹狀結構,實現了對容器中的進程交互、監控和回收。注:Docker還利用了其餘名空間(UTS,IPC,USER)等實現了各類系統資源的隔離,因爲這些內容和進程管理關聯很少,本文不會涉及。docker

當建立一個Docker容器的時候,就會新建一個PID名空間。容器啓動進程在該名空間內PID爲1。當PID1進程結束以後,Docker會銷燬對應的PID名空間,並向容器內全部其它的子進程發送SIGKILL。shell

下面咱們來作一些試驗,下面咱們會利用官方的Redis鏡像建立兩個容器,並觀察裏面的進程。
若是你在Windows或Mac上利用"docker-machine",請利用docker-machine ssh default進入Boot2docker虛擬機ubuntu

建立名爲"redis"的容器,並在容器內部和宿主機中查看容器中的進程信息api

docker@default:~$ docker run -d --name redis redis f6bc57cc1b464b05b07b567211cb693ee2a682546ed86c611b5d866f6acc531c docker@default:~$ docker exec redis ps -ef UID PID PPID C STIME TTY TIME CMD redis 1 0 0 01:49 ? 00:00:00 redis-server *:6379 root 11 0 0 01:49 ? 00:00:00 ps -ef docker@default:~$ docker top redis UID PID PPID C STIME TTY TIME CMD 999 9302 1264 0 01:49 ? 00:00:00 redis-server *:6379 

建立名爲"redis2"的容器,並在容器內部和宿主機中查看容器中的進程信息數組

docker@default:~$ docker run -d --name redis2 redis 356eca186321ab6ef4c4337aa0c7de2af1e01430587d6b0e1add2e028ed05f60 docker@default:~$ docker exec redis2 ps -ef UID PID PPID C STIME TTY TIME CMD redis 1 0 0 01:50 ? 00:00:00 redis-server *:6379 root 10 0 4 01:50 ? 00:00:00 ps -ef docker@default:~$ docker top redis2 UID PID PPID C STIME TTY TIME CMD 999 9342 1264 0 01:50 ? 00:00:00 redis-server *:6379 

咱們可使用docker exec命令進入容器PID名空間,並執行應用。經過ps -ef命令,能夠看到每一個Redis容器都包含一個PID爲1的進程,"redis-server",它是容器的啓動進程,具備特殊意義。

利用docker top命令,可讓咱們從宿主機操做系統中看到容器的進程信息。在兩個容器中的"redis-server"是兩個獨立的進程,可是他們擁有相同的父進程 Docker Daemon。因此Docker能夠父子進程的方式在Docker Daemon和Redis容器之間進行交互。

另外一個值得注意的方面是,docker exec命令能夠進入指定的容器內部執行命令。由它啓動的進程屬於容器的namespace和相應的cgroup。可是這些進程的父進程是Docker Daemon而非容器的PID1進程。

咱們下面會在Redis容器中,利用docker exec命令啓動一個"sleep"進程

docker@default:~$ docker exec -d redis sleep 2000
docker@default:~$ docker exec redis ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
redis        1  0  0 02:26 ? 00:00:00 redis-server *:6379 root  11  0  0 02:26 ? 00:00:00 sleep 2000 root  21  0  0 02:29 ? 00:00:00 ps -ef docker@default:~$ docker top redis UID PID PPID C STIME TTY TIME CMD 999  9955  1264  0 02:12 ? 00:00:00 redis-server *:6379 root  9984  1264  0 02:13 ? 00:00:00 sleep 2000 

咱們能夠清楚的看到exec命令建立的sleep進程屬Redis容器的名空間,可是它的父進程是Docker Daemon。

若是咱們在宿主機操做系統中手動殺掉容器的啓動進程(在上文示例中是redis-server),容器會自動結束,而容器名空間中全部進程也會退出。

docker@default:~$ PID=$(docker inspect --format="{{.State.Pid}}" redis) docker@default:~$ sudo kill $PID docker@default:~$ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 356eca186321 redis "/entrypoint.sh redis" 23 minutes ago Up 4 minutes 6379/tcp redis2 f6bc57cc1b46 redis "/entrypoint.sh redis" 23 minutes ago Exited (0) 4 seconds ago redis 

經過以上示例:

  • 每一個容器有獨立的PID名空間,
  • 容器的生命週期和其PID1進程一致
  • 利用docker exec能夠進入到容器的名空間中啓動進程

此外,自從Docker 1.5以後,docker run命令引入了--pid=host參數來支持使用宿主機PID名空間來啓動容器進程,這樣能夠方便的實現容器內應用和宿主機應用之間的交互:好比利用容器中的工具監控和調試宿主機進程。

如何指明容器PID1進程

在Docker容器中的初始化進程(PID1進程)在容器進程管理上具備特殊意義。它能夠被Dockerfile中的ENTRYPOINTCMD指令所指明;也能夠被docker run命令的啓動參數所覆蓋。瞭解這些細節能夠幫助咱們更好地瞭解PID1的進程的行爲。

關於ENTRYPOINT和CMD指令的不一樣,咱們能夠參見官方的Dockerfile說明和最佳實踐

值得注意的一點是:在ENTRYPOINT和CMD指令中,提供兩種不一樣的進程執行方式 shell 和 exec

在 shell 方式中,CMD/ENTRYPOINT指令以以下方式定義

CMD executable param1 param2 

這種方式中的PID1進程是以/bin/sh -c 」executable param1 param2」方式啓動的

而在 exec 方式中,CMD/ENTRYPOINT指令以以下方式定義

CMD ["executable","param1","param2"] 

注意這裏的可執行命令和參數是利用JSON字符串數組的格式定義的,這樣PID1進程會以 executable param1 param2 方式啓動的。另外,在docker run命令中指明的命令行參數也是以 exec 方式啓動的。

爲了解釋兩種不一樣運行方式的區別,咱們利用不一樣的Dockerfile分別建立兩個Redis鏡像

"Dockerfile_shell"文件內容以下,會利用shell方式啓動redis服務

FROM ubuntu:14.04 RUN apt-get update && apt-get -y install redis-server && rm -rf /var/lib/apt/lists/* EXPOSE 6379 CMD "/usr/bin/redis-server" 

"Dockerfile_exec"文件內容以下,會利用exec方式啓動redis服務

FROM ubuntu:14.04 RUN apt-get update && apt-get -y install redis-server && rm -rf /var/lib/apt/lists/* EXPOSE 6379 CMD ["/usr/bin/redis-server"] 

而後基於它們構建兩個鏡像"myredis:shell"和"myredis:exec"

docker build -t myredis:shell -f Dockerfile_shell . docker build -t myredis:exec -f Dockerfile_exec . 

運行"myredis:shell"鏡像,咱們能夠發現它的啓動進程(PID1)是/bin/sh -c "/usr/bin/redis-server",而且它建立了一個子進程/usr/bin/redis-server *:6379

docker@default:~$ docker run -d --name myredis myredis:shell 49f7fc37f4b7cf1ed7f5296537a93b2ad23b1b6686a05e5c7e40e9a2b2d3665e docker@default:~$ docker exec myredis ps -ef UID PID PPID C STIME TTY TIME CMD root 1 0 0 08:12 ? 00:00:00 /bin/sh -c "/usr/bin/redis-server" root 5 1 0 08:12 ? 00:00:00 /usr/bin/redis-server *:6379 root 8 0 0 08:12 ? 00:00:00 ps -ef 

下面運行"myredis:exec"鏡像,咱們能夠發現它的啓動進程是/usr/bin/redis-server *:6379,並無其餘子進程存在。

docker@default:~$ docker run -d --name myredis2 myredis:exec d1df0e4f4e3bbe36fca94f08df9ad3306fa1dee86415c853ddc5593fb9fa5673 docker@default:~$ docker exec myredis2 ps -ef UID PID PPID C STIME TTY TIME CMD root 1 0 0 08:13 ? 00:00:00 /usr/bin/redis-server *:6379 root 8 0 0 08:13 ? 00:00:00 ps -ef 

由此咱們能夠清楚的看到,以exec和shell方式執行命令可能會致使容器的PID1進程不一樣。然而這又有什麼問題呢?

緣由在於:PID1進程對於操做系統而言具備特殊意義。操做系統的PID1進程是init進程,以守護進程方式運行,是全部其餘進程的祖先,具備完整的進程生命週期管理能力。在Docker容器中,PID1進程是啓動進程,它也會負責容器內部進程管理的工做。而這也將致使進程管理在Docker容器內部和完整操做系統上的不一樣。

進程信號處理

信號是Unix/Linux中進程間異步通訊機制。Docker提供了兩個命令docker stopdocker kill來向容器中的PID1進程發送信號。

當執行docker stop命令時,docker會首先向容器的PID1進程發送一個SIGTERM信號,用於容器內程序的退出。若是容器在收到SIGTERM後沒有結束, 那麼Docker Daemon會在等待一段時間(默認是10s)後,再向容器發送SIGKILL信號,將容器殺死變爲退出狀態。這種方式給Docker應用提供了一個優雅的退出(graceful stop)機制,容許應用在收到stop命令時清理和釋放使用中的資源。而docker kill能夠向容器內PID1進程發送任何信號,缺省是發送SIGKILL信號來強制退出應用。

注:從Docker 1.9開始,Docker支持中止容器時向其發送自定義信號,開發者能夠在Dockerfile使用STOPSIGNAL指令,或docker run命令中使用--stop-signal參數中指明。缺省是SIGTERM

咱們來看看不一樣的PID1進程,對進程信號處理的不一樣之處。首先,咱們使用docker stop命令中止由 exec 模式啓動的「myredis2」容器,並檢查其日誌

docker@default:~$ docker stop myredis2 myredis2 docker@default:~$ docker logs myredis2 [1] 11 Feb 08:13:01.631 # 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 _._ _.-``__ ''-._ _.-`` `. `_. ''-._ Redis 2.8.4 (00000000/0) 64 bit .-`` .-```. ```\/ _.,_ ''-._ ( ' , .-` | `, ) Running in stand alone mode |`-._`-...-` __...-.``-._|'` _.-'| Port: 6379 | `-._ `._ / _.-' | PID: 1 `-._ `-._ `-./ _.-' _.-' |`-._`-._ `-.__.-' _.-'_.-'| | `-._`-._ _.-'_.-' | http://redis.io `-._ `-._`-.__.-'_.-' _.-' |`-._`-._ `-.__.-' _.-'_.-'| | `-._`-._ _.-'_.-' | `-._ `-._`-.__.-'_.-' _.-' `-._ `-.__.-' _.-' `-._ _.-' `-.__.-' [1] 11 Feb 08:13:01.632 # Server started, Redis version 2.8.4 [1] 11 Feb 08:13:01.633 # 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] 11 Feb 08:13:01.633 * The server is now ready to accept connections on port 6379 [1 | signal handler] (1455179074) Received SIGTERM, scheduling shutdown... [1] 11 Feb 08:24:34.259 # User requested shutdown... [1] 11 Feb 08:24:34.259 * Saving the final RDB snapshot before exiting. [1] 11 Feb 08:24:34.262 * DB saved on disk [1] 11 Feb 08:24:34.262 # Redis is now ready to exit, bye bye... docker@default:~$ 

咱們發現對「myredis2」容器的stop命令幾乎馬上生效;並且在容器日誌中,咱們看到了「Received SIGTERM, scheduling shutdown...」的內容,說明「redis-server」進程接收到了SIGTERM消息,並優雅地退出。

咱們再對利用 shell 模式啓動的「myredis」容器發出中止操做,並檢查其日誌

docker@default:~$ docker stop myredis myredis docker@default:~$ docker logs myredis [5] 11 Feb 08:12:40.108 # 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 _._ _.-``__ ''-._ _.-`` `. `_. ''-._ Redis 2.8.4 (00000000/0) 64 bit .-`` .-```. ```\/ _.,_ ''-._ ( ' , .-` | `, ) Running in stand alone mode |`-._`-...-` __...-.``-._|'` _.-'| Port: 6379 | `-._ `._ / _.-' | PID: 5 `-._ `-._ `-./ _.-' _.-' |`-._`-._ `-.__.-' _.-'_.-'| | `-._`-._ _.-'_.-' | http://redis.io `-._ `-._`-.__.-'_.-' _.-' |`-._`-._ `-.__.-' _.-'_.-'| | `-._`-._ _.-'_.-' | `-._ `-._`-.__.-'_.-' _.-' `-._ `-.__.-' _.-' `-._ _.-' `-.__.-' [5] 11 Feb 08:12:40.109 # Server started, Redis version 2.8.4 [5] 11 Feb 08:12:40.109 # 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. [5] 11 Feb 08:12:40.109 * The server is now ready to accept connections on port 6379 docker@default:~$ 

咱們發現對」myredis」容器的stop命令暫停了一下子才結束,並且在日誌中咱們沒有看到任何收到SIGTERM信號的內容。緣由其PID1進程sh沒有對SIGTERM信號的處理邏輯,因此它忽略了所接收到的SIGTERM信號。當Docker等待stop命令執行10秒鐘超時以後,Docker Daemon發送SIGKILL強制殺死sh進程,並銷燬了它的PID名空間,其子進程redis-server也在收到SIGKILL信號後被強制終止。若是此時應用還有正在執行的事務或未持久化的數據,強制進程退出可能致使數據丟失或狀態不一致。

經過這個示例咱們能夠清楚的理解PID1進程在信號管理的重要做用。因此,

  • 容器的PID1進程須要可以正確的處理SIGTERM信號來支持優雅退出。
  • 若是容器中包含多個進程,須要PID1進程可以正確的傳播SIGTERM信號來結束全部的子進程以後再退出。
  • 確保PID1進程是指望的進程。缺省sh/bash進程沒有提供SIGTERM的處理,須要經過shell腳原本設置正確的PID1進程,或捕獲SIGTERM信號。

另外須要注意的是:因爲PID1進程的特殊性,Linux內核爲他作了特殊處理。若是它沒有提供某個信號的處理邏輯,那麼與其在同一個PID名空間下的進程發送給它的該信號都會被屏蔽。這個功能的主要做用是防止init進程被誤殺。咱們能夠驗證在容器內部發出的SIGKILL信號沒法殺死PID1進程

docker@default:~$ docker start myredis myredis docker@default:~$ docker exec myredis kill -9 1 docker@default:~$ docker top myredis UID PID PPID C STIME TTY TIME CMD root 3586 1290 0 08:45 ? 00:00:00 /bin/sh -c "/usr/bin/redis-server" root 3591 3586 0 08:45 ? 00:00:00 /usr/bin/redis-server *:6379 

孤兒進程與殭屍進程管理

熟悉Unix/Linux進程管理的同窗對多進程應用並不陌生。

當一個子進程終止後,它首先會變成一個「失效(defunct)」的進程,也稱爲「殭屍(zombie)」進程,等待父進程或系統收回(reap)。在Linux內核中維護了關於「殭屍」進程的一組信息(PID,終止狀態,資源使用信息),從而容許父進程可以獲取有關子進程的信息。若是不能正確回收「殭屍」進程,那麼他們的進程描述符仍然保存在系統中,系統資源會緩慢泄露。

大多數設計良好的多進程應用能夠正確的收回殭屍子進程,好比NGINX master進程能夠收回已終止的worker子進程。若是須要本身實現,則可利用以下方法:
1. 利用操做系統的waitpid()函數等待子進程結束並請除它的僵死進程,
2. 因爲當子進程成爲「defunct」進程時,父進程會收到一個SIGCHLD信號,因此咱們能夠在父進程中指定信號處理的函數來忽略SIGCHLD信號,或者自定義收回處理邏輯。

下面這些文章詳細介紹了對殭屍進程的處理方法

若是父進程已經結束了,那些依然在運行中的子進程會成爲「孤兒(orphaned)」進程。在Linux中Init進程(PID1)做爲全部進程的父進程,會維護進程樹的狀態,一旦有某個子進程成爲了「孤兒」進程後,init就會負責接管這個子進程。當一個子進程成爲「殭屍」進程以後,若是其父進程已經結束,init會收割這些「殭屍」,釋放PID資源。

然而因爲Docker容器的PID1進程是容器啓動進程,它們會如何處理那些「孤兒」進程和「殭屍」進程?

下面咱們作幾個試驗來驗證不一樣的PID1進程對殭屍進程不一樣的處理能力

首先在myredis2容器中啓動一個bash進程,並建立子進程「sleep 1000」

docker@default:~$ docker restart myredis2 myredis2 docker@default:~$ docker exec -ti myredis2 bash root@d1df0e4f4e3b:/# sleep 1000 

在另外一個終端窗口,查看當前進程,咱們能夠發現一個sleep進程是bash進程的子進程。

docker@default:~$ docker exec myredis2 ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1  0  0 12:21 ? 00:00:00 /usr/bin/redis-server *:6379 root  8  0  0 12:21 ? 00:00:00 bash root  21  8  0 12:21 ? 00:00:00 sleep 1000 root  22  0  3 12:21 ? 00:00:00 ps -ef 

咱們殺死bash進程以後查看進程列表,這時候bash進程已經被殺死。這時候sleep進程(PID爲21),雖然已經結束,並且被PID1進程(redis-server)接管,可是其沒有被父進程回收,成爲殭屍狀態。

docker@default:~$ docker exec myredis2 kill -9 8 docker@default:~$ docker exec myredis2 ps -ef UID PID PPID C STIME TTY TIME CMD root 1 0 0 12:09 ? 00:00:00 /usr/bin/redis-server *:6379 root 21 1 0 12:10 ? 00:00:00 [sleep] <defunct> root 32 0 0 12:10 ? 00:00:00 ps -ef docker@default:~$ 

這是由於PID1進程「redis-server」沒有考慮過做爲init對殭屍子進程的回收的場景。

咱們來作另外一個試驗,在用/bin/sh做爲PID1進程的myredis容器中,再啓動一個bash進程,並建立子進程「sleep 1000」

docker@default:~$ docker start myredis myredis docker@default:~$ docker exec -ti myredis bash root@49f7fc37f4b7:/# sleep 1000 

查看容器中進程狀況,

docker@default:~$ docker exec myredis ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1  0  0 01:29 ? 00:00:00 /bin/sh -c "/usr/bin/redis-server" root  5  1  0 01:29 ? 00:00:00 /usr/bin/redis-server *:6379 root  8  0  0 01:30 ? 00:00:00 bash root  22  8  0 01:30 ? 00:00:00 sleep 1000 root  36  0  0 01:30 ? 00:00:00 ps -ef 

咱們殺死bash進程以後查看進程列表,發現「bash」和「sleep 1000」進程都已經被殺死和回收

docker@default:~$ docker exec myredis kill -9 8 docker@default:~$ docker exec myredis ps -ef UID PID PPID C STIME TTY TIME CMD root 1 0 0 01:29 ? 00:00:00 /bin/sh -c "/usr/bin/redis-server" root 5 1 0 01:29 ? 00:00:00 /usr/bin/redis-server *:6379 root 45 0 0 01:31 ? 00:00:00 ps -ef docker@default:~$ 

這是由於sh/bash等應用能夠自動清理殭屍進程。

關於殭屍進程在Docker中init處理所需注意細節的詳細描述,能夠在以下文章獲得

簡單而言,若是在容器中運行多個進程,PID1進程須要有能力接管「孤兒」進程並回收「殭屍」進程。咱們能夠
1. 利用自定義的init進程來進行進程管理,好比 S6 , phusion myinitdumb-inittini 等
2. Bash/sh等缺省提供了進程管理能力,若是須要能夠做爲PID1進程來實現正確的進程回收。

進程監控

在Docker中,若是docker run命令中指明瞭restart policy,Docker Daemon會監控PID1進程,並根據策略自動重啓已結束的容器。

restart 策略 結果
no 不自動重啓,缺省值
on-failure[:max-retries] 當PID1進程退出值非0時,自動重啓容器;能夠指定最大重試次數
always 永遠自動重啓容器;當Docker Daemon啓動時,會自動啓動容器
unless-stopped 永遠自動重啓容器;當Docker Daemon啓動時,若是以前容器不爲stoped狀態就自動啓動容器

注意:爲防止頻繁重啓故障應用致使系統過載,Docker會在每次重啓過程當中會延遲一段時間。Docker重啓進程的延遲時間從100ms開始並每次加倍,如100ms,200ms,400ms等等。

利用Docker內置的restart策略能夠大大簡化應用進程監控的負擔。可是Docker Daemon只是監控PID1進程,若是容器在內包含多個進程,仍然須要開發人員來處理進程監控。

你們必定很是熟悉SupervisorMonit等進程監控工具,他們能夠方便的在容器內部中實現進程監控。Docker提供了相應的文檔來介紹,互聯網上也有不少資料,咱們今天就再也不贅述了。

另外利用Supervisor等工具做爲PID1進程是在容器中支持多進程管理的主要實現方式;和簡單利用shell腳本fork子進程相比,採用Supervisor等工具備不少好處:

  • 一些傳統的服務不能以PID1進程的方式執行,利用Supervisor能夠方便的適配
  • Supervisor這些監控工具大多提供了對SIGTERM的信號傳播支持,能夠支持子進程優雅的退出

然而值得注意的是:Supervisor這些監控工具大多沒有徹底提供Init支持的進程管理能力,若是須要支持子進程回收的場景須要配合正確的PID1進程來完成

總結

進程管理在Docker容器中和在完整的操做系統有一些不一樣之處。在每一個容器的PID1進程,須要可以正確的處理SIGTERM信號來支持容器應用的優雅退出,同時要能正確的處理孤兒進程和殭屍進程。

在Dockerfile中要注意shell模式和exec模式的不一樣。一般而言咱們鼓勵使用exec模式,這樣能夠避免由無心中選擇錯誤PID1進程所引入的問題。

在Docker中「一個容器一個進程的方式」並不是絕對化的要求,然而在一個容器中實現對於多個進程的管理必須考慮更多的細節,好比子進程管理,進程監控等等。因此對於常見的需求,好比日誌收集,性能監控,調試程序,咱們依然建議採用多個容器組裝的方式來實現。

[在此處輸入文章標題]

 

 

摘要: Docker在進程管理上有一些特殊之處,若是不注意這些細節中的魔鬼就會帶來一些隱患。另外Docker鼓勵一個容器一個進程(one process per container)」的方式。這種方式很是適合以單進程爲主的微服務架構的應用。然而因爲一些傳統的應用是由若干緊耦合的多個進程構成的,這些進程難以

Docker在進程管理上有一些特殊之處,若是不注意這些細節中的魔鬼就會帶來一些隱患。另外Docker鼓勵一個容器一個進程(one process per container)」的方式。這種方式很是適合以單進程爲主的微服務架構的應用。然而因爲一些傳統的應用是由若干緊耦合的多個進程構成的,這些進程難以拆分到不一樣的容器中,因此在單個容器內運行多個進程便成了一種折衷方案;此外在一些場景中,用戶指望利用Docker容器來做爲輕量級的虛擬化方案,動態的安裝配置應用,這也須要在容器中運行多個進程。而在Docker容器中的正確運行多進程應用將給開發者帶來更多的挑戰。

說明: 14553279344763

今天咱們會分析Docker中進程管理的一些細節,並介紹一些常見問題的解決方法和注意事項。

容器的PID namespace(名空間)

Docker中,進程管理的基礎就是Linux內核中的PID名空間技術。在不一樣PID名空間中,進程ID是獨立的;即在兩個不一樣名空間下的進程能夠有相同的PID

Linux內核爲全部的PID名空間維護了一個樹狀結構:最頂層的是系統初始化時建立的root namespace(根名空間),再建立的新PID namespace就稱之爲child namespace(子名空間),而原先的PID名空間就是新建立的PID名空間的parent namespace(父名空間)。經過這種方式,系統中的PID名空間會造成一個層級體系。父節點能夠看到子節點中的進程,並能夠經過信號等方式對子節點中的進程產生影響。反過來,子節點不能看到父節點名空間中的任何內容,也不可能經過killptrace影響父節點或其餘名空間中的進程。

Docker中,每一個Container都是Docker Daemon的子進程,每一個Container進程缺省都具備不一樣的PID名空間。經過名空間技術,Docker實現容器間的進程隔離。另外Docker Daemon也會利用PID名空間的樹狀結構,實現了對容器中的進程交互、監控和回收。注:Docker還利用了其餘名空間(UTSIPCUSER)等實現了各類系統資源的隔離,因爲這些內容和進程管理關聯很少,本文不會涉及。

當建立一個Docker容器的時候,就會新建一個PID名空間。容器啓動進程在該名空間內PID1。當PID1進程結束以後,Docker會銷燬對應的PID名空間,並向容器內全部其它的子進程發送SIGKILL

下面咱們來作一些試驗,下面咱們會利用官方的Redis鏡像建立兩個容器,並觀察裏面的進程。
若是你在WindowsMac上利用"docker-machine",請利用docker-machine ssh default進入Boot2docker虛擬機

建立名爲"redis"的容器,並在容器內部和宿主機中查看容器中的進程信息

docker@default:~$ docker run -d --name redis redis

f6bc57cc1b464b05b07b567211cb693ee2a682546ed86c611b5d866f6acc531c

docker@default:~$ docker exec redis ps -ef

UID        PID  PPID  C STIME TTY          TIME CMD

redis        1     0  0 01:49 ?        00:00:00 redis-server *:6379

root        11     0  001:49 ?        00:00:00 ps -ef

docker@default:~$ docker top redis

UID                 PID                 PPID                C                   STIME               TTY                 TIME                CMD

999                 9302                1264                0                   01:49               ?                   00:00:00            redis-server *:6379

建立名爲"redis2"的容器,並在容器內部和宿主機中查看容器中的進程信息

docker@default:~$ docker run -d --name redis2 redis

356eca186321ab6ef4c4337aa0c7de2af1e01430587d6b0e1add2e028ed05f60

docker@default:~$ docker exec redis2 ps -ef

UID        PID  PPID  C STIME TTY          TIME CMD

redis        1     0  0 01:50 ?        00:00:00 redis-server *:6379

root        10     0  401:50 ?        00:00:00 ps -ef

docker@default:~$ docker top redis2

UID                 PID                 PPID                C                   STIME               TTY                 TIME                CMD

999                 9342                1264                0                   01:50               ?                   00:00:00            redis-server *:6379

咱們可使用docker exec命令進入容器PID名空間,並執行應用。經過ps -ef命令,能夠看到每一個Redis容器都包含一個PID1的進程,"redis-server",它是容器的啓動進程,具備特殊意義。

利用docker top命令,可讓咱們從宿主機操做系統中看到容器的進程信息。在兩個容器中的"redis-server"是兩個獨立的進程,可是他們擁有相同的父進程 Docker Daemon。因此Docker能夠父子進程的方式在Docker DaemonRedis容器之間進行交互。

另外一個值得注意的方面是,docker exec命令能夠進入指定的容器內部執行命令。由它啓動的進程屬於容器的namespace和相應的cgroup。可是這些進程的父進程是Docker Daemon而非容器的PID1進程。

咱們下面會在Redis容器中,利用docker exec命令啓動一個"sleep"進程

docker@default:~$ docker exec -d redis sleep 2000

docker@default:~$ docker exec redis ps -ef

UID        PID  PPID  C STIME TTY          TIME CMD

redis        1     0  0 02:26 ?        00:00:00 redis-server *:6379

root        11     0  0 02:26 ?        00:00:00 sleep 2000

root        21     0  0 02:29 ?        00:00:00 ps -ef

docker@default:~$ docker top redis

UID                 PID                 PPID                C                   STIME               TTY                 TIME                CMD

999                 9955                1264                0                   02:12               ?                   00:00:00            redis-server *:6379

root                9984                1264                0                   02:13               ?                   00:00:00            sleep 2000

咱們能夠清楚的看到exec命令建立的sleep進程屬Redis容器的名空間,可是它的父進程是Docker Daemon

若是咱們在宿主機操做系統中手動殺掉容器的啓動進程(在上文示例中是redis-server),容器會自動結束,而容器名空間中全部進程也會退出。

docker@default:~$ PID=$(docker inspect --format="{{.State.Pid}}" redis)

docker@default:~$ sudo kill $PID

docker@default:~$ docker ps -a

CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                     PORTS               NAMES

356eca186321        redis               "/entrypoint.sh redis"   23 minutes ago      Up 4 minutes               6379/tcp            redis2

f6bc57cc1b46        redis               "/entrypoint.sh redis"   23 minutes ago      Exited (0) 4 seconds ago                       redis

經過以上示例:

·         每一個容器有獨立的PID名空間,

·         容器的生命週期和其PID1進程一致

·         利用docker exec能夠進入到容器的名空間中啓動進程

此外,自從Docker 1.5以後,docker run命令引入了--pid=host參數來支持使用宿主機PID名空間來啓動容器進程,這樣能夠方便的實現容器內應用和宿主機應用之間的交互:好比利用容器中的工具監控和調試宿主機進程。

如何指明容器PID1進程

Docker容器中的初始化進程(PID1進程)在容器進程管理上具備特殊意義。它能夠被Dockerfile中的ENTRYPOINTCMD指令所指明;也能夠被docker run命令的啓動參數所覆蓋。瞭解這些細節能夠幫助咱們更好地瞭解PID1的進程的行爲。

關於ENTRYPOINTCMD指令的不一樣,咱們能夠參見官方的Dockerfile說明和最佳實踐

·         https://docs.docker.com/engine/reference/builder/#entrypoint

·         https://docs.docker.com/engine/reference/builder/#cmd

值得注意的一點是:在ENTRYPOINTCMD指令中,提供兩種不一樣的進程執行方式 shell  exec

 shell 方式中,CMD/ENTRYPOINT指令以以下方式定義

CMD executable param1 param2

這種方式中的PID1進程是以/bin/sh -c 」executable param1 param2」方式啓動的

而在 exec 方式中,CMD/ENTRYPOINT指令以以下方式定義

CMD ["executable","param1","param2"]

注意這裏的可執行命令和參數是利用JSON字符串數組的格式定義的,這樣PID1進程會以 executable param1 param2 方式啓動的。另外,在docker run命令中指明的命令行參數也是以 exec 方式啓動的。

爲了解釋兩種不一樣運行方式的區別,咱們利用不一樣的Dockerfile分別建立兩個Redis鏡像

"Dockerfile_shell"文件內容以下,會利用shell方式啓動redis服務

FROM ubuntu:14.04

RUN apt-get update && apt-get -y install redis-server && rm -rf /var/lib/apt/lists/*

EXPOSE 6379

CMD "/usr/bin/redis-server"

"Dockerfile_exec"文件內容以下,會利用exec方式啓動redis服務

FROM ubuntu:14.04

RUN apt-get update && apt-get -y install redis-server && rm -rf /var/lib/apt/lists/*

EXPOSE 6379

CMD ["/usr/bin/redis-server"]

而後基於它們構建兩個鏡像"myredis:shell""myredis:exec"

docker build -t myredis:shell -f Dockerfile_shell .

docker build -t myredis:exec -f Dockerfile_exec .

運行"myredis:shell"鏡像,咱們能夠發現它的啓動進程(PID1)/bin/sh -c "/usr/bin/redis-server",而且它建立了一個子進程/usr/bin/redis-server *:6379

docker@default:~$ docker run -d --name myredis myredis:shell

49f7fc37f4b7cf1ed7f5296537a93b2ad23b1b6686a05e5c7e40e9a2b2d3665e

docker@default:~$ docker exec myredis ps -ef

UID        PID  PPID  C STIME TTY          TIME CMD

root         1     0  008:12 ?        00:00:00/bin/sh -c "/usr/bin/redis-server"

root         5     1  008:12 ?        00:00:00/usr/bin/redis-server *:6379

root         8     0  008:12 ?        00:00:00 ps -ef

下面運行"myredis:exec"鏡像,咱們能夠發現它的啓動進程是/usr/bin/redis-server *:6379,並無其餘子進程存在。

docker@default:~$ docker run -d --name myredis2 myredis:exec

d1df0e4f4e3bbe36fca94f08df9ad3306fa1dee86415c853ddc5593fb9fa5673

docker@default:~$ docker exec myredis2 ps -ef

UID        PID  PPID  C STIME TTY          TIME CMD

root         1     0  0 08:13 ?        00:00:00 /usr/bin/redis-server *:6379

root         8     0  008:13 ?        00:00:00 ps -ef

由此咱們能夠清楚的看到,以execshell方式執行命令可能會致使容器的PID1進程不一樣。然而這又有什麼問題呢?

緣由在於:PID1進程對於操做系統而言具備特殊意義。操做系統的PID1進程是init進程,以守護進程方式運行,是全部其餘進程的祖先,具備完整的進程生命週期管理能力。在Docker容器中,PID1進程是啓動進程,它也會負責容器內部進程管理的工做。而這也將致使進程管理在Docker容器內部和完整操做系統上的不一樣。

進程信號處理

信號是Unix/Linux中進程間異步通訊機制。Docker提供了兩個命令docker stopdocker kill來向容器中的PID1進程發送信號。

當執行docker stop命令時,docker會首先向容器的PID1進程發送一個SIGTERM信號,用於容器內程序的退出。若是容器在收到SIGTERM後沒有結束,那麼Docker Daemon會在等待一段時間(默認是10s)後,再向容器發送SIGKILL信號,將容器殺死變爲退出狀態。這種方式給Docker應用提供了一個優雅的退出(graceful stop)機制,容許應用在收到stop命令時清理和釋放使用中的資源。而docker kill能夠向容器內PID1進程發送任何信號,缺省是發送SIGKILL信號來強制退出應用。

注:從Docker 1.9開始,Docker支持中止容器時向其發送自定義信號,開發者能夠在Dockerfile使用STOPSIGNAL指令,或docker run命令中使用--stop-signal參數中指明。缺省是SIGTERM

咱們來看看不一樣的PID1進程,對進程信號處理的不一樣之處。首先,咱們使用docker stop命令中止由 exec 模式啓動的「myredis2」容器,並檢查其日誌

docker@default:~$ docker stop myredis2

myredis2

docker@default:~$ docker logs myredis2

[1] 11 Feb 08:13:01.631 # Warning: no config file specified, using the default config. Inorderto specify a config fileuse /usr/bin/redis-server /path/to/redis.conf

                _._                                                  

           _.-``__ ''-._                                            

      _.-``    `.  `_.  ''-._           Redis 2.8.4 (00000000/0) 64bit

  .-`` .-```.  ```\/    _.,_ ''-._                                  

 (    '      ,       .-`  | `,    )     Running in stand alone mode

 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 6379

 |    `-._   `._    /     _.-'    |     PID: 1

  `-._    `-._  `-./  _.-'    _.-'                                  

 |`-._`-._    `-.__.-'    _.-'_.-'|                                  

 |    `-._`-._        _.-'_.-'    |           http://redis.io       

  `-._    `-._`-.__.-'_.-'    _.-'                                  

 |`-._`-._    `-.__.-'    _.-'_.-'|                                 

 |    `-._`-._        _.-'_.-'    |                                 

  `-._    `-._`-.__.-'_.-'    _.-'                                  

      `-._    `-.__.-'    _.-'                                      

          `-._        _.-'                                          

              `-.__.-'                                              

 

[1] 11 Feb 08:13:01.632 # Server started, Redis version2.8.4

[1] 11 Feb 08:13:01.633 # WARNING overcommit_memory issetto0! Background save may fail underlowmemory condition. To fix this issue add'vm.overcommit_memory = 1'to /etc/sysctl.conf andthen reboot or run the command 'sysctl vm.overcommit_memory=1'for this to take effect.

[1] 11 Feb 08:13:01.633 * The serverisnow ready toaccept connections on port 6379

[1 | signal handler] (1455179074) Received SIGTERM, scheduling shutdown...

[1] 11 Feb 08:24:34.259 # User requested shutdown...

[1] 11 Feb 08:24:34.259 * Saving the final RDB snapshotbefore exiting.

[1] 11 Feb 08:24:34.262 * DB saved on disk

[1] 11 Feb 08:24:34.262 # Redis isnow ready toexit, bye bye...

docker@default:~$

咱們發現對「myredis2」容器的stop命令幾乎馬上生效;並且在容器日誌中,咱們看到了「Received SIGTERM, scheduling shutdown...」的內容,說明「redis-server」進程接收到了SIGTERM消息,並優雅地退出。

咱們再對利用 shell 模式啓動的「myredis」容器發出中止操做,並檢查其日誌

docker@default:~$ docker stop myredis

myredis

docker@default:~$ docker logs myredis

[5] 11 Feb 08:12:40.108 # Warning: no config file specified, using the default config. Inorderto specify a config fileuse /usr/bin/redis-server /path/to/redis.conf

                _._                                                  

           _.-``__ ''-._                                            

      _.-``    `.  `_.  ''-._           Redis 2.8.4 (00000000/0) 64bit

  .-`` .-```.  ```\/    _.,_ ''-._                                  

 (    '      ,       .-`  | `,    )     Running in stand alone mode

 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 6379

 |    `-._   `._    /     _.-'    |     PID: 5

  `-._    `-._  `-./  _.-'    _.-'                                  

 |`-._`-._    `-.__.-'    _.-'_.-'|                                 

 |    `-._`-._        _.-'_.-'    |           http://redis.io       

  `-._    `-._`-.__.-'_.-'    _.-'                                  

 |`-._`-._    `-.__.-'    _.-'_.-'|                                 

 |    `-._`-._        _.-'_.-'    |                                 

  `-._    `-._`-.__.-'_.-'    _.-'                                  

      `-._    `-.__.-'    _.-'                                      

          `-._        _.-'                                           

              `-.__.-'                                              

 

[5] 11 Feb 08:12:40.109 # Server started, Redis version2.8.4

[5] 11 Feb 08:12:40.109 # WARNING overcommit_memory issetto0! Background save may fail underlowmemory condition. To fix this issue add'vm.overcommit_memory = 1'to /etc/sysctl.conf andthen reboot or run the command 'sysctl vm.overcommit_memory=1'for this to take effect.

[5] 11 Feb 08:12:40.109 * The serverisnow ready toaccept connections on port 6379

docker@default:~$

咱們發現對」myredis」容器的stop命令暫停了一下子才結束,並且在日誌中咱們沒有看到任何收到SIGTERM信號的內容。緣由其PID1進程sh沒有對SIGTERM信號的處理邏輯,因此它忽略了所接收到的SIGTERM信號。當Docker等待stop命令執行10秒鐘超時以後,Docker Daemon發送SIGKILL強制殺死sh進程,並銷燬了它的PID名空間,其子進程redis-server也在收到SIGKILL信號後被強制終止。若是此時應用還有正在執行的事務或未持久化的數據,強制進程退出可能致使數據丟失或狀態不一致。

經過這個示例咱們能夠清楚的理解PID1進程在信號管理的重要做用。因此,

·         容器的PID1進程須要可以正確的處理SIGTERM信號來支持優雅退出。

·         若是容器中包含多個進程,須要PID1進程可以正確的傳播SIGTERM信號來結束全部的子進程以後再退出。

·         確保PID1進程是指望的進程。缺省sh/bash進程沒有提供SIGTERM的處理,須要經過shell腳原本設置正確的PID1進程,或捕獲SIGTERM信號。

另外須要注意的是:因爲PID1進程的特殊性,Linux內核爲他作了特殊處理。若是它沒有提供某個信號的處理邏輯,那麼與其在同一個PID名空間下的進程發送給它的該信號都會被屏蔽。這個功能的主要做用是防止init進程被誤殺。咱們能夠驗證在容器內部發出的SIGKILL信號沒法殺死PID1進程

docker@default:~$ docker start myredis

myredis

docker@default:~$ docker exec myredis kill -91

docker@default:~$ docker top myredis

UID                 PID                 PPID                C                   STIME               TTY                 TIME                CMD

root                3586                1290                0                   08:45               ?                   00:00:00            /bin/sh -c "/usr/bin/redis-server"

root                3591                3586                0                   08:45               ?                   00:00:00            /usr/bin/redis-server *:6379

孤兒進程與殭屍進程管理

熟悉Unix/Linux進程管理的同窗對多進程應用並不陌生。

當一個子進程終止後,它首先會變成一個失效(defunct)」的進程,也稱爲殭屍(zombie進程,等待父進程或系統收回(reap)。在Linux內核中維護了關於殭屍進程的一組信息(PID,終止狀態,資源使用信息),從而容許父進程可以獲取有關子進程的信息。若是不能正確回收殭屍進程,那麼他們的進程描述符仍然保存在系統中,系統資源會緩慢泄露。

大多數設計良好的多進程應用能夠正確的收回殭屍子進程,好比NGINX master進程能夠收回已終止的worker子進程。若是須要本身實現,則可利用以下方法:
1.
利用操做系統的waitpid()函數等待子進程結束並請除它的僵死進程,
2.
因爲當子進程成爲「defunct」進程時,父進程會收到一個SIGCHLD信號,因此咱們能夠在父進程中指定信號處理的函數來忽略SIGCHLD信號,或者自定義收回處理邏輯。

下面這些文章詳細介紹了對殭屍進程的處理方法

·         http://www.microhowto.info/howto/reap_zombie_processes_using_a_sigchld_handler.html

·         http://lbolla.info/blog/2014/01/23/die-zombie-die

若是父進程已經結束了,那些依然在運行中的子進程會成爲孤兒(orphaned進程。在LinuxInit進程(PID1)做爲全部進程的父進程,會維護進程樹的狀態,一旦有某個子進程成爲了孤兒進程後,init就會負責接管這個子進程。當一個子進程成爲殭屍進程以後,若是其父進程已經結束,init會收割這些殭屍,釋放PID資源。

然而因爲Docker容器的PID1進程是容器啓動進程,它們會如何處理那些孤兒進程和殭屍進程?

下面咱們作幾個試驗來驗證不一樣的PID1進程對殭屍進程不一樣的處理能力

首先在myredis2容器中啓動一個bash進程,並建立子進程「sleep 1000」

docker@default:~$ docker restart myredis2

myredis2

docker@default:~$ docker exec -ti myredis2 bash

root@d1df0e4f4e3b:/# sleep 1000

 

在另外一個終端窗口,查看當前進程,咱們能夠發現一個sleep進程是bash進程的子進程。

docker@default:~$ docker exec myredis2 ps -ef

UID        PID  PPID  C STIME TTY          TIME CMD

root         1     0  0 12:21 ?        00:00:00 /usr/bin/redis-server *:6379

root         8     0  0 12:21 ?        00:00:00 bash

root        21     8  0 12:21 ?        00:00:00 sleep 1000

root        22     0  3 12:21 ?        00:00:00 ps -ef

咱們殺死bash進程以後查看進程列表,這時候bash進程已經被殺死。這時候sleep進程(PID21),雖然已經結束,並且被PID1進程(redis-server)接管,可是其沒有被父進程回收,成爲殭屍狀態。

docker@default:~$ docker exec myredis2 kill -98

docker@default:~$ docker exec myredis2 ps -ef

UID        PID  PPID  C STIME TTY          TIME CMD

root         1     0  012:09 ?        00:00:00 /usr/bin/redis-server *:6379

root        21     1  012:10 ?        00:00:00 [sleep] <defunct>

root        32     0  012:10 ?        00:00:00 ps -ef

docker@default:~$

 

這是由於PID1進程「redis-server」沒有考慮過做爲init對殭屍子進程的回收的場景。

咱們來作另外一個試驗,在用/bin/sh做爲PID1進程的myredis容器中,再啓動一個bash進程,並建立子進程「sleep 1000」

docker@default:~$ docker start myredis

myredis

docker@default:~$ docker exec -ti myredis bash

root@49f7fc37f4b7:/# sleep 1000

查看容器中進程狀況,

docker@default:~$ docker exec myredis ps -ef

UID        PID  PPID  C STIME TTY          TIME CMD

root         1     0  0 01:29 ?        00:00:00 /bin/sh -c "/usr/bin/redis-server"

root         5     1  0 01:29 ?        00:00:00 /usr/bin/redis-server *:6379

root         8     0  0 01:30 ?        00:00:00 bash

root        22     8  0 01:30 ?        00:00:00 sleep 1000

root        36     0  0 01:30 ?        00:00:00 ps -ef

咱們殺死bash進程以後查看進程列表,發現「bash」「sleep 1000」進程都已經被殺死和回收

docker@default:~$ docker exec myredis kill -98

docker@default:~$ docker exec myredis ps -ef

UID        PID  PPID  C STIME TTY          TIME CMD

root         1     0  001:29 ?        00:00:00 /bin/sh -c "/usr/bin/redis-server"

root         5     1  001:29 ?        00:00:00 /usr/bin/redis-server *:6379

root        45     0  001:31 ?        00:00:00 ps -ef

docker@default:~$

這是由於sh/bash等應用能夠自動清理殭屍進程。

關於殭屍進程在Dockerinit處理所需注意細節的詳細描述,能夠在以下文章獲得

·         http://www.oschina.net/translate/docker-and-the-pid-1-zombie-reaping-problem

簡單而言,若是在容器中運行多個進程,PID1進程須要有能力接管孤兒進程並回收殭屍進程。咱們能夠
1.
利用自定義的init進程來進行進程管理,好比 S6  phusion myinitdumb-inittini 
2. Bash/sh
等缺省提供了進程管理能力,若是須要能夠做爲PID1進程來實現正確的進程回收。

進程監控

Docker中,若是docker run命令中指明瞭restart policyDocker Daemon會監控PID1進程,並根據策略自動重啓已結束的容器。

restart 策略

結果

no

不自動重啓,缺省值

on-failure[:max-retries]

PID1進程退出值非0時,自動重啓容器;能夠指定最大重試次數

always

永遠自動重啓容器;當Docker Daemon啓動時,會自動啓動容器

unless-stopped

永遠自動重啓容器;當Docker Daemon啓動時,若是以前容器不爲stoped狀態就自動啓動容器

注意:爲防止頻繁重啓故障應用致使系統過載,Docker會在每次重啓過程當中會延遲一段時間。Docker重啓進程的延遲時間從100ms開始並每次加倍,如100ms200ms400ms等等。

利用Docker內置的restart策略能夠大大簡化應用進程監控的負擔。可是Docker Daemon只是監控PID1進程,若是容器在內包含多個進程,仍然須要開發人員來處理進程監控。

你們必定很是熟悉SupervisorMonit等進程監控工具,他們能夠方便的在容器內部中實現進程監控。Docker提供了相應的文檔來介紹,互聯網上也有不少資料,咱們今天就再也不贅述了。

另外利用Supervisor等工具做爲PID1進程是在容器中支持多進程管理的主要實現方式;和簡單利用shell腳本fork子進程相比,採用Supervisor等工具備不少好處:

·         一些傳統的服務不能以PID1進程的方式執行,利用Supervisor能夠方便的適配

·         Supervisor這些監控工具大多提供了對SIGTERM的信號傳播支持,能夠支持子進程優雅的退出

然而值得注意的是:Supervisor這些監控工具大多沒有徹底提供Init支持的進程管理能力,若是須要支持子進程回收的場景須要配合正確的PID1進程來完成

總結

進程管理在Docker容器中和在完整的操做系統有一些不一樣之處。在每一個容器的PID1進程,須要可以正確的處理SIGTERM信號來支持容器應用的優雅退出,同時要能正確的處理孤兒進程和殭屍進程。

Dockerfile中要注意shell模式和exec模式的不一樣。一般而言咱們鼓勵使用exec模式,這樣能夠避免由無心中選擇錯誤PID1進程所引入的問題。

Docker一個容器一個進程的方式並不是絕對化的要求,然而在一個容器中實現對於多個進程的管理必須考慮更多的細節,好比子進程管理,進程監控等等。因此對於常見的需求,好比日誌收集,性能監控,調試程序,咱們依然建議採用多個容器組裝的方式來實現。

相關文章
相關標籤/搜索