本系列文章將介紹Docker的有關知識:html
(1)Docker 安裝及基本用法node
(2)Docker 鏡像linux
(3)Docker 容器的隔離性 - 使用 Linux namespace 隔離容器的運行環境nginx
(4)Docker 容器的隔離性 - 使用 cgroups 限制容器使用的資源docker
(5)Docker 網絡shell
對於每一個軟件,除了它自身的代碼之外,它的運行還須要有一個運行環境和依賴。無論這個軟件是象往常同樣運行在物理機或者虛機之中,仍是運行在如今的容器之中,這些都是不變的。在傳統環境中,軟件在運行以前也須要通過 代碼開發->運行環境準備 -> 安裝軟件 -> 運行軟件 等環節,在容器環境中,中間的兩個環節被鏡像製做過程替代了。也就是說,鏡像的製做也包括運行環境準備和安裝軟件等兩個主要環節,以及一些其餘環節。所以,Docker 容器鏡像其實並無什麼新的理論,只是這過程有了新的方式而已。json
鏡像(image)是動態的容器的靜態表示(specification),包括容器所要運行的應用代碼以及運行時的配置。Docker 鏡像包括一個或者多個只讀層( read-only layers ),所以,鏡像一旦被建立就不再能被修改了。一個運行着的Docker 容器是一個鏡像的實例( instantiation )。從同一個鏡像中運行的容器包含有相同的應用代碼和運行時依賴。可是不像鏡像是靜態的,每一個運行着的容器都有一個可寫層( writable layer ,也成爲容器層 container layer),它位於底下的若干只讀層之上。運行時的全部變化,包括對數據和文件的寫和更新,都會保存在這個層中。所以,從同一個鏡像運行的多個容器包含了不一樣的容器層。ubuntu
Docker 有兩種方式來建立一個容器鏡像:vim
好比,一臺主機安裝的是 Centos 操做系統,如今在上面跑一個 Ubuntu 容器。此時,Host OS 是 Centos,Guest OS 是 Ubuntu。Guest OS 也被成爲容器的 Base Image。c#
一些說明:
可見,容器的 base image 並不真的是 base OS。Base image 會遠遠比 base OS 更輕量。它只安裝發行版特殊的部分(userland 軟件)。
那爲何還須要 base image 呢?這是由於,docker 容器文件系統與 host OS 是隔離的。容器鏡像中的應用軟件沒法看到主機文件系統,除非將主機文件系統掛載爲容器的卷。所以,能夠想像一下,你容器中的應用依賴於各類操做系統庫,所以咱們不得不將這些庫打包到鏡像之中。另外,base image 會讓咱們使用到各個發行版的包管理系統,好比 yum 和 apt-get。並且,各個linux 發行版的 base image 也不是普通的發行版,而是一個簡化了的版本。並且,base image 並不帶有 linux 內核,由於容器會使用主機的內核。
所以,須要注重理解 image 和 OS 這兩個概念。之因此成爲 base image,而不是 base OS,是由於 base image 中並不包括完整的 OS。而這一點,是容器與虛擬機以前的本質區別之一。那就是,容器並無虛擬化,而是共享主機上的linux 內核。
從上面內容能夠看出,容器把 linux 鏡像從內核空間和用戶空間進行了分開管理。對 host OS 來講,它更側重於內核,再加上少許的用戶空間內容;對 Guest OS 來講,它側重於(只有)用戶空間,只包括庫文件、編譯器、配置文件,以及用戶代碼。
常見的容器基礎鏡像:
所以,用戶須要仔細選擇容器的 base image,不只從上表中的幾個方面,還包括性能、安全性等一些因素。
Ubuntu 更是推出了只有 29M 的 Minimal Ubuntu 容器鏡像,具體在這裏 https://blog.ubuntu.com/2018/07/09/minimal-ubuntu-released。
在使用 Dockerfile 建立容器以前,須要先準備一個 Dockerfile 文件,而後運行 docker build 命令來建立鏡像。咱們經過下面的例子來看看Docker 建立容器的過程。
FROM ubuntu:14.04 MAINTAINER sammy "sammy@sammy.com" RUN apt-get update RUN apt-get -y install ntp EXPOSE 5555 CMD ["/usr/sbin/ntpd"]
這是一個很是簡單的Dockerfile,它的目的是基於 Ubuntu 14.04 基礎鏡像安裝 ntp 從而生成一個新的鏡像。看看其過程:
root@devstack:/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 "sammy@sammy.com" ---> 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 "sammy@sammy.com"
本例中依然是從 Cache 中環境新的鏡像。在第一次的時候,Docker 會建立一個臨時的容器 1be8f33c1846,而後運行 MAINTAINER 命令,再使用 docker commit 生成新的鏡像
Step 2 : MAINTAINER sammy "sammy@sammy.com" ---> 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 \"sammy@sammy.com\"" ]
所以能夠認爲只是鏡像的元數據發生了改變。生成的新的鏡像做爲中間鏡像會被保存在 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 來獲取它:
root@devstack:/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 。和上面步驟生成的鏡像相比,它又多了一層:
root@devstack:/home/sammy# docker images -a | grep 9cc05cf6f48d <none> <none> 9cc05cf6f48d 10 hours ago 212.8 MB root@devstack:/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 結果中:
root@devstack:/home/sammy# docker images | grep af678df648bc sammy_ntp2 latest af678df648bc 11 hours ago 212.8 MB
咱們可使用 docker history 命令查看該鏡像中每一層的信息:
root@devstack:/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 "sammy@sa 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 鏡像是基於基礎鏡像的多層疊加,最終構成和容器的 rootfs (根文件系統)。當 Docker 建立一個容器時,它會在基礎鏡像的容器層之上添加一層新的薄薄的可寫容器層。接下來,全部對容器的變化,好比寫新的文件,修改已有文件和刪除文件,都只會做用在這個容器層之中。所以,經過不拷貝完整的 rootfs,Docker 減小了容器所佔用的空間,以及減小了容器啓動所需時間。
COW,copy-on-write 技術,一方面帶來了容器啓動的快捷,另外一方也形成了容器鏡像大小的增長。每一次 RUN 命令都會在鏡像上增長一層,每一層都會佔用磁盤空間。舉個例子,在 Ubuntu 14.04 基礎鏡像中運行 RUN apt-get upgrade 會在保留基礎層的同時再建立一個新層來放全部新的文件,而不是修改老的文件,所以,新的鏡像大小會超過直接在老的文件系統上作更新時的文件大小。所以,爲了減小鏡像大小起見,全部文件相關的操做,好比刪除,釋放和移動等,都須要儘量地放在一個 RUN 指令中進行。
好比說,經過將上面的示例 Dockerfile 修改成:
FROM ubuntu:14.04 MAINTAINER sammy "sammy@sammy.com" RUN apt-get update && apt-get -y install ntp EXPOSE 5555 CMD ["/usr/sbin/ntpd"]
結果產生的鏡像,不只層數少了一層(7 -> 6),並且大小減小了 0.001M :),由於這個例子比較特殊,文件都是添加,而沒有更新,所以size 的降低很是小。
這篇文章 10 things to avoid in docker containers 列舉了一些在使用容器時須要避免的作法,包括:
容器鏡像的內容,實際上是一個 json 文件加上 tar 包。以很是小的鏡像 kubernetes/pause 爲例,咱們來作個實驗:
(1)將鏡像導出爲 tar 文件
root@kub-node-1:/home/ubuntu/kub/image# docker save -o pause.tar kubernetes/pause:latest root@kub-node-1:/home/ubuntu/kub/image# ls pause.tar
(2)解壓 pause.tar 文件
root@kub-node-1:/home/ubuntu/kub/image# tar -xf pause.tar
root@kub-node-1:/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的名稱:
root@kub-node-1:/home/ubuntu/kub/image# cat repositories {"kubernetes/pause":{"latest":"afa9f35badc97e21193ee701222d9edfc5b0f0e5c518d357eb8b016d8287cda7"}}
而 manifest.json 文件則保持的是鏡像的元數據,包括真正元數據 json 文件的名稱及每一層的名稱,tag 等:
root@kub-node-1:/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" ] }
每一個文件夾中的內容爲:
root@kub-node-1:/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中包含的文件:
root@kub-node-1:/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 能夠經過讀取 Dockerfile 的內容來生成容器鏡像。Dockerfile 的每一行都是 INSTRUCTION arguments 格式,即 「指令 參數」。關於 Dockerfile 的預防,請參考 https://docs.docker.com/engine/reference/builder/。下面只是就一些主要的指令作一些說明。
# Usage: ADD [source directory or URL] [destination directory]
ADD /my_app_folder /my_app_folder
例子:
FROM ubuntu:14.04 MAINTAINER Sammy Liu <sammy.liu@unknow.com> ADD temp dockfile ENTRYPOINT top
ADD 指令會將本地 temp 目錄中的文件拷貝到容器的 dockfile 目錄下面,從而在鏡像中增長一個 layer。在未指定絕對路徑的時候,會放到 WORKDIR 目錄下面。
root@cc2a5605f905:/# ls dockfile/ dockerfile-add dockerfile-cmd dockerfile-env dockerfile-ports dockerfile-user dockerfile-user-h root@cc2a5605f905:/# pwd /
那二者有什麼區別呢?
# 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
,若是有多個,只有最後一個生效。
ENTRYPOINT :設置默認應用,會保證每次容器被建立後該應用都會被執行。CMD 和 ENTRYPOINT 的關係會在下面詳細解釋。
# 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 變量
root@devstack:/home/sammy/ntponubuntu# docker exec -it envc41 bash root@ba460e0e9dc4:/# echo $abc 1 root@ba460e0e9dc4:/# echo $def 2
# Usage: EXPOSE [port] EXPOSE 8080
# Usage: FROM [image name]
FROM ubuntu
# Usage: MAINTAINER [name]
MAINTAINER authors_name
# Usage: RUN [command]
RUN aptitude install -y ntp
語法:
# Usage: USER [UID]
USER 751
Dockerfile 中的默認用戶是基礎鏡像中所使用的用戶。好比,你的鏡像是從一個使用非 root 用戶 sammy 的鏡像繼承而來的,那麼你的 Dockerfile 中 RUN 指定運行的命令的用戶就會使用 sammy 用戶。
舉例:
(1)建立 dockerfile 文件
root@devstack:/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.
# Usage: VOLUME ["/dir_1", "/dir_2" ..] VOLUME ["/my_files"]
# Usage: WORKDIR /path
WORKDIR ~/
這是 Docker 1.12 版本中新引入的指令,其語法爲 HEALTHCHECK [OPTIONS] CMD command。 來看一個例子:
FROM ubuntu:14.04 MAINTAINER Sammy Liu <sammy.liu@unknow.com> 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 狀態。
root@devstack:/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 root@devstack:/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 root@devstack:/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 是在容器以內運行的,所以,你須要確保其命令或者腳本存在於容器以內而且能夠被運行。
容器的端口必須被髮出(publish)出來後才能被外界使用。Dockerfile 中的 EXPOSE 只是「標記」某個端口會被暴露出來,只有在使用了 docker run -p 或者 -P 後,端口才會被「發出」出來,此時端口才能被使用。
舉例:
(1)Dockerfile
FROM ubuntu:14.04 MAINTAINER Sammy Liu <sammy.liu@unknow.com> 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" } ] }
該端口會正確返回:
root@devstack:/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 <sammy.liu@unknow.com> 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
只是爲其餘命令提供所需信息的元數據,或者只是告訴容器操做人員有哪些已知選擇。它只是做爲記錄機制,也就是告訴用戶哪些端口會提供服務。它保存在容器的元數據中。這兩個指令都指定了運行容器時所運行的命令。如下是它們共存的一些規則:
沒有 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 <sammy.liu@unknow.com> CMD top ENTRYPOINT ps
此時會運行的指令爲 /bin/sh -c ps /bin/sh -c top
可是實際上只是運行了 ps:
root@devstack:/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 root@devstack:/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 <sammy.liu@unknow.com> CMD ["-n", "10"] ENTRYPOINT top
啓動容器後運行的命令爲 /bin/sh -c top -n 10.
當咱們從docker鏡像倉庫中下載的鏡像不能知足咱們的需求時,咱們能夠經過如下兩種方式對鏡像進行更改。
經過如下步驟,採用第一種方法,在 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中。問題解決。
參考連接