了解 CMD 與 ENTRYPOINT 曾提到 container 即 process,那接下來就要了解 Docker 是如何啟動 process 的。
在 build Laravel image 有提到 CMD 在執行指令的模式有分 exec 模式與 shell 模式,先回顧這兩個模式在寫法上的差異如下:
# exec 模式
CMD ["php", "artisan", "serve"]
# shell 模式
CMD php artisan serve
因為是執行指令,所以 RUN 與 ENTRYPOINT 也有一樣的模式。
雖然最終都會跑 php artisan serve
指令,但跑的方法不大一樣。以下使用 ubuntu image 來做說明
簡單來說,就是直接執行指令,因此會是 PID 1 process。使用 PID 1 process 的好處是,使用 docker stop
指令發出 SIGTERM
後,會是由 PID 1 process 收到,做 graceful shutdown 相容比較簡單。
FROM ubuntu
CMD ["ps", "-o", "ppid,pid,user,args"]
因 CMD 設定的指令是要執行 ps -o ppid,pid,user,args
,所以 ps 出來的結果,ps 指令為 PID 1。
這是官方建議的方法。
Shell 模式是透過 /bin/sh -c
執行指令,因此會先有 /bin/sh -c
的 PID 1 process,然後在底下開子 process。在這個情況下,docker stop
指令發出的 SIGTERM
信號將會由 shell 收到。
FROM ubuntu
CMD ps -o ppid,pid,user,args
這次 ps 指令為 PID 7。
當然,使用 shell 也是有方便的地方,它可以直接在指令上使用環境變數,比方說:
FROM node_project
CMD npm run $NODE_ENV
這在 exec 是辦不到的。有些討論會提到 exec 如果要取環境變數,可以改成下面的寫法:
FROM ubuntu
CMD ["/bin/sh", "-c", "cd $HOME && ps -o ppid,pid,user,args"]
相信現在讀者應該知道了,其實改成 shell 的寫法就行了:
FROM ubuntu
CMD cd $HOME && ps -o ppid,pid,user,args
反過來說,exec mode 才能自行決定要用什麼 shell 來執行指令。
上面使用 ubuntu image 做範例,說明了 exec 模式和 shell 模式的差異。但不同 image 在 shell 模式下又會不大一樣,原因是 /bin/sh
在大多數 image 都是用 link 的形式連結到其他 shell:
$ docker run --rm -it ubuntu ls -l /bin/sh
lrwxrwxrwx 1 root root 4 Jul 18 2019 /bin/sh -> dash
$ docker run --rm -it debian ls -l /bin/sh
lrwxrwxrwx 1 root root 4 Sep 8 07:00 /bin/sh -> dash
$ docker run --rm -it centos ls -l /bin/sh
lrwxrwxrwx 1 root root 4 Nov 8 2019 /bin/sh -> bash
$ docker run --rm -it alpine ls -l /bin/sh
lrwxrwxrwx 1 root root 12 May 29 14:20 /bin/sh -> /bin/busybox
從上面範例可以看到,四個 image 就有三種不同的 shell:bash、dash、ash(BusyBox)
這些 shell 執行 /bin/sh -c
在 process 表現的行為又不大一樣,可以參考以下範例:
Debian 因沒有內建 ps,且與 Ubuntu 一樣是使用 dash,因此就不做為範例
docker run --rm -it ubuntu /bin/sh -c ps
docker run --rm -it ubuntu ps
docker run --rm -it centos /bin/sh -c ps
docker run --rm -it centos ps
docker run --rm -it alpine /bin/sh -c ps
docker run --rm -it alpine ps
這裡可以發現,只有 ubuntu 才會多卡一層 process,其他不會。就這個範例來看,其實只有 dash 才會有 PID 1 process 被 shell 佔走的問題。筆者建議若要使用 shell 模式的話,還是拿 base image 實驗一下比較保險。
docker exec
除了 docker run
以外,還有 docker exec
也是透過 Docker 啟動 process 的,我們來看看它啟動會發生什麼事:
# Terminal 1
docker run --rm -it --name test alpine
# 使用 top 指令查看目前的 process
top
# Terminal 2
docker exec -it test sh
# 安裝 Vim
apk add --no-cache vim
# Terminal 1 離開的時候,Terminal 2 的 process 也會跟著結束
exit
首先 Terminal 1 啟動 container 進入 shell,然後啟動 top,可以看到目前 PID 1 為 /bin/sh
--也就是啟動 container 那時的 shell。而 top 的 PPID 是 PID 1。
接著切換 Terminal 2 使用 docker exec
進入 container 並下安裝指令,這時 top 裡面看到多出兩個 process,一個是 docker exec
的 sh
指令 PID 7,它的 PPID 跟啟動 container 的 /bin/sh
一樣為 0,筆者推測這應該代表都是從 Docker 直接啟動的 process。另一個 process 則是安裝指令的 process。
最後 Terminal 1 執行 exit 把 PID 1 結束後,沒有父子關係的 PID 7 還是一樣被結束掉了。筆者推測 Docker 主要是因為要把 container 移除,所以會把裡面全部的 process 都淨空,只是使用什麼信號就不確定了,照範例的執行速度來看,很有可能是 SIGKILL
。
因筆者對發信號實際運作不熟悉,因此這裡僅能做推測。
因為 container 即 process,所以了解 process 的生命週期非常重要,包括如何啟動,以及什麼時候要回收資源等。