跳至主要内容

2.1 容器的生命週期

這個章節,我們將會先從使用容器,啟動容器、執行容器到最後刪除容器,在跟著實作的同時,也可以思考一下 Docker 的運作模式,最後會找到一個清晰的脈絡,漸漸地就可以對 Docker 有更高的熟悉度。

啟動容器

我們建立一個 nginx 的容器作為開頭:

$ docker container run --publish 80:80 nginx
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
2022/10/01 07:38:25 [notice] 1#1: using the "epoll" event method
2022/10/01 07:38:25 [notice] 1#1: nginx/1.23.1
2022/10/01 07:38:25 [notice] 1#1: built by gcc 10.2.1 20210110 (Debian 10.2.1-6)
2022/10/01 07:38:25 [notice] 1#1: OS: Linux 5.10.124-linuxkit
2022/10/01 07:38:25 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576
2022/10/01 07:38:25 [notice] 1#1: start worker processes
2022/10/01 07:38:25 [notice] 1#1: start worker process 31
2022/10/01 07:38:25 [notice] 1#1: start worker process 32
2022/10/01 07:38:25 [notice] 1#1: start worker process 33

接著打開你的瀏覽器輸入:http://localhost,就能看到以下的畫面,恭喜您運行了人生第一個 Docker 容器。

相較以往如果要在 macOS 上運行 nginx,需要使用 brew install nginx 的方式,還會將執行檔留存在電腦上,使用 Docker 啟動可以說是輕鬆無負擔。

Nginx 歡迎畫面

退出非背景執行容器

接著回到終端機的畫面,會發現其似乎停滯了,這些文字都是運行 nginx 的輸出,至於要如何把運行中的容器退出呢?在非背景執行的情況下,可以採用 Ctrl + C 的方式來退出容器。

2022/10/01 07:43:52 [notice] 33#33: exiting
2022/10/01 07:43:52 [notice] 33#33: exit
2022/10/01 07:43:52 [notice] 32#32: exiting
2022/10/01 07:43:52 [notice] 32#32: exit
2022/10/01 07:43:52 [notice] 31#31: exiting
2022/10/01 07:43:52 [notice] 31#31: exit
2022/10/01 07:43:52 [notice] 1#1: signal 17 (SIGCHLD) received from 32
2022/10/01 07:43:52 [notice] 1#1: worker process 32 exited with code 0
2022/10/01 07:43:52 [notice] 1#1: worker process 33 exited with code 0
2022/10/01 07:43:52 [notice] 1#1: signal 29 (SIGIO) received
2022/10/01 07:43:52 [notice] 1#1: signal 17 (SIGCHLD) received from 31
2022/10/01 07:43:52 [notice] 1#1: worker process 31 exited with code 0
2022/10/01 07:43:52 [notice] 1#1: exit <- 輸入 Ctrl + C

$

列出運行中的容器

輸入 docker container list 指令可以列出運行中的容器:

$ docker container list
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

空無一物,是因為剛剛的 nginx 容器已經被退出了,並不在運行容器的名單中。

列出包含退出狀態的容器

加入 --all 的參數能夠列出包含退出狀態的容器,也可以透過下方指令看到剛剛的 nginx 容器:

$ docker container list --all
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
273a9357234e nginx "/do.." 9 mi... Exited char..

啟動退出狀態的容器

對於退出狀態的容器,我們並不需要使用 docker container run 的方式來啟動,而是可以直接對容器執行 docker container start 來喚醒。

$ docker container start 273a93
273a93

這裡後面帶入的 273a93 則為容器獨一無二的 ID,在您電腦上所執行的 nginx 容器和我的 ID 是不會相同的。

而您本機的 nginx 容器 ID 則在 docker container list --all 時可以看到,只需要輸入前幾碼讓 Docker 能夠比對並找到要啟動的容器即可。

這時候回到瀏覽器輸入 http://localhost 又會看到 nginx 重新被啟動了!

退出背景執行的容器

透過 docker container start 的方式,會發現容器自己進入了背景執行,這時候 Ctrl + C 的方式沒有辦法退出容器,我們可以透過 docker container stop 來退出容器。

$ docker container stop 273a93
273a93

刪除退出狀態的容器

刪除容器其實有兩種做法,這邊先介紹要如何刪除一個已經進入退出狀態的容器,在上一小節,我們退出了在背景執行的 nginx 容器,接下來我們要刪除它,完整一個容器的生命週期。

所以我們輸入 docker container rm 這段指令搭配容器的 ID,這邊的 rm 意指 remove 的意思。

$ docker container rm 273a93
273a93

接著為了證實容器被刪除這件事,我們再次列出包含退出狀態的所有容器吧!

$ docker container list --all
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

背景執行容器

在上一個小節我們重新運作停止的 nginx 容器後,發現其進入背景執行的狀態,那要如何在一開始啟動容器時就指定進入背景狀態呢?

只要在原先的指令上加入 --detach 的參數,便能夠使容器在一開始就進入背景執行狀態。

$ docker container run --publish 80:80 --detach nginx
5cef016820f622def62a46d59f4a9c2e812b616168558492d8bb6864db2f661c

強制刪除正在運行中的容器

前面介紹過的刪除容器只能應用在退出狀態的容器,若是我們直接對運作中的容器執行刪除指令,會出現以下的錯誤訊息:

$ docker container rm 5cef01
Error response from daemon: You cannot remove a running container 5cef016820f622def62a46d59f4a9c2e812b616168558492d8bb6864db2f661c. Stop the container before attempting removal or force remove

內容提示我們,請先退出再刪除,或是使用 force remove 的方式,只要加入 --force 的參數,就能夠強制刪除運行中的容器。

$ docker container rm --force 5cef01
5cef01

接著為了證實容器被刪除這件事,我們再次列出所有包含退出的容器吧!

$ docker container list --all
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

替容器命名

在之前的範例中,我們都是使用 Docker 所提供的 ID 在對容器進行操作,但這在實務上顯然不太實際,畢竟每一次的 ID 都是不一樣的,很難精準的操控容器。

所以我們也可以透過 --name 的參數替容器命名,如下方示範,我們把容器命名為 nginx,方便我們之後的操作:

$ docker container run --name nginx --publish 80:80 --detach nginx # 不換行
ce6d6951713d3fec44849bd643c4aea6dabc3548a650fa015e4c451821ed4677

列出容器來看看吧!

$ docker container list
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ce6d6951... nginx "/do.." 2 min.. Up.... 0.0.0.0:80->80/tcp nginx

接著我們可以把前面所學到的指令都套用在這個容器的名字上,就不需要再使用容器的 ID ,而是他的名字,下方為操作的範例,諸如此類:

$ docker container stop nginx
nginx

$ docker container rm --force nginx
nginx

$ docker container start nginx
nginx

觀看容器內的 Logs

在學會背景執行容器後,會遇到沒辦法觀看 Logs ( 事件紀錄 ) 的情況發生,這時候很簡單,接續上一個小節,我們對 nginx 這個容器執行 docker container logs 的指令。

$ docker container logs nginx
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
2022/08/30 16:23:35 [notice] 1#1: using the "epoll" event method
2022/08/30 16:23:35 [notice] 1#1: nginx/1.23.1
2022/08/30 16:23:35 [notice] 1#1: built by gcc 10.2.1 20210110 (Debian 10.2.1-6)
2022/08/30 16:23:35 [notice] 1#1: OS: Linux 5.10.104-linuxkit
2022/08/30 16:23:35 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576
2022/08/30 16:23:35 [notice] 1#1: start worker processes
2022/08/30 16:23:35 [notice] 1#1: start worker process 32
2022/08/30 16:23:35 [notice] 1#1: start worker process 33
2022/08/30 16:23:35 [notice] 1#1: start worker process 34

$

但這只是單純地把 Logs 印出來,在開發時,我們需要一直持續追蹤 Logs 的進展,這時候加上 --follow 的參數,就能夠達到想要的效果。

$ docker container logs --follow nginx
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
2022/08/30 16:23:35 [notice] 1#1: using the "epoll" event method
2022/08/30 16:23:35 [notice] 1#1: nginx/1.23.1
2022/08/30 16:23:35 [notice] 1#1: built by gcc 10.2.1 20210110 (Debian 10.2.1-6)
2022/08/30 16:23:35 [notice] 1#1: OS: Linux 5.10.104-linuxkit
2022/08/30 16:23:35 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576
2022/08/30 16:23:35 [notice] 1#1: start worker processes
2022/08/30 16:23:35 [notice] 1#1: start worker process 32
2022/08/30 16:23:35 [notice] 1#1: start worker process 33
2022/08/30 16:23:35 [notice] 1#1: start worker process 34

# 這邊會像是當機一樣,等待新的 Logs 產生

同理,要離開只需要執行 Ctrl + C 的指令就能夠離開輸出的畫面。

以上為 Docker 容器最基本的幾種操作方式,可以說是涵蓋了新手會使用到的基本操作指令,而在實際使用過後,想必對於這一切是感到霧煞煞,接下來我們將會針對剛剛的操作做講解。

啟動容器時發生了什麼?

在本章的最開頭,我們啟動了 nginx 的容器,而您的電腦在幾秒鐘的時間就有了 nginx 的服務,接著我們透過終端機透露出的端倪來一步步解析究竟發生了什麼事。

$ docker container run --publish 80:80 nginx
Unable to find image 'nginx:latest' locally <- 找不到
latest: Pulling from library/nginx
...

Logs 的第一行顯示出了 Docker 在本地端找不到 nginx:latest 這個映像檔,所以從 library/nginx 這邊拉取了 nginx:latest 這個映像檔。

也就是從預設的映像檔儲存庫 ( DockerHub ) 中拉出了這個映像檔到本地端。

如果您本身也使用過 Git 這個工具,應該對於這個動作不感到陌生,只是這邊的儲存庫預設是 DockerHub 的 nginx 倉庫,所以不需要指定從哪裡取得映像檔。

最基本的運作流程如下圖:

容器啟動基礎流程

上述的啟動流程並不包含了 --publish 80:80 這個部份,所以我們再把圖片中的流程畫得更細節一些:

完整容器啟動流程

讓我們在用文字搭配上圖再敘述一次流程,解釋清楚整個啟動容器的流程:

圖片中的第一步:

先確認本地端是否有指令中指定的映像檔,若是存在直接跳至第三步根據指令啟動容器;若是不存在,則是移動至第二步

圖片中的第二步:

從預設的映像檔儲存庫中下載指令中指定的映像檔至本地端。

圖片中的第三步:

根據指令中的映像檔啟動容器。

圖片中的第四步:

根據指令中的額外參數加入設定。

但在真實的運作流程中,第三步以及第四步會是結合在一起的,並不會分開執行,只是為了讓新手們更好理解整個容器啟動的流程才會這樣繪製。

列出容器時的資訊

在前面實際操作時時常常使用到的 docker container list,讓我們來看看這個指令透露出什麼值得參考的資訊吧!

$ docker container list
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ce6d6951... nginx "/do.." 2 min.. Up.... 0.0.0.0:80->80/tcp nginx

CONTAINER ID:

我們在執行容器時,Docker 會賦予容器一個獨一無二的 ID,以便 Docker 能夠辨識出容器的差別。

IMAGE:

啟動時指令中所指定的映像檔名稱。

COMMAND:

容器啟動時的啟動指令,這和 Docker 映像檔篇CMD 指令息息相關,也可以在啟動容器時傳入不同的啟動指令來取代原先的指令,後面也會提到。

CREATED:

何時啟動的容器。

STATUS:

總共有 created、restarting、running、removing、paused、exited 以及 dead 七種狀態,根據字面上的意思也很好理解,總之就是目前容器的狀態。

PORTS:

啟動時指令中指定容器的 port 該對應到本機的哪個 port。

NAMES:

這個容器的名字,如果沒有替容器命名則會是由 Docker 隨機分配取名。

如何重新啟動容器卻不要背景執行?

在剛剛練習容器的生命週期時,我們發現重新啟動退出狀態的容器會自動改成背景執行,這是 Docker 預設的行為,只要是 docker container start 的指令,都會預設改為背景執行模式。

其實我們可以使用 --attach 的方式在重新啟動時直接將應用程式的 Logs 顯示出來。

$ docker container start --attach nginx
docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: IPv6 listen already enabled
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
2022/10/01 08:58:48 [notice] 1#1: using the "epoll" event method
2022/10/01 08:58:48 [notice] 1#1: nginx/1.23.1
2022/10/01 08:58:48 [notice] 1#1: built by gcc 10.2.1 20210110 (Debian 10.2.1-6)
2022/10/01 08:58:48 [notice] 1#1: OS: Linux 5.10.124-linuxkit
2022/10/01 08:58:48 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576
2022/10/01 08:58:48 [notice] 1#1: start worker processes
2022/10/01 08:58:48 [notice] 1#1: start worker process 25
2022/10/01 08:58:48 [notice] 1#1: start worker process 26
2022/10/01 08:58:48 [notice] 1#1: start worker process 27
# 等待中

容器生命週期練習

每一個章節都會提供一些小小的練習來熟悉本章的內容,通常會比較難一點;若是想不出來也不需要灰心,有些時候可能是指令不熟悉導致,可以往前翻閱來加強記憶喔,亦或是翻到後面的解答章節也可以幫助你解惑喔,記得 --help 以及 docs.docker.com 會是你最好的夥伴。

  1. 請你在背景執行三個不同的服務,分別是 nginx、postgres、httpd ( apache ),並且分別給容器命名
  2. nginx 要執行在 80:80,postgres 要執行在 5432:5432,httpd 則要執行在 8080:80
  3. 當你在啟動 postgres 容器時,需要給予環境變數 --env POSTGRES_PASSWORD=mysecretpassword 才能夠正確啟動
  4. 使用 docker container logs 的指令來確認服務都有正常啟動
  5. 停止並刪除上述三個容器
  6. 使用 docker container list --all 來確認容器都有徹底刪除

容器生命週期練習解答

  1. 請你在背景執行三個不同的服務,分別是 nginx、postgres、httpd ( apache ),並且分別給容器命名;nginx 要執行在 80:80,postgres 要執行在 5432:5432,httpd 則要執行在 8080:80,以及啟動 postgres 容器時,需要給予環境變數 --env POSTGRES_PASSWORD=mysecretpassword

由於讀者們在練習時,本機應該是沒有 postgre、httpd 這些映像檔,所以會重現容器啟動時所發生的一切,可以回想一下剛剛的流程,或是往回翻閱,是不是如你所想呢?

$ docker container run --detach --name nginx --publish 80:80 nginx
b9ba4eadf1896ea33d6a01ba6e6ac009a5e2e5b4ae93e94879f2843f8ac536d1

$ docker container run --detach --name pg --publish 5432:5432 --env POSTGRES_PASSWORD=mysecretpassword postgres
3b7b5f7a1ef24a58603e77dd6e2e2873b79f96a5efbaea877139941f40ad5bbb

$ docker container run --detach --name apache --publish 8080:80 httpd
91298103ea23f0275ddfbd3f062791f32bbd187dac9bffe7d5acaaa33435b820
  1. 使用 docker container logs 的指令來確認服務都有正常啟動。
$ docker container logs nginx
/docker-entrypoint.sh: /docker-entrypoint.d/ is n...
/docker-entrypoint.sh: Looking for shell scripts in ...
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-o...
10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx...
10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx...

$ docker container logs pg
PostgreSQL init process complete; ready for start up.

2022-08-27 12:23:37.566 UTC [1] LOG: starting Postre..
2022-08-27 12:23:37.566 UTC [1] LOG: listening on IP..
2022-08-27 12:23:37.566 UTC [1] LOG: listening on IPv..
2022-08-27 12:23:37.574 UTC [1] LOG: listening on Un..
2022-08-27 12:23:37.584 UTC [60] LOG: database syst..
2022-08-27 12:23:37.594 UTC [1] LOG: database system...

$ docker container logs apache
AH00558: httpd: Could not reliably determine the serve..
AH00558: httpd: Could not reliably determine the ser...
[Sat Aug 27 12:22:50.192367 2022] [mpm_event:notice] [pid 1:tid 1405....]
[Sat Aug 27 12:22:50.194186 2022] [core:notice] [pid 1:tid 140564....]
  1. 停止並刪除上述三個容器,停止以及刪除的指令都可以搭配複數的容器,不需要一個一個打。
---- 先退出,再刪除 ----
$ docker container stop nginx pg apache
nginx
pg
apache

$ docker container rm nginx pg apache
nginx
pg
apache

---- 強制刪除 ----
$ docker container rm --force nginx pg apache
nginx
pg
apache
  1. 使用 docker container list --all 來確認容器都有徹底刪除。
$ docker container list --all
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES