Docker通過讀取Dockerfile中的指令自動構建鏡像,一個文本文件包含構建鏡像的所有指令。Dockerfile遵循特定的格式和指令集,您可以在Dockerfile中引用它們。
Docker鏡像由只讀層組成,每一層代表一個Dockerfile指令集。每一層都是前一層變化的增量。考慮這個Dockerfile:
FROMubuntu:18.04COPY . /appRUN make /appCMD python/app/app.py每條指令創建一個層:
當你運行一個鏡像生成一個容器,在底層的頂部添加一個可寫層(容器層)。對正在運行容器所做的所有更改,比如寫文件,修改已存在的文件,和刪除文件,都被寫入這個可寫空口層。
Dockerfile定義的容器應該生成儘可能短暫的容器,通過「短暫」,我們的意思是容器可以被停止或銷毀,然後重建並用最小的設置和配置。從而充分利用容器的分層存儲。
執行dockerbuild命令時,當前工作目錄稱為構建上下文。 默認情況下,假定Dockerfile位於此處,但您可以使用文件標誌(-f)指定其他位置。 無論Dockerfile實際存在於何處,當前目錄中的所有文件和目錄的遞歸內容都將作為構建上下文發送到Docker守護程序。
構建上下文件示例:
創建並CD進入構建上下文目錄,將「hello」寫入名為hello的文本文件中,並創建一個在其上運行cat的Dockerfile。從構建上下文(.)中構建鏡像。
mkdirmyproject && cd myprojectecho "hello"> helloecho -e "FROMbusybox\nCOPY /hello /\nRUN cat /hello" > Dockerfiledocker build -t helloapp:v1 .將Dockerfile和hello移動到單獨的目錄中並構建映像的第二個版本(不依賴於上一次構建的緩存)。使用-f指向Dockerfile並指定構建上下文的目錄:
mkdir -p dockerfiles contextmv Dockerfile dockerfiles && mv hello contextdocker build --no-cache -t helloapp:v2 -f dockerfiles/Dockerfile context無意中包含構建不必要的文件會導致更大的構建上下文和更大的鏡像,這將增加構建時間,拉取和推送鏡像的時間以及容器運行時佔用更多的存儲空間。查看構建上下文大小,在構建Dockerfile時查找類似信息。
Sending buildcontext to Docker daemon 187.8MBDocker能通過stdin與本址或遠程構建上下文管道Dockerfile來構建鏡像。通過stdin管道Dockerfile對於執行一次性構建非常有用,無需將Dockerfile寫入磁碟,或者在生成Dockerfile的情況下,並且之後不應該持久化。
例如,以下命令是等效的:
echo -e 'FROMbusybox\nRUN echo "hello world"' | docker build – docker build-<<EOFFROM busyboxRUN echo"hello world"EOF在Dockerfile不需要將文件複製到映像中並且提高構建速度的情況下,省略構建上下文非常有用,因為沒有文件發送到守護程序。
注意:如果使用此語法,嘗試構建鏡像時使用COPY或ADD的Dockerfile將失敗,以下示例說明了這一點:
mkdir examplecd exampletouch somefile.txt docker build t myimage:latest -<<EOFFROM busyboxCOPYsomefile.txt .RUN cat /somefile.txtEOF...Step 2/3 :COPY somefile.txt .COPY failed:stat /var/lib/docker/tmp/docker-builder249218248/somefile.txt: no such file ordirectory3.使用stdin的Dockerfile從本地上下文構建鏡像使用此語法使用本地文件系統上下文構建鏡像,但使用stdin中的Dockerfile。語法使用-f(--file)選項指定要使用的Dockerfile,使用連字符(-)作為文件名來指示Docker從stdin讀取Dockerfile
docker build[OPTIONS] -f- PATH下面的示例使用當前目錄(.)作為構建上下文,並使用stdin作為Dockerfile構建鏡像。
# create adirectory to work inmkdir examplecd example # create anexample filetouchsomefile.txt # build animage using the current directory as context, and a Dockerfile passed throughstdindocker build-t myimage:latest -f- . <<EOFFROM busyboxCOPYsomefile.txt .RUN cat/somefile.txtEOF4.使用stdin的Dockerfile從遠程上下文構建鏡像使用此語法使用遠程git倉庫上下文構建鏡像,但使用stdin中的Dockerfile。語法使用-f(--file)選項指定要使用的Dockerfile,使用連字符(-)作為文件名來指示Docker從stdin讀取Dockerfile。
docker build[OPTIONS] -f- PATH如果您希望從倉庫構建映像不包含Dockerfile,或者您希望使用自定義Dockerfile構建,而不維護自己的倉庫分支,則此語法非常有用。
下面的示例使用stdin中的Dockerfile構建映像,並在GitHub上的「hello-world」Git倉庫中添加README.md文件。
docker build -t myimage:latest -f- https://github.com/docker-library/hello-world.git<<EOFFROM busyboxCOPYREADME.md .EOF使用遠程Git存儲庫構建鏡像作為構建上下文時,Docker會在本地計算機上執行倉庫的git克隆,並將這些文件作為構建上下文發送到守護程序。 此功能要求在運行dockerbuild命令的主機上安裝git。
要排除與構建無關的文件(不重構源倉庫),請使用.dockerignore文件。 此文件支持類似於.gitignore文件的排除模式。有關創建的信息,請參閱.dockerignore文件。
多階段構建允許您大幅減小最終鏡像的大小,而無需減少中間層和文件的數量。
由於鏡像是在構建過程的最後階段構建的,因此可以通過利用構建緩存來最小化鏡像層。
例如,如果您的構建包含多個層,則可以從較不頻繁更改(以確保構建緩存可重用)到更頻繁更改的順序進行排序:
安裝構建應用程式所需的工具
安裝或更新庫依賴項
生成您的應用程式
Go應用程式的Dockerfile如下所示:
FROM golang:1.11-alpine AS build # Installtools required for project# Run `dockerbuild --no-cache .` to update dependenciesRUN apk add --no-cache gitRUN go get github.com/golang/dep/cmd/dep # Listproject dependencies with Gopkg.toml and Gopkg.lock# Theselayers are only re-built when Gopkg files are updatedCOPY Gopkg.lock Gopkg.toml /go/src/project/WORKDIR /go/src/project/# Installlibrary dependenciesRUN depensure -vendor-only # Copy theentire project and build it# This layeris rebuilt when a file changes in the project directoryCOPY ./go/src/project/RUN go build -o /bin/project # Thisresults in a single layer imageFROM scratchCOPY--from=build /bin/project /bin/projectENTRYPOINT["/bin/project"]CMD["--help"]不要安裝不必要的包
為了降低複雜性,依賴性,文件大小和構建時間,請避免安裝額外的或不必要的軟體包,即便它們可能「很好」。例如,您不需要在資料庫映像中包含文本編輯器。
每個容器應該只有一個關係。 將應用程式分離到多個容器中可以更容易地水平擴展和重用容器。例如,Web應用程式堆棧可能包含三個獨立的容器,每個容器都有自己獨特的映像,以分離的方式管理Web應用程式,資料庫和緩存。
將每個容器限制為一個進程是一個很好的經驗法則,但它不是一個硬性規則。例如,不僅可以使用init進程生成容器,而且某些程序可能會自行生成其他進程。 例如,Celery可以生成多個工作進程,Apache可以為每個請求創建一個進程。
使用您的最佳判斷,儘可能保持容器清潔和模塊化。如果容器彼此依賴,則可以使用Docker容器網絡來確保這些容器可以進行通信。
在老版本的Docker中,最大限度地減少鏡像中的層數以確保它們具有更高的性能。 添加了以下特性以減少此限制:
儘可能的,通過按字母順序排序多行參數來緩解以後的更改。這有助於避免重複包並使更容易更新。這也使PR更容易閱讀和審查。在反斜槓(\)之前添加空格也有幫助。
這是一個編譯包依賴示例:
RUN apt-getupdate && apt-get install -y \ bzr \ cvs \ git \ mercurial \ subversion構建映像時,Docker會逐步執行Dockerfile中的指令,按指定的順序執行每個指令。在檢查每條指令時,Docker會在其緩存中查找可以重用的現有映像,而不是創建新的(重複)映像。
如果您根本不想使用緩存,可以在docker build命令中使用--no-cache=true選項。但是,如果您確實讓Docker使用其緩存,那麼了解它何時可以找到匹配的鏡像層非常重要。 Docker遵循的基本規則概述如下:
從已經在高速緩存中的鏡像開始,下一條指令將從基礎鏡像導出的所有子鏡像層進行比較,以查看它們中的一個是否使用完全相同的指令構建。如果不是,則緩存無效。
在大多數情況下,只需將Dockerfile中的指令與其中一個子鏡像層進行比較即可。 但是,某些指令需要更多的檢查和解析。
對於ADD和COPY指令,將檢查鏡像中文件的內容,並為每個文件計算校驗和。在這些校驗和中不考慮文件的最後修改時間和最後訪問時間。在緩存查找期間,將校驗和與現有映像中的校驗和進行比較。如果文件中的任何內容(例如內容和元數據)發生了任何更改,則緩存將失效。
除了ADD和COPY命令之外,緩存檢查不會查看容器中的文件以確緩存匹配。例如,在執行RUNapt-get -y update命令時,不檢查容器中更新的文件以確定是否存在緩存命中。在這種情況下,只需使用命令字符串本身來查找匹配項。
緩存無效後,所有後續Dockerfile命令都會生成新鏡像,並且不使用緩存。
這些建議旨在幫助您創建高效且可維護的Dockerfile。
儘可能使用當前的官方鏡像作為鏡像的基礎。我們推薦Alpine鏡像,因為它是嚴格控制的並且尺寸小(目前小於5 MB),同時仍然是完整的Linux發行版。
您可以為鏡像添加標籤,以幫助按項目組織鏡像,記錄許可信息,輔助自動化或其他原因。對於每個標籤,添加以LABEL開頭並帶有一個或多個鍵值對。以下示例顯示了不同的可接受格式。內容包括解釋性意見。
必須引用帶空格的字符串或必須轉義空格。 內引號字符(「)也必須轉義。
LABELcom.example.version="0.0.1-beta"LABELvendor1="ACME Incorporated"LABELvendor2=ZENITH\ IncorporatedLABELcom.example.release-date="2015-02-12"LABELcom.example.version.is-production=""鏡像可以有多個標籤。在Docker 1.10之前,建議將所有標籤組合到單個LABEL指令中以防止創建額外的層。這不再是必需的,但仍然支持組合標籤。
# 在一行設置多個標籤LABEL com.example.version="0.0.1-beta"com.example.release-date="2015-02-12"以上也可以寫成:
# 一次設置多個標籤,使用行連續符進行換行LABELvendor=ACME\ Incorporated \ com.example.is-beta= \ com.example.is-production="" \ com.example.version="0.0.1-beta" \ com.example.release-date="2015-02-12"在使用反斜槓分隔的多行拆分長或複雜的RUN語句,以使Dockerfile更具可讀性,可理解性和可維護性。
APT-GETRUN最常見的用例可能是apt-get安裝軟體包,所以RUN apt-get命令有幾個需要注意的問題。
避免RUNapt-get update和dist-upgrade,因為父映像中的許多「基本」包無法在非特權容器內升級。如果鏡像中包含的包已過期,請與其維護人員聯繫。如果你知道有一個需要更新的特定包foo,請使用apt-get install -y foo自動更新。
始終將RUNapt-get update與apt-get install結合在同一個RUN語句中。例如:
RUN apt-get update && apt-get install -y \ package-bar \ package-baz \ package-foo
在RUN語句中單獨使用apt-get update會導致緩存問題和後續的apt-get install失敗。例如,假設你有一個Dockerfile:
以上也可以寫成:
RUN apt-getupdateRUN apt-getinstall -y curl構建映像後,所有層都在Docker緩存中。假設您稍後通過apt-get install添加額外的包來修改:
# FROMubuntu:18.04RUN apt-get updateRUN apt-getinstall -y curl nginxDocker將初始和修改的指令視為相同,並重用前面步驟中的緩存。因此,不會執行apt-get update,因為構建使用緩存版本。由於apt-get update未運行,因此您的構建可能會獲得curl和nginx軟體包的舊版本。
使用RUNapt-get update && apt-get install -y可確保您的Dockerfile安裝最新的軟體包版本,無需進一步編碼或手動幹預。這種技術被稱為「緩存破壞」。您還可以通過指定包版本來實現緩存清除。這稱為版本固定,例如:
RUN apt-getupdate && apt-get install -y \ package-bar \ package-baz \ package-foo=1.3.*版本固定強制構建以檢索特定版本,而不管緩存中的內容是什麼。此技術還可以減少由於所需包中意外更改而導致的故障。
如下是一個結構良好的RUN指令,它演示了所有apt-get建議。
RUN apt-getupdate && 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/*s3cmd參數指定版本1.1*。如果鏡像使用以前的舊版本,則指定新版本會導致apt-get update緩存失效,並確保安裝新版本。列出每行的包也可以防止包重複中的錯誤。
此外,當您通過刪除/var/lib/apt/lists/清理apt緩存時,它會減小映像大小,因為apt緩存不存儲在層中。 由於RUN語句以apt-get update開頭,因此在apt-get install之前始終刷新包緩存。
官方Debian和Ubuntu映像自動運行apt-get clean,因此不需要顯式調用。
USING PIPES某些RUN命令依賴於使用管道符(|)將一個命令的輸出傳遞到另一個命令的能力,如下例所示:
RUN wget –O –https://some.site | wc –l> /numberDocker使用/bin/sh -c解釋器執行這些命令,該解釋器僅評估管道中最後一個操作的退出代碼以確定成功。在上面的示例中,只要wc -l命令成功,即使wget命令失敗,此構建步驟也會成功並生成新映像。
如果希望命令由於管道中任何階段的錯誤而失敗,請在之前添加set -o pipefail &&,以確保意外錯誤可防止構建無意義的鏡像。例如:
RUN set -opipefail && wget -O - https:並不是所有shell都支持 –o pipefail
在基於Debian的映像上的dash shell的情況下,請考慮使用RUN的時候明確選擇支持pipefail選項的shell。 例如:
RUN ["/bin/bash","-c", "set -o pipefail && wget -O - https://some.site |wc -l > /number"]CMD
CMD指令應該用於運行鏡像包含的軟體以及任何參數。CMD應該幾乎總是以CMD [「executable」,「param1」,「param2」.]的形式使用。因此,如果鏡像用於服務,例如Apache和Rails,則可以運行類似CMD[「apache2」,「 - DFOREGROUND」]的內容。實際上,建議將這種形式的指令用於任何基於服務的鏡像。
在大多數其他情況下,CMD應該被賦予一個交互式shell,例如bash,python和perl。例如,CMD [「perl」,「 - de0」],CMD [「python」]或CMD [「php」,「 - a」]。使用這個表單意味著當你執行像docker run -it python這樣的東西時,你將被放入一個可用的shell中,準備就緒。 CMD應該很少以CMD [「param」,「param」]的方式與ENTRYPOINT一起使用,除非您和您的預期用戶已經非常熟悉ENTRYPOINT的工作原理。
EXPOSEEXPOSE指令指示容器偵聽連接的埠。因此,您應該為您的應用程式使用通用的傳統埠。 例如,包含Apache Web伺服器的映像將使用EXPOSE 80,而包含MongoDB的映像將使用EXPOSE 27017等。
對於外部訪問,您可以執行docker run,該標誌指示如何將指定埠映射到他們選擇的埠。對於容器連結,Docker為從接收容器返回源的路徑提供環境變量(即MYSQL_PORT_3306_TCP)。
ENV為了使新軟體更易於運行,您可以使用ENV更新容器安裝的軟體的PATH環境變量。 例如,ENV PATH/usr/local/nginx/bin:$PATH確保CMD [「nginx」]正常運行。
ENV指令對於提供特定於您希望容納的服務的必需環境變量也很有用,例如Postgres’s的PGDATA。
最後,ENV還可用於設置常用版本號,以便更容易維護版本,如以下示例所示:
ENV PG_MAJOR9.3ENV PG_VERSION 9.3.4RUN curl -SLhttp://example.com/postgres-$PG_VERSION.tar.xz | tar -xJC /usr/src/postgress&& …ENV PATH/usr/local/postgres-$PG_MAJOR/bin:$PATH類似於在程序中使用常量變量(解耦硬編碼),此方法允許您更改單個ENV指令以自動神奇地破壞容器中的軟體版本。
每條ENV都會創建一個新的中間層,就像RUN命令一樣。這意味著即使您在將來的鏡像中取消設置環境變量,它仍然會在此鏡像層中保留,並且其值可以導出。您可以通過創建如下所示的Dockerfile來測試它,然後構建它。
FROM alpineENV ADMIN_USER="mark"RUN echo$ADMIN_USER > ./markRUN unsetADMIN_USER $ docker run--rm test sh -c 'echo $ADMIN_USER'mark要防止這種情況,並且實際上取消環境變量,請使用帶有RUN命令執行shell執行 set和unset變量。您可以使用; 或&&分隔命令。如果您使用第二種方法,並且其中一個命令失敗,則docker構建也會失敗。這通常是一個好辦法。使用\作為Linux Dockerfiles的行連續符可以提高可讀性。 您還可以將所有命令放入shell腳本中,並使用RUN命令運行該shell腳本。
FROM alpineRUN export ADMIN_USER="mark"\ && echo $ADMIN_USER > ./mark \ && unset ADMIN_USER
CMD sh$ docker run--rm test sh -c 'echo $ADMIN_USER'ADDor COPY儘管ADD和COPY在功能上相似,但一般來說,COPY是優選的。那是因為它比ADD更透明。COPY僅支持將本地文件複製到容器中,而ADD具有一些功能(如本地的tar提取和遠程URL支持),這些功能並不是很明顯。 因此,ADD的最佳用途是將本地tar文件自動提取到鏡像中,如ADD rootfs.tar.xz /所示。
如果您有多個使用上下文中不同文件的Dockerfile步驟,請單獨複製它們,而不是一次複製它們。 這可確保每個步驟的構建緩存僅在特定所需文件更改時失效(強制重新執行該步驟)。例如:
COPY requirements.txt /tmp/RUN pipinstall --requirement /tmp/requirements.txtCOPY . /tmp/由於鏡像大小很重要,因此強烈建議不要使用ADD從遠程URL獲取包。你應該使用curl或wget代替。這樣,您可以刪除提取後不再需要的文件,也不必在圖像中添加其他鏡像層。 例如,你應該避免做以下事情:
ADD http://example.com/big.tar.xz /usr/src/things/RUN tar -xJf/usr/src/things/big.tar.xz -C /usr/src/thingsRUN make -C/usr/src/things all最佳做法:
RUN mkdir -p/usr/src/things \ && curl -SLhttp://example.com/big.tar.xz \ | tar-xJC /usr/src/things \ && make -C /usr/src/things all對於不需要ADD的tar自動提取功能的其他項目(文件,目錄),應始終使用COPY。
ENTRYPOINTENTRYPOINT的最佳用途是設置圖像的主程序,允許該鏡像執行命令一樣運行(然後使用CMD作為默認標誌)。
讓我們從命令行工具s3cmd的鏡像示例開始:
ENTRYPOINT["s3cmd"]CMD["--help"]現在可以像這樣運行鏡像來顯示命令的幫助:
或使用正確的參數執行命令:
這個很有用,因為鏡像名稱可以兼作二進位文件的引用,如上面的命令所示。
ENTRYPOINT指令也可以與輔助腳本結合使用,使其能夠以與上述命令類似的方式運行,即使啟動該工具可能需要多個步驟。
例如,Postgres官方鏡像使用以下腳本作為其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"$@"配置應用進程PID為1
此腳本使用exec Bash命令,以便最終運行的應用程式成為容器的PID 1.這允許應用程式接收發送到容器的任何Unix信號。
幫助程序腳本被複製到容器中並通過容器啟動時的ENTRYPOINT運行:
COPY./docker-entrypoint.sh /ENTRYPOINT["/docker-entrypoint.sh"]CMD["postgres"]該腳本允許用戶以多種方式與Postgres交互。它可以簡單地啟動Postgres:
或者,它可用於運行Postgres並將參數傳遞給伺服器:
$ docker runpostgres postgres --help最後,它還可以用來啟動一個完全不同的工具,比如Bash:
$ docker run –rm–it postgres bash
VOLUMEVOLUME指令用於掛載由docker容器創建的任何資料庫存儲區域,配置存儲或文件/文件夾。強烈建議您將VOLUME用於鏡像的任何可變或用戶可維護部分。
USER如果服務可以在沒有權限的情況下運行,請使用USER更改為非root用戶。首先在Dockerfile中創建用戶和組,例如RUN groupadd -r postgres && useradd --no-log-init -r -gpostgres postgres。
考慮一個顯式的UID/GID
鏡像中的用戶和組被分配了非確定性UID/GID,因為無論鏡像如何重建,都會分配「下一個」UID/GID。 因此,如果它很重要,您應該分配一個顯式的UID/GID。
避免安裝或使用sudo,因為它具有不可預測的TTY和可能導致問題的信號轉發行為。如果您絕對需要與sudo類似的功能,例如將守護程序初始化為root但將其作為非root運行,請考慮使用「gosu」。
最後,為了減少層數和複雜性,請避免頻繁地來回切換USER。
WORKDIR為了清晰和可靠,您應該始終使用WORKDIR的絕對路徑。 此外,您應該使用WORKDIR而不是像這樣激進指令RUN CD . && do-something,這些指令難以閱讀,故障排除和維護。
ONBUILD在當前Dockerfile構建完成後執行ONBUILD命令。 ONBUILD在從當前鏡像派生的任何子鏡像中執行。將ONBUILD命令視為父Dockerfile為子Dockerfile提供的指令。
Docker構建在子Dockerfile中的任何命令之前執行ONBUILD命令。
ONBUILD對於將從給定鏡像構建的鏡像非常有用。例如,您可以使用ONBUILD作為語言堆棧映像,在Dockerfile中構建使用該語言編寫的任意用戶軟體。
從ONBUILD構建的鏡像應該獲得一個單獨的標記,例如:ruby:1.9-onbuild或ruby:2.0-onbuild。
將ADD或COPY放入ONBUILD時要小心。如果新構建的上下文缺少要添加的資源,則「onbuild」鏡像將發生災難性故障。如上所述,添加單獨的標記有助於通過Dockerfile作者做出選擇來緩解這種情況。
這些官方圖像具有示例性Dockerfile:
Go https://hub.docker.com/_/golang/
Perl https://hub.docker.com/_/perl/
Hy https://hub.docker.com/_/hylang/
Ruby https://hub.docker.com/_/ruby/
原文連結:https://docs.docker.com/develop/develop-images/dockerfile_best-practices/
本文檔PDF下載方法
關注新鈦雲服訂閱號
在後臺回復關鍵詞DOCKER
了解新鈦雲服
新鈦雲服正式獲批工信部ISP/IDC(含網際網路資源協作)牌照
深耕專業,矗立鰲頭,新鈦雲服獲千萬Pre-A輪融資
原電訊盈科中國區副總裁加入新鈦雲服「附專訪」
新鈦雲服,打造最專業的Cloud MSP+,做企業業務和雲之間的橋梁
新鈦雲服一周年,完成兩輪融資,服務五十多家客戶
上海某倉儲物流電子商務公司混合雲解決方案
新鈦雲服出品的部分精品技術乾貨
萬字長文:雲架構設計原則|附PDF下載
新鈦雲服安全專家談Docker Hub被黑事件
使Kubernetes更好用的22個開源工具
剛剛,OpenStack 第 19 個版本來了,附28項特性詳細解讀!
Ceph OSD故障排除|萬字經驗總結
七個用於Docker和Kubernetes防護的安全工具
運維人的終身成長,從清單管理開始|萬字長文!
99%運維不知道,系統文件md5變了,竟然是因為.
OpenStack與ZStack深度對比:架構、部署、計算存儲與網絡、運維監控等