1. 程式人生 > 其它 >Dockerfile語法及構建簡單映象

Dockerfile語法及構建簡單映象

Dockerfile語法及構建簡單映象

前面使用過docker commit去構建映象

Docker並不建議使用者通過這種方式構建映象。原因如下:

  1. 這是一種手工建立映象的方式,容易出錯,效率低且可重複性弱。比如要在 debian base 映象中也加入 vi,還得重複前面的所有步驟。
  2. 更重要的:使用者並不知道映象是如何創建出來的,裡面是否有惡意程式。也就是說無法對映象進行審計,存在安全隱患。

既然 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,FROMCMD都不算做映象層

[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

然後將這個檔案COPYDockerfile檔案中,在之前的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來執行的命令

這兩個都算作是啟動指令,也就是必須啟動容器才會去執行的指令,一般用來啟動執行程式使用

結論:當ENTRYPOINTCMD同時存在時,ENTRYPOINT生效

ENTRYPOINT和CMD使用格式

shell和exec兩種格式

shell格式

CMD commandENTRYPOINT 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/echohello 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