编写Dockerfiles的最佳做法
編寫Dockerfiles的最佳做法
Docker可以通過從Dockerfile包含所有命令的文本文件中讀取?指令,自動(dòng)構(gòu)建圖像,以便構(gòu)建給定圖像所需的順序。Dockerfile堅(jiān)持一個(gè)具體的格式,并使用一組具體的說明。您可以在Dockerfile參考頁面上了解基礎(chǔ)知識?。如果你是新來Dockerfile的,你應(yīng)該從那里開始。
本文檔介紹了Docker,Inc.和Docker社區(qū)推薦的最佳做法和方法,以創(chuàng)建易于使用,有效?Dockerfile的。我們強(qiáng)烈建議您遵循這些建議(實(shí)際上,如果您正在創(chuàng)建官方圖片,則必須遵守這些做法)。
您可以在buildpack-depsDockerfile中看到許多這些做法和建議。
注意:有關(guān)這里提到的任何Dockerfile命令的更詳細(xì)的解釋,請?jiān)L問Dockerfile參考頁面。
一般準(zhǔn)則和建議
容器應(yīng)該是短暫的
由您Dockerfile定義的圖像生成的容器應(yīng)盡可能短暫。通過“短暫的”,我們意味著它可以被停止和破壞,一個(gè)新的建立和建立,絕對最小的設(shè)置和配置。您可能需要查看“12因子”應(yīng)用程序方法的“?過程”部分,了解以無國籍方式運(yùn)行容器的動(dòng)機(jī)。
使用.dockerignore文件
在大多數(shù)情況下,最好將每個(gè)Dockerfile放在一個(gè)空目錄中。然后,僅添加構(gòu)建Dockerfile所需的文件。為了增加構(gòu)建的性能,您可以通過向該.dockerignore目錄添加文件來排除文件和目錄。此文件支持類似于.gitignore文件的排除模式。有關(guān)創(chuàng)建的信息,請參閱.dockerignore文件。
避免安裝不必要的包
為了減少復(fù)雜性,依賴關(guān)系,文件大小和構(gòu)建時(shí)間,您應(yīng)該避免安裝額外的或不必要的軟件包,因?yàn)樗鼈兛赡堋昂芎谩薄@?#xff0c;您不需要在數(shù)據(jù)庫中包含文本編輯器圖片。
每個(gè)容器應(yīng)該只有一個(gè)問題
將應(yīng)用程序解耦到多個(gè)容器中可以更輕松地水平擴(kuò)展和重新使用容器。例如,Web應(yīng)用程序堆棧可能由三個(gè)獨(dú)立的容器組成,每個(gè)容器具有自己獨(dú)特的映像,以解耦的方式管理Web應(yīng)用程序,數(shù)據(jù)庫和內(nèi)存中緩存。
你可能聽說應(yīng)該有“每個(gè)集裝箱一個(gè)進(jìn)程”。雖然這個(gè)口頭禪意圖很好,但并不一定每個(gè)容器只能有一個(gè)操作系統(tǒng)進(jìn)程。除了現(xiàn)在可以使用init進(jìn)程產(chǎn)生容器的事實(shí)之外,一些程序可能會(huì)自行產(chǎn)生其他進(jìn)程。例如,芹菜可以產(chǎn)生多個(gè)工作進(jìn)程,或阿帕奇可以創(chuàng)建每個(gè)請求的過程。雖然“每個(gè)集裝箱的一個(gè)過程”通常是一個(gè)很好的經(jīng)驗(yàn)法則,但這不是一個(gè)艱難和快速的規(guī)則。盡可能地判斷容器是否干凈,模塊化。
如果容器依賴于彼此,則可以使用Docker容器網(wǎng)絡(luò)?來確保這些容器可以通信。
最小化層數(shù)
您需要找到可用性(從而長期可維護(hù)性)之間的平衡,Dockerfile并最大限度地減少其使用的層數(shù)。對您使用的層數(shù)進(jìn)行戰(zhàn)略和謹(jǐn)慎。
排序多行參數(shù)
只要有可能,通過以字母數(shù)字排序多行參數(shù)來緩解以后的變化。這將幫助您避免重復(fù)的包,并使列表更容易更新。這也使得PR更容易閱讀和審查。在反斜杠(\)之前添加一個(gè)空格也有幫助。
這是buildpack-deps圖像的一個(gè)例子:
RUN?apt-get?update?&&?apt-get?install?-y?\bzr?\cvs?\git?\mercurial?\subversion構(gòu)建緩存
在構(gòu)建映像的過程中,Docker將按照指定Dockerfile的順序執(zhí)行每個(gè)指令。隨著每條指令的檢查,Docker將在其緩存中查找可重用的現(xiàn)有映像,而不是創(chuàng)建一個(gè)新的(重復(fù))映像。如果您不想使用緩存,您可以使用--no-cache=true?該docker build命令的選項(xiàng)。
但是,如果您確實(shí)讓Docker使用其緩存,那么了解何時(shí)會(huì)找到匹配的映像是非常重要的。Docker將遵循的基本規(guī)則如下:
從已經(jīng)在緩存中的基本圖像開始,將下一條指令與從該基本圖像導(dǎo)出的所有子圖像進(jìn)行比較,以查看其中一條是否使用完全相同的指令構(gòu)建。如果沒有,則緩存無效。
在大多數(shù)情況下,簡單地比較Dockerfile與其中一個(gè)子圖像的指令是足夠的。但是,某些說明需要更多的檢查和解釋。
對于ADD和COPY指令,檢查圖像中文件的內(nèi)容,并為每個(gè)文件計(jì)算校驗(yàn)和。在這些校驗(yàn)和中不考慮文件的最后修改和最后訪問的時(shí)間。在緩存查找期間,將校驗(yàn)和與現(xiàn)有映像中的校驗(yàn)和進(jìn)行比較。如果文件(如內(nèi)容和元數(shù)據(jù))中有任何變化,則緩存無效。
除了ADD和COPY命令之外,緩存檢查不會(huì)查看容器中的文件來確定緩存匹配。例如,當(dāng)處理RUN apt-get -y update命令時(shí),將不會(huì)檢查在容器中更新的文件以確定是否存在高速緩存命中。在這種情況下,只需使用命令字符串本身來查找匹配。
一旦緩存無效,所有后續(xù)Dockerfile命令將生成新的映像,并且高速緩存將不被使用。
Dockerfile指令
您可以在下面找到建議,以便最好地編寫可用于其中的各種說明Dockerfile。
FROM
用于FROM指令的Docker文件引用
只要有可能,使用當(dāng)前的官方存儲庫作為您的圖像的基礎(chǔ)。我們建議使用Debian鏡像,?因?yàn)樗欠浅?yán)格的控制,并保持最小(目前在150 mb),而仍然是一個(gè)完整的分布。
標(biāo)簽
了解對象標(biāo)簽
您可以為圖像添加標(biāo)簽,以幫助按項(xiàng)目組織圖像,記錄許可信息,幫助自動(dòng)化或其他原因。對于每個(gè)標(biāo)簽,添加LABEL以一個(gè)或多個(gè)鍵值對開頭的行。以下示例顯示不同的可接受格式。解釋性意見包括在內(nèi)。
注意:如果您的字符串包含空格,則必須使用引號或空格必須轉(zhuǎn)義。如果您的字符串包含內(nèi)部引號字符("),也可以轉(zhuǎn)義它們。
#?Set?one?or?more?individual?labelsLABEL?com.example.version="0.0.1-beta"LABEL?vendor="ACME?Incorporated"LABEL?com.example.release-date="2015-02-12"LABEL?com.example.version.is-production=""#?Set?multiple?labels?on?one?lineLABEL?com.example.version="0.0.1-beta"?com.example.release-date="2015-02-12"#?Set?multiple?labels?at?once,?using?line-continuation?characters?to?break?long?linesLABEL?vendor=ACME\?Incorporated?\??????com.example.is-beta=?\??????com.example.is-production=""?\??????com.example.version="0.0.1-beta"?\??????com.example.release-date="2015-02-12"有關(guān)可接受的標(biāo)簽鍵和值的指導(dǎo),請參閱了解對象標(biāo)簽。有關(guān)查詢標(biāo)簽的信息,請參閱管理對象標(biāo)簽中過濾相關(guān)的項(xiàng)目?。
Dock指令的Docker文件引用
和往常一樣,為了使您Dockerfile更易于閱讀,易于理解和可維護(hù),RUN可以用多個(gè)行分隔長條或復(fù)合語句,并以反斜杠分隔。
APT-GET的
可能最常用的用例RUN是應(yīng)用程序apt-get。該?RUN apt-get命令,因?yàn)樗惭b軟件包,有幾個(gè)需要注意的問題。
您應(yīng)該避免RUN apt-get upgrade或者dist-upgrade,基本圖像中的許多“基本”軟件包將不會(huì)在非特權(quán)容器內(nèi)升級。如果基本圖像中包含的包裝已過期,則應(yīng)聯(lián)系其維護(hù)者。如果您知道有特定的軟件包,foo需要更新,請使用?apt-get install -y foo自動(dòng)更新。
總是在同一個(gè)?語句中結(jié)合?RUN apt-get update使用,例如:apt-get installRUN
????RUN?apt-get?update?&&?apt-get?install?-y?\package-bar?\package-baz?\package-fooapt-get update在RUN語句中單獨(dú)使用會(huì)導(dǎo)致緩存問題和后續(xù)apt-get install指令失敗。例如,說你有一個(gè)Docker文件:
????FROM?ubuntu:14.04RUN?apt-get?updateRUN?apt-get?install?-y?curl構(gòu)建圖像后,所有圖層都在Docker緩存中。假設(shè)你以后apt-get install通過添加額外的包來修改:
????FROM?ubuntu:14.04RUN?apt-get?updateRUN?apt-get?install?-y?curl?nginxDocker將初始和修改的指令看作是相同的,并重新使用先前步驟的緩存。結(jié)果apt-get update是不執(zhí)行,因?yàn)闃?gòu)建使用緩存的版本。因?yàn)閍pt-get update沒有運(yùn)行,你的構(gòu)建可能會(huì)有一個(gè)過時(shí)的版本curl和nginx包。
使用?RUN apt-get update && apt-get install -y確保您的Dockerfile安裝最新的軟件包版本,無需進(jìn)一步的編碼或手動(dòng)干預(yù)。這種技術(shù)被稱為“緩存破解”。您還可以通過指定包版本來實(shí)現(xiàn)緩存清除。這被稱為版本固定,例如:
????RUN?apt-get?update?&&?apt-get?install?-y?\package-bar?\package-baz?\package-foo=1.3.*版本固定強(qiáng)制構(gòu)建以檢索特定版本,而不管緩存中有什么。這種技術(shù)還可以減少由于所需軟件包中意外的更改導(dǎo)致的故障。
以下是一個(gè)RUN格式正確的指導(dǎo),顯示所有apt-get?建議。
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/*該s3cmd指令指定的版本1.1.*。如果圖像以前使用過舊版本,則指定新版本會(huì)導(dǎo)致緩存apt-get update破壞,并確保新版本的安裝。在每行上列出包也可以防止包重復(fù)中的錯(cuò)誤。
另外,當(dāng)您通過刪除/var/lib/apt/lists?減少映像大小來清理apt緩存時(shí),由于apt緩存未存儲在圖層中。由于?RUN語句開始apt-get update,包緩存將始終在之前刷新apt-get install。
注意:Debian和Ubuntu的官方圖像自動(dòng)運(yùn)行apt-get clean,因此不需要顯式調(diào)用。
使用管道
一些RUN命令取決于使用管道字符(|)將一個(gè)命令的輸出管道傳輸?shù)搅硪粋€(gè)命令的能力,如以下示例所示:
RUN?wget?-O?-?https://some.site?|?wc?-l?>?/numberDocker使用/bin/sh -c解釋器執(zhí)行這些命令,該解釋器僅評估管道中最后一個(gè)操作的退出代碼以確定成功。在上述示例中,只要wc -l命令成功,即使wget命令失敗,此構(gòu)建步驟也可以成功并生成新映像。
如果您希望命令由于管道中任何階段的錯(cuò)誤而失敗set -o pipefail &&,請先確認(rèn)出現(xiàn)意外的錯(cuò)誤會(huì)阻止構(gòu)建過程無意中成功。例如:
RUN?set?-o?pipefail?&&?wget?-O?-?https://some.site?|?wc?-l?>?/number注意:并非所有的shell都支持該-o pipefail選項(xiàng)。在這種情況下(例如dashshell,它是基于Debian的映像的默認(rèn)shell),請考慮使用exec表單RUN?來明確選擇一個(gè)支持該pipefail選項(xiàng)的shell?。例如:
RUN?["/bin/bash",?"-c",?"set?-o?pipefail?&&?wget?-O?-?https://some.site?|?wc?-l?>?/number"]CMD
用于CMD指令的Docker文件引用
該CMD指令應(yīng)用于運(yùn)行圖像包含的軟件以及任何參數(shù)。CMD應(yīng)該幾乎總是以形式使用CMD [“executable”, “param1”, “param2”…]。因此,如果圖像用于服務(wù),例如Apache和Rails,則可以運(yùn)行類似的操作?CMD ["apache2","-DFOREGROUND"]。實(shí)際上,這種形式的指令是推薦用于任何基于服務(wù)的圖像。
在其他大多數(shù)情況下,CMD應(yīng)該給出一個(gè)交互式的shell,比如bash,python和perl。例如,CMD ["perl", "-de0"],CMD ["python"],或?CMD [“php”, “-a”]。使用此表單意味著當(dāng)您執(zhí)行類似的操作?docker run -it python時(shí),您將被放入可用的shell中,隨時(shí)可以使用。?CMD應(yīng)該很少的方式使用CMD [“param”, “param”]會(huì)同ENTRYPOINT,除非你和你預(yù)期的用戶已經(jīng)非常熟悉如何ENTRYPOINT?工作的。
EXPOSE
用于EXPOSE指令的Docker文件引用
該EXPOSE指令指示容器將偵聽連接的端口。因此,您應(yīng)該為應(yīng)用程序使用通用的傳統(tǒng)端口。例如,包含Apache Web服務(wù)器EXPOSE 80的映像將使用,而包含MongoDB的映像將使用EXPOSE 27017等等。
對于外部訪問,您的用戶可以執(zhí)行docker run一個(gè)標(biāo)志,指示如何將指定的端口映射到他們選擇的端口。對于容器鏈接,Docker為從容器容器返回源(即MYSQL_PORT_3306_TCP)的路徑提供環(huán)境變量。
ENV
用于ENV指令的Docker文件引用
為了使新軟件更容易運(yùn)行,您可以使用它ENV來更新PATH容器安裝的軟件的?環(huán)境變量。例如,ENV PATH /usr/local/nginx/bin:$PATH將確保CMD [“nginx”]?只是工作。
該ENV指令對于提供特定于要集中化的服務(wù)所需的環(huán)境變量(如Postgres's)也很有用?PGDATA。
最后,ENV也可以用于設(shè)置常用的版本號,以便版本顛覆更容易維護(hù),如以下示例所示:
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類似于在程序中具有常量變量(與硬編碼值相反),這種方法允許您更改單個(gè)ENV指令以自動(dòng)神奇地碰撞容器中的軟件版本。
添加或復(fù)制
用于ADD指令的
Dockerfile引用用于COPY指令的Dockerfile引用
雖然ADD并且COPY在功能上類似,但一般來說COPY?是優(yōu)選的。這是因?yàn)樗韧该鞫雀逜DD。COPY只支持將本地文件基本復(fù)制到容器中,同時(shí)ADD具有一些功能(如本地僅提取和遠(yuǎn)程URL支持),這些功能并不明顯。因此,最好的用途ADD是本地tar文件自動(dòng)提取到圖像中,如圖所示ADD rootfs.tar.xz /。
如果您有多個(gè)Dockerfile步驟可以從上下文中使用不同的文件,那么COPY它們可以單獨(dú)使用,而不是一次使用。如果特定需要的文件更改,這將確保每個(gè)步驟的構(gòu)建緩存僅被無效(強(qiáng)制重新運(yùn)行該步驟)。
例如:
COPY?requirements.txt?/tmp/RUN?pip?install?--requirement?/tmp/requirements.txtCOPY?.?/tmp/導(dǎo)致RUN步驟的緩存無效的數(shù)量減少,而不是放在?COPY . /tmp/前面。
由于圖像尺寸很重要,ADD因此強(qiáng)烈不鼓勵(lì)使用遠(yuǎn)程URL提取包;?你應(yīng)該使用curl或wget替代。這樣,您可以刪除在解壓后不再需要的文件,而不必在圖像中添加另一個(gè)圖層。例如,你應(yīng)該避免這樣做:
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?-SL?http://example.com/big.tar.xz?\|?tar?-xJC?/usr/src/things?\&&?make?-C?/usr/src/things?all對于不需要ADDtar自動(dòng)提取功能的其他項(xiàng)目(文件,目錄),您應(yīng)該始終使用COPY。
ENTRYPOINT
用于ENTRYPOINT指令的Dockerfile引用
最好的用途ENTRYPOINT是設(shè)置圖像的主要命令,允許該圖像像該命令一樣運(yùn)行(然后CMD用作默認(rèn)標(biāo)志)。
我們從一個(gè)命令行工具圖像的例子開始s3cmd:
ENTRYPOINT?["s3cmd"]CMD?["--help"]現(xiàn)在可以像這樣運(yùn)行映像來顯示命令的幫助:
$?docker?run?s3cmd或使用正確的參數(shù)執(zhí)行命令:
$?docker?run?s3cmd?ls?s3://mybucket這是有用的,因?yàn)閳D像名稱可以加倍作為二進(jìn)制文件的參考,如上面的命令所示。
該ENTRYPOINT指令也可以與輔助腳本組合使用,允許其以類似于上述命令的方式運(yùn)行,即使啟動(dòng)該工具可能需要多于一個(gè)步驟。
例如,Postgres Official Image?使用以下腳本作為其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?"$@"注:此腳本使用的execbash命令?,使最終運(yùn)行的應(yīng)用程序成為容器的PID 1.這允許應(yīng)用程序接收發(fā)送到容器任何Unix信號。有關(guān)ENTRYPOINT?詳細(xì)信息,請參閱幫助。
幫助腳本被復(fù)制到容器中并通過ENTRYPOINT容器開始運(yùn)行:
COPY?./docker-entrypoint.sh?/ENTRYPOINT?["/docker-entrypoint.sh"]此腳本允許用戶以多種方式與Postgres進(jìn)行交互。
它可以簡單地啟動(dòng)Postgres:
$?docker?run?postgres或者,它可以用于運(yùn)行Postgres并將參數(shù)傳遞給服務(wù)器:
$?docker?run?postgres?postgres?--help最后,它也可以用來啟動(dòng)一個(gè)完全不同的工具,比如Bash:
$?docker?run?--rm?-it?postgres?bash卷
VOLUME
用于VOLUME指令的Docker文件引用
該VOLUME指令應(yīng)用于公開您的docker容器創(chuàng)建的任何數(shù)據(jù)庫存儲區(qū)域,配置存儲或文件/文件夾。強(qiáng)烈建議您使用圖像VOLUME的任何可變和/或用戶可維修的部分。
用戶
Docker文件引用USER指令
如果一個(gè)服務(wù)可以沒有權(quán)限運(yùn)行,可以使用USER來更改非root用戶。通過創(chuàng)建在用戶和組開始Dockerfile喜歡的東西RUN groupadd -r postgres && useradd --no-log-init -r -g postgres postgres。
注意:圖像中的用戶和組獲得非確定性的UID / GID,因?yàn)椤跋乱粋€(gè)”UID / GID被分配,而不管圖像重建。所以,如果是至關(guān)重要的,你應(yīng)該分配一個(gè)顯式的UID / GID。
注意:由于?Go存檔/ tar包處理稀疏文件中的一個(gè)未解決的錯(cuò)誤,嘗試在Docker容器中創(chuàng)建一個(gè)足夠大的UID的用戶可能會(huì)導(dǎo)致磁盤耗盡,因?yàn)?var/log/faillog容器層中填充有NUL(\ 0 )字符?將--no-log-init標(biāo)志傳遞給useradd可以解決這個(gè)問題。Debian / Ubuntu?adduser包裝器不支持該--no-log-init標(biāo)志,應(yīng)該避免。
您應(yīng)避免安裝或使用,sudo因?yàn)樗哂胁豢深A(yù)測的TTY和信號轉(zhuǎn)發(fā)行為,可能會(huì)導(dǎo)致比解決問題更多的問題。如果您絕對需要類似的功能sudo(例如,以root用戶身份初始化守護(hù)程序,但以非root身份運(yùn)行),則可以使用?“gosu”。
最后,為了減少層次和復(fù)雜性,請避免頻繁地進(jìn)行USER切換。
WORKDIR
Docker文件參考WORKDIR指令
為了清楚和可靠,您應(yīng)該始終為您的絕對路徑?WORKDIR。此外,您應(yīng)該使用,WORKDIR而不是增加的指令,如RUN cd … && do-something難以閱讀,排除故障和維護(hù)。
ONBUILD
用于ONBUILD指令的Dockerfile引用
一個(gè)ONBUILD命令在當(dāng)前Dockerfile構(gòu)建完成后執(zhí)行。?ONBUILD在導(dǎo)出FROM當(dāng)前圖像的任何子圖像中執(zhí)行。將該ONBUILD命令視為父母Dockerfile給予孩子的指示Dockerfile。
Docker構(gòu)建ONBUILD在子節(jié)點(diǎn)的任何命令之前執(zhí)行命令?Dockerfile。
ONBUILD對于將要構(gòu)建FROM給定圖像的圖像很有用。例如,您將使用ONBUILD一個(gè)語言堆棧映像構(gòu)建在該語言中編寫的任意用戶軟件?Dockerfile,您可以在Ruby的ONBUILD變體中看到。
建立的圖像ONBUILD應(yīng)該有一個(gè)單獨(dú)的標(biāo)簽,例如:?ruby:1.9-onbuild或ruby:2.0-onbuild。
把時(shí)要小心,ADD或COPY在ONBUILD。如果新版本的上下文缺少添加的資源,“onbuild”映像將會(huì)嚴(yán)重失敗。如上所述,添加單獨(dú)的標(biāo)簽將有助于通過允許Dockerfile作者做出選擇來緩解這一點(diǎn)。
我們的公共號
轉(zhuǎn)載于:https://blog.51cto.com/wuguiyunwei/1934599
總結(jié)
以上是生活随笔為你收集整理的编写Dockerfiles的最佳做法的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: centos6.9下安装composer
- 下一篇: scala数据结构之Maps和Tuple