Docker操作命令详解
docker基本概念
1. Image Definition
鏡像 Image 就是一堆只讀層 read-only layer 的統一視角。
001.png對于某個鏡像Image實例,可能由多個只讀層構成,它們重疊在一起。除了最下面一層,其它層都會有一個指針指向下一層。這些層都能夠在主機的文件系統上訪問到。Docker使用的文件系統為統一文件系統 union file system,該技術能夠將不同的層整合成一個文件系統,為這些層提供了一個統一的視角,這樣就隱藏了多層的存在,在用戶的角度看來,只存在一個文件即 Image實例,如右圖視角的形式。
你可以在你的主機文件系統上找到有關這些層的文件。需要注意的是,在一個運行中的容器內部,這些層是不可見的。在linux系統中,鏡像文件存在于/var/lib/docker/aufs目錄下。
$ sudo tree -L 1 /var/lib/docker//var/lib/docker/ ├── aufs ├── containers ├── graph ├── init ├── linkgraph.db ├── repositories-aufs ├── tmp ├── trust └── volumes 7 directories, 2 files2. Container Definition
容器container的定義和鏡像image幾乎一模一樣,也是一堆層的統一視角,唯一區別在于容器的最上面那一層是可讀可寫的。
002.png容器的定義并沒有提及容器是否在運行,沒錯,這是故意的。
要點:容器 = 鏡像 + 可讀層。并且容器的定義并沒有提及是否要運行容器。
接下來,我們將會討論運行態容器。
3. Running Container Definition
一個運行態容器running container被定義為一個可讀寫的統一文件系統加上隔離的進程空間和包含其中的進程。
003.png文件系統隔離技術使得Docker成為了一個前途無量的技術。一個容器中的進程可能會對文件進行修改、刪除、創建,這些改變都將作用于可讀寫層(read-write layer)。
004.png我們可以通過運行以下命令來驗證我們上面所說的:
docker run ubuntu touch happiness.txt即便是這個ubuntu容器不再運行,我們依舊能夠在主機的文件系統上找到這個新文件。
find / -name happiness.txt/var/lib/docker/aufs/diff/860a7b...889/happiness.txt4. Image Layer Definition
為了將零星的數據整合起來,我們提出了鏡像層image layer這個概念。下面的這張圖描述了一個鏡像層,通過圖片我們能夠發現一個層并不僅僅包含文件系統的改變,它還能包含了其他重要信息。
005.png元數據metadata就是關于這個層的額外信息,它不僅能夠讓Docker獲取運行和構建時的信息,還包括父層的層次信息。需要注意,只讀層和讀寫層都包含元數據。
image.png除此之外,每一層都包括了一個指向父層的指針。如果一個層沒有這個指針,說明它處于最底層。
image.pngMetadata Location:
我發現在我自己的主機上,鏡像層(image layer)的元數據被保存在名為”json”的文件中,比如說:
/var/lib/docker/graph/e809f156dc985.../json
e809f156dc985...就是這層的id
一個容器的元數據好像是被分成了很多文件,但或多或少能夠在/var/lib/docker/containers/<id>目錄下找到,<id>就是一個可讀層的id。這個目錄下的文件大多是運行時的數據,比如說網絡,日志等等。
5. docker create <image-id>
image.pngdocker create命令為指定的鏡像image添加了一個可讀層,構成了一個新的容器。注意,這個容器并沒有運行。
image.png6. docker start <container-id>
image.pngDocker start命令為容器文件系統創建了一個進程隔離空間。注意,每一個容器只能夠有一個進程隔離空間。
7. docker run <image-id>
image.png看到這個命令,讀者通常會有一個疑問:docker start和 docker run命令有什么區別。
image.png從圖片可以看出,docker run命令先是利用鏡像創建了一個容器,然后運行這個容器。這個命令非常的方便,并且隱藏了兩個命令的細節,但從另一方面來看,這容易讓用戶產生誤解。
題外話:繼續我們之前有關于Git的話題,docker run命令類似于git pull命令。git pull命令就是git fetch 和 git merge兩個命令的組合,同樣的,docker run就是docker create和docker start兩個命令的組合。
8. docker ps
image.pngdocker ps命令會列出所有運行中的容器。這隱藏了非運行態容器的存在,如果想要找出這些容器,我們需要使用下面這個命令。
docker ps –a image.pngdocker ps –a命令會列出所有的容器,不管是運行的,還是停止的。
9. docker images
image.pngdocker images命令會列出了所有頂層top-level鏡像。實際上,在這里我們沒有辦法區分一個鏡像和一個只讀層,所以我們提出了top-level鏡像。只有創建容器時使用的鏡像或者是直接pull下來的鏡像能被稱為頂層top-level鏡像,并且每一個頂層鏡像下面都隱藏了多個鏡像層。
docker images –a image.pngdocker images –a命令列出了所有的鏡像,也可以說是列出了所有的可讀層。如果你想要查看某一個image-id下的所有層,可以使用docker history來查看。
10. docker stop <container-id>
image.pngdocker stop命令會向運行中的容器發送一個SIGTERM的信號,然后停止所有的進程。
11. docker kill <container-id>
image.pngdocker kill 命令向所有運行在容器中的進程發送了一個不友好的SIGKILL信號。
12. docker pause <container-id>
image.pngdocker stop和docker kill命令會發送UNIX的信號給運行中的進程,docker pause命令則不一樣,它利用了cgroups的特性將運行中的進程空間暫停。具體的內部原理你可以在這里找到:https://www.kernel.org/doc/Doc ... m.txt,但是這種方式的不足之處在于發送一個SIGTSTP信號對于進程來說不夠簡單易懂,以至于不能夠讓所有進程暫停。
13. docker rm <container-id>
image.pngdocker rm命令會移除構成容器的可讀寫層。注意,這個命令只能對非運行態容器執行。
docker rmi <image-id> image.pngdocker rmi命令會移除構成鏡像的一個只讀層。你只能夠使用docker rmi來移除最頂層top level layer(也可以說是鏡像),你也可以使用-f參數來強制刪除中間的只讀層。
14. docker commit <container-id>
image.pngdocker commit命令將容器的可讀寫層轉換為一個只讀層,這樣就把一個容器轉換成了不可變的鏡像。
image.png15. docker build
image.pngdocker build命令非常有趣,它會反復的執行多個命令。
image.png我們從上圖可以看到,build命令根據Dockerfile文件中的FROM指令獲取到鏡像,然后重復執行:
- 1)run(create和start)
- 2)修改
- 3)commit
在循環中的每一步都會生成一個新的層,因此許多新的層會被創建。
16. docker exec <running-container-id>
image.pngdocker exec命令會在運行中的容器執行一個新進程。
17. docker inspect <container-id> or <image-id>
image.pngdocker inspect命令會提取出容器或者鏡像最頂層的元數據。
18. docker save <image-id>
image.pngdocker save命令會創建一個鏡像的壓縮文件,這個文件能夠在另外一個主機的Docker上使用。和export命令不同,這個命令為每一個層都保存了它們的元數據。這個命令只能對鏡像生效。
19. docker export <container-id>
image.pngdocker export命令創建一個tar文件,并且移除了元數據和不必要的層,將多個層整合成了一個層,只保存了當前統一視角看到的內容(譯者注:expoxt后的容器再import到Docker中,通過docker images –tree命令只能看到一個鏡像;而save后的鏡像則不同,它能夠看到這個鏡像的歷史鏡像)。
20. docker history <image-id>
image.pngdocker history命令遞歸地輸出指定鏡像的歷史鏡像。
21. 刪除所有終止的容器
docker rm $(docker ps -a -q)docker基本命令
1. 查看docker信息(version、info)
# 查看docker版本 $docker version # 顯示docker系統的信息 $docker info2. 對image的操作(search、pull、images、rmi、history)
# 檢索image $docker search image_name # 下載image $docker pull image_name # 列出鏡像列表; # -a, --all=false Show all images; --no-trunc=false Don't truncate output; -q, --quiet=false Only show numeric IDs $docker images # 刪除一個或者多個鏡像; # -f, --force=false Force; --no-prune=false Do not delete untagged parents $docker rmi image_name # 顯示一個鏡像的歷史; # --no-trunc=false Don't truncate output; -q, --quiet=false Only show numeric IDs $docker history image_name3. 啟動容器(run)
docker容器可以理解為在沙盒中運行的進程。這個沙盒包含了該進程運行所必須的資源,包括文件系統、系統類庫、shell 環境等等。但這個沙盒默認是不會運行任何程序的。你需要在沙盒中運行一個進程來啟動某一個容器。這個進程是該容器的唯一進程,所以當該進程結束的時候,容器也會完全的停止。
# 在容器中運行"echo"命令,輸出"hello word" $docker run image_name echo "hello word" # 交互式進入容器中 $docker run -i -t image_name /bin/bash # 在容器中安裝新的程序 $docker run image_name apt-get install -y app_nameNote: 在執行apt-get 命令的時候,要帶上-y參數。如果不指定-y參數的話,apt-get命令會進入交互模式,需要用戶輸入命令來進行確認,但在docker環境中是無法響應這種交互的。apt-get 命令執行完畢之后,容器就會停止,但對容器的改動不會丟失。
4. 查看容器(ps)
# 列出當前所有正在運行的container $docker ps # 列出所有的container $docker ps -a # 列出最近一次啟動的container $docker ps -l5. 保存對容器的修改(commit)
當你對某一個容器做了修改之后(通過在容器中運行某一個命令),可以把對容器的修改保存下來,這樣下次可以從保存后的最新狀態運行該容器。
# 保存對容器的修改; -a, --author="" Author; -m, --message="" Commit message $docker commit ID new_image_nameNote:image相當于類,container相當于實例,不過可以動態給實例安裝新軟件,然后把這個container用commit命令固化成一個image。
6. 對容器的操作(rm、stop、start、kill、logs、diff、top、cp、restart、attach)
# 刪除所有容器 $docker rm `docker ps -a -q` # 刪除單個容器; -f, --force=false; -l, --link=false Remove the specified link and not the underlying container; -v, --volumes=false Remove the volumes associated to the container $docker rm Name/ID # 停止、啟動、殺死一個容器 $docker stop Name/ID $docker start Name/ID $docker kill Name/ID # 從一個容器中取日志; # -f, --follow=false Follow log output; -t, --timestamps=false Show timestamps $docker logs Name/ID # 列出一個容器里面被改變的文件或者目錄,list列表會顯示出三種事件,A 增加的,D 刪除的,C 被改變的 $docker diff Name/ID # 顯示一個運行的容器里面的進程信息 $docker top Name/ID # 從容器里面拷貝文件/目錄到本地一個路徑 $docker cp Name:/container_path to_path $docker cp ID:/container_path to_path # 重啟一個正在運行的容器; # -t, --time=10 Number of seconds to try to stop for before killing the container, Default=10 $docker restart Name/ID # 附加到一個運行的容器上面; # --no-stdin=false Do not attach stdin; --sig-proxy=true Proxify all received signal to the process $docker attach IDNote: attach命令允許你查看或者影響一個運行的容器。你可以在同一時間attach同一個容器。你也可以從一個容器中脫離出來,是從CTRL-C。
7. 保存和加載鏡像(save、load)
當需要把一臺機器上的鏡像遷移到另一臺機器的時候,需要保存鏡像與加載鏡像。
# 保存鏡像到一個tar包; -o, --output="" Write to an file $docker save image_name -o file_path # 加載一個tar包格式的鏡像; -i, --input="" Read from a tar archive file $docker load -i file_path # 機器a $docker save image_name > /home/save.tar # 使用scp將save.tar拷到機器b上,然后: $docker load < /home/save.tar8、 登錄registry server(login)
# 登陸registry server; -e, --email="" Email; -p, --password="" Password; -u, --username="" Username $docker login9. 發布image(push)
# 發布docker鏡像 $docker push new_image_name10. 根據Dockerfile 構建出一個容器
# build # --no-cache=false Do not use cache when building the image # -q, --quiet=false Suppress the verbose output generated by the containers # --rm=true Remove intermediate containers after a successful build # -t, --tag="" Repository name (and optionally a tag) to be applied to the resulting image in case of success $docker build -t image_name Dockerfile_path11. 修改鏡像名稱
docker tag server:latest myname/server:latest or docker tag d583c3ac45fd myname/server:latestDocker Problem
Dockerfile 的問題
雖然 Dockerfile 簡化了鏡像構建的過程,并且把這個過程可以進行版本控制,但是不正當的 Dockerfile 使用也會導致很多問題:
- docker 鏡像太大:如果你經常使用鏡像或者構建鏡像,一定會遇到那種很大的鏡像,甚至有些能達到 2G 以上
- docker 鏡像的構建時間過長:每個 build 都會耗費很長時間,對于需要經常構建鏡像(比如單元測試)的地方這可能是個大問題
- 重復勞動:多次鏡像構建之間大部分內容都是完全一樣而且重復的,但是每次都要做一遍,浪費時間和資源
Dockerfile 和鏡像構建
Dockerfile 是由一個個指令組成的,每個指令都對應著最終鏡像的一層。每行的第一個單詞就是命令,后面所有的字符串是這個命令的參數,關于 Dockerfile 支持的命令以及它們的用法,可以參考 官方文檔 ,這里不再贅述。
當運行 docker build 命令的時候,整個的構建過程是這樣的:
- 讀取 Dockerfile 文件發送到 docker daemon
- 讀取當前目錄的所有文件(context),發送到 docker daemon
- 對 Dockerfile 進行解析,處理成命令加上對應參數的結構
- 按照順序循環遍歷所有的命令,對每個命令調用對應的處理函數進行處理
- 每個命令(除了 FROM)都會在一個容器執行,執行的結果會生成一個新的鏡像
- 為最后生成的鏡像打上標簽
1. 使用統一的 base 鏡像
有些文章講優化鏡像會提倡使用盡量小的基礎鏡像,比如 busybox 或者 alpine 等。我更推薦使用統一的大家比較熟悉的基礎鏡像,比如 ubuntu,centos 等,因為基礎鏡像只需要下載一次可以共享,并不會造成太多的存儲空間浪費。它的好處是這些鏡像的生態比較完整,方便我們安裝軟件,除了問題進行調試。
2. 動靜分離
經常變化的內容和基本不會變化的內容要分開,把不怎么變化的內容放在下層,創建出來不同基礎鏡像供上層使用。比如可以創建各種語言的基礎鏡像,python2.7、python3.4、go1.7、java7等等,這些鏡像包含了最基本的語言庫,每個組可以在上面繼續構建應用級別的鏡像。
3. 最小原則:只安裝必需的東西
為了降低復雜性、減少依賴、減小文件大小、節約構建時間,你應該避免安裝任何不必要的包,不要僅僅為了“錦上添花”而安裝某個包。因為鏡像的擴展很容易,而且運行容器的時候也很方便地對其進行修改。這樣可以保證鏡像盡可能小,構建的時候盡可能快,也保證未來的更快傳輸、更省網絡資源。例如,不要在數據庫鏡像中包含一個文本編輯器。
4. 一個原則:每個鏡像只有一個功能
不要在容器里運行多個不同功能的進程,每個鏡像中只安裝一個應用的軟件包和文件,需要交互的程序通過 pod(kubernetes 提供的特性) 或者容器之間的網絡進行交流。這樣可以保證模塊化,不同的應用可以分開維護和升級,也能減小單個鏡像的大小。
5. 使用更少的層
雖然看起來把不同的命令盡量分開來,寫在多個命令中容易閱讀和理解。但是這樣會導致出現太多的鏡像層,而不好管理和分析鏡像,而且鏡像的層是有限的。盡量把相關的內容放到同一個層,使用換行符進行分割,這樣可以進一步減小鏡像大小,并且方便查看鏡像歷史。
6. 減少每層的內容
盡管只安裝必須的內容,在這個過程中也可能會產生額外的內容或者臨時文件,我們要盡量讓每層安裝的東西保持最小。
- 比如使用 --no-install-recommends 參數告訴 apt-get 不要安裝推薦的軟件包
- 安裝完軟件包,清楚 /var/lib/apt/list/ 緩存
- 刪除中間文件:比如下載的壓縮包
- 刪除臨時文件:如果命令產生了臨時文件,也要及時刪除
- 使用 .dockerignore 文件:創建一個 .dockerignore 文件來指定要忽略的文件和目錄。.dockerignore 文件的排除模式語法和 Git 的 .gitignore 文件類似。
7. 不要在 Dockerfile 中修改文件的權限
因為 docker 鏡像是分層的,任何修改都會新增一個層,修改文件或者目錄權限也是如此。如果修改大文件或者目錄的權限,會把這些文件復制一份,這樣很容易導致鏡像很大。
解決方案也很簡單,要么在添加到 Dockerfile 之前就把文件的權限和用戶設置好,要么在容器啟動腳本(entrypoint)做這些修改。
8. 利用 cache 來加快構建速度
在鏡像的構建過程中,Docker 會遍歷 Dockerfile 文件中的指令,然后按順序執行。在執行每條指令之前,Docker 都會在緩存中查找是否已經存在可重用的鏡像,如果有就使用現存的鏡像,不再重復創建。如果你不想在構建過程中使用緩存,你可以在 docker build 命令中使用--no-cache=true選項。
不過從 1.10 版本開始,Content Addressable Storage 的引入導致緩存功能的失效,目前引入了 --cache-from 參數可以手動指定一個鏡像來使用它的緩存。
但是,如果你想在構建的過程中使用緩存,你得明白什么時候會,什么時候不會找到匹配的鏡像。Docker 遵循的基本規則如下:
- 從一個基礎鏡像開始(FROM 指令指定),下一條指令將和該基礎鏡像的所有子鏡像進行匹配,檢查這些子鏡像被創建時使用的指令是否和被檢查的指令完全一樣。如果不是,則緩存失效。
- 在大多數情況下,只需要簡單地對比 Dockerfile 中的指令和子鏡像。然而,有些指令需要更多的檢查和解釋。
- 對于 ADD 和 COPY 指令,鏡像中對應文件的內容也會被檢查,每個文件都會計算出一個校驗和。文件的最后修改時間和最后訪問時間不會納入校驗。在緩存的查找過程中,會將這些校驗和和已存在鏡像中的文件校驗和進行對比。如果文件有任何改變,比如內容和元數據,緩存失效。
- 除了 ADD 和 COPY 指令,緩存匹配過程不會查看臨時容器中的文件來決定緩存是否匹配。例如,當執行完 RUN apt-get -y update指令后,容器中一些文件被更新,但 Docker 不會檢查這些文件。這種情況下,只有指令字符串本身被用來匹配緩存。
一旦緩存失效,所有后續的 Dockerfile 指令都將產生新的鏡像,緩存不會被使用。
9. 版本控制和自動構建
最好把 Dockerfile 和對應的應用代碼一起放到版本控制中,然后能夠自動構建鏡像。這樣的好處是可以追蹤各個版本鏡像的內容,方便了解不同鏡像有什么區別,對于調試和回滾都有好處。
另外,如果運行鏡像的參數或者環境變量很多,也要有對應的文檔給予說明,并且文檔要隨著 Dockerfile 變化而更新,這樣任何人都能參考著文檔很容易地使用鏡像,而不是下載了鏡像不知道怎么用。
10. 一個容器只運行一個進程
在大多數情況下,你應該保證在一個容器中只運行一個進程。將多個應用解耦到不同容器中,可以保證應用的橫向擴展性和重用容器。如果你一個服務依賴于另一個服務,可以利用容器鏈接(link)。
11. 將多行參數排序
將多行參數按字母順序排序(比如要安裝多個包時)。這可以幫助你避免重復包含同一個包,更新包列表時也更容易。也便于 PRs 閱讀和省察。建議在反斜杠符號\之前添加一個空格,以增加可讀性。
下面來自buildpack-deps鏡像的例子:
RUN apt-get update && apt-get install -y \bzr \cvs \git \mercurial \subversion12. 不要在 Dockerfile 定義公共端口
如果在 Dockerfile 中定義了公共端口,你就只能運行一個實例。一般來說都是自定義私有端口,然后在運行時使用 -p 參數指定公共端口。
# private and public mapping EXPOSE 80:8080# private only EXPOSE 8013. 在定義 CMD 和 ENTRYPOINT 時使用數組
定義定義 CMD 和 ENTRYPOINT 時可以使用下面2種方法。但是如果使用方法1的時候,docker 會在前面自動加上 /bin/sh -c ,這樣就會導致某個非預期的結果。因此盡量使用方法2也就是數組方式。
CMD /bin/echo # or CMD ["/bin/echo"]Dockerfile 指令
下面針對 Dockerfile 中各種指令的最佳編寫方式給出建議。
FROM
只要有可能,請使用當前官方倉庫作為構建你鏡像的基礎。我們推薦使用 Debian image ,因為它被嚴格控制并保持最小尺寸(當前小于 150 mb),但仍然是一個完整的發行版。
LABEL
你可以給鏡像添加標簽來幫助組織鏡像、記錄許可信息、輔助自動化構建,或者因為其他的原因。每個標簽一行,由 LABEL 開頭加上一個或多個標簽對。下面的示例展示了各種不同的可能格式。注釋內容是解釋。
注意:如果你的字符串中包含空格,將字符串放入引號中或者對空格使用轉義。如果字符串內容本身就包含引號,必須對引號使用轉義。
# Set one or more individual labels LABEL com.example.version="0.0.1-beta" LABEL vendor="ACME Incorporated" LABEL com.example.release-date="2015-02-12" LABEL com.example.version.is-production=""# Set multiple labels on one line LABEL com.example.version="0.0.1-beta" com.example.release-date="2015-02-12"# Set multiple labels at once, using line-continuation characters to break long lines LABEL vendor=ACME\ Incorporated \com.example.is-beta= \com.example.is-production="" \com.example.version="0.0.1-beta" \com.example.release-date="2015-02-12"RUN
一如往常,保持你的 Dockerfile 文件更具可讀性,可理解性,以及可維護性,將長的或復雜的RUN聲明用反斜杠分割成多行。
apt-get
也許 RUN 指令最常見的用例是安裝包用的 apt-get 。因為 RUN apt-get 指令會安裝包,所以有幾個問題需要注意。
不要使用RUN apt-get upgrade或dist-upgrade,因為許多基礎鏡像中的“必須”包不會在一個非特權容器中升級。如果基礎鏡像中的某個包過時了,你應該聯系它的維護者。如果你確定某個特定的包,比如 foo ,需要升級,使用apt-get install -y foo就行,該指令會自動升級 foo 包。
永遠將RUN apt-get update和apt-get install組合成一條RUN聲明,例如:
RUN apt-get update && apt-get install -y \package-bar \package-baz \package-foo將apt-get update放在一條單獨的RUN聲明中會導致緩存問題以及后續的apt-get install失敗。比如,假設你有一個 Dockerfile 文件:
FROM ubuntu:14.04 RUN apt-get update RUN apt-get install -y curl構建鏡像后,所有的層都在 Docker 的緩存中。假設你后來又修改了其中的apt-get install,添加了一個包:
FROM ubuntu:14.04 RUN apt-get update RUN apt-get install -y curl nginxDocker 發現修改后的RUN apt-get update指令和之前的完全一樣。所以,apt-get update不會執行,而是使用之前的緩存鏡像。因為apt-get update沒有運行,后面的apt-get install可能安裝的是過時的curl和nginx版本。
使用RUN apt-get update && apt-get install -y可以確保你的 Dockerfiles 每次安裝的都是包的最新的版本,而且這個過程不需要進一步的編碼或額外干預。這項技術叫作 cache busting 。你也可以顯示指定一個包的版本號來達到 cache-busting。這就是所謂的固定版本,例如:
RUN apt-get update && apt-get install -y \package-bar \package-baz \package-foo=1.3.*固定版本會迫使構建過程檢索特定的版本,而不管緩存中有什么。這項技術也可以減少因所需包中未預料到的變化而導致的失敗。
下面是一個RUN指令的示例模板,展示了所有關于apt-get的建議。
RUN apt-get update && apt-get install -y \aufs-tools \automake \build-essential \curl \dpkg-sig \libcap-dev \libsqlite3-dev \mercurial \reprepro \ruby1.9.1 \ruby1.9.1-dev \s3cmd=1.1.* \&& rm -rf /var/lib/apt/lists/*其中s3cmd指令指定了一個版本號1.1.0*。如果之前的鏡像使用的是更舊的版本,指定新的版本會導致apt-get udpate緩存失效并確保安裝的是新版本。
另外,清理掉 apt 緩存,刪除var/lib/apt/lists可以減小鏡像大小。因為RUN指令的開頭為apt-get udpate,包緩存總是會在apt-get install之前刷新。
注意:官方的 Debian 和 Ubuntu 鏡像會自動運行apt-get clean,所以不需要顯示的調用apt-get clean。
CMD
CMD 指令用于執行目標鏡像中包含的軟件,可以包含參數。 CMD 大多數情況下都應該以 CMD ["executable", "param1", "param2"…]的形式使用。因此,如果創建鏡像的目的是為了部署某個服務(比如 Apache、Rails…),你可能會執行類似于CMD ["apache2","-DFOREGROUND"]形式的命令。實際上,我們建議任何服務鏡像都使用這種形式的命令。
多數情況下, CMD 都需要一個交互式的 shell(bash,Python,perl,etc),例如,CMD ["perl","-de0"],CMD ["php","-a"]。使用這種形式意味著,當你執行類似docker run -it python時,你會進入一個準備好的 shell 中。 CMD 應該在極少的情況下才能以CMD ["param","param"]的形式與 ENTRYPOINT 協同使用,除非你和你的預期用戶都對 ENTRYPOINT 的工作方式十分熟悉。
EXPOSE
EXPOSE 指令用于指定容器將要監聽連接的端口。因此,你應該為你的應用程序使用常見熟知的端口。例如,提供 Apache web 服務的鏡像將使用 EXPOSE 80,而提供 MongoDB 服務的鏡像使用 EXPOSE 27017,等等。
對于外部訪問,鏡像用戶可以在執行docker run時使用一個標志來指示如何將指定的端口映射到所選擇的端口。對于容器 鏈接,Docker 提供環境變量從接收容器回溯到源容器(例如,MYSQL_PORT_3306_TCP)。
ENV
為了便于新程序運行,你可以使用ENV來為容器中安裝的程序更新PATH環境變量。例如,ENV PATH /usr/local/nginx/bin:$PATH將確保CMD ["nginx"]能正確運行。
ENV 指令也可用于為你想要容器化的服務提供必要的環境變量,比如 Postgres 需要的 PGDATA 。
最后, ENV 也能用于設置常見的版本號,以便維護 version bumps,參考下面的示例:
ENV PG_MAJOR 9.3 ENV PG_VERSION 9.3.4 RUN curl -SL http://example.com/postgres-$PG_VERSION.tar.xz | tar -xJC /usr/src/postgress && … ENV PATH /usr/local/postgres-$PG_MAJOR/bin:$PATH類似于程序中的常量(與硬編碼的值相對),這種方法可以讓你只需改變單條 ENV 指令來自動改變容器中的軟件版本。
ADD 和 COPY
雖然ADD和COPY功能類似,但一般優先使用COPY。因為它比ADD更透明。COPY只支持簡單將本地文件拷貝到容器中,而ADD有一些并不明顯的功能(比如本地 tar 提取和遠程 URL 支持)。因此,ADD的最佳用例是將本地 tar 文件自動提取到鏡像中,例如ADD rootfs.tar.xz。
如果你的 Dockerfiles 有多個步驟需要使用上下文中不同的文件。單獨COPY每個文件,而不是一次性COPY完。這將保證每個步驟的構建緩存只在特定的文件變化時失效。
例如:
COPY requirements.txt /tmp/ RUN pip install --requirement /tmp/requirements.txt COPY . /tmp/如果將COPY . /tmp/放置在RUN指令之前,只要.目錄中任何一個文件變化,都會導致后續指令的緩存失效。
為了讓鏡像盡量小,最好不要使用ADD指令從遠程 URL 獲取包,而是使用curl和wget。這樣你可以在文件提取完之后刪掉不再需要的文件,可以避免在鏡像中額外添加一層。(譯者注:ADD指令不能和其他指令合并,所以前者ADD指令會單獨產生一層鏡像。而后者可以將獲取、提取、安裝、刪除合并到同一條RUN指令中,只有一層鏡像。)比如,你應該盡量避免下面這種用法:
ADD http://example.com/big.tar.xz /usr/src/things/ RUN tar -xJf /usr/src/things/big.tar.xz -C /usr/src/things RUN make -C /usr/src/things all而是使用下面這種:
RUN mkdir -p /usr/src/things \&& curl -SL http://example.com/big.tar.xz \| tar -xJC /usr/src/things \&& make -C /usr/src/things all上面使用的管道操作,所以沒有中間文件需要刪除。
對于其他不需要ADD的自動提取(tar)功能的文件或目錄,你應該堅持使用COPY。
ENTRYPOINT
ENTRYPOINT 的最佳用處是設置鏡像的主命令,允許將鏡像當成命令本身來運行(用CMD提供默認選項)。
例如,下面的示例鏡像提供了命令行工具s3cmd:
ENTRYPOINT ["s3cmd"] CMD ["--help"]現在該鏡像直接這么運行,顯示命令幫助:
$ docker run s3cmd或者提供正確的參數來執行某個命令:
$ docker run s3cmd ls s3://mybucket這很有用,因為鏡像名還可以當成命令行的參考。
ENTRYPOINT指令也可以結合一個輔助腳本使用,和前面命令行風格類似,即使啟動工具需要不止一個步驟。
例如,Postgres 官方鏡像使用下面的腳本作為 ENTRYPOINT :
set -eif [ "$1" = 'postgres' ]; thenchown -R postgres "$PGDATA"if [ -z "$(ls -A "$PGDATA")" ]; thengosu postgres initdbfiexec gosu postgres "$@" fiexec "$@"注意:該腳本使用了 Bash 的內置命令 exec,所以最后運行的進程就是容器的 PID 為1的進程。這樣,進程就可以接收到任何發送給容器的 Unix 信號了。
該輔助腳本被拷貝到容器,并在容器啟動時通過ENTRYPOINT執行:
該腳本可以讓用戶用幾種不同的方式和 Postgres 交互。
你可以很簡單地啟動 Postgres :
$ docker run postgres也可以執行 Postgres 并傳遞參數:
$ docker run postgres postgres --help最后,你還可以啟動另外一個完全不同的工具,比如 Bash :
$ docker run --rm -it postgres bashVOLUME
VOLUME 指令用于暴露任何數據庫存儲區域,配置文件,或容器創建的文件和目錄。強烈建議使用 VOLUME 來管理鏡像中的可變部分和鏡像用戶可以改變部分。
USER
如果某個服務不需要特權執行,建議使用 USER 指令切換到非 root 用戶。先在 Dockerfile 中使用類似RUN groupadd -r postgres && useradd -r -g postgres 。 postgres 的指令創建用戶和用戶組。
注意:在鏡像中,用戶和用戶組每次被分配的 UID/GID 都是不確定的,下次重新構建鏡像時被分配到的 UID/GID 可能會不一樣。如果要依賴確定的 UID/GID,你應該顯示的指定一個 UID/GID。
你應該避免使用sudo,因為它不可預期的 TTY 和信號轉發行為可能造成的問題比解決的還多。如果你真的需要和sudo類似的功能(例如,以 root 權限初始化某個守護進程,以非 root 權限執行它),你可以使用gosu。
最后,為了減少層數和復雜度,避免頻繁地使用USER來回切換用戶。
WORKDIR
為了清晰性和可靠性,你應該總是在WORKDIR中使用絕對路徑。另外,你應該使用WORKDIR來替代類似于RUN cd ... && do-something的指令,后者難以閱讀、排錯和維護。
ONBUILD
ONBUILD中的命令會在當前鏡像的子鏡像構建時執行。可以把ONBUILD命令當成父鏡像的 Dockerfile 傳遞給子鏡像的 Dockerfile 的指令。
在子鏡像的構建過程中, Docker 會在執行 Dockerfile 中的任何指令之前,先執行父鏡像通過 ONBUILD 傳遞的指令。
當從給定鏡像構建新鏡像時,ONBUILD指令很有用。例如,你可能會在一個語言棧鏡像中使用ONBUILD,語言棧鏡像用于在 Dockerfile 中構建用戶使用相應語言編寫的任意軟件,正如 Ruby 的 ONBUILD 變體
使用 ONBUILD 構建的鏡像應用一個單獨的標簽,例如:ruby:1.9-onbuild或ruby:2.0-onbuild。
在 ONBUILD 中使用ADD或 COPY 時要格外小心。如果新的構建上下文中缺少對應的資源, onbuild 鏡像會災難性地失敗。添加一個單獨的標簽,允許 Dockerfile 的作者做出選擇,將有助于緩解這種情況。
Docker官方經典參考范例
- GO
- perl
- hy
- ruby
作者:慢清塵
鏈接:https://www.jianshu.com/p/25029238c011
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
總結
以上是生活随笔為你收集整理的Docker操作命令详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 利用docker编译Android源码
- 下一篇: 一篇不一样的docker原理解析