Dockerfile語法及構建簡單映象
Dockerfile語法及構建簡單映象
前面使用過docker commit
去構建映象
Docker並不建議使用者通過這種方式構建映象。原因如下:
- 這是一種手工建立映象的方式,容易出錯,效率低且可重複性弱。比如要在 debian base 映象中也加入 vi,還得重複前面的所有步驟。
- 更重要的:使用者並不知道映象是如何創建出來的,裡面是否有惡意程式。也就是說無法對映象進行審計,存在安全隱患。
既然 docker commit 不是推薦的方法,我們幹嘛還要花時間學習呢?
原因是:即便是用 Dockerfile(推薦方法)構建映象,底層也 docker commit 一層一層構建新映象的。學習 docker commit 能夠幫助我們更加深入地理解構建過程和映象的分層結構。
準備構建映象
需要建立一個Dockerfile檔案,檔名必須是這個
[root@localhost ~]# vim Dockerfile
# 新增
FROM centos
RUN yum -y install httpd
RUN yum -y install net-tools
RUN yum -y install elinks
CMD ["/bin/bash"]
構建映象
使用docker build
進行映象的構建,最後需要指定Dockerfile檔案所在路徑
[root@localhost ~]# docker build -t chai/centos-http-net /root Successfully built 09266c896243 Successfully tagged chai/centos-http-net:latest
看到最後輸出兩條Successfully則構建成功。
它會根據檔案中寫的內容,使用centos映象例項化一個容器,進入容器中執行三個yum命令
檢視已經構建好的映象
[root@localhost ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
chai/centos-http-net latest 09266c896243 10 seconds ago 581MB
使用新的映象例項化容器
[root@localhost ~]# docker run -it --rm --name test chai/centos-http-net /bin/bash [root@50da58a03736 /]# ifconfig # 命令可以執行即成功
映象構建過程
在構建命令執行時輸出的一大堆資訊中,是執行Dockerfile中的每一行,最關鍵的幾行資訊如下
Step 1/5 : FROM centos # 呼叫centos
---> 5e35e350aded # centos映象id
Step 2/5 : RUN yum install httpd -y
---> Running in a16ddf07c140 # 執行一個臨時容器來執行install httpd
Removing intermediate container a16ddf07c140 # 完成後刪除臨時的容器id
---> b51207823459 # 生成一個映象
Step 3/5 : RUN yum install net-tools -y
---> Running in 459c8823018a # 執行一個臨時容器執行install net-tools
Removing intermediate container 459c8823018a # 完成後刪除臨時容器id
---> 5b6c30a532d4 # 再生成一個映象
Step 4/5 : RUN yum install elinks -y
---> Running in a2cb490f9b2f # 執行一個臨時容器執行install elinks
Removing intermediate container a2cb490f9b2f # 完成後刪除臨時容器id
---> 24ba4735814b # 生成一個映象
Step 5/5 : CMD ["/bin/bash"]
---> Running in 792333c88ba8 # 執行臨時容器,執行/bin/bash
Removing intermediate container 792333c88ba8 # 完成後刪除臨時容器id
---> 09266c896243 # 生成映象
Successfully built 09266c896243 # 最終成功後的映象id就是最後生成的映象id
每一步生成一個映象,都屬於一個docker commit
的執行結果
在這個過程中一共生成了三個映象層,都會被儲存在graph中,包括層與層之間的關係,檢視docker images
中生成的映象id是否為最後生成的映象id,FROM
和CMD
都不算做映象層
[root@localhost ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
chai/centos-http-net latest 09266c896243 10 seconds ago 581MB
通過docker history
也可以看到簡單的構建過程,這幾個過程的size容量加起來也就是最終生成映象的大小,也可將這裡的映象id和上面過程中的id進行對比,我們所看到的是三個yum就是形成的三個映象層
[root@localhost ~]# docker history chai/centos-http-net:latest
IMAGE CREATED CREATED BY SIZE COMMENT
09266c896243 17 minutes ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B
24ba4735814b 17 minutes ago /bin/sh -c yum install elinks -y 121MB
5b6c30a532d4 18 minutes ago /bin/sh -c yum install net-tools -y 112MB
b51207823459 18 minutes ago /bin/sh -c yum install httpd -y 145MB
5e35e350aded 4 months ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B
<missing> 4 months ago /bin/sh -c #(nop) LABEL org.label-schema.sc… 0B
<missing> 4 months ago /bin/sh -c #(nop) ADD file:45a381049c52b5664… 203MB
映象的快取特性
之前文件中說到,nginx和httpd兩個映象都是基於debian系統製作的映象,所以會使用相同的一部分映象層去安裝,而這個映象被docker所共享,只需要下載一次即可
還是重新下載這兩個映象看一下是怎麼進行使用
下載nginx映象
[root@localhost ~]# docker pull nginx
Using default tag: latest
latest: Pulling from library/nginx
68ced04f60ab: Pull complete
28252775b295: Pull complete
a616aa3b0bf2: Pull complete
下載httpd映象
[root@localhost ~]# docker pull httpd
Using default tag: latest
latest: Pulling from library/httpd
68ced04f60ab: Already exists # 已經存在
35d35f1e0dc9: Pull complete
8a918bf0ae55: Pull complete
d7b9f2dbc195: Pull complete
d56c468bde81: Pull complete
仔細觀察下載的內容中,有兩個id是一樣的
68ced04f60ab
,在下載httpd的時候,會報出已經存在,因為在下載nginx時已經下載過了,這是因為這兩個程式都是基於debian作業系統製作的映象,這裡就將這個id的映象共享使用了,也就時使用了同一個映象的快取
自己構建的映象也是具有這個快取的特性的,在前面構建了一個映象chai/centos-http-net
,那我們根據構建這個映象的Dockerfile
檔案的基礎上進行一點點小的修改
效仿hello-world
最小映象的方法,也就是構建一個文件的映象
[root@localhost ~]# vim testfile
# 新增內容
This is a FeiYi's file
然後將這個檔案COPY
到Dockerfile
檔案中,在之前的Dockerfile
進行修改
[root@localhost ~]# vim Dockerfile
FROM centos
RUN yum -y install httpd
RUN yum -y install net-tools
RUN yum -y install elinks
COPY testfile / # 將物理機當前目錄的testfile檔案複製到映象中的根目錄
CMD ["/bin/bash"]
將以上Dockerfile進行構建映象,發現構建的很快,也沒有很多的輸出資訊,而且除了新加的複製檔案以外,其他的三個RUN映象層,都多了一行Using cache
,這是因為他們形成的映象層已經作為快取被構建的這個映象使用
[root@localhost ~]# docker build -t test /root
Sending build context to Docker daemon 4.334MB
Step 1/6 : FROM centos
---> 5e35e350aded
Step 2/6 : RUN yum -y install httpd
---> Using cache
---> f01998ebc3ff
Step 3/6 : RUN yum -y install net-tools
---> Using cache
---> a3c39d101d8b
Step 4/6 : RUN yum -y install elinks
---> Using cache
---> 7c705388a610
Step 5/6 : COPY testfile /
---> 2111837dd86c
Step 6/6 : CMD ["/bin/bash"]
---> Running in 36782884ce16
Removing intermediate container 36782884ce16
---> 7b529720bf53
Successfully built 7b529720bf53
Successfully tagged test:latest
映象層還有一個特點
如果改變了Dockerfile中映象層的順序,
[root@localhost ~]# vim Dockerfile
FROM centos
RUN yum -y install httpd
COPY testfile /
RUN yum -y install net-tools
RUN yum -y install elinks
CMD ["/bin/bash"]
然後進行構建,就會發現所有的內容全部重新構建
[root@localhost ~]# docker build -t test2 /root
這個特點就時層與層之間的關係,他們第一次構建時的順序可以理解為他們之間的層級關係,如果執行順序不一樣,那麼他們之間的層級關係也不一樣,就不會去使用快取構建。他們之間的關係被儲存在graphDB中,如果讀取不到相同的關係,是不會去使用快取的。
–no-cache
--no-cache
可以指定你構建映象時,不適用已經存在的映象層,也就是不使用快取的特性
使用該引數重新構建剛才的Dockerfile
[root@localhost ~]# docker build -t test3 /root --no-cache
這樣就會將所有的Dockerfile中的映象層重新下載構建,而不是使用快取。
Dockerfile檔案排錯方法
當個構建映象時Dockerfile中報錯,先來製作一個錯誤的Dockerfile
[root@localhost ~]# vim Dockerfile
FROM busybox
RUN touch tmpfile
RUN /bin/bash -c echo "continue to build..."
COPY testfile /
使用這個Dockerfile去構建映象
[root@localhost ~]# docker build -t testerror /root
Sending build context to Docker daemon 4.336MB
Step 1/4 : FROM busybox
---> 83aa35aa1c79
Step 2/4 : RUN touch tmpfile
---> Running in 41a8dad29cd6
Removing intermediate container 41a8dad29cd6
---> 8cd5c9a720bb
Step 3/4 : RUN /bin/bash -c echo "continue to build..."
---> Running in bc1849fa8144
/bin/sh: /bin/bash: not found
The command '/bin/sh -c /bin/bash -c echo "continue to build..."' returned a non-zero code: 127
發現構建映象過程中出現了報錯/bin/sh: /bin/bash: not found
可以看到報錯資訊是從第三步才開始的,說明前兩步是沒有問題的,可以通過進入前兩步最後結束的映象id中去檢視錯誤,進入前兩層的映象id是一個正常的容器環境,將第三步無法執行的命令,在容器中執行,將會看到真正的錯誤是沒有/bin/bash
這個環境
[root@localhost ~]# docker run -it 8cd5c9a720bb
/ # /bin/bash -c echo "continue to build..."
sh: /bin/bash: not found
因為構建這個映象使用的是busybox,它使用的環境是/bin/sh
修改後,重新構建成功
[root@localhost ~]# docker build -t testerror /root
Successfully built ae5870fff063
Successfully tagged testerror:latest
執行容器後,可以看到建立的tmpfile和複製testfile
[root@localhost ~]# docker run -it testerror
/ # ls
bin etc proc sys tmp usr
dev home root testfile tmpfile var
Dockerfile檔案語法
常用構建映象指令
FROM # 指定base映象
MAINTAINER # 指定映象作者,後面根任意字串
COPY # 把檔案從host複製到映象內
COPY src dest
COPY ["src","dest"]
src:只能是檔案
ADD # 用法和COPY一樣,唯一不同時src可以是壓縮包,表示解壓縮到dest位置,src也可以是目錄
ENV # 設定環境變數可以被接下來的映象層引用,並且會加入到映象中
ENV MY_VERSION 1.3
RUN yum -y install http-$MY_VERSION
# 當進入該映象的容器中echo $MY_VERSION會輸出1.3
EXPOSE # 指定容器中的程序監聽的埠(介面),會在docker ps -a中的ports中顯示
EXPOSE 80
VOLUME # 容器卷,後面會講到,把host的路徑mount到容器中
VOLUME /root/htdocs /usr/local/apahce2/htdocs
WORKDIR # 為後續的映象層設定工作路徑
# 如果不設定,Dockerfile檔案中的每一條命令都會返回到初始狀態
# 設定一次後,會一直在該路經執行之後的分層,需要WORKDIR /回到根目錄
CMD # 啟動容器後預設執行的命令,使用構建完成的映象例項化為容器時,進入後預設執行的命令
# 這個命令會被docker run啟動命令替代
# 如:docker -it --rm centos echo "hello"
# echo "hello"會替代CMD執行的命令
CMD ["nginx", "-g", "daemon off"] # 該映象例項化後的容器,進入後執行nginx啟動服務
ENTRYPOINT # 容器啟動時執行的命令,不會被docker run的啟動命令替代
RUN/CMD/ENTRYPOINT區別
在語法中說到CMD和ENTRYPOINT是容器啟動後和容器啟動時,執行的命令,RUN是構建映象時執行的命令。三者的區別究竟在什麼地方,通過一個例子來看
使用這三者來構建一個映象
[root@localhost ~]# vim Dockerfile
# 新增
FROM centos
RUN yum -y install net-tools
CMD echo "hello chai"
ENTRYPOINT echo "hello mupei"
[root@localhost ~]# docker build -t chai /root
Successfully built 10dec59bba24
Successfully tagged chai:latest
構建完成後,執行映象
[root@localhost ~]# docker run -it chai
hello mupei
很明顯在構建構成中RUN
已經完成了它的工作
RUN:執行命令並建立新的映象層,主要用於安裝軟體包
而在執行映象後,只輸出了hello mupei
,是ENTRYPOINT
來執行的命令
這兩個都算作是啟動指令,也就是必須啟動容器才會去執行的指令,一般用來啟動執行程式使用
結論:當ENTRYPOINT
和CMD
同時存在時,ENTRYPOINT
生效
ENTRYPOINT和CMD使用格式
shell和exec兩種格式
shell格式
CMD command
、ENTRYPOINT command
shell格式會始終呼叫一個shell程式去執行命令
通過一個例子來看
[root@localhost ~]# vim Dockerfile
FROM centos
RUN yum -y install net-tools
ENV name chai
ENTRYPOINT echo "hello $name"
[root@localhost ~]# docker build -t pei /root
Successfully built cf475e27d587
Successfully tagged pei:latest
[root@localhost ~]# docker run -it pei
hello chai
當指令執行時,shell會呼叫/bin/bash
exec格式
CMD ["命令", "選項", "引數"]
、ENTRYPOINT ["命令", "選項", "引數"]
exec格式下無法去呼叫ENV定義的變數,如果非要讓exec格式去讀取變數的話,它的命令的位置就要使用一個shell環境。因為變數的讀取就是使用shell去讀取的。
如:
ENTRYPOINT ["/bin/sh", "-c", "echo hello,$變數名"]
再看一個例子
[root@localhost ~]# vim Dockerfile
FROM centos
RUN ["yum", "-y", "install", "net-tools"]
CMD ["/bin/echo", "hello chai"]
ENTRYPOINT ["/bin/echo", "hello world"]
[root@localhost ~]# docker build -t feiyi /root
Successfully built 52189c01eaf5
Successfully tagged feiyi:latest
執行容器後,只有ENTRYPOINT
的命令是正常執行了,輸出了我們需要的hello world
,
ENTRYPOINT ["/bin/echo", "hello world"]
:這一行中,/bin/echo
是命令,hello world
是執行的引數
而CMD
中的/bin/echo
和hello chai
都做為結果輸出,並沒有被當做命令
[root@localhost ~]# docker run -it feiyi
hello world /bin/echo hello chai
結論:當使用exec格式時,ENTRYPOINT
的第一個引數被識別為命令,CMD的引數按順序變為ENTRYPOINT
命令的引數
這個結論相當於Dockerfile檔案中的以下兩行=echo "hello world /bin/echo hello chai"
CMD ["/bin/echo", "hello chai"]
ENTRYPOINT ["/bin/echo", "hello world"]
即:
[root@localhost ~]# docker run -it feiyi
hello world /bin/echo hello chai
[root@localhost ~]# echo "hello world /bin/echo hello chai"
hello world /bin/echo hello chai