要如何在 container 裡運行多個 process

延續 Docker 啟動 process 的主題,因 container 即 process,因此合理的設計方法會是一個 container 只執行一個 process。而且 Dockerfile 也只能設定一個 ENTRYPOINT 和 CMD,實際上也很難跑多個 process。

但如果真的需要一個 container 同時跑多個 process 的話,該怎麼做呢?

開始前,先來定義簡單的目標:

  1. 使用 httpd image,Alpine 版本
  2. 在同一個 container 裡,同時啟動 httpd 與 top 兩個指令

使用 docker exec

山不轉,路轉。即然 Dockerfile 或 docker run 不行的話,那就先 docker rundocker exec 吧!

# 啟動 Apache
docker run --rm -it --name test httpd:2.4-alpine

# 啟動 top
docker exec -it test top

這個方法非常單純易懂,但代價也是龐大的。這個做法的問題點昨天有提到,主要是 docker exec 的 process 在 PID 1 process 結束的時候,沒有辦法正常地收到 SIGTERM 通知。

再來另一個也非常麻煩的問題是,無法使用「一個」 Docker 指令完成啟動兩個 process,即使是 Docker Compose 也一樣。代表這需要靠腳本或其他方法來組合 Docker 指令,這將會讓維運 container 的人吃盡苦頭。


docker exec 不適合的話,那接下來也只剩一種方向:在 CMD 或 ENTRYPOINT 執行某個特別的腳本或程式,由它來啟動所有需要的 process。

使用 shell script

寫一個 shell script,讓它去啟動每個 process ,然後在結尾跑一個無窮迴圈即可:

#!/bin/sh

# 啟動 Apache
httpd-foreground &

# 啟動 top
top -b

# 無窮迴圈
while [[ true ]]; do
sleep 1
done

這個方法有一樣的問題--process 無法收到 SIGTERM 信號,而且比 docker exec 更加嚴重。docker exec 的方法,至少還會有一個 process 收到信號,而 shell script 方法則是所有 process 都收不到信號。

可以用類似的概念實作 Chromium + PHP for Docker

#!/bin/sh

set -e

# 若有帶參數的話,則 chromedriver 會作為背景執行,然後再直接執行需要的 process。
if [ "$1" != "" ]; then
chromedriver > /var/log/chromedriver.log 2>&1 &
exec $@
fi

chromedriver

使用 Supervisor

Supervisor 是一個能控制多個 process 執行的管理器。以 Supervisor 改寫 Dockerfile 如下:

FROM httpd:2.4-alpine

RUN apk add --no-cache supervisor

COPY ./supervisord.conf /etc/supervisor.d/supervisord.ini
CMD ["supervisord", "-c", "/etc/supervisord.conf"]

這裡看到還有個設定是 supervisord.conf,內容如下:

[supervisord]
nodaemon=true

[program:apache2]
command=httpd-foreground
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0

[program:top]
command=top -b
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0

從執行結果的 log 可以看到 Supervisor 是跑在 PID 1,而 Apache 跑在 PID 8、top 跑在 PID 9 ,兩個 process 都有正常啟動。

2020-10-12 15:24:38,955 INFO supervisord started with pid 1
2020-10-12 15:24:39,961 INFO spawned: 'apache2' with pid 8
2020-10-12 15:24:39,965 INFO spawned: 'top' with pid 9

接著故意使用 Ctrl + C 來中止程式,可以發現 Supervisor 有把 SIGTERM 信號傳送給 Apache 與 top,並把中止程式的任務完成:

2020-10-12 15:24:52,055 WARN received SIGINT indicating exit request
2020-10-12 15:24:52,057 INFO waiting for apache2, top to die
2020-10-12 15:24:52,059 INFO stopped: top (terminated by SIGTERM)
2020-10-12 15:24:52,088 INFO stopped: apache2 (exit status 0)

今日自我回顧

從今天的範例可以發現,想要在同一個 container 跑多個 process 是很困難的。因此,最好的方法就是:

一個 container 只執行一個 process!

參考資料