理解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名空間會形成一個層級體系。父節點可以看到子節點中的進程,並可以通過信號等方式對子節點中的進程產生影響。反過來,子節點不能看到父節點名空間中的任何內容,也不可能通過kill或ptrace影響父節點或其他名空間中的進程。

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

當創建一個Docker容器的時候,就會新建一個PID名空間。容器啟動進程在該名空間內PID為1。當PID1進程結束之后,Docker會銷毀對應的PID名空間,並向容器內所有其它的子進程發送SIGKILL。

下面我們來做一些試驗,下面我們會利用官方的Redis鏡像創建兩個容器,並觀察里面的進程。
如果你在Windows或Mac上利用"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 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一個容器一個進程的方式並非絕對化的要求,然而在一個容器中實現對於多個進程的管理必須考慮更多的細節,比如子進程管理,進程監控等等。所以對於常見的需求,比如日志收集,性能監控,調試程序,我們依然建議采用多個容器組裝的方式來實現。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM