您好,登錄后才能下訂單哦!
怎么看待Dockerfile最佳實踐,針對這個問題,這篇文章詳細介紹了相對應的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。
雖然 Dockerfile 簡化了鏡像構建的過程,并且把這個過程可以進行版本控制,但是不正當的 Dockerfile 使用也會導致很多問題:
docker 鏡像太大。如果你經常使用鏡像或者構建鏡像,一定會遇到那種很大的鏡像,甚至有些能達到 數G
docker 鏡像的構建時間過長。每個 build 都會耗費很長時間,對于需要經常構建鏡像(比如單元測試)的地方這可能是個大問題
重復勞動。多次鏡像構建之間大部分內容都是完全一樣而且重復的,但是每次都要做一遍,浪費時間和資源
容器模型是進程而不是機器,不需要開機初始化。在需要時運行,不需要時停止,能夠刪除后重建,并且配置和啟動的最小化。
在 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 命令更新了容器中的文件,并不會被緩存檢查策略作為緩存匹配的依據。
官方Image 使用的時區基本上都是標準的 UTC 時間,如果容器想使用中國標準時間,基于Debian的系統在Dockerfile中加入
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime RUN echo "Asia/Shanghai" >> /etc/timezone
基于Centos的系統在Dockerfile中加入
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
有時候你可能感覺官方的源更新或者安裝軟件比較慢,可以在Dockerfile修改官方默認源,例如alpine想使用阿里的源可以在Dockerfile中加入:
RUN echo -e "http://mirrors.aliyun.com/alpine/v3.5/main\nhttp://mirrors.aliyun.com/alpine/v3.5/community" > /etc/apk/repositories
推薦使用官方倉庫中的鏡像作為基礎鏡像。
把復雜的或過長的 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 ["executable","param1","param2"] 這樣的格式。如果鏡像是用來運行服務,需要使用 CMD["apache2","-DFOREGROUND"] ,這種格式的指令適用于任何服務性質的鏡像。
ENTRYPOINT 應該用于 鏡像的主命令,并使用 CMD 作為默認設置,以 s3cmd 為例:
ENTRYPOINT ["s3cmd"] CMD ["--help"]
獲取幫助:
docker run s3cmd
或者執行命令:
docker run s3cmd ls s3://mybucket
這在鏡像名與程序重名時非常有用。
ENTRYPOINT 也可以啟動自定義腳本: 定義腳本:
#!/bin/bash set -e if [ "$1" = 'postgres' ]; then chown -R postgres "$PGDATA" if [ -z "$(ls -A "$PGDATA")" ]; then gosu postgres initdb fi exec gosu postgres "$@" fi exec "$@"
注意:這段腳本使用了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
應該盡可能地使用默認端口。例如Apache web服務使用EXPOSE 80,MongoDB使用EXPOSE 27017。
可以使用ENV更新PATH環境變量。例如,ENV PATH /usr/local/nginx/bin:$PATH可以確保CMD [“nginx”]正常運行。
ENV還可以提供程序所需要的環境變量,例如Postgres的 PGDATA。
ENV可以設置版本等信息。使版本信息更易于維護。
ENV PG_MAJOR 9.3 ENV PG_VERSION 9.3.4 RUN 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 與 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 掛載。
如果服務不需要特權來運行,使用 USER 指令切換到非root用戶。使用 **RUN groupadd -r mysql && useradd -r -g mysql mysql **之后用 USER mysql 切換用戶
要避免使用 sudo 來提升權限,因為它帶來的問題遠比它能解決的問題要多。如果你確實需要這樣的特性,那么可以選擇使用 gosu 。
最后,不要反復的切換用戶。減少不必要的layers。
為了清晰和可維護性,應該使用WORKDIR來定義工作路徑。推薦使用WORKDIR來代替RUN cd … && do-something 這樣的指令。
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/src ONBUILD RUN /usr/local/bin/python-build --dir /app/src [...]
如果基于 image-A 創建新的鏡像時,新的Dockerfile中使用FROM image-A指定基礎鏡像時,會自動執行ONBUILD指令內容,等價于在后面添加了兩條指令。
FROM image-A #Automatically run the following ADD . /app/src RUN /usr/local/bin/python-build --dir /app/src
假設我們要制作 Node.js 所寫的應用的鏡像。我們都知道 Node.js 使用 npm 進行包管理,所有依賴、配置、啟動信息等會放到 package.json 文件里。在拿到程序代碼后,需要先進行 npm install 才可以獲得所有需要的依賴。然后就可以通過 npm start 來啟動應用。因此,一般來說會這樣寫 Dockerfile:
FROM node:slim RUN mkdir /app WORKDIR /app COPY ./package.json /app RUN [ "npm", "install" ] COPY . /app/ CMD [ "npm", "start" ]
把這個 Dockerfile 放到 Node.js 項目的根目錄,構建好鏡像后,就可以直接拿來啟動容器運行。但是如果我們還有第二個 Node.js 項目也差不多呢?好吧,那就再把這個 Dockerfile 復制到第二個項目里。那如果有第三個項目呢?再復制么?文件的副本越多,版本控制就越困難,讓我們繼續看這樣的場景維護的問題。
如果第一個 Node.js 項目在開發過程中,發現這個 Dockerfile 里存在問題,比如敲錯字了、或者需要安裝額外的包,然后開發人員修復了這個 Dockerfile,再次構建,問題解決。第一個項目沒問題了,但是第二個項目呢?雖然最初 Dockerfile 是復制、粘貼自第一個項目的,但是并不會因為第一個項目修復了他們的 Dockerfile,而第二個項目的 Dockerfile 就會被自動修復。
那么我們可不可以做一個基礎鏡像,然后各個項目使用這個基礎鏡像呢?這樣基礎鏡像更新,各個項目不用同步 Dockerfile 的變化,重新構建后就繼承了基礎鏡像的更新?好吧,可以,讓我們看看這樣的結果。那么上面的這個 Dockerfile 就會變為:
FROM node:slim RUN mkdir /app WORKDIR /app CMD [ "npm", "start" ]
這里我們把項目相關的構建指令拿出來,放到子項目里去。假設這個基礎鏡像的名字為 my-node 的話,各個項目內的自己的 Dockerfile 就變為:
FROM my-node COPY ./package.json /app RUN [ "npm", "install" ] COPY . /app/
基礎鏡像變化后,各個項目都用這個 Dockerfile 重新構建鏡像,會繼承基礎鏡像的更新。
那么,問題解決了么?沒有。準確說,只解決了一半。如果這個 Dockerfile 里面有些東西需要調整呢?比如 npm install 需要統一加一些參數,那怎么辦?這一行 RUN 是不可能放入基礎鏡像的,因為涉及到了當前項目的 ./package.json,難道又要一個個修改么?所以說,這樣制作基礎鏡像,只解決了原來的 Dockerfile 的前4條指令的變化問題,而后面三條指令的變化則完全沒辦法處理。
ONBUILD 可以解決這個問題。讓我們用 ONBUILD 重新寫一下基礎鏡像的 Dockerfile:
FROM node:slim RUN mkdir /app WORKDIR /app ONBUILD COPY ./package.json /app ONBUILD RUN [ "npm", "install" ] ONBUILD COPY . /app/ CMD [ "npm", "start" ]
這次我們回到原始的 Dockerfile,但是這次將項目相關的指令加上 ONBUILD,這樣在構建基礎鏡像的時候,這三行并不會被執行。然后各個項目的 Dockerfile 就變成了簡單地:
FROM my-node
是的,只有這么一行。當在各個項目目錄中,用這個只有一行的 Dockerfile 構建鏡像時,之前基礎鏡像的那三行 ONBUILD 就會開始執行,成功的將當前項目的代碼復制進鏡像、并且針對本項目執行 npm install,生成應用鏡像。
類似Java,Go等編譯型項目,可以使用ONBUILD指令進行優化Dockerfile。
編寫onbuild Dockerfile如下:
FROM maven:3-jdk-8 RUN mkdir -p /usr/src/app WORKDIR /usr/src/app ONBUILD ADD . /usr/src/app ONBUILD RUN mvn install
然后所有依賴maven編譯的項目Dockerfile可以簡化成如下形式:
FROM maven:3.3-jdk-8-onbuild CMD ["java","-jar","/usr/src/app/target/demo-1.0-SNAPSHOT-jar-with-dependencies.jar"]
關于怎么看待Dockerfile最佳實踐問題的解答就分享到這里了,希望以上內容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關注億速云行業資訊頻道了解更多相關知識。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。