dockerfile arg_Dockerfile最佳实践
帆仔
16年入職網易,先后負責過多個重要手游項目;關注自動化、容器、云等方向;在容器領域,docker 公司提出的容器鏡像已經成為目前容器打包交付的事實標準。構建鏡像需要編寫 Dockerfile,如何編寫一個優雅的 Dockerfile 呢?在 Docker 公司的官方文檔中給出了一篇 ?
Best practices for writing Dockerfiles。
(https://g.126.fm/03ncYHS)
本文在此基礎上做了一些刪減,力圖讓大家在短時間內寫出一份不錯的 Dockerfile。
本文分為三個部分,首先會直接給出一份 Dockerfile 的參考模板,然后說明如和構建高效的鏡像并解釋這個模板這樣組織的原因,最后會補充說明一些編寫過程中的常見問題。
一份簡單的Dockerfile參考模板
docker 官方給出的參考文檔中給出的 Dockerfile 指令接近 20 個,而我們平時在編寫的時候,經常用到的不超過 10 個。因此,這里給出了一份 Dockerfile 的參考模板,幾乎可以覆蓋大部分的使用場景。
FROM?base_image:tag????#?引用基礎鏡像?*必要*ARG?arg_key[=default_value1]?????#?聲明變量
ENV?env_key=value2?????#?聲明環境變量
#?構建幾乎不變的部分,例如整體的目錄結構,build時依賴的文件和工具包等
COPY?src?dst
RUN?command1?&&?command2?...
WORKDIR?/path/to/work/dir???#?設置工作目錄?
#?構建較少變動的部分,例如應用的依賴的文件、依賴的包等
COPY?src?dst
RUN?command3?&&?command4?...
#?構建經常變動的部分,例如應用的編譯生成
COPY?src?dst
RUN?command5?&&?command6?...
#?容器入口??*必要*
ENTRYPOINT?["/entry.app"]??#?指定容器啟動時默認執行的命令
CMD?["--options"]?#?指定容器啟動時默認命令的默認參數
構建高效鏡像生命周期
容器的一個重要的特點就是能夠快速迭代,因此在容器鏡像迭代的各個環節也應該盡量做到簡潔高效。
1. 鏡像build
精簡 context:每次 build,context 都會復制給 docker daemon,因此要去掉 context 中無關的部分
多層鏡像:如果鏡像很復雜,通常將其分成基礎鏡像(適用于多種應用,內容基本不變的部分)和應用鏡像,應用鏡像通過 FROM 基礎鏡像來減少 build 的步驟
利用構建緩存(build cache):每次在 build 時,docker daemon 會默認從已在緩存中的父鏡像開始,將下一條指令與從該基本鏡像派生的所有子鏡像進行比較,以查看是否其中一個是使用完全相同的指令構建的。如果不是,則緩存無效。因此,為了能夠提高緩存的命中率,在編寫 Dockerfile 時,應該盡量按照變動的頻率來組織(如上文中的模板)
減少 layers:RUN, COPY, ADD 等指令會在 build 時產生對應的 layer,在較舊的 Docker 版本中,需要最小化鏡像中的層數以確保其性能。因此,使用&&來連接多個 RUN 命令是一個常用的方法(如上文中的模板)
使用 multi-stage builds:新特性,后文會詳細介紹
2. 鏡像pull
docker 官方詳細描述了 docker 鏡像和容器在宿主機上的存儲方式:https://docs.docker.com/storage/storagedriver/,簡單來說就是:
鏡像層 ,只讀,使用相同鏡像的多個容器共用一份。鏡像又按照 layers 分層:
每層都有獨立的 ID
不同鏡像如果有相同 ID 的 layer 時,共用一份
容器層,可寫,采用寫時復制,容器在運行時修改的內容會在這一層
根據鏡像的存儲方式,我們也可以加快鏡像的 pull 過程:
多層鏡像:和 build 時的分層鏡像一樣,利用本地已經存儲的基礎鏡像來減少需要 pull 的 size
利用 image layer 復用相同層:和 build 時利用緩存類似,利用本地已經存儲的 layer 來減少需要 pull 的 size
鏡像預熱:提前或空閑時 pull 鏡像
常見問題
1. 注意Dockerfile中的指令是逐條執行,且相互獨立
#?下面這種寫法會報錯,第二個RUN執行時的WORKDIR依舊是原來的目錄,不是/some/dirRUN?cd?/some/dir
RUN?bash?script.sh
#?改成下面兩種之一
RUN?cd?/some/dir?&&?bash?script.sh
RUN?bash?/some/dir/script.sh
2. 提防“過度”緩存
前文也提到過,Dockerfile 中每條指令逐條執行,且相互獨立。大部分的指令在 build 時會生成對應的一層(layer),并被緩存。這種機制在絕大部分的情況下都工作的很好,但是有時也會產生問題:
#?Dockerfile1FROM?ubuntu:18.04
RUN?apt-get?update
RUN?apt-get?install?-y?nginx
#?Dockerfile2
FROM?ubuntu:18.04
RUN?apt-get?update
RUN?apt-get?install?-y?nginx?curl
如上,原 Dockerfile1 使用一段時間之后修改成 Dockerfile2(只修改了install這一行)。由于緩存機制(假設之前 build 的緩存還存在),Dockerfile2 在 build 時,update這一行不會真的執行,而是直接拿之前的緩存。此時安裝的 nginx 和 curl 可能就不是當前的最新版本。
#?官方推薦的apt-get使用方式:RUN?apt-get?update?&&?apt-get?install?-y?\
????curl?\
????nginx=1.16.*?\
????&&?rm?-rf?/var/lib/apt/lists/*
3. ARG與ENV
兩種指令都可以用來定義變量,但是使用上有很多要注意的點:
FROM 前的 ARG 只能在 FROM 中使用,如果在 FROM 后也要使用,需要重新聲明
FROM?xxx${key}xxxx
ARG?key?#?這里需要再次聲明才能使用
ARG 變量的作用范圍是 build 階段 ARG 之后的指令,不會帶入鏡像
ENV 環境變量作用范圍是 build 階段 ENV 聲明的指令,并且會編入鏡像,容器運行時也會這些環境變量也生效
CMD 和 ENTRYPOINT 中不能使用 ARG 和 ENV 定義的變量
當 ARG 和 ENV 變量同名時(無論是誰先定義),ENV 環境變量的值會覆蓋 ARG 變量
ENV 會產生中間層(layer),被編入鏡像,即使使用 unset 也無法去掉,例如:
ENV?ADMIN_USER="mark"??#?此時產生了layer
RUN?echo?$ADMIN_USER?>?./mark
RUN?unset?ADMIN_USER?#?使用unset只是去掉了build時的環境變量,但是最終生成的鏡像中還是會有這個變量
#?運行鏡像還是會打印環境變量
docker?run?--rm?test?sh?-c?'echo?$ADMIN_USER'
mark
#?如果想要消除這種影響,可以改成:
FROM?alpine
RUN?export?ADMIN_USER="mark"?\
????&&?echo?$ADMIN_USER?>?./mark?\
????&&?unset?ADMIN_USER
CMD?sh
4. COPY與ADD
兩個指令幾乎相同,當你只想復制本地 context 中的文件到鏡像中時,請無腦用 COPY。
COPY 與 ADD 使用時,注意以下規則:
注意文件的屬性,復制時可以同時修改屬主和屬組 COPY/ADD [--chown=:]
如果不清楚目錄與反斜線對這兩個指令的影響,對所有目錄都加上反斜線就比較好理解了,如COPY / /,因為:
是目錄時,是否帶反斜線都只會復制目錄下的所有文件,不會復制目錄本身,如果要復制目錄本身,需要使用``的父目錄
是目錄時,必須帶反斜線才會把文件復制到dest下
必須在 context 下,不能使用../跳出 context
ADD 指令除了 COPY 的所有功能外,還有以下特性,如非必要,盡量少用:
是本地 tar 文件(常見的壓縮格式)時,會自動解包
可以是 url,支持從遠程拉取
5. CMD與ENTRYPOINT
又是一對很類似的指令,使用時需要注意:
CMD 單獨使用時,用來指定容器啟動時默認執行的命令
ENTRYPOINT 單獨使用時,可以完全取代 CMD
ENTRYPOINT 和 CMD 一起使用時,CMD 變成 ENTRYPOINT 的默認參數
推薦使用 ENTRYPOINT/CMD 的 exec 書寫形式:即ENTRYPOINT ["entry.app", "arg"],因為 shell 書寫形式(ENTRYPOINT entry.app arg)會額外啟動 shell 進程
下表列出了 CMD 與 ENTRYPOINT 的各種組合時的效果:
另外,通過在 docker run 最后的添加字段,可以指定 ENTRYPOINT 的實際參數
??#?鏡像?test_entrypoint??ENTRYPOINT?["./entry.app"]
??CMD?["--help"]
??#?運行?test_entrypoint
??docker?run?test_entrypoint?#?即./entry.app?--help
??#?帶參數運行
??docker?run?test_entrypoint?-a?-t??#?即?./entry.app?-a?-t
6. multi-stage builds
Docker 17.05 之后的版本支持一種新的 build 方式:多階段構建(multi-stage builds)。與傳統方式的區別在與,多階段構建能夠使用多個 FROM 將整個 build 階段分成多個階段:
通過為不同階段命名,可以通過一份 Dockerfile 來管理 debug、test、product 等多種環境的鏡像
通過COPY --from=stage_name,來復制中間 stage 的文件到目標階段,使得最終生成更小的鏡像
例如,上文提到的模板就可以通過多階段構建的方式來優化。假設我們最終只想得到 entry.app 及其運行環境,而不需要它的編譯環境,那么可以通過如下方式優化最終生成的鏡像的大小:
#?使用多階段構建,這里命名一個builder階段,生成編譯后的appFROM?base_image:tag?AS?builder???
ARG?arg_key[=default_value1]?????#?聲明變量
ENV?env_key=value2?????#?聲明環境變量
#?構建整體的目錄結構,build時依賴的文件和工具包等
COPY?src?dst
RUN?command1?&&?command2?...
WORKDIR?/path/to/work/dir???#?設置工作目錄?
#?構建編譯環境
COPY?src?dst
RUN?command3?&&?command4?...
#?編譯生成entry.app
COPY?src?dst
RUN?compile_entry_app
#?構建最終鏡像的階段,只保留應用和其運行環境,編譯的依賴都不需要
FROM?base_image:tag
COPY?src?dest????#?復制運行環境
WORKDIR?/path/to/work/dir???#?設置工作目錄?
COPY?--from=builder?entry.app?.?#?從builder階段復制app
#?容器入口
ENTRYPOINT?["/entry.app"]??#?指定容器啟動時默認執行的命令
CMD?["--options"]?#?指定容器啟動時默認命令的默認參數
往期精彩
NEW
﹀
﹀
﹀
ceph 部分數據所有副本先后故障的搶救
那些年,CDN 踩過的坑
智能監控中的時間序列預測
使用 d3.js 繪制資源拓撲圖
運維里的人工智能
總結
以上是生活随笔為你收集整理的dockerfile arg_Dockerfile最佳实践的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: new file https 找不到路径
- 下一篇: excel可视化图表插件_Excel新版