- 指導方針
創建短暫的容器
意思是 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。
https://docs.docker.com/engine/reference/builder/#from
LABEL - 通過object幫助組織image,每行以LABEL開頭,有一個或多個 key-value 對。
https://docs.docker.com/config/labels-custom-metadata/
RUN - 把長且復雜的 RUN 語句用反斜線分割成多行,保持 Dockerfile 更可讀,可理解,可維護。
https://docs.docker.com/engine/reference/builder/#run
把 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 - https://some.site | wc -l > /number
不是所有的 shell 都支持 -o pipefail , 基於 debian 的 image 需要指定 bash
例如:RUN [ “/bin/bash”, “-c”, “set -o pipefail && wget -O - https://some.site | wc -l > /number" ]
CMD - 該指令用於運行 image 內的軟件,隨同任何參數。
https://docs.docker.com/engine/reference/builder/#cmd
格式形式為 CMD ["command", "param1", "param2"],這個形式的指令推薦用在任何基於服務的 image。
在大多數其他案例中,CMD 應該給一個交互式的shell,比如 CMD ["php", "-a"],意味着執行 docker run -it php,你會有一個可用的shell。
CMD 很少以 CMD ["param", "param"] 的方式與 ENTRYPOINT 協同,除非你和用戶已經非常熟悉 ENTRYPOINT 是如何工作的。
EXPOSE - 該指令指示 container 在哪一個端口監聽用於連接。
https://docs.docker.com/engine/reference/builder/#expose
因此,應該使用常見傳統的端口用於應用軟件。例如 包含 Apache 的 image 將使用 EXPOSE 80,而包含 MongoDB 的 image 將使用 EXPOSE 27017 等等。
用於外部訪問,使用者可以執行 docker run 帶上一個標記來標識映射指定端口到他們選擇的端口。
對於容器連接,Docker 為從接收容器返回到源的路徑提供環境變量。(如,MYSQL_PORT_3306_TCP)
ENV - 為了使新軟件更容易運行,可以使用 ENV 來更新容器中安裝軟件的 PATH 環境變量。
https://docs.docker.com/engine/reference/builder/#env
例如: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。
https://docs.docker.com/engine/reference/builder/#add |
https://docs.docker.com/engine/reference/builder/#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 http://example.com/big.tar.xz /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 http://example.com/big.tar.xz \ | tar -xJC /usr/src/things \ && make -C /usr/src/things all
對於不需要 ADD 自動解壓能力的文件和目錄,你應該總是使用 COPY。
ENTRYPOINT - 最好的使用點是設置 image 的主命令,允許 image 像命令一樣運行(接着使用 CMD 做為默認標記)。
https://docs.docker.com/engine/reference/builder/#entrypoint
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 容器創建的文件/目錄。
https://docs.docker.com/engine/reference/builder/#volume
強烈建議你把 VOLUMN 使用在 image 中易變的或用戶維護的部分。
USER - 如果一個服務不需要權限可以運行,使用 USER 切換為非 root 用戶。
https://docs.docker.com/engine/reference/builder/#user
通過在 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。
https://github.com/tianon/gosu
為了減少層次和復雜性,避免頻繁的切換用戶。
WORKDIR - 為了清晰和可靠,你應該總是為 WORKDIR 使用絕對路徑。
https://docs.docker.com/engine/reference/builder/#workdir
同樣,你應該使用 WORKDIR 而不是 RUN cd ... && do-something ,更難度閱讀和排除問題以及維護。
ONBUILD - 命令在當前 Dockerfile 構建完畢執行。
https://docs.docker.com/engine/reference/builder/#onbuild
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 來減輕這種影響。
