1. 程式人生 > >理解Docker(2):Docker 映象

理解Docker(2):Docker 映象

來源:http://www.cnblogs.com/sammyliu/p/5877964.html

對於每個軟體,除了它自身的程式碼以外,它的執行還需要有一個執行環境和依賴。不管這個軟體是象往常一樣執行在物理機或者虛機之中,還是執行在現在的容器之中,這些都是不變的。在傳統環境中,軟體在執行之前也需要經過 程式碼開發->執行環境準備 -> 安裝軟體 -> 執行軟體 等環節,在容器環境中,中間的兩個環節被映象製作過程替代了。也就是說,映象的製作也包括執行環境準備和安裝軟體等兩個主要環節,以及一些其他環節。因此,Docker 容器映象其實並沒有什麼新的理論,只是這過程有了新的方式而已。

  映象(image)是動態的容器的靜態表示(specification),包括容器所要執行的應用程式碼以及執行時的配置。Docker 映象包括一個或者多個只讀層( read-only layers ),因此,映象一旦被建立就再也不能被修改了。一個執行著的Docker 容器是一個映象的例項( instantiation )。從同一個映象中執行的容器包含有相同的應用程式碼和執行時依賴。但是不像映象是靜態的,每個執行著的容器都有一個可寫層( writable layer ,也成為容器層 container layer),它位於底下的若干只讀層之上。執行時的所有變化,包括對資料和檔案的寫和更新,都會儲存在這個層中。因此,從同一個映象執行的多個容器包含了不同的容器層。

 Docker 有兩種方式來建立一個容器映象:

  • 建立一個容器,執行若干命令,再使用 docker commit 來生成一個新的映象。不建議使用這種方案。
  • 建立一個 Dockerfile 然後再使用 docker build 來建立一個映象。大多人會使用 Dockerfile 來建立映象。

 1. docker build 生成映象

1.1 生成過程例項

 在使用 Dockerfile 建立容器之前,需要先準備一個 Dockerfile 檔案,然後執行 docker build 命令來建立映象。我們通過下面的例子來看看Docker 建立容器的過程。

複製程式碼

FROM ubuntu:14.04

MAINTAINER sammy "[email protected]"

RUN apt-get update

RUN apt-get -y install ntp

EXPOSE 5555

CMD ["/usr/sbin/ntpd"]

複製程式碼

這是一個非常簡單的Dockerfile,它的目的是基於 Ubuntu 14.04 基礎映象安裝 ntp 從而生成一個新的映象。看看其過程:

複製程式碼

[email protected]:/home/sammy/ntponubuntu# docker build -t sammy_ntp2 .
Sending build context to Docker daemon 2.048 kB
Step 1 : FROM ubuntu:14.04
 ---> 4a725d3b3b1c
Step 2 : MAINTAINER sammy "
[email protected]
" ---> Using cache ---> c4299e3f774c Step 3 : RUN apt-get update ---> Using cache ---> 694a19d54103 Step 4 : RUN apt-get -y install ntp ---> Running in 9bd153c65a76 Reading package lists... ... Fetched 561 kB in 10s (51.1 kB/s) Selecting previously unselected package libedit2:amd64. (Reading database ... 11558 files and directories currently installed.) ... Processing triggers for libc-bin (2.19-0ubuntu6.9) ... Processing triggers for ureadahead (0.100.0-16) ... ---> 9cc05cf6f48d Removing intermediate container 9bd153c65a76 Step 5 : EXPOSE 5555 ---> Running in eb4633151d98 ---> f5c96137bec9 Removing intermediate container eb4633151d98 Step 6 : CMD /usr/sbin/ntpd ---> Running in e81b1eae3678 ---> af678df648bc Removing intermediate container e81b1eae3678 Successfully built af678df648bc

複製程式碼

Dockerfile 中的每個步驟都會對應每一個 docker build 輸出中的 step。

Step 1:FROM ubuntu:14.04

獲取基礎映象 ubuntu:14.04. Docker 首先會在本地查詢,如果找到了,則直接利用;否則從 Docker registry 中下載。在第一次使用這個基礎映象的時候,Docker 會從 Docker Hub 中下載這個映象,並儲存在本地:

複製程式碼

Step 1 : FROM ubuntu:14.04
14.04: Pulling from library/ubuntu
862a3e9af0ae: Pull complete
6498e51874bf: Pull complete
159ebdd1959b: Pull complete
0fdbedd3771a: Pull complete
7a1f7116d1e3: Pull complete
Digest: sha256:5b5d48912298181c3c80086e7d3982029b288678fccabf2265899199c24d7f89
Status: Downloaded newer image for ubuntu:14.04
 ---> 4a725d3b3b1c

複製程式碼

以後再使用的時候就直接使用這個映象而不再需要下載了。

Step 2:MAINTAINER sammy "[email protected]"

本例中依然是從 Cache 中環境新的映象。在第一次的時候,Docker 會建立一個臨時的容器 1be8f33c1846,然後執行 MAINTAINER 命令,再使用 docker commit 生成新的映象

Step 2 : MAINTAINER sammy "[email protected]"
 ---> Running in 1be8f33c1846
 ---> c4299e3f774c

通過這個臨時容器的過程(create -> commit -> destroy),生成了新的映象 c4299e3f774c:

2016-09-16T21:58:09.010886393+08:00 container create 1be8f33c18469f089d1eee8c444dad1ff0c7309be82767092082311379245358 (image=sha256:4a725d3b3b1cc18c8cbd05358ffbbfedfe1eb947f58061e5858f08e2899731ee, name=focused_poitras)
2016-09-16T21:58:09.060071206+08:00 container commit 1be8f33c18469f089d1eee8c444dad1ff0c7309be82767092082311379245358 (comment=, image=sha256:4a725d3b3b1cc18c8cbd05358ffbbfedfe1eb947f58061e5858f08e2899731ee, name=focused_poitras)
2016-09-16T21:58:09.071988068+08:00 container destroy 1be8f33c18469f089d1eee8c444dad1ff0c7309be82767092082311379245358 (image=sha256:4a725d3b3b1cc18c8cbd05358ffbbfedfe1eb947f58061e5858f08e2899731ee, name=focused_poitras)

這個映象是基於 ubuntu 14.04 基礎映象生成的,layers 沒有變化,只是元資料 CMD 發生了改變:

"Cmd": [
                "/bin/sh",
                "-c",
                "#(nop) ",
                "MAINTAINER sammy \"[email protected]\""
            ]

因此可以認為只是映象的元資料發生了改變。生成的新的映象作為中間映象會被儲存在 cache 中。

 Step 3: RUN apt-get update

本例中Docker 仍然從快取中獲取了映象。在第一次的時候,Docker 仍然是通過建立臨時容器在執行 docker commit 的方式來建立新的映象:

複製程式碼

Step 3 : RUN apt-get update
 ---> Running in 8b3b97af3bd7
Ign http://archive.ubuntu.com trusty InRelease
Get:1 http://archive.ubuntu.com trusty-updates InRelease [65.9 kB]
...
Get:22 http://archive.ubuntu.com trusty/universe amd64 Packages [7589 kB]
Fetched 22.2 MB in 16min 21s (22.6 kB/s)
Reading package lists...
 ---> 694a19d54103
Removing intermediate container 8b3b97af3bd7

複製程式碼

通過以上步驟,生成了新的中間映象 694a19d54103,它也會被儲存在快取中。你可以使用 docker inspect 694a19d54103 命令檢視該中間映象,但是無法在docker images 列表中找到它,這是因為 docker images 預設隱藏了中間狀態的映象,因此你需要使用 docker images -a 來獲取它:

[email protected]:/home/sammy# docker images -a | grep 694a19d54103
<none>                  <none>              694a19d54103        11 hours ago        210.1 MB

該映象和原始映象相比,多了一個 layer,它儲存的是 apt-get update 命令所帶來的變化:

複製程式碼

"RootFS": {
            "Type": "layers",
            "Layers": [
                "sha256:102fca64f92471ff7fca48e55807ae2471502822ba620292b0a06ebcab907cf4",
                "sha256:24fe29584c046f2a88f7f566dd0bf7b08a8c0d393dfad8370633b0748bba8cbc",
                "sha256:530d731d21e1b1bbe356d70d3bca4d72d76fed89e90faab271d29bd58c8ccea4",
                "sha256:344f56a35ff9fc747ada7d2b88bd21c49b2ec404872662cbaf0a65201873c0c6",
                "sha256:ffb6ddc7582aa7e2e73f102df3ffcd272e59b7cf3f7abefe08d11a7c85dea53a",
                "sha256:a1afe95c99b39c30b5c1d3e8fda451bd3f066be304616197f1046e64cf6cda93" #這一層是新加的
            ]
        }

複製程式碼

Step 4: RUN apt-get -y install ntp

和上面 Step 3 過程一樣,這個步驟也會通過建立臨時容器,執行該命令,再使用 docker commit 命令生成一箇中間映象 9cc05cf6f48d 。和上面步驟生成的映象相比,它又多了一層:

複製程式碼

[email protected]:/home/sammy# docker images -a | grep 9cc05cf6f48d
<none>                  <none>              9cc05cf6f48d        10 hours ago        212.8 MB
[email protected]:/home/sammy# docker inspect --format={{'.RootFS.Layers'}} 9cc05cf6f48d
[sha256:102fca64f92471ff7fca48e55807ae2471502822ba620292b0a06ebcab907cf4 
sha256:24fe29584c046f2a88f7f566dd0bf7b08a8c0d393dfad8370633b0748bba8cbc 
sha256:530d731d21e1b1bbe356d70d3bca4d72d76fed89e90faab271d29bd58c8ccea4 
sha256:344f56a35ff9fc747ada7d2b88bd21c49b2ec404872662cbaf0a65201873c0c6 
sha256:ffb6ddc7582aa7e2e73f102df3ffcd272e59b7cf3f7abefe08d11a7c85dea53a 
sha256:a1afe95c99b39c30b5c1d3e8fda451bd3f066be304616197f1046e64cf6cda93 
sha256:a93086f33a2b7ee18eec2454b468141f95a403f5081284b6f177f83cdb3d54ba]

複製程式碼

Step 5: EXPOSE 5555

 這一步和上面的 Step 2 一樣,Docker 生成了一個臨時容器,執行 EXPOSE 55 命令,再通過 docker commit 建立了中間映象 f5c96137bec9。該映象的 layers 沒有變化,但是元資料發生了一些變化,包括:

複製程式碼

"ExposedPorts": {
                "5555/tcp": {}
            }
"Cmd": [
                "/bin/sh",
                "-c",
                "#(nop) ",
                "EXPOSE 5555/tcp"
            ]

複製程式碼

Step 6: CMD ["/usr/sbin/ntpd"]

這一步和上面的步驟相同,最終它建立了映象 af678df648bc,該映象只是修改了 CMD 元資料:

 "Cmd": [
                "/bin/sh",
                "-c",
                "#(nop) ",
                "CMD [\"/usr/sbin/ntpd\"]"
            ]

該映象也是Docker 根據本 Dockerfile 生成的最終映象。它也出現在了 docker images 結果中:

[email protected]:/home/sammy# docker images | grep af678df648bc
sammy_ntp2              latest              af678df648bc        11 hours ago        212.8 MB

我們可以使用 docker history 命令檢視該映象中每一層的資訊:

複製程式碼

[email protected]:/home/sammy/ntponubuntu# docker history af678df648bc
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
af678df648bc        16 hours ago        /bin/sh -c #(nop)  CMD ["/usr/sbin/ntpd"]       0 B
f5c96137bec9        16 hours ago        /bin/sh -c #(nop)  EXPOSE 5555/tcp              0 B
9cc05cf6f48d        16 hours ago        /bin/sh -c apt-get -y install ntp               2.679 MB
694a19d54103        16 hours ago        /bin/sh -c apt-get update                       22.17 MB
c4299e3f774c        17 hours ago        /bin/sh -c #(nop)  MAINTAINER sammy "[email protected]   0 B
4a725d3b3b1c        3 weeks ago         /bin/sh -c #(nop) CMD ["/bin/bash"]             0 B
<missing>           3 weeks ago         /bin/sh -c mkdir -p /run/systemd && echo 'doc   7 B
<missing>           3 weeks ago         /bin/sh -c sed -i 's/^#\s*\(deb.*universe\)$/   1.895 kB
<missing>           3 weeks ago         /bin/sh -c rm -rf /var/lib/apt/lists/*          0 B
<missing>           3 weeks ago         /bin/sh -c set -xe   && echo '#!/bin/sh' > /u   194.6 kB
<missing>           3 weeks ago         /bin/sh -c #(nop) ADD file:ada91758a31d8de3c7   187.8 MB

複製程式碼

以上過程說明:

  • 容器映象包括元資料和檔案系統,其中檔案系統是指對基礎映象的檔案系統的修改,元資料不影響檔案系統,只是會影響容器的配置
  • 每個步驟都會生成一個新的映象,新的映象與上一次的映象相比,要麼元資料有了變化,要麼檔案系統有了變化而多加了一層
  • Docker 在需要執行指令時通過建立臨時映象,執行指定的命令,再通過 docker commit 來生成新的映象
  • Docker 會將中間映象都儲存在快取中,這樣將來如果能直接使用的話就不需要再從頭建立了。關於映象快取,請搜尋相關文件。

1.2 Docker 映象分層,COW 和 映象大小(size)

1.2.1 映象分層和容器層

  從上面例子可以看出,一個 Docker 映象是基於基礎映象的多層疊加,最終構成和容器的 rootfs (根檔案系統)。當 Docker 建立一個容器時,它會在基礎映象的容器層之上新增一層新的薄薄的可寫容器層。接下來,所有對容器的變化,比如寫新的檔案,修改已有檔案和刪除檔案,都只會作用在這個容器層之中。因此,通過不拷貝完整的 rootfs,Docker 減少了容器所佔用的空間,以及減少了容器啟動所需時間。

1.2.2 COW 和映象大小

  COW,copy-on-write 技術,一方面帶來了容器啟動的快捷,另一方也造成了容器映象大小的增加。每一次 RUN 命令都會在映象上增加一層,每一層都會佔用磁碟空間。舉個例子,在 Ubuntu 14.04 基礎映象中執行 RUN apt-get upgrade 會在保留基礎層的同時再建立一個新層來放所有新的檔案,而不是修改老的檔案,因此,新的映象大小會超過直接在老的檔案系統上做更新時的檔案大小。因此,為了減少映象大小起見,所有檔案相關的操作,比如刪除,釋放和移動等,都需要儘可能地放在一個 RUN 指令中進行。

  比如說,通過將上面的示例 Dockerfile 修改為:

FROM ubuntu:14.04
MAINTAINER sammy "[email protected]"
RUN apt-get update && apt-get -y install ntp
EXPOSE 5555
CMD ["/usr/sbin/ntpd"]

結果產生的映象,不僅層數少了一層(7 -> 6),而且大小減少了 0.001M :),因為這個例子比較特殊,檔案都是新增,而沒有更新,因此size 的下降非常小。

1.2.3 使用容器需要避免的一些做法

這篇文章 10 things to avoid in docker containers 列舉了一些在使用容器時需要避免的做法,包括:

  • 不要在容器中儲存資料(Don’t store data in containers
  • 將應用打包到映象再部署而不是更新到已有容器(Don’t ship your application in two pieces
  • 不要產生過大的映象 (Don’t create large images
  • 不要使用單層映象 (Don’t use a single layer image
  • 不要從執行著的容器上產生映象 (Don’t create images from running containers )
  • 不要只是使用 “latest”標籤 (Don’t use only the “latest” tag
  • 不要在容器內執行超過一個的程序 (Don’t run more than one process in a single container )
  • 不要在容器內儲存 credentials,而是要從外面通過環境變數傳入 ( Don’t store credentials in the image. Use environment variables
  • 不要使用 root 使用者跑容器程序(Don’t run processes as a root user )
  • 不要依賴於IP地址,而是要從外面通過環境變數傳入 (Don’t rely on IP addresses )

1.3 映象的內容

容器映象的內容,其實是一個 json 檔案加上 tar 包。以非常小的映象 kubernetes/pause 為例,我們來做個實驗:

(1)將映象匯出為 tar 檔案

[email protected]:/home/ubuntu/kub/image# docker save -o pause.tar kubernetes/pause:latest
[email protected]:/home/ubuntu/kub/image# ls
pause.tar

(2)解壓 pause.tar 檔案

複製程式碼

[email protected]:/home/ubuntu/kub/image# tar -xf pause.tar

[email protected]:/home/ubuntu/kub/image/pause# ls -l
total 280
drwxr-xr-x 2 root root 4096 Jan 23 09:02 afa9f35badc97e21193ee701222d9edfc5b0f0e5c518d357eb8b016d8287cda7
drwxr-xr-x 2 root root 4096 Jul 19 2014 e0b1695ad29a961b7e28713942942786692107d7f9087d72ccf9bbc0a3ab133e
drwxr-xr-x 2 root root 4096 Jan 23 09:20 e3caa892ed5297d0c98916b251c5be1d26c3a50b581fe145e3a6516c00531464
-rw-r--r-- 1 root root 1691 Jul 19 2014 f9d5de0795395db6c50cb1ac82ebed1bd8eb3eefcebb1aa724e01239594e937b.json
-rw-r--r-- 1 root root 366 Jan 1 1970 manifest.json
-rw------- 1 root root 258560 Jan 23 09:02 pause.tar
-rw-r--r-- 1 root root 99 Jan 1 1970 repositories

複製程式碼

其中的 repositories 檔案的內容,就是映象名稱、版本、最上層的layer的名稱:

[email protected]:/home/ubuntu/kub/image# cat repositories
{"kubernetes/pause":{"latest":"afa9f35badc97e21193ee701222d9edfc5b0f0e5c518d357eb8b016d8287cda7"}}

而 manifest.json 檔案則保持的是映象的元資料,包括真正元資料 json 檔案的名稱及每一層的名稱,tag 等:

[email protected]:/home/ubuntu/kub/image# cat manifest.json
[{"Config":"f9d5de0795395db6c50cb1ac82ebed1bd8eb3eefcebb1aa724e01239594e937b.json","RepoTags":["kubernetes/pause:latest"],"Layers":["e0b1695ad29a961b7e28713942942786692107d7f9087d72ccf9bbc0a3ab133e/layer.tar","e3caa892ed5297d0c98916b251c5be1d26c3a50b581fe145e3a6516c00531464/layer.tar","afa9f35badc97e21193ee701222d9edfc5b0f0e5c518d357eb8b016d8287cda7/layer.tar"]}]

f9d5de0795395db6c50cb1ac82ebed1bd8eb3eefcebb1aa724e01239594e937b.json 檔案則真正包含映象的所有元資料。

而剩下的3個資料夾則與該映象的3個layers 一一對應:

複製程式碼

"RootFS": {
            "Type": "layers",
            "Layers": [
                "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef",
                "sha256:e16a89738269fec22db26ec6362823a9ec42d0163685d88ba03c4fb5d5e723f6",
                "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef"
            ]
        }

複製程式碼

每個資料夾中的內容為:

[email protected]:/home/ubuntu/kub/image# ls e0b1695ad29a961b7e28713942942786692107d7f9087d72ccf9bbc0a3ab133e -l
total 12
-rw-r--r-- 1 root root  393 Jul 19  2014 json
-rw-r--r-- 1 root root 1024 Jul 19  2014 layer.tar
-rw-r--r-- 1 root root    3 Jul 19  2014 VERSION

因為 pause 映象比較特殊,解壓 layer.tar 後沒有檔案。如果看 nginx 映象的某層的 layer.tar 檔案,則能看到該layer中包含的檔案:

複製程式碼

[email protected]:/home/ubuntu/kub/image/nginx/2c9d2d9d91f48573ea451f8d529e88dee79d64782892def6063fdda3f127d33c# ls -l
total 39268
drwxr-xr-x  2 root root     4096 Jan  8 21:49 bin
drwxr-xr-x 13 root root     4096 Jan  8 21:54 etc
-rw-r--r--  1 root root      469 Jan  8 23:32 json
drwxr-xr-x  3 root root     4096 Dec 10 08:00 lib
drwx------  2 root root     4096 Jan  8 21:56 root
drwxr-xr-x  2 root root     4096 Jan  8 21:28 run
drwxr-xr-x  2 root root     4096 Jan  8 21:49 sbin
drwxr-xr-x  7 root root     4096 Dec 10 08:00 usr
drwxr-xr-x  5 root root     4096 Dec 10 08:00 var
-rw-r--r--  1 root root        3 Jan  8 23:32 VERSION

複製程式碼

從以上分析可見,

  • docker 映象中主要就是 tar 檔案包和元資料 json 檔案
  • docker 映象的打包過程,其實就是將每一層對應的檔案打包過程,最後組成一個單一的 tar 檔案
  • docker 映象的使用過程,其實就是將一層層的 tar 檔案接包到檔案系統的過程。

2. Dockerfile 語法

上面的步驟說明了 Docker 可以通過讀取 Dockerfile 的內容來生成容器映象。Dockerfile 的每一行都是 INSTRUCTION arguments 格式,即 “指令 引數”。關於 Dockerfile 的預防,請參考 https://docs.docker.com/engine/reference/builder/。下面只是就一些主要的指令做一些說明。

2.1 幾個主要指令

2.1.1 ADD 和 COPY

Add:將 host 上的檔案拷貝到或者將網路上的檔案下載到容器中的指定目錄

# Usage: ADD [source directory or URL] [destination directory]
ADD /my_app_folder /my_app_folder

例子:

FROM ubuntu:14.04
MAINTAINER Sammy Liu <[email protected]>
ADD temp dockfile
ENTRYPOINT top

ADD 指令會將本地 temp 目錄中的檔案拷貝到容器的 dockfile 目錄下面,從而在映象中增加一個 layer。在未指定絕對路徑的時候,會放到 WORKDIR 目錄下面。

[email protected]:/# ls dockfile/
dockerfile-add  dockerfile-cmd  dockerfile-env  dockerfile-ports  dockerfile-user  dockerfile-user-h
[email protected]:/# pwd
/

那兩者有什麼區別呢?

  • ADD 多了2個功能, 下載URL和對支援的壓縮格式的包進行解壓.  其他都一樣。比如 ADD http://foo.com/bar.go /tmp/main.go 會將檔案從因特網上方下載下來,ADD /foo.tar.gz /tmp/ 會將壓縮檔案解壓再COPY過去
  • 如果你不希望壓縮檔案拷貝到container後會被解壓的話, 那麼使用COPY。
  • 如果需要自動下載URL並拷貝到container的話, 請使用ADD

2.1.2 CMD

CMD:在容器被建立後執行的命令,和 RUN 不同,它是在構造容器時候所執行的命令

# Usage 1: CMD application "argument", "argument", ..
CMD "echo" "Hello docker!"

CMD 有三種格式:

  • CMD ["executable","param1","param2"] (like an exec, preferred form)
  • CMD ["param1","param2"] (作為 ENTRYPOINT 的引數)
  • CMD command param1 param2 (作為 shell 執行)

一個Dockerfile裡只能有一個CMD,如果有多個,只有最後一個生效。

2.1.3 ENTRYPOINT

ENTRYPOINT :設定預設應用,會保證每次容器被建立後該應用都會被執行。CMD 和 ENTRYPOINT 的關係會在下面詳細解釋。

2.1.4 ENV:設定環境變數,可以使用多次

# Usage: ENV key value
ENV SERVER_WORKS 4

設定了後,後續的RUN命令都可以使用,並且會作為容器的環境變數。舉個例子,下面是 dockfile:

FROM ubuntu:14.04
ENV abc=1
ENV def=2
ENTRYPOINT top

生成映象:docker build -t envimg4 -f dockerfile-env . 其元資料包括了這兩個環境變數:

"Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
                "abc=1",
                "def=2"
            ],

啟動容器:docker run -it --name envc41 envimg4。也能看到:

"Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
                "abc=1",
                "def=2"
            ]

進入容器:能看到定義的 abc 和 def 變數

[email protected]:/home/sammy/ntponubuntu# docker exec -it envc41 bash
[email protected]:/# echo $abc
1
[email protected]:/# echo $def
2

2.1.5 EXPOSE :向容器外暴露一個埠

# Usage: EXPOSE [port]
EXPOSE 8080

2.1.6 FROM:指定進行的基礎映象,必須是第一條指令

# Usage: FROM [image name]
FROM ubuntu

2.1.7 MAINTAINER:可以在任意地方使用,設定映象的作者

# Usage: MAINTAINER [name]
MAINTAINER authors_name

2.1.8 RUN:執行命令,結果會生成映象中的一個新層

# Usage: RUN [command]
RUN aptitude install -y ntp

2.1.9 USER:設定該映象的容器的主程序所使用的使用者,以及後續 RUN, CMD 和 ENTRYPOINT 指令執行所使用的使用者

語法:

# Usage: USER [UID]
USER 751 

Dockerfile 中的預設使用者是基礎映象中所使用的使用者。比如,你的映象是從一個使用非 root 使用者 sammy 的映象繼承而來的,那麼你的 Dockerfile 中 RUN 指定執行的命令的使用者就會使用 sammy 使用者。

舉例:

(1)建立 dockerfile 檔案

[email protected]:/home/sammy/dockerfile# cat dockerfile-user
FROM ubuntu:14.04
USER 1000
ENTRYPOINT top

(2)建立映象:docker build -t dockerfile-user-1000 -f dockerfile-user .

(3)啟動容器:docker run -it --name c-user-1000-3 dockerfile-user-1000 top

能看出來當前使用者ID 為 1000: 

PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
    1 1000      20   0    4440    648    548 S  0.0  0.0   0:00.00 sh
    5 1000      20   0   19840   1296    984 R  0.0  0.1   0:00.00 top 

(4)基於該映象再創造一個映象,然後再啟動一個容器,可以發現容器中程序所使用的使用者ID 同樣為 1000. 

2.1.10 VOLUME:允許容器訪問host上某個目錄

# Usage: VOLUME ["/dir_1", "/dir_2" ..]
VOLUME ["/my_files"]

2.1.11 WORKDIR:設定 CMD 所指定命令的執行目錄

# Usage: WORKDIR /path
WORKDIR ~/

2.1.12 HEALTHCHECK: 容器健康檢查

這是 Docker 1.12 版本中新引入的指令,其語法為 HEALTHCHECK [OPTIONS] CMD command。 來看一個例子:

複製程式碼

FROM ubuntu:14.04
MAINTAINER Sammy Liu <[email protected]>
RUN apt-get update
RUN apt-get -y install curl
EXPOSE 8888
CMD while true; do echo 'hello world' | nc -l -p 8888; done
HEALTHCHECK --interval=10s --timeout=2s CMD curl -f http://localhost:8888/ || exit 1

複製程式碼

在啟動容器後,其health 狀態首先是 starting,然後在過了10秒做了第一次健康檢查成功後,變為 healthy 狀態。

[email protected]:/home/sammy/dockerfile# docker ps | grep c-health2
4c459eef1894        img-health2         "/bin/sh -c 'while tr"   7 seconds ago       Up 6 seconds (health: starting)   8888/tcp                  c-health2
[email protected]:/home/sammy/dockerfile# docker ps | grep c-health2
4c459eef1894        img-health2         "/bin/sh -c 'while tr"   9 seconds ago       Up 8 seconds (health: starting)   8888/tcp                  c-health2
[email protected]:/home/sammy/dockerfile# docker ps | grep c-health2
4c459eef1894        img-health2         "/bin/sh -c 'while tr"   11 seconds ago      Up 11 seconds (healthy)     8888/tcp                  c-health2

需要注意的是 CMD 是在容器之內執行的,因此,你需要確保其命令或者指令碼存在於容器之內並且可以被執行。

2.2 幾個比較繞的地方

2.2.1 EXPOSE 和 docker run -p -P 之間的關係

容器的埠必須被髮出(publish)出來後才能被外界使用。Dockerfile 中的 EXPOSE 只是“標記”某個埠會被暴露出來,只有在使用了 docker run -p 或者 -P 後,端口才會被“發出”出來,此時端口才能被使用。

舉例:

(1)Dockerfile

FROM ubuntu:14.04
MAINTAINER Sammy Liu <[email protected]>
CMD while true; do echo 'hello world' | nc -l -p 8888; done

(2)建立映象:docker build -t no-exposed-ports -f dockerfile-ports .

(3)啟動容器1:docker run -d --name no-exposed-ports1 no-exposed-ports。此容器沒有 exposed 和 published 任何埠。

(4)啟動容器2:docker run -d --name no-exposed-ports2 -p 8888:8888 no-exposed-ports

此時容器的 8888 埠被髮布為主機上的 8888 埠:

複製程式碼

"Ports": {
                "8888/tcp": [
                    {
                        "HostIp": "0.0.0.0",
                        "HostPort": "8888"
                    }
                ]
            }

複製程式碼

該埠會正確返回:

[email protected]:/home/sammy/dockerfile# telnet 0.0.0.0 8888
Trying 0.0.0.0...
Connected to 0.0.0.0.
Escape character is '^]'.
hello world
Connection closed by foreign host.

(5)使用 -P 引數:docker run -d --name no-exposed-ports3 -P no-exposed-ports

此時沒有任何埠被 published,說明 Docker 在使用了 “-P” 情形下只是自動將 exposed 的埠 published。

(6)使用 -p 加上一個不存在的埠:docker run -d --name no-exposed-ports4 -p 8889:8889 no-exposed-ports

此時,8889 埠會被暴露,但是沒法使用。說明 -p 會將沒有 exposed 的埠自動 exposed 出來。

(7)修改 dockerfile 為:

FROM ubuntu:14.04
MAINTAINER Sammy Liu <[email protected]>
EXPOSE 8888
CMD while true; do echo 'hello world' | nc -l -p 8888; done

建立映象exposed-ports, 再執行 docker run -d --name exposed-ports1 -P exposed-ports 建立一個容器,此時 8888 埠自動被 published 為主機上的 32776 埠:

複製程式碼

"Ports": {
                "8888/tcp": [
                    {
                        "HostIp": "0.0.0.0",
                        "HostPort": "32776"
                    }
                ]
            }

複製程式碼

可見:

  • EXPOSE或者--expose只是為其他命令提供所需資訊的元資料,或者只是告訴容器操作人員有哪些已知選擇。它只是作為記錄機制,也就是告訴使用者哪些埠會提供服務。它儲存在容器的元資料中。
  • 使用 -p 釋出特定埠。如果該埠已經被 exposed,則釋出它;如果它還沒有被 exposed,則它會被 exposed 和 published。Docker 不會檢查容器埠的正確性。
  • 使用 -P 時 Docker 會自動將所有已經被 exposed 的埠發出出來。

2.2.2 CMD 和 ENTRYPOINT

這兩個指令都指定了執行容器時所執行的命令。以下是它們共存的一些規則:

  • Dockerfile 至少需要指定一個 CMD 或者 ENTRYPOINT 指令
  • CMD 可以用來指定 ENTRYPOINT 指令的引數
  沒有 ENTRYPOINT ENTRYPOINT exec_entry p1_entry ENTRYPOINT [“exec_entry”, “p1_entry”]
沒有 CMD 錯誤,不允許 /bin/sh -c exec_entry p1_entry  exec_entry p1_entry
CMD [“exec_cmd”, “p1_cmd”]  exec_cmd p1_cmd  /bin/sh -c exec_entry p1_entry exec_cmd p1_cmd  exec_entry p1_entry exec_cmd p1_cmd
CMD [“p1_cmd”, “p2_cmd”]  p1_cmd p2_cmd  /bin/sh -c exec_entry p1_entry p1_cmd p2_cmd  exec_entry p1_entry p1_cmd p2_cmd
CMD exec_cmd p1_cmd  /bin/sh -c exec_cmd p1_cmd  /bin/sh -c exec_entry p1_entry /bin/sh -c exec_cmd p1_cmd  exec_entry p1_entry /bin/sh -c exec_cmd p1_cmd
備註 只有 CMD 時,執行 CMD 定義的指令  CMD 和 ENTRYPOINT 都存在時,CMD 的指令作為 ENTRYPOINT 的引數  

 舉例:

(1)同時有 CMD 和 ENTRYPOINT

FROM ubuntu:14.04
MAINTAINER Sammy Liu <[email protected]>
CMD top
ENTRYPOINT ps

此時會執行的指令為 /bin/sh -c ps /bin/sh -c top

但是實際上只是運行了 ps:

複製程式碼

[email protected]:/home/sammy/dockerfile# /bin/sh -c ps /bin/sh -c top
  PID TTY          TIME CMD
10789 pts/3    00:00:00 su
10790 pts/3    00:00:00 bash
18479 pts/3    00:00:00 sh
18480 pts/3    00:00:00 ps
[email protected]:/home/sammy/dockerfile# /bin/sh -c ps
  PID TTY          TIME CMD
10789 pts/3    00:00:00 su
10790 pts/3    00:00:00 bash
18481 pts/3    00:00:00 sh
18482 pts/3    00:00:00 ps

複製程式碼

(2)CMD 作為 ENTRYPOINT 的引數

FROM ubuntu:14.04
MAINTAINER Sammy Liu <[email protected]>
CMD ["-n", "10"]
ENTRYPOINT top

啟動容器後執行的命令為 /bin/sh -c top -n 10.

3. 在 Docker hub 上建立自己的映象

當我們從docker映象倉庫中下載的映象不能滿足我們的需求時,我們可以通過以下兩種方式對映象進行更改。

  • 從已經建立的容器中更新映象,並且提交這個映象
  • 使用 Dockerfile 指令來建立一個新的映象

通過以下步驟,採用第一種方法,在 docker hub 上建立自己的映象:

(1)建立 docker hub 帳號。https://hub.docker.com/

(2)基於一個映象完成某些操作。比如基於 nginx 映象,安裝 ping ifconfig 等網路工具。首先執行 docker run -it nginx /bin/bash 基於 nginx:latest 建立一個容器,然後在容器中執行 apt-get 命令安裝軟體,然後執行 exit 退出容器。

(3)將容器中的內容儲存為一個映象

docker commit -m="install net tools" -a="sammyliu8" 3f8a4339aadd sammyliu8/nginx:v1

這裡的 3f8a4339aadd 為剛才容器的ID。此時,能在本地看到該映象:

(4)執行 docker login 登入 docker hub

(5)執行 docker push sammyliu8/nginx 將映象上傳到 docker hub。此時在 Docker hub 介面上能看到該映象了。

 

(6)在其他節點上,可以執行 docker pull sammyliu8/nginx 拉該映象了。

(7)不過,這樣做出來的新nginx有個問題,那就是nginx 服務不會自動起來。這是因為,官方的 nginx 的CMD 為 nginx -g "daemon off;",但是新的映象的CMD 為 /bin/bash。但是,執行前面命令啟動的容器又無法安裝軟體。因此,只能先按照上面的步驟啟動一個容器,製作映象,然後基於該映象再不帶命令地再啟一個容器,在另一個視窗中,使用docker commit 將其儲存為新的映象,並上傳到docker hub中。問題解決。