本篇文章將通過一個非常典型的 Python Web 應用作為案例,講解 Docker 容器使用的主要場景。包括構建鏡像、啟動鏡像、分享鏡像、在鏡像中操作、在鏡像中掛載宿主機目錄、對容器使用的資源進行限制、管理容器的狀態和如何保持容器始終運行。熟悉了這些操作,你也就基本上摸清了 Docker 容器的核心功能。
前期準備:需要一臺已經安裝了 Docker 的 Linux 虛擬機。
容器與宿主機之間如何共享文件
容器技術使用了 rootfs 機制和 Mount Namespace,構建出了一個同宿主機完全隔離開的文件系統環境。但是我們使用過程中經常會遇到這樣兩個問題:
- 容器裡進程新建的文件,怎麼才能讓宿主機獲取到?
- 宿主機上的文件和目錄,怎麼才能讓容器裡的進程訪問到?
這正是 Docker Volume 要解決的問題:Volume 機制,允許你將宿主機上指定的目錄或者文件,掛載到容器裡面進行讀取和修改。
在 Docker 項目裡,它支持兩種 Volume 聲明方式,可以把宿主機目錄掛載進容器的 /test 目錄當中:
<code>docker run -v /test ...
docker run -v /home:/test ... /<code>
而這兩種聲明方式的本質,實際上是相同的:都是把一個宿主機的目錄掛載進了容器的 /test 目錄。
只不過,在第一種情況下,由於你並沒有顯示聲明宿主機目錄,那麼 Docker 就會默認在宿主機上創建一個臨時目錄 /var/lib/docker/volumes/[VOLUME_ID]/_data,然後把它掛載到容器的 /test 目錄上。而在第二種情況下,Docker 就直接把宿主機的 /home 目錄掛載到容器的 /test 目錄上。
啟動容器時,給他聲明一個 volume
<code>docker run -d -v /test helloworld /<code>
容器啟動之後,我們來查看一下這個容器 的 Volume 在宿主機上的對應的目錄:
<code>docker volume ls
DRIVER VOLUME NAME
local dc195c8ad14ad505832461d9f37da889c54ef284ebaf777a100e10a932217ad3 /<code>
或者執行docker inspect CONTAINER_ID命令查看,命令輸出的 Mounts 字段中 Source 的值就是宿主機上的目錄:
<code>"Mounts": \\[
{
"Type": "volume",
"Name": "dc195c8ad14ad505832461d9f37da889c54ef284ebaf777a100e10a932217ad3",
"Source": "/var/lib/docker/volumes/dc195c8ad14ad505832461d9f37da889c54ef284ebaf777a100e10a932217ad3/_data",
"Destination": "/test",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
\\], /<code>
然後,查看宿主機上的路徑:
<code>ls /var/lib/docker/volumes/dc195c8ad14ad505832461d9f37da889c54ef284ebaf777a100e10a932217ad3/_data /<code>
這個 _data 文件夾,就是這個容器的 Volume 在宿主機上對應的臨時目錄了。接下來,我們在容器的 Volume 裡,添加一個文件 text.txt:
<code>docker exec -it cf53b766fa6f /bin/sh
cd test/
touch text.txt /<code>
這時,我們再回到宿主機,就會發現 text.txt 已經出現在了宿主機上對應的臨時目錄裡了:
<code>ls /var/lib/docker/volumes/dc195c8ad14ad505832461d9f37da889c54ef284ebaf777a100e10a932217ad3/_data
text.txt /<code>
因為容器運行時產生的文件,在容器停止後將會消失。因此,將容器的目錄映射到宿主機的某個目錄,一個重要使用場景是持久化容器中產生的文件,比如應用的日誌。
給容器加上資源限制
其實容器是運行在宿主機上的特殊進程,多個容器之間是共享宿主機的操作系統內核的。默認情況下,容器並沒有被設定使用操作系統資源的上限。
有些情況下,我們需要限制容器啟動後佔用的宿主機操作系統的資源。Docker 可以利用 Linux Cgroups 機制可以給容器設置資源使用限制。
Linux Cgroups 的全稱是 Linux Control Group。它最主要的作用,就是限制一個進程組能夠使用的資源上限,包括 CPU、內存、磁盤、網絡帶寬等等。Docker 正是利用這個特性限制容器使用宿主上的 CPU、內存。
下面啟動容器的方式,給這個 Python 應用加上 CPU 和 Memory 限制:
<code>docker run -it --cpu-period=100000 --cpu-quota=20000 -m 300M helloworld /<code>
–cpu-period 和–cpu-quota 組合使用來限制容器使用的 CPU 時間。表示在–cpu-period 的一段時間內,容器只能被分配到總量為 --cpu-quota 的 CPU 時間。-m 選項則限制了容器使用宿主機內存的上限。
上面啟動容器的命令,將容器使用的 CPU 限制設定在最高 20%,內存使用最多是 300MB。
容器的啟動、重啟、停止與刪除
前面我們使用過docker ps 查看當前運行中的容器,如果加上 -a 選項,則可以查看運行中和已經停止的所有容器。現在,看一下我的系統中目前的所有容器:
<code>$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
525a8c3fc769 helloworld "python app.py" 4 hours ago Up 3 minutes 80/tcp hardcore_feistel
1695ed10e2cb helloworld "python app.py" 4 hours ago Up 3 minutes 0.0.0.0:5000->80/tcp focused_margulis7
a242ecaf6cf6 helloworld "python app.py" 5 hours ago Exited (0) 4 hours ago dazzling_khayyam
be0439b30b2a helloworld "python app.py" 5 hours ago Created vigilant_lalande /<code>
從輸出中可以看到目前有四個容器,有兩個容器處於 Up 狀態,也就是處於運行中的狀態,一個容器處於 Exited(0) 狀態,也就是退出狀態,一個處於 Created 狀態。
這裡補充說明一下 docker ps -a 的輸出結果,一共包含 7 列數據,分別是 CONTAINER ID、IMAGE、COMMAND、CREATED、STATUS、PORTS 和 NAMES。這些列的含義分別如下所示:
- CONTAINER ID:容器 ID,唯一標識容器
- IMAGE:創建容器時所用的鏡像
- COMMAND:在容器最後運行的命令
- CREATED:容器創建的時間
- STATUS:容器的狀態
- PORTS:對外開放的端口號
- NAMES:容器名(具有唯一性,docker 負責命名)
獲取到容器的 ID 之後,可以對容器的狀態進行修改,比如容器 1695ed10e2cb 進行停止、啟動、重啟:
<code>docker stop 1695ed10e2cb
docker start 1695ed10e2cb
docker restart 1695ed10e2cb /<code>
刪除容器,有兩種操作:
<code>docker rm 1695ed10e2cb
docker rm -f 1695ed10e2cb /<code>
不帶-f 選項,只能刪除處於非 Up 狀態的容器,帶上-f 則可以刪除處於任何狀態下的容器。
Docker 容器狀態的流轉關係,可以下面這張圖:
這張圖對容器的生命週期有了清晰的描述,總結了容器各種狀態之間是如何轉換的。
需要注意一點是容器可以先創建容器,稍後再啟動。也就是可以先執行docker create 創建容器(處於 Created 狀態),再通過docker start 以後臺方式啟動容器。docker run 命令實際上是 docker create 和 docker start 的組合。
維持容器始終保持運行狀態
docker run 指令有一個參數--restart,在容器中啟動的進程正常退出或發生 OOM 時, docker 會根據 --restart 的策略判斷是否需要重啟容器。但如果容器是因為執行 docker stop 或 docker kill 退出,則不會自動重啟。
docker 支持如下 restart 策略:
- no – 容器退出時不要自動重啟。這個是默認值。
- on-failure[:max-retries] – 只在容器以非 0 狀態碼退出時重啟。可選的,可以退出 docker daemon 嘗試重啟容器的次數。
- always – 不管退出狀態碼是什麼始終重啟容器。當指定 always 時,docker daemon 將無限次數地重啟容器。容器也會在 daemon 啟動時嘗試重啟容器,不管容器當時的狀態如何。
- unless-stopped – 不管退出狀態碼是什麼始終重啟容器。不過當 daemon 啟動時,如果容器之前已經為停止狀態,不啟動它。
在每次重啟容器之前,不斷地增加重啟延遲(上一次重啟的雙倍延遲,從 100 毫秒開始),來防止影響服務器。這意味著 daemon 將等待 100ms, 然後 200 ms, 400 ms, 800 ms, 1600 ms 等等,直到超過 on-failure 限制,或執行 docker stop 或 docker rm -f。
如果容器重啟成功(容器啟動後並運行至少 10 秒),然後 delay 重置為默認的 100ms。
重啟策略示例:
<code>docker run --restart=always 1695ed10e2cb # restart 策略為 always,使得容器退出時 ,docker 將重啟它。並且是無限制次數重啟。
docker run --restart=on-failure:10 redis #restart 策略為 on-failure, 最大重啟次數為 10 的次。容器以非 0 狀態連續退出超過 10 次,docker 將中斷嘗試重啟這個容器。 /<code>
可以通過 docker inspect 來查看已經嘗試重啟容器了多少次。例如,獲取容器 1695ed10e2cb 的重啟次數:
<code>docker inspect -f "{{ .RestartCount }}" 1695ed10e2cb /<code>
或者獲取上一次容器重啟時間:
<code>docker inspect -f "{{ .State.StartedAt }}" 1695ed10e2cb /<code>
以上,歡迎大家留言探討。
本文首發於 liuchunming.net ,作者授權發佈。
閱讀更多 霍格沃茲軟件測試學院 的文章