[Docker]寫 Dockerfile 的最佳實踐理論
本文作者:farwish
原文連結:https://www.cnblogs.com/farwish/p/9231228.html
·指導方針
建立短暫的容器
意思是 container 可以停止和銷燬,接著以最小化啟動和配置進行重新構建和替換。
理解構建的上下文
使用 docker build ,當前工作環境稱為 構建的上下文,預設 Dockerfile 是在同級目錄找,可透過 -f 指定 Dockerfile。
無論 Dockerfile 實際在哪裡,當前目錄的所有遞迴的檔案和目錄的內容被髮送到 docker daemon 作為構建的上下文。
(無意中包含的不必要檔案會增加 image 大小,增加 build/pull/push 時間和 container 執行時大小)
上下文內容支援本地 PATH 和遠端 URL,docker build [OPTIONS] PATH | URL | -
17.05開始支援用 stdin 管道化 Dockerfile 的內容
docker build -t foo:v1 . -f-<<<EOF
FROM ubuntu:16.04
RUN echo "hello"
COPY./copy-files
EOF
使用 .dockerignore 排除內容進入構建上下文
示例
使用多級構建
不需要努力去減少中間層數量和檔案,從變化少的層到經常變化的層來排序(這樣可以保證複用到構建歷史快取)
不同層的順序安排:安裝工具 -> 安裝庫依賴 -> 生成應用
不安裝不必要的包
解耦應用
每一個 container 應該只關心一件事。解耦應用到多個容器可以讓水平擴充套件更容易和重複使用容器。比如 web 技術棧的分為 應用/資料庫/快取 三個不同的容器。
最小化層的數量
在 17.05 及更高版本中,透過 multi-stage 多級構建減少了這個限制。
給多行引數排序
幫助避免包重複和更容易修改,更易讀。
Run apt-get update && apt-get install -y \
bzr \
cvs \
git \
mercurial
利用構建快取
如果不想在構建中使用快取的,給 docker build 命令加 --no-cache=true 選項。
·Dockerfile 指令
FROM - 只要有可能,使用當前官方倉庫的作為基礎 image。
LABEL - 透過object幫助組織image,每行以LABEL開頭,有一個或多個 key-value 對。https://docs.docker.com/config/labels-custom-metadata/
RUN - 把長且複雜的 RUN 語句用反斜線分割成多行,保持 Dockerfile 更可讀,可理解,可維護。
把 update 和 install 放在一行,保證 Dockerfile 安裝最近的包版本,如下:
RUN apt-get update && apt-get install -y \
package-foo \
package-bar
APT-GET - 在 apt-get 應用中可能是最常使用的用於 RUN 的命令。因為它可以安裝包,RUN apt-get 有幾個陷阱需要提防。
不要使用 RUN apt-get dist-upgrade 和 dist-upgrade。因為許多來自父級 image 的基礎的包不能在沒有許可權的 container 中升級。
如果父級 image 中的一個 package 過時了,聯絡它的維護者。
如果你知道有一個特定的包 foo 需要升級,使用 apt-get install -y foo 來自動更新。
總是把 RUN apt-get update 和 apt-get install 合併為一條 RUN 語句,確保無干涉的安裝最新的包版本。
RUN apt-get update && apt-get install -y \
package-foo \
package-bar \
package-baz=1.3.*
&& rm -rf /var/lib/apt/lists/* # 透過移除 apt cache 減小 image 尺寸
單獨在一行 RUN 語句中使用 apt-get update 會引起快取問題,隨後的 apt-get install 指令失敗,這叫做’ cache busting ’,同樣可以透過指定包版本獲取 cache-busting,這叫做版本固定,這可以避免由包變化而引起的意外失敗。
官方 debian 和 ubuntu 的 image 會自動執行 *apt-get clean*,所以明確呼叫不是必需的。
使用管道 - 如果想讓管道連線的命令在任何階段遇到錯誤時就失敗,在命令前追加 set -o pipefail && 來保證遇到未期的錯誤時阻止構建。
例如:RUN set -o pipefail && wget -O - | wc -l > /number
不是所有的 shell 都支援 -o pipefail , 基於 debian 的 image 需要指定 bash
例如:RUN [ “/bin/bash”, “-c”, “set -o pipefail && wget -O - | wc -l > /number" ]
CMD - 該指令用於執行 image 內的軟體,隨同任何引數。
格式形式為 CMD ["command", "param1", "param2"],這個形式的指令推薦用在任何基於服務的 image。
在大多數其他案例中,CMD 應該給一個互動式的shell,比如 CMD ["php", "-a"],意味著執行 docker run -it php,你會有一個可用的shell。
CMD 很少以 CMD ["param", "param"] 的方式與 ENTRYPOINT 協同,除非你和使用者已經非常熟悉 ENTRYPOINT 是如何工作的。
EXPOSE - 該指令指示 container 在哪一個埠監聽用於連線。
因此,應該使用常見傳統的埠用於應用軟體。例如 包含 Apache 的 image 將使用 EXPOSE 80,而包含 MongoDB 的 image 將使用 EXPOSE 27017 等等。
用於外部訪問,使用者可以執行 docker run 帶上一個標記來標識對映指定埠到他們選擇的埠。
對於容器連線,Docker 為從接收容器返回到源的路徑提供環境變數。(如,MYSQL_PORT_3306_TCP)
ENV - 為了使新軟體更容易執行,可以使用 ENV 來更新容器中安裝軟體的 PATH 環境變數。
例如:ENV PATH /usr/local/nginx/bin:$PATH 保證 CMD ["nginx"] 能執行。
ENV 指令 在 為你希望容器化的服務提供所需的環境變數 上同樣有用,例如 Postgres 的 PGDATA。
每個 ENV 行建立一個新的中間層,就像 RUN 命令。這意味著即使在之後的層 unset 這個環境變數,它仍然存在於這個層,並且它的值可以被列印。測試如下:
FROM alpine
ENV ADMIN_USER="mark"
RUN echo $ADMIN_USER > ./mark
RUN unset ADMIN_USER
CMD sh
$ docker run --rm -it test sh echo $ADMIN_USER
要阻止這種情況,真正 unset 環境變數,使用 RUN 指令執行 shell 命令,在一個獨立的層中 set, use, unset 變數。
使用 ; 或 && 來分割命令,使用 && 只要一個命令失敗,docker build 也失敗。
FROM alpine
ENV export ADMIN_USER="mark" \
&& echo $ADMIN_USER > ./mark \
&& unset ADMIN_USER
CMD sh
$ docker run --rm -it test sh echo $ADMIN_USER
ADD 或 COPY - 儘管兩者功能相似,一般來講,首選 COPY。
因為 COPY 比 ADD 更易懂。COPY 只支援從本地檔案到 container 的基本複製,而 ADD 有一些不明顯的特性(如,本地 tar包 自動解壓和支援遠端 URL)
因此 ADD 的最佳使用是本地 tar 檔案在 image 中的自動解壓,如,ADD rootfs.tar.xz / .
如果 Dockerfile 有多個步驟使用了上下文中的不同檔案,單獨的複製它們,而不是一次複製所有。這確保每一步的構建快取在檔案發生改變時是失效的,如:
COPY requirements.txt /tmp
RUN pip install --requirement /tmp/requirements.txt
COPY . /tmp/
要讓 RUN 步驟導致更少的快取失效,那麼把 COPY . /tmp/ 放到其前面。
因為 image 大小問題,使用 ADD 從遠端地址拉取包是極不鼓勵的;你應該使用 curl 或 wget 代替。這種方式你可以在解壓後把不需要的檔案刪除,不需要把其它層加到你的 image 中。
避免做的是:
ADD /usr/src/things/
RUN tar -zJf /usr/src/things/big.tar.xz -C /usr/src/things
RUN make -C /usr/src/things all
代替的做法是:
RUN mkdir -p /usr/src/things \
&& curl -SL \
|tar -xJC /usr/src/things \
&& make -C /usr/src/things all
對於不需要 ADD 自動解壓能力的檔案和目錄,你應該總是使用 COPY。
ENTRYPOINT - 最好的使用點是設定 image 的主命令,允許 image 像命令一樣執行(接著使用 CMD 做為預設標記)。
ENTRYPOINT ["s3cmd"]
CMD ["--help"]
現在 image 可以像這樣顯示命令的幫助資訊:$ docker run s3cmd
或者使用正確的引數來執行一個命令:$ docker run s3cmd ls s3://mybucket
這是有用的,因為 image 名稱可以兩次作為到如上命令二進位制的引用。
ENTRYPOINT 指令同樣可以用來和一個幫助指令碼結合,允許它做和命令方式一樣的事,儘管需要多進行一步 - 寫指令碼,以下是 Postgres 官方 image 的 ENTRYPOINT 使用的指令碼:
#!/bin/bash
set -e
if [ "$1" = 'postgres' ]; then
chown -R postgres "$PGDATA"
if [ -z "$(ls -A "$PGDATA")" ]; then
gosu postgres initdb
fi
fi
exec "$@"
把幫助指令碼複製到 container 中,並在 container 啟動時透過 ENTRYPOINT 執行:
COPY ./docker-entrypoint.sh /
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["postgres"]
幫助指令碼允許命令有多個引數可以互動,比如:
$ docker run postgres
$ docker run postgres postgres --help
$ docker run --rm -it postgres bash
VOLUME - 指令應該用於暴露任何資料庫儲存區域,配置區域,或 docker 容器建立的檔案/目錄。
強烈建議你使用 VOLUMN 在 image 中易變的或使用者維護的部分。
USER - 如果一個服務不需要許可權可以執行,使用 USER 切換為非 root 使用者。
透過在 Dockerfile 中建立使用者和組開始開始:RUN groupadd -r postgres && useradd --no-log-init -r -g postgres postgres
image 中的使用者和組被分配一個非確定的 UID/GID,下次 image 重建時重新分配。所以如果 UID/GID 至關重要,你應該分配一個準確的。
(由於Go archive/tar 包中未解決的bug,在docker 容器中建立相當長的UID會耗盡磁碟,因為容器層中的 /var/log/faillog 填充了NULL字元,權宜措施是傳遞 --no-log-init 給 useradd)
避免使用 sudo,如果你確實需要像 sudo 一樣的功能,例如像 root 一樣初始化守護程式但以非 root 使用者執行,考慮使用 gosu。
為了減少層次和複雜性,避免頻繁的切換使用者。
WORKDIR - 為了清晰和可靠,你應該總是為 WORKDIR 使用絕對路徑。
同樣,你應該使用 WORKDIR 而不是 RUN cd ... && do-something ,更難度閱讀、排除問題和維護。
ONBUILD - 命令在當前 Dockerfile 構建完畢執行。
ONBUILD 在當前 FROM image 派生的任何子 image 中執行。可以認為 ONBUILD 命令是父 Dockerfile 給子 Dockerfile 的指令。
Docker 構建在任何子 Dockerfile 中的命令前執行 ONBUILD。
ONBUILD 對於從給定的 image 構建 image 時有用。例如,你想為一個語言棧的 包含該語言寫的任意使用者軟體到 Dockerfile 中的 image 使用 ONBUILD。
從 ONBUILD 構建的 image 應該有一個分割 tag,例如,ruby:1.9-onbuild 或 ruby-2.0-onbuild
當把 ADD 或 COPY 放到 ONBUILD 時要小心。如果新構建的上下文缺少被新增的資源,onbuild 構建 image 會災難性失敗。透過如上新增分割的 tag 來減輕這種影響。
Guides: https://docs.docker.com/develop/develop-images/dockerfile_best-practices
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31473948/viewspace-2157061/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- docker 筆記3 dockerfile語法及最佳實踐Docker筆記
- Dockerfile 安全最佳實踐Docker
- Docker實戰-編寫DockerfileDocker
- 理解RESTful:理論與最佳實踐REST
- go專案dockerfile最佳實踐GoDocker
- [Docker 系列]docker 學習七,DockerFile 編寫和實戰Docker
- 【Docker 系列】docker 學習七,DockerFile 編寫和實戰Docker
- 【Docker】動手寫Dockerfile學習DockerDocker
- Docker | dockerfile 檔案編寫Docker
- Docker的基本使用及DockerFile的編寫Docker
- 最佳化您的部署:Docker 映象最佳實踐Docker
- 編寫 Android Library 的最佳實踐Android
- 製作 Python Docker 映象的最佳實踐PythonDocker
- mysql讀寫分離的最佳實踐MySql
- Docker容器日誌管理最佳實踐Docker
- 自然語言處理的最佳實踐自然語言處理
- Dockerfile 實踐及梳理Docker
- 編寫git commit資訊的最佳實踐GitMIT
- 編寫優雅程式碼的最佳實踐
- 編寫架構文件的最佳實踐 - Singh架構
- Docker的Dockerfile指令Docker
- Docker多階段構建最佳實踐Docker
- 使用nodejs構建Docker image最佳實踐NodeJSDocker
- 20個異常處理的最佳實踐
- Java中的異常處理最佳實踐Java
- 批處理最佳實踐 - Vlad Mihalcea
- Dockerfile實踐小提示Docker
- IDEA的Docker外掛實戰(Dockerfile篇)IdeaDocker
- js正則理論與實踐JS
- DevOps 從理論到實踐指南dev
- Flutter 自定義 Widget(理論+實踐)Flutter
- 【譯】編寫git commit資訊的最佳實踐GitMIT
- Spring Boot 編寫 API 的 10條最佳實踐Spring BootAPI
- 寫給自己的git多人開發最佳實踐Git
- Docker最佳實踐:5個方法精簡映象Docker
- 處理Java異常的9個最佳實踐Java
- Java異常處理的9個最佳實踐Java
- 處理Java異常的10個最佳實踐Java