好處
生產(chǎn)實(shí)踐中一定優(yōu)先使用 Dockerfile 的方式構(gòu)建鏡像,因?yàn)槭褂?Dockerfile 構(gòu)建鏡像可以帶來如下好處:
● 易于版本化管理,Dockerfile 本身是一個文本文件,方便存放在代碼倉庫中做版本管理,可以很方便地找到各個版本之間的變更歷史;
● 過程可追溯,Dockerfile 的每一行指令代表一個鏡像層,根據(jù) Dockerfile 的內(nèi)容即可明確地查看鏡像的完整構(gòu)建過程;
● 屏蔽構(gòu)建環(huán)境異構(gòu),使用 Dockerfile 構(gòu)建鏡像無須考慮構(gòu)建環(huán)境,基于相同 Dockerfile 無論在哪里運(yùn)行,構(gòu)建結(jié)果都一致。
書寫原則
雖然好處不少,但使用不當(dāng)也會引發(fā)很多問題,例如:
● 鏡像構(gòu)建時間過長,導(dǎo)致鏡像構(gòu)建失?。?br> ● 鏡像層數(shù)過多,導(dǎo)致鏡像文件過大。
如果要在生產(chǎn)環(huán)境中編寫出最優(yōu)的 Dockerfile,首先應(yīng)該盡量遵循相關(guān)的原則:
(1) 單一職責(zé)
由于容器的本質(zhì)是進(jìn)程,一個容器代表一個進(jìn)程,因此不同功能的應(yīng)用應(yīng)該盡量拆分為不同的容器,每個容器負(fù)責(zé)單一業(yè)務(wù)進(jìn)程。
(2) 提供注釋信息
Dockerfile 也是一種代碼,應(yīng)該保持良好的代碼編寫習(xí)慣,晦澀難懂的代碼盡量添加注釋,讓協(xié)作者可以一目了然地知道每行代碼的作用,并且方便擴(kuò)展和使用。
(3) 保持容器最小化
應(yīng)該避免安裝無用的軟件包,這樣不僅可以加快容器構(gòu)建速度,還可以避免鏡像體積過大。
(4) 合理選擇基礎(chǔ)鏡像
容器的核心是應(yīng)用,因此只要基礎(chǔ)鏡像能夠滿足應(yīng)用的運(yùn)行環(huán)境即可。
(5) 使用.dockerignore文件
使用 .dockerignore 文件可以在構(gòu)建時忽略一些不需要參與構(gòu)建的文件,從而提升構(gòu)建效率。類似于在使用 git 時,我們可以使用 .gitignore 文件忽略一些不需要做版本管理的文件。
| 規(guī)則 | 含義 |
|---|---|
| # | # 開頭的表示注釋 |
| /tmp | 匹配當(dāng)前目錄下任何以 tmp 開頭的文件或者文件夾 |
| *.md | 匹配以 .md 為后綴的任意文件 |
| my? | 匹配以 my 開頭并且以任意字符結(jié)尾的文件(?代表任意一個字符) |
| !README.md | ! 表示排除忽略 例如 .dockerignore 定義如下: *.md !README.md 表示除了 README.md 文件外所有以 .md 結(jié)尾的文件。 |
(6) 盡量使用構(gòu)建緩存
Docker 構(gòu)建過程中,每一條 Dockerfile 指令都會提交為一個鏡像層,下一條指令都是基于上一條指令構(gòu)建的。如果構(gòu)建時發(fā)現(xiàn)要構(gòu)建的鏡像層的父鏡像層已經(jīng)存在,并且下一條命令使用了相同的指令,即可命中構(gòu)建緩存。
基于 Docker 構(gòu)建時的緩存特性,我們可以把不輕易改變的指令放到 Dockerfile 前面(例如安裝軟件包),而可能經(jīng)常發(fā)生改變的指令放在 Dockerfile 末尾(例如編譯應(yīng)用程序)。Docker 構(gòu)建時判斷是否需要使用緩存的規(guī)則如下:
- 從當(dāng)前構(gòu)建層開始,比較所有的子鏡像,檢查所有的構(gòu)建指令是否與當(dāng)前完全一致,如果不一致,則不使用緩存;
- 一般情況下,只需要對比構(gòu)建指令即可判斷是否需要使用緩存,但是有些指令除外(例如ADD和COPY);
- 對于ADD和COPY指令不僅要校驗(yàn)命令是否一致,還要為即將拷貝到容器的文件計算校驗(yàn)和(根據(jù)文件內(nèi)容計算出的一個數(shù)值,如果兩個文件計算的數(shù)值一致,表示兩個文件內(nèi)容一致 ),命令和校驗(yàn)和完全一致,才認(rèn)為命中緩存。
(7) 正確設(shè)置時區(qū)
從 Docker Hub 拉取的官方操作系統(tǒng)鏡像大多數(shù)都是 UTC 時間(世界標(biāo)準(zhǔn)時間),如果容器應(yīng)用對時間敏感,需要使用中國區(qū)標(biāo)準(zhǔn)時間(東八區(qū)),則可以在 Dockerfile 中添加以下指令:
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo "Asia/Shanghai" >> /etc/timezone
(8) 使用國內(nèi)軟件源加快鏡像構(gòu)建速度
常用的官方操作系統(tǒng)鏡像基本都是國外的,如果我們構(gòu)建鏡像的時候想要安裝一些軟件包可能會非常慢,以 CentOS 7 使用 163 軟件源為例:
# CentOS7-Base-163.repo
#
[base]
name=CentOS-$releasever - Base - 163.com
repo=os
baseurl=http://mirrors.163.com/centos/$releasever/os/$basearch/
gpgcheck=1
gpgkey=http://mirrors.163.com/centos/RPM-GPG-KEY-CentOS-7
[updates]
name=CentOS-$releasever - Updates - 163.com
repo=updates
baseurl=http://mirrors.163.com/centos/$releasever/updates/$basearch/
gpgcheck=1
gpgkey=http://mirrors.163.com/centos/RPM-GPG-KEY-CentOS-7
[extras]
name=CentOS-$releasever - Extras - 163.com
repo=extras
baseurl=http://mirrors.163.com/centos/$releasever/extras/$basearch/
gpgcheck=1
gpgkey=http://mirrors.163.com/centos/RPM-GPG-KEY-CentOS-7
[centosplus]
name=CentOS-$releasever - Plus - 163.com
baseurl=http://mirrors.163.com/centos/$releasever/centosplus/$basearch/
gpgcheck=1
enabled=0
gpgkey=http://mirrors.163.com/centos/RPM-GPG-KEY-CentOS-7
隨后在 Dockerfile 中添加如下指令:
COPY CentOS7-Base-163.repo /etc/yum.repos.d/CentOS7-Base.repo
(9) 最小化鏡像層數(shù)
在構(gòu)建鏡像時盡可能地減少 Dockerfile 指令行數(shù),主要是 RUN 指令如下:
RUN yum -y install make net-tools
書寫建議
(1) RUN
RUN 指令在構(gòu)建時將會生成一個新的鏡像層并且執(zhí)行 RUN 指令后面的內(nèi)容,使用 RUN 指令時應(yīng)該盡量遵循以下原則:
- 當(dāng) RUN 指令后面跟的內(nèi)容比較復(fù)雜時,建議使用反斜杠(\)結(jié)尾并且換行;
- RUN 指令后面的內(nèi)容盡量按照字母順序排序,提高可讀性。
FROM centos:7
RUN yum -y install automake \
curl \
python \
vim \
(2) CMD 和 ENTRYPOINT
CMD 和 ENTRYPOINT 指令都是容器運(yùn)行的命令入口,它們基本使用格式分兩種:
-
CMD/ENTRYPOINT ["command" , "param"],這種格式是使用Linux的exec實(shí)現(xiàn)的,一般稱為exec 模式,這種書寫格式為指令后跟 json 數(shù)組,也是 Docker 推薦使用的格式。 -
CMD/ENTRYPOINT command param,這種格式是基于 shell 實(shí)現(xiàn)的,一般稱為shell 模式,Docker 會以/bin/sh -c command的方式執(zhí)行命令。
它們之間的區(qū)別如下:
- Dockerfile 中如果使用了 ENTRYPOINT 指令,啟動 Docker 容器時需要使用 --entrypoint 參數(shù)才能覆蓋 Dockerfile 中的 ENTRYPOINT 指令 ,而使用 CMD 設(shè)置的命令則可以被 docker run 后面的參數(shù)直接覆蓋。
- ENTRYPOINT 指令可以結(jié)合 CMD 指令使用,也可以單獨(dú)使用,而 CMD 指令只能單獨(dú)使用。
- 使用 exec 模式啟動容器時,指令指定的命令就是容器的 1 號進(jìn)程,而 shell 模式啟動的進(jìn)程在容器中實(shí)際并不是 1 號進(jìn)程。
- 如果希望鏡像足夠靈活,推薦使用 CMD 指令。如果鏡像只執(zhí)行單一的具體程序,并且不希望在執(zhí)行 docker run 時覆蓋默認(rèn)程序,建議使用 ENTRYPOINT。
(3) ADD 和 COPY
ADD 和 COPY 指令功能類似,都是從外部往容器內(nèi)添加文件。但是 COPY 指令只支持基本的文件和文件夾拷貝功能,ADD 則支持更多文件來源類型,比如自動提取 tar 包,并且可以支持源文件為 URL 格式。
日常應(yīng)用,更推薦使用 COPY 指令, 因此該指令更加透明,僅支持本地文件向容器拷貝,而且可以更好地利用構(gòu)建緩存,有效減小鏡像體積。
### 當(dāng)想用 ADD 向容器中添加 URL 文件時,可使用如下寫法替代:
RUN wget -O /tmp/memtester-4.3.0.tar.gz http://pyropus.ca/software/memtester/old-versions/memtester-4.3.0.tar.gz \
&& tar -xvf /tmp/memtester-4.3.0.tar.gz -C /tmp \
&& make -C /tmp/memtester-4.3.0 && make -C /tmp/memtester-4.3.0 install
(4) WORKDIR
為了使構(gòu)建過程更加清晰明了,推薦使用 WORKDIR 來指定容器的工作路徑,盡量避免使用 RUN cd /work/path 這樣的指令來切換工作路徑。