1. 程式人生 > >《容器高手實戰: Dockerfile最佳實踐》

《容器高手實戰: Dockerfile最佳實踐》

Dockerfile最佳實踐
一個容器對應一個程序
一個Docker容器應該只對應一個程序,也就是一個Docker 映象一般只包含一個應用的製品包(比如.jar)。

在需要組合多個程序的場景,使用容器組(比如Docker Compose,或Kubernetes Pod)。

選用合適的基礎映象

  • 選用基礎映象的原則:
  • 使用官方提供的基礎映象(official)
  • 基礎映象應該提供足夠的支援,使得Dockerfile儘量簡單(easy enough)
  • 基礎映象要足夠精簡,儘量不要包含不需要的內容(simple enough)
  • 使用指定標籤(版本)的基礎映象,不使用latest標籤的基礎映象 (explicit)

把最少改動的步驟放在最前面
把最少改動的步驟放在最前面,也就是準備應用的COPY步驟要放在安裝工具和準備環境的RUN命令之後,能夠重用前面構建的層的cache,防止每次都要重複構建。Dockefile中步驟一般為:

選擇基礎映象,比如:
FROM openjdk:8-jdk-stretch

安裝Dockerfile後面步驟需要用到的工具和docker execdebug時需要用到的工具,比如:
RUN apt-get update && apt-get upgrade -y && apt-get install -y git curl && rm -rf /var/lib/apt/lists/*

其他的一些準備環境步驟,比如:
RUN curl -fsSL ${JENKINS_URL} -o /usr/share/jenkins/jenkins.war \
&& echo "${JENKINS_SHA} /usr/share/jenkins/jenkins.war" | sha256sum -c -

準備應用,比如:
COPY jenkins.sh /usr/local/bin/jenkins.sh

宣告程式的入口點,比如:
ENTRYPOINT ["/sbin/tini", "--", "/usr/local/bin/jenkins.sh"]

暴露埠,比如:
EXPOSE 8080

Dockerfile的步驟中只有RUN、COPY和ADD才會建立層,其它指令的先後順序主要是根據Dockerfile的語法、邏輯結構和習慣。

參考文件:

https://github.com/jenkinsci/docker/blob/master/Dockerfile
Docker構建上下文中不要包含不需要的檔案
執行docker build [options] PATH構建Docker映象時,Docker會將PATH路徑下的全部內容作為構建上下文傳給Docker Daemon。參見docker build日誌中的第一句Sending build context to Docker daemon。

如果PATH路徑下的內容太多,會導致映象構建很慢,如果Dockerfile書寫不當,還會引入不必要的檔案,從而導致映象變大,影響映象構建和拉取速度。可以通過.dockerignore檔案在Docker構建時不要將指定內容包含在構建上下文中。

.dockerignore例子:

# ignore .git and .cache folders
.git
.cache
# ignore all *.class files in all folders, including build root
**/*.class
# ignore all markdown files (md)
*.md

還可以將Dockerfile和製品包(比如.jar)放到一個乾淨的新目錄下,再來構建映象,參見:

https://github.com/cookcodeblog/gs-rest-service-maven/blob/master/docker-build.sh

https://github.com/cookcodeblog/gs-rest-service-maven/blob/master/Dockerfile

參考文件:

https://docs.docker.com/engine/reference/builder/#dockerignore-file
https://codefresh.io/docker-tutorial/not-ignore-dockerignore/
多階段構建
Docker提供了多階段構建(multistag-builds)的功能,但是一般在做多階段構建時不需要這麼複雜,只需要分成以下2步:

先構建出應用製品包,比如用Maven構建出應用的.jar包

在docker build構建Docker映象時,將上一步生成的.jar包複製到映象中

減少Docker映象層的數量
Dokcerfile中的RUN、COPY和ADD命令才會建立映象層,因此減少Docker映象層的數量就是要減少這幾個命令的次數,特別是RUN命令的次數。

在安裝工具時可以在一句命令中安裝多個工具:

正例:

apt-get install -y git curl

反例:

apt-get install -y git
apt-get install -y curl

用&&拼接多個命令:

正例:
RUN apt-get update && apt-get upgrade -y && apt-get install -y git curl

反例:
RUN apt-get update
RUN apt-get upgrade -y
RUN apt-get install -y git curl

用\來在拼接多個命令時換行,增加可讀性

RUN curl -fsSL https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-static-$(dpkg --print-architecture) -o /sbin/tini \
&& curl -fsSL https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-static-$(dpkg --print-architecture).asc -o /sbin/tini.asc \
&& gpg --no-tty --import ${JENKINS_HOME}/tini_pub.gpg \
&& gpg --verify /sbin/tini.asc \
&& rm -rf /sbin/tini.asc /root/.gnupg \
&& chmod +x /sbin/tini

使用專門的user和group
如果不以root使用者來執行應用,則可以使用專門的user和group。

以Jenkins為例,Jenkins映象使用了jenkins(1000)/jenkins(1000)的user(uid)/group(gid):

ARG user=jenkins
ARG group=jenkins
ARG uid=1000
ARG gid=1000
ARG JENKINS_HOME=/var/jenkins_home

RUN mkdir -p $JENKINS_HOME \
&& chown ${uid}:${gid} $JENKINS_HOME \
&& groupadd -g ${gid} ${group} \
&& useradd -d "$JENKINS_HOME" -u ${uid} -g ${gid} -m -s /bin/bash ${user}

USER ${user}

檢視Jenkins的user/group資訊:

# 進入容器
docker exec -it <cotainer_id> /bin/bash

# 檢視user / group資訊
cat /etc/passwd | grep jenkins

# 每行7個欄位,以:隔開
# 1. Username: jenkins
# 2. Password: x表示加密的密碼
# 3. User ID(UID): 1000
# 4. Group ID(GID): 1000
# 5. User ID Info: User描述資訊
# 6. Home directory: Home目錄
# 7. Command/Shell: Command或Shell的絕對路徑,比如/bin/bash,為/bin/false表示不允許執行bash
jenkins:x:1000:1000:Linux User,,,:/var/jenkins_home:/bin/bash

參考文件:

https://www.cyberciti.biz/faq/understanding-etcpasswd-file-format/
一次構建,多環境執行
在Dockerfile中使用ENV指令定義環境變數並可設定預設值,通過docker run -e指定執行時環境變數。

容易混淆的Dockerfile的指令
ADD和COPY
推薦使用更為簡單的COPY指令。

ADD命令雖然可以提供額外的下載和解壓等功能,但是下載可以通過curl命令,解壓可以通過tar -zxvf指令來操作。

VOLUME
在Dockerfile中VOLUME指令在docker run 時會在宿主機下的/var/lib/docker/volumes目錄下新建<volume_id>的持久卷目錄。如果是下次docker start時則會重用之前的持久卷。

# 列出持久卷
docker volume ls

# 檢視某個容器的持久卷
docker inspect <container_id> | less
# 查詢Mounts關鍵字

使用docker run -v可以在執行容器時指定持久卷目錄,也可以使用已存在的目錄。

CMD和ENTRYPOINT
CMD語法:CMD ["executable", "param1", "param2"…]

ENTRYPOINT語法:ENTRYPOINT ["executable", "param1", "param2"…]

Dockerfile中應該只包含一個CMD或一個ENTRYPOINT。

包含多個CMD時,只有最後一個CMD才會生效,並會讓Dockerfile難懂。

同時包含CMD和ENTRYPOINT時,CMD中的引數其實是ENTRYPOINT的引數,讓Dockerfile難懂。

ARG和ENV
ARG是構建時引數,通過docker build --build-arg arg=value指定。

ENV是執行時引數,通過docker run -e var=value指定。

在Dockerfile中也可以用ARG來給ENV賦值,例如:

ARG JENKINS_HOME=/var/jenkins_home
ENV JENKINS_HOME $JENKINS_HOME

ARG JENKINS_VERSION
ENV JENKINS_VERSION ${JENKINS_VERSION:-2.121.1}

參考文件
https://docs.docker.com/develop/develop-images/dockerfile_best-practices/
https://takacsmark.com/dockerfile-tutorial-by-example-dockerfile-best-practices-2018/
https://spring.io/guides/gs/spring-boot-docker/

轉載:https://blog.csdn.net/nklinsirui/article/details/9611