Dockerfile 最佳實務

來源:互聯網
上載者:User
原文連結:https://my.oschina.net/u/2612999/blog/1036388  摘要: Dockerfile 最佳實務

雖然 Dockerfile 簡化了鏡像構建的過程,並且把這個過程可以進資料列版本設定,但是不正當的 Dockerfile 使用也會導致很多問題: docker 鏡像太大。如果你經常使用鏡像或者構建鏡像,一定會遇到那種很大的鏡像,甚至有些能達到 數G docker 鏡像的構建時間過長。每個 build 都會耗費很長時間,對於需要經常構建鏡像(比如單元測試)的地方這可能是個大問題 重複勞動。多次鏡像構建之間大部分內容都是完全一樣而且重複的,但是每次都要做一遍,浪費時間和資源 一般指導方針和建議 容器應該是短暫的

容器模型是進程而不是機器,不需要開機初始化。在需要時運行,不需要時停止,能夠刪除後重建,並且配置和啟動的最小化。 使用.dockerignore檔案

在 docker build 的時候,忽略部分無用的檔案和目錄可以提高構建的速度。比如.git目錄。dockerignore的定義類似gitignore。具體使用方式可以參考連結。 避免安裝不必要的安裝包

為了減少鏡像的複雜性、鏡像大小和構建時間,應該避免安裝無用的包。 每個容器只運行一個進程

一個容器只運行一個進程。容器起到了隔離應用隔離資料的作用,不同的應用運行在不同的容器讓叢集的縱向擴充以及容器的複用都變的更加簡單。需要多個應用互動時請使用 link 命令進行組合或者使用docker-compose。 最小化層數

需要掌握好Dockerfile的可讀性和鏡像層數之間的平衡。不推薦使用過多的鏡像層。 多行命令按字母排序

命令列按字母順序排序有助於避免重複執行和提高Dockerfile可讀性。apt-get update 應與 apt-get install 組合,換行使用反斜線(\)。例如:

RUN apt-get update && apt-get install -y \  bzr \  cvs \  git \  mercurial \  subversion
構建緩衝

Dockerfile的每條指令都會將結果提交為新的鏡像。下一條指令基於上一條指令的鏡像進行構建。如果一個鏡像擁有相同的父鏡像和指令(除了 ADD ),Docker將會使用鏡像而不是執行該指令,即緩衝。

因此,為了有效利用緩衝,盡量保持Dockerfile一致,並且將不變的放在前面而經常改變放在末尾。

如不希望使用緩衝,在執行 docker build 的時候加上參數 --no-cache=true 。

Docker匹配鏡像決定是否使用緩衝的規則如下: 從緩衝中存在的基礎鏡像開始,比較所有子鏡像,檢查它們構建的指令是否和當前的是否完全一致。如果不一致則緩衝不匹配。 多數情況中,比較Dockerfile中的指令是足夠的。然而,特定的指令需要做更多的判斷。 ADD COPY 指令中,將要添加到鏡像中的檔案也要被檢查。通常是檢查檔案的校正和(checksum)。 緩衝匹配檢查並不檢查容器中的檔案。例如,當使用 RUN apt-get -y update 命令更新了容器中的檔案,並不會被緩衝檢查策略作為緩衝匹配的依據。 Dockerfile指令 FROM

使用官方倉庫中的鏡像作為基礎鏡像,推薦使用 Debian image ,大小保持在100mb上下,且仍是完整的發行版。 RUN

把複雜的或過長的 RUN 語句寫成以 \ 結尾的多行的形式,以提高可讀性和可維護性。

apt-get update 和 apt-get install 一起執行,否則 apt-get install 會出現異常。

避免運行 apt-get upgrade 或 dist-upgrade ,在無特權的容器中,很多 必要 的包不能正常升級。如果基礎鏡像過時了,應當聯絡維護者。 推薦 apt-get update && apt-get install -y package-a package-b 這種方式,先更新,之後安裝最新的軟體包。

RUN apt-get update && apt-get install -y \    aufs-tools \    automake \    build-essential \    curl \    dpkg-sig \    libcap-dev \    libsqlite3-dev \    mercurial \    reprepro \    ruby1.9.1 \    ruby1.9.1-dev \    s3cmd=1.1.* \ && rm -rf /var/lib/apt/lists/*

此外,你可以通過移除/var/lib/apt/lists減少鏡像大小。

注意:官方的Ubuntu和Debian會自動運行apt-get clean,所以不需要顯式的調用 CMD

推薦使用 CMD ["executable","param1","param2"] 這樣的格式。如果鏡像是用來運行服務,需要使用 CMD["apache2","-DFOREGROUND"] ,這種格式的指令適用於任何服務性質的鏡像。 ENTRYPOINT

ENTRYPOINT 應該用於 鏡像的主命令,並使用 CMD 作為預設設定,以 s3cmd 為例:

ENTRYPOINT ["s3cmd"]CMD ["--help"]

擷取協助:

docker run s3cmd

或者執行命令:

docker run s3cmd ls s3://mybucket

這在鏡像名與程式重名時非常有用。

ENTRYPOINT 也可以啟動自訂指令碼: 定義指令碼:

#!/bin/bashset -eif [ "$1" = 'postgres' ]; then    chown -R postgres "$PGDATA"    if [ -z "$(ls -A "$PGDATA")" ]; then        gosu postgres initdb    fi    exec gosu postgres "$@"fiexec "$@"

注意:這段指令碼使用了exec命令以確保最終應用程式在容器內啟動的PID為1。這段指令碼允許容器接收Unix signals。

COPY ./docker-entrypoint.sh /ENTRYPOINT ["/docker-entrypoint.sh"]

這段指令碼為使用者提供了多種和 Postgres 互動的途徑:

你可以簡單地啟動 Postgres:

docker run postgres。

或者運行 postgres 並傳入參數:

docker run postgres postgres --help。

你甚至可以從鏡像中啟動一個完全不同的程式,比如 Bash:

docker run --rm -it postgres bash
EXPOSE

應該儘可能地使用預設連接埠。例如Apache web服務使用EXPOSE 80,MongoDB使用EXPOSE 27017。 ENV 可以使用ENV更新PATH環境變數。例如,ENV PATH /usr/local/nginx/bin:$PATH可以確保CMD [“nginx”]正常運行。 ENV還可以提供者所需要的環境變數,例如Postgres的 PGDATA。 ENV可以設定版本等資訊。使版本資訊更易於維護。

ENV PG_MAJOR 9.3ENV PG_VERSION 9.3.4RUN curl -SL http://example.com/postgres-$PG_VERSION.tar.xz | tar -xJC /usr/src/postgress && …ENV PATH /usr/local/postgres-$PG_MAJOR/bin:$PATH
ADD or COPY

雖然 ADD 與 COPY 功能類似,但推薦使用 COPY 。 COPY 只支援基本的檔案拷貝功能,更加的可控。而 ADD 具有更多特定,比如tar檔案自動提取,支援URL。 通常需要提取tarball中的檔案到容器的時候才會用到 ADD 。

如果在Dockerfile中使用多個檔案,每個檔案應使用單獨的 COPY 指令。這樣,只有出現檔案變化的指令才會不使用緩衝。

為了控制鏡像的大小,不建議使用 ADD 指令擷取URL檔案。正確的做法是在 RUN 指令中使用 wget 或 curl 來擷取檔案,並且在檔案不需要的時候刪除檔案。

RUN mkdir -p /usr/src/things \    && curl -SL http://example.com/big.tar.gz \    | tar -xJC /usr/src/things \    && make -C /usr/src/things all
VOLUME

VOLUME 通常用作資料卷,對於任何可變的檔案,包括資料庫檔案、程式碼程式庫、或者容器所建立的檔案/目錄等都應該使用 VOLUME 掛載。 USER

如果服務不需要特權來運行,使用 USER 指令切換到非root使用者。使用 RUN groupadd -r mysql && useradd -r -g mysql mysql 之後用 USER mysql 切換使用者

要避免使用 sudo 來提升許可權,因為它帶來的問題遠比它能解決的問題要多。如果你確實需要這樣的特性,那麼可以選擇使用 gosu 。

最後,不要反覆的切換使用者。減少不必要的layers。 WORKDIR

為了清晰和可維護性,應該使用WORKDIR來定義工作路徑。推薦使用WORKDIR來代替RUN cd … && do-something 這樣的指令。 ONBUILD

Dockerfile 中的其它指令都是為了定製當前鏡像而準備的,唯有 ONBUILD 是為了協助別人定製自己而準備的。

ONBUILD指令用來設定一些觸發的指令,用於在當該鏡像被作為基礎鏡像來建立其他鏡像時(也就是Dockerfile中的FROM為當前鏡像時)執行一些操作,ONBUILD中定義的指令會在用於產生其他鏡像的Dockerfile檔案的FROM指令之後被執行,上述介紹的任何一個指令都可以用於ONBUILD指令,可以用來執行一些因為環境而變化的操作,使鏡像更加通用。

注意: ONBUILD中定義的指令在當前鏡像的build中不會被執行。 可以通過查看docker inspect <image>命令執行結果的OnBuild鍵來查看某個鏡像ONBUILD指令定義的內容。 ONBUILD中定義的指令會當做引用該鏡像的Dockerfile檔案的FROM指令的一部分來執行,執行順序會按ONBUILD定義的先後順序執行,如果ONBUILD中定義的任何一個指令運行失敗,則會使FROM指令中斷並導致整個build失敗,當所有的ONBUILD中定義的指令成功完成後,會按正常順序繼續執行build。 ONBUILD中定義的指令不會繼承到當前引用的鏡像中,也就是當引用ONBUILD的鏡像建立完成後將會清除所有引用的ONBUILD指令。 ONBUILD指令不允許嵌套,例如ONBUILD ONBUILD ADD . /data是不允許的。 ONBUILD指令不會執行其定義的FROM或MAINTAINER指令。 例如,Dockerfile使用如下的內容建立了鏡像 image-A :

[...]ONBUILD ADD . /app/srcONBUILD RUN /usr/local/bin/python-build --dir /app/src[...]

如果基於 image-A 建立新的鏡像時,新的Dockerfile中使用FROM image-A指定基礎鏡像時,會自動執行ONBUILD指令內容,等價於在後面添加了兩條指令。

FROM image-A#Automatically run the followingADD . /app/srcRUN /usr/local/bin/python-build --dir /app/src
使用情境 Node.js

假設我們要製作 Node.js 所寫的應用的鏡像。我們都知道 Node.js 使用 npm 進行包管理,所有依賴、配置、啟動資訊等會放到 package.json 檔案裡。在拿到程式碼後,需要先進行 npm install 才可以獲得所有需要的依賴。然後就可以通過 npm start 來啟動應用。因此,一般來說會這樣寫 Dockerfile:

FROM node:slimRUN mkdir /appWORKDIR /appCOPY ./package.json /appRUN [ "npm", "install" ]COPY . /app/CMD [ "npm", "start" ]

把這個 Dockerfile 放到 Node.js 項目的根目錄,構建好鏡像後,就可以直接拿來啟動容器運行。但是如果我們還有第二個 Node.js 項目也差不多呢。好吧,那就再把這個 Dockerfile 複製到第二個項目裡。那如果有第三個項目呢。再複製麼。檔案的副本越多,版本控制就越困難,讓我們繼續看這樣的情境維護的問題。

如果第一個 Node.js 項目在開發過程中,發現這個 Dockerfile 裡存在問題,比如敲錯字了、或者需要安裝額外的包,然後開發人員修複了這個 Dockerfile,再次構建,問題解決。第一個項目沒問題了,但是第二個項目呢。雖然最初 Dockerfile 是複製、粘貼自第一個項目的,但是並不會因為第一個項目修複了他們的 Dockerfile,而第二個項目的 Dockerfile 就會被自動修複。

那麼我們可不可以做一個基礎鏡像,然後各個項目使用這個基礎鏡像呢。這樣基礎鏡像更新,各個項目不用同步 Dockerfile 的變化,重新構建後就繼承了基礎鏡像的更新。好吧,可以,讓我們看看這樣的結果。那麼上面的這個 Dockerfile 就會變為:

FROM node:slimRUN mkdir /appWORKDIR /appCMD [ "npm", "start" ]

這裡我們把項目相關的構建指令拿出來,放到子項目裡去。假設這個基礎鏡像的名字為 my-node 的話,各個項目內的自己的 Dockerfile 就變為:

FROM my-nodeCOPY ./package.json /appRUN [ "npm", "install" ]COPY . /app/

基礎鏡像變化後,各個項目都用這個 Dockerfile 重新構建鏡像,會繼承基礎鏡像的更新。

那麼,問題解決了麼。沒有。準確說,只解決了一半。如果這個 Dockerfile 裡面有些東西需要調整呢。比如 npm install 需要統一加一些參數,那怎麼辦。這一行 RUN 是不可能放入基礎鏡像的,因為涉及到了當前項目的 ./package.json,難道又要一個個修改麼。所以說,這樣製作基礎鏡像,只解決了原來的 Dockerfile 的前4條指令的變化問題,而後面三條指令的變化則完全沒辦法處理。

ONBUILD 可以解決這個問題。讓我們用 ONBUILD 重新寫一下基礎鏡像的 Dockerfile:

FROM node:slimRUN mkdir /appWORKDIR /appONBUILD COPY ./package.json /appONBUILD RUN [ "npm", "install" ]ONBUILD COPY . /app/CMD [ "npm", "start" ]

這次我們回到原始的 Dockerfile,但是這次將項目相關的指令加上 ONBUILD,這樣在構建基礎鏡像的時候,這三行並不會被執行。然後各個項目的 Dockerfile 就變成了簡單地:

FROM my-node

是的,只有這麼一行。當在各個項目目錄中,用這個只有一行的 Dockerfile 構建鏡像時,之前基礎鏡像的那三行 ONBUILD 就會開始執行,成功的將當前項目的代碼複製進鏡像、並且針對本項目執行 npm install,產生應用鏡像。 Maven

類似Java,Go等編譯型項目,可以使用ONBUILD指令進行最佳化Dockerfile。

編寫onbuild Dockerfile如下:

FROM maven:3-jdk-8RUN mkdir -p /usr/src/appWORKDIR /usr/src/appONBUILD ADD . /usr/src/appONBUILD RUN mvn install

然後所有依賴maven編譯的項目Dockerfile可以簡化成如下形式:

FROM maven:3.3-jdk-8-onbuildCMD ["java","-jar","/usr/src/app/target/demo-1.0-SNAPSHOT-jar-with-dependencies.jar"]
官方Dockerfile樣本: Go Perl Hy Ruby © 著作權歸作者所有

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.