Dockerfile最佳實踐(一)

在“Docker部署您的第一個應用程序”一篇中,我們已經使用了Dockerfile來構建鏡像,這一篇將補充Dockerfile經常使用指令。

Dockerfile最佳实践(一)


Docker可以通過讀取Dockerfile中的指令來生成鏡像。Dockerfile是一個文本文件,用戶對鏡像操作的所有指令都可以寫在Dockerfile文件中,最後使用docker build來構建鏡像。

在“Docker部署您的第一個應用程序”中,我們使用了命令“docker image build -t bulletinboard:1.0 . ”,docker image build命令通過讀取Dockerfile和指定的上下文來構建鏡像,命令結尾有一個“." 點,這個點就是構建鏡像的上下文。

上下文是遞歸進行處理的。因此,即包括該上下文下的所有子目錄。

鏡像的構建是由Docker守護進程(Docker daemon)完成的,而不是由CLI。構建過程首先要做的是將整個上下文(遞歸地)發送給守護進程。在大多數情況下,最好從一個空目錄作爲上下文開始,並將Dockerfile保存在該目錄中。只添加生成Dockerfile所需的文件。

注意:千萬不要使用"/"根作爲上下文,例如如下命令,因爲它將會將宿主機"/"根目錄下所有文件傳輸到Docker的守護進程,可以在開發環境嘗試執行如下命令進行驗證。

# docker image build /

若要在構建的上下文中將配置文件或包構建到鏡像中,可在Dockerfile中使用COPY指令。若要提高構建性能,可通過在上下文目錄中添加.dockerignore文件來排除文件和目錄。通常Dockerfile,位於上下文的根目錄中,在docker build中使用-f標誌可以指定文件系統中任何地方的docker file,使用-t標誌可以指定構建鏡像的倉庫以及tag標籤,例如:

# cat >/tmp/centos <<EOF
FROM centos:latest
MAINTAINER [email protected]
EOF
# docker image build -f /tmp/centos -t centos:v0.1 .
# docker images

REPOSITORY TAG IMAGE ID CREATED SIZE

centos v0.1 7eab7b4cc6ea 38 seconds ago 220MB

您也可以指定構建鏡像的多個倉庫以及tag,例如:

# docker image build -f /tmp/centos -t t01/centos:v0.1 -t t02/centos:v0.2 .
# docker images

REPOSITORY TAG IMAGE ID CREATED SIZE

centos v0.1 7eab7b4cc6ea 3 minutes ago 220MB

t01/centos v0.1 7eab7b4cc6ea 3 minutes ago 220MB

t02/centos v0.2 7eab7b4cc6ea 3 minutes ago 220MB

注意:當前所指的倉庫和tag均位於當前宿主機,在其他宿主機上,您是無法獲取這些鏡像的(除非您推送到您的docker hub賬戶下或其他方式),後續將會講到docker的私有倉庫registry或Harobor來遠程分享我們做好的鏡像。

使用Dockerfile構建鏡像步驟總結如下:

1、爲鏡像創建一個目錄,如bulletin-board-app

2、進入bulletin-board-app目錄,在該目錄下創建並完成Dockerfile文件編寫

3、鏡像所需要的文件或代碼都拷貝到bulletin-board-app目錄

4、如果bulletin-board-app目錄下有文件是構建時不需要的,則可以創建並編寫.dockerignore文件來忽略不需要的文件

5、在bulletin-board-app目錄下執行docker image build命令,並指定上下文位置爲".",如命令”docker image build -t test/bulletinboard .“

Docker守護進程在執行Dockerfile中的指令之前,會先對Dockerfile執行初步驗證,如果語法不正確,則返回相關錯誤,如果是參數錯誤,例如目標目錄不存在則不會檢查,直到執行到該指令時拋出錯誤。

Docker守護進程逐個執行Dockerfile中的指令,必要時將每條指令的結果提交給新鏡像,最後輸出新鏡像的ID。Docker守護進程將自動清理您發送的上下文。

注意,每個指令都是獨立運行的,因此上一指命的執行不會對下一個指令產生任何影響。

只要有可能,Docker將會重用中間鏡像(緩存),以顯著加快Docker的構建過程。並且在控制檯會輸出Using cache消息。

演示示例:

# cat >Dockerfile <<EOF
FROM alpine:3.2
MAINTAINER [email protected]
RUN apk update && apk add socat && rm -r /var/cache/
CMD env | grep _TCP= | (sed 's/.*_PORT_\([0-9]*\)_TCP=tcp:\/\/\(.*\):\(.*\)/socat -t 100000000 TCP4-LISTEN:\1,fork,reuseaddr TCP4:\2:\3 \&/' && echo wait) | sh
EOF

第一次構建

# docker build -t demo/demo:v0.1 .

Sending build context to Docker daemon 2.048kB

Step 1/4 : FROM alpine:3.2

3.2: Pulling from library/alpine

95f5ecd24e43: Pull complete

Digest: sha256:ddac200f3ebc9902fb8cfcd599f41feb2151f1118929da21bcef57dc276975f9

Status: Downloaded newer image for alpine:3.2

---> 98f5f2d17bd1

Step 2/4 : MAINTAINER [email protected]

---> Running in fa3786732ad5

Removing intermediate container fa3786732ad5

---> 6f5007fa547d

Step 3/4 : RUN apk update && apk add socat && rm -r /var/cache/

---> Running in b157222691fb

fetch http://dl-cdn.alpinelinux.org/alpine/v3.2/main/x86_64/APKINDEX.tar.gz

v3.2.3-474-g10ee65f [http://dl-cdn.alpinelinux.org/alpine/v3.2/main]

OK: 5294 distinct packages available

(1/4) Installing ncurses-terminfo-base (5.9)

(2/4) Installing ncurses-libs (5.9)

(3/4) Installing readline (6.3.008)

(4/4) Installing socat (1.7.3.0)

Executing busybox-1.23.2.trigger

OK: 7 MiB in 19 packages

Removing intermediate container b157222691fb

---> 58c5258280f7

Step 4/4 : CMD env | grep _TCP= | (sed 's/.*_PORT_\([0-9]*\)_TCP=tcp:\/\/\(.*\):\(.*\)/socat -t 100000000 TCP4-LISTEN:\1,fork,reuseaddr TCP4:\2:\3 \&/' && echo wait) | sh

---> Running in ca843dd16f02

Removing intermediate container ca843dd16f02

---> 7bf06f4ab80b

Successfully built 7bf06f4ab80b

Successfully tagged demo/demo:v0.1

第二次構建

# docker build -t demo/demo:v0.2 .

Sending build context to Docker daemon 2.048kB

Step 1/4 : FROM alpine:3.2

---> 98f5f2d17bd1

Step 2/4 : MAINTAINER [email protected]

---> Using cache

---> 6f5007fa547d

Step 3/4 : RUN apk update && apk add socat && rm -r /var/cache/

---> Using cache

---> 58c5258280f7

Step 4/4 : CMD env | grep _TCP= | (sed 's/.*_PORT_\([0-9]*\)_TCP=tcp:\/\/\(.*\):\(.*\)/socat -t 100000000 TCP4-LISTEN:\1,fork,reuseaddr TCP4:\2:\3 \&/' && echo wait) | sh

---> Using cache

---> 7bf06f4ab80b

Successfully built 7bf06f4ab80b

Successfully tagged demo/demo:v0.2

生成緩存僅用於具有本地父鏈的鏡像。這也意味着這些緩存鏡像均是由之前的構建生成的,或者整個鏡像鏈是用docker load方式加載的。如果需要指定鏡像緩存,可以使用--cache from選項。使用--cache from指定的鏡像不需要有父鏈,可以從其他倉庫中拉取。

完成構建後,就可以考慮將存儲在本地倉庫的鏡像推送到遠端倉庫(例如:Harobor)

BuildKit

從18.09版開始,Docker支持一個新的構建工具buildkit,moby/buildkit項目(https://github.com/moby/buildkit)。與現有的實現工具相比,BuildKit提供了許多特性:

1、檢測並跳過執行未使用的構建階段

2、並行化獨立構建階段

3、構建過程中只增量地傳輸上下文中更改的文件

4、檢測並跳過在上下文中傳輸未使用的文件

5、許多新特性在外部Dockerfile實現

6、避免與API的其餘部分(中間鏡像和容器)產生副作用

7、自動修剪並設置生成緩存的優先級

要使用BuildKit,需要在調用docker build命令之前在CLI上設置環境變量DOCKER_BUILDKIT=1。

要了解可用於基於BuildKit的構建的實驗Dockerfile語法,請參閱BuildKit文檔(https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/experimental.md)。

Dockerfile指令與語法

Dockerfile中的指令不區分大小寫。但是,按慣例是要大寫,以便更容易地將它們與參數區分開來。

Docker按順序運行Dockerfile中的指令。Dockerfile必須以“FROM”指令開頭。當然,FROM指令之前可以有註釋和全局參數。FROM指令指定了父鏡像。FROM前面只能有一個或多個ARG指令,這些指令聲明Dockerfile的FROM行中使用的參數。

Docker將以#開頭的行視爲註釋。

1、FROM 鏡像:標籤

指定新鏡像是基於哪個(基礎)鏡像創建,每一個鏡像的創建都需要一條FROM指令,例如:

FROM centos:latest

2、MAINTAINER 名字/郵箱

維護人信息,例如:

MAINTAINER [email protected]

3、ADD 源文件 新鏡像目錄

將源文件複製到新創建鏡像中,源文件要與Dockerfile同屬一個目錄,ADD指令會自動解壓tar、tgz包,例如:

ADD example.tgz /data

4、COPY 源文件 目標目錄

將源文複製到新建像中,源文件要與Dockerfile所屬同一個目錄,同ADD類似,例如:

COPY sources.list /etc/apt

5、ENV 關鍵字 值

設置變量或環境變量,例如:

ENV foo /var/www/html

上述表示變量foo的值是/var/www/html

6、RUN 命令

基於現有鏡像執行命令,並提交到新鏡像上,通常在安裝軟件包時使用RUN,例如:

RUN yum -y install sysstat

7、WORKDIR 目錄

指定工作目錄,通過WORKDIR設置工作目錄後,Dockerfile中其後的命令RUN、CMD、ENTRYPOINT、ADD、COPY等命令都會在該目錄下執行,後續登錄基於該鏡像的容器缺省路徑就是WORKDIR。

8、EXPOSE 端口號

指定Docker容器從該鏡像運行時所開啓的端口,例如:

EXPOSE 80

9、VOLUME 掛載點

Docker容器從該鏡像運行時會設置一個掛載點,例如:

VOLUME /data

10、CMD["要運行的程序","參數1","參數2"]

容器啓動時要運行的命令或腳本,Dockerfile只能有一條CMD命令,如果有多條,則執行最後一條,另外執行docker run 命令時若使用了/bin/bash,則會覆蓋CMD。例如:

CMD ["/bin/bash","/root/start.sh"]

示例演示

1、創建Dockerfile

# mdkir demo
# cd demo
# cat > Dockerfile <<EOF
#My first image
FROM ubuntu:latest
MAINTAINER [email protected]
ENV foo /var/www/html
WORKDIR ${foo}
ADD code.tgz $foo
COPY sources.list /etc/apt
COPY start.sh /root/
RUN chmod 755 /root/start.sh
RUN mkdir /data
VOLUME /data
RUN apt-get -y update && apt-get -y install sysstat lsof net-tools procps vim bash
RUN apt-get -y install apache2
RUN ln -fs /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN date
COPY ports.conf /etc/apache2/ports.conf
ADD example.tgz /data
EXPOSE 80
CMD ["/bin/bash","/root/start.sh"]
EOF

上述Dockerfile中用到的相關腳本配置如下:

# cat >sources.list <<EOF
deb http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ bionic-proposed main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ bionic-proposed main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiverse
EOF
# cat >ports.conf <<EOF
ServerName localhost
Listen 80
<IfModule ssl_module>
Listen 443
</IfModule>
<IfModule mod_gnutls.c>
Listen 443
</IfModule>
EOF
# cat >start.sh <<EOF
#!/bin/bash
apache2ctl start
bash
EOF

2、通過Dockerfile構建鏡像

# docker image build -t test/httpd:v0.1 .
# docker images

REPOSITORY TAG IMAGE ID CREATED SIZE

test/httpd v0.1 9a9a2b7dd312 2 minutes ago 165MB

httpd latest 2ae34abc2ed0 3 weeks ago 165MB

3、基於鏡像運行容器

# docker container run -idt -p 80 --name test_httpd01 test/httpd:v0.1

6e7a40ec63b618bf043b45d334c289df782f02e19617dc0686c3be41a582e047

注意:在創建容器時不要加/bin/bash,不然會覆蓋CMD導致apache服務不啓動。

4、查看容器狀態並確認端口處於監聽狀態

# docker ps -a

CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

a919ee33ae0e test/httpd:v0.1 "/bin/bash /root/sta…" 3 minutes ago Up 3 minutes 0.0.0.0:32787->80/tcp test_httpd01

# docker exec -it test_httpd01 netstat -antp

Active Internet connections (servers and established)

Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name

tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 18/apache2

5、測試

# curl http://127.0.0.1:32787

hello

6、刪除容器和鏡像,容器的生命週期結束

# docker stop test_httpd01
# docker rm test_httpd01
# docker rmi $(docker images |grep "test/httpd" |awk '{print $3}')


總結
對於有一定Linux基礎的童鞋,編寫Dockerfile是比較簡單的,但仍然需要注意一些細節,比如ADD、COPY指令差異,RUN與CMD指令差異等。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章