# 前言
本篇分為:「關於容器」、「容器常用操作」、「留下容器資料:數據卷(Volume)以及掛載宿主目錄/文件」三部分。
首先回憶一下前面提過的容器概念,再進入容器相關指令的解析。最後介紹 volume 以及宿主目錄的掛載,可用於留存執行過程中產生的容器資料,避免資料伴隨容器的移除一起消失。
# 關於容器
關於什麼是容器、為什麼需要容器、它跟虛擬機有什麼不同等困惑,已在【Day 1】Docker 基本概念中釐清過。
容器的本質就是程序(porcess)。
——《Docker——從入門到實踐》
簡單來說,容器的目的就是要在一個獨立的環境(沙盒),執行一項應用程式,而容器的本質就是一個程序(應用程式),當程序執行結束,容器的任務就結束,便退出執行(Exit)。
容器環境的獨立性,就如同 Docker 鯨魚上頭承載的那一盒盒貨櫃箱(containers),貨櫃間彼此獨立,同時與底部乘載它的鯨魚區隔(以宿主為基礎,但與宿主環境區隔,每個容器擁有自己獨立的檔案系統、網路配置等等)。
上一篇提到 image 是容器的樣版,包含了應用程式執行所需的完整條件(即包含除了底層 kernel 以外,像是應用程式、依賴庫等資源)。當 docker run
指令把 image 實體化為容器時,會以這個靜態樣板 image(唯讀)為基礎疊加一層容器存儲層(可讀寫),用以存儲任何容器執行期間產生的資料,另外再加上一些容器相關配置(如容器與宿主機器間的接口、容器的 IP 位址等等)。
馬上來看看怎麼操作容器吧。
# 容器常用的操作:
- 啟動容器(把 image 實體化)-----
docker run
- 手動終止容器的運行 ---------------
docker stop
- 再次啟動已終止的容器 -------------
docker start
- 重新啟動執行中的容器 -------------
docker restart
(= stop + start) - 進入運行中的容器進行操作 ------------
docker exec -it
或docker attach
- 列出(所有)本地容器 -------------
docker ps (-a)
- (強制)刪除容器 ------------------
docker rm (-f)
- 一鍵移除所有已終止的容器 --------
$ docker container prune
1. 啟動容器(把 image 實體化):$ docker run <image>
當下令
docker run
時,docker 背後做了一系列操作:
(擷取自《Docker——從入門到實踐》)- 檢查本地是否有指定的<image>,若無就從 registry 下載
- 用<image>建立一個容器,並啟動
- 分配一個檔案系統,在 image(唯讀)外頭 mount 一層容器存儲層(可讀寫)
- 從宿主主機配置的網橋接口中橋接一個虛擬接口到容器去
- 從地址持配置一個 ip 位址給容器
- 執行指定的應用程序
- 執行完畢後,容器被終止
docker run
可分為 foreground(前台)和 detached(分離)兩種模式。- foreground(預設):
- 直接在當前宿主機上執行,並印出執行過程的 logs。
- detached(加上 -d 參數
docker run -d <image>
):- 僅回傳所運行的 container_ID,不會印出 logs。
- 欲查看 logs,則要用
docker container logs <container>
。
- 無論是以 foreground 或 detached 方式執行,容器都會在指定的程序執行結束時,自動終止運作。
- foreground(預設):
2. 手動終止容器的運行:$ docker stop <container>
- 終止容器運行,但所做變更仍存在於容器存儲層。
3. 再次啟動已終止的容器:$ docker start <container>
- 把終止狀態的 container 再次執行。
A stopped container can be restarted with all its previous changes intact using docker start.
—— 官網(docker run)
- 以
docker start
再次執行已終止的 container 時,之前對 container 的變更都還會完整存在。因預設情況下,變更會存在掛載的容器存儲層,容器終止時,容器存儲層仍存在。直到 container 被移除(docker rm <container>
),容器存儲層才會跟著 container 一起被移除。
4. 重新啟動執行中的容器:$ docker restart <container>
- 等同於
docker stop
+docker start
。重新啟動不會移除之前對 container 的變更(跟docker start
一樣)。
5. 進入運行中的容器進行操作: $ docker exec -it <running_state_container>
和 $ docker attach <running_state_container>
- 結果同樣是進入容器互動模式,但兩者背後做的事不一樣(進入容器互動模式的方式不同),所以行為不同。
[參考:difference between docker attach and docker exec]
(1) docker exec -it
:
指令說明(docker 的參數是可以合併的,故
-it
等同於-i -t
):- -i(--interactive):容器的輸出會接到當前的畫面(我們才得以看到命令執行結果)。
- -t(--tty):pseudo tty(pseudo terminal,偽終端),允許我們發送 input 給容器。
指令意涵:
- 在執行中的容器上,開新一個程序。像是
docker exec -it <container_ID> bash
就是在運行中的容器裡,執行 bash。 - 這時若使用 exit 離開/終止,僅終止 exec 所執行的程序(像是 bash),不是終止容器。所以從
exec
exit 不會中斷容器的運作。
- 在執行中的容器上,開新一個程序。像是
範例:
$ docker run -dit ubuntu # -d 在背後執行容器;-it 容器互動模式
$ docker exec -it <container> bash # 進入容器
root@container_ID:/#
(2) docker attach
:
指令意涵:
- 「attach(附加)」標準輸入、標準輸出、標準錯誤到執行中的 container,而不是在上面另開新程序。
- 若使用 exit 離開/終止,則會終止 container。因為是進入到執行中的 container 用 attach 的標準輸入輸出與它互動
- 一個運作中的 container 只能被 attach 一次(只會有一個標準輸入輸出的實體)。即使一次從多個終端 attach,也只會看到相同的輸出(其中一個終端輸入的命令卡住,所有終端都卡住)。
官方範例:
$ docker run -d --name topdemo ubuntu /usr/bin/top -b
$ docker attach topdemo
root@container_ID:/#
6. 列出(所有)本地容器:$ docker ps (-a)
- -a(--all):列出所有容器(包含終止狀態的)
7.(強制)刪除容器:$ docker rm (-f) <container>
- -f(--force):強制刪除執行中的容器(docker 會發送 SIGKILL 信號給容器,kill process 後再刪除)
docker rm <container>
把容器從本地移除時,此容器的存儲層就會一起被刪掉。
8. 一鍵移除所有已終止的容器:$ docker container prune
- 因為輕量的容器很方便創建,一不注意本地可能堆積一些已終止且用不到的容器,
docker container prune
可一鍵清理掉。 - 效果等同於把它們一個個分別
docker rm
。
# 留下容器資料:數據卷(Volume)以及掛載宿主目錄/文件
容器創建時,在唯讀的 Image 上會疊加一層可讀寫的容器存儲層,用來記錄運行過程中讀寫的資料。然而容器存儲層會隨著 docker rm <container>
而一起被刪除。
為了保存容器執行過程中產生的資料,可以指定把資料存在 Volume 或指定的宿主機目錄。
#1 數據卷(Volume)
Volume 其實就類似 mount 宿主機的目錄/文件,一個 volume 也可以被多個容器共享。只是比起 mount 宿主目錄/文件,docker 還提供 volume 其他功能或指令,方便管理。
Volume 相關指令:
- 創建一個 volume --------------------------
docker volume create <volume_name>
- 查看所有 volumes -------------------------
docker volume ls
- 查看特定 volume 的詳情 ------------------
docker volume inspect <volume_name>
- 運行容器時,把 volume 掛載到容器上 --- docker run 時加入
--mount
或-v
docker run -d -P \ --name <container_name> --mount source=<volume_name>,target=<target_path_in_container> \ # 或 -v <volume_name>:<target_path_in_container> \ <image> \ <command>
- 藉由容器查看掛載的 volume ---------------
docker inspect <container>
(裡頭的 Mounts 項目) - 清除所有無主的 volumes -------------------
docker volume prune
(※無主:建立了,但沒被掛載在任何容器上) - 移除容器的同時移除掛載的 volume --------
docker rm -v <container>
#2 掛載宿主機目錄/文件
掛載的方式與 volume 差不多:
docker run -d -P \
--name <container_name>
--mount type=bind,source=<local_src_path>,target=<target_path_in_container>(,readonly) \
# 或 -v <local_src_path>:<target_path_in_container> \
<image> \
<command>
- 掛載的宿主目錄預設為可讀寫,若加上 readonly 則為唯讀,變更該項目會報錯。
- 也可以掛載單一文件。即上例中的 <local_src_path> 與 <target_path_in_container> 都替換為文件 path,很適合用來記錄容器執行過程的 log 檔案。
# 以 -v 或 --mount 掛載的差別
- 若 <local_src_path> 不存在,-v 會自動創建,--mount 會報錯
- 作者建議對 docker 尚不熟悉者優先使用 --mount
# 結語
容器是 docker 的核心觀念,本文主要從應用層面切入了解容器。像是 docker run
背後的一系列操作;docker exec
和 docker attach
兩者雖都可以進入容器,但因途徑不同而產生不同行為;藉由掛載 volume 或宿主目錄文件,可避免容器運行資料被移除掉等等。
另外,「容器的本質是程序(process),指定的程序一旦執行完畢,容器的生命週期就結束」也是這本書強調的觀念之一。
關於 docker 究竟如何實現容器技術,就下一篇再討論吧(docker 底層運作)。