Dockerfile是一個文本文件,包含了一條條指令,每條指令對應構建一層鏡像,Docker基於它來構建一個完整鏡像。本文介紹Dockerfile的經常使用指令及相應的最佳實踐建議。php
Docker鏡像經過docker build
指令構建,該指令執行時當前的工做目錄就是docker構建的上下文,即build context,上下文中的文件及目錄都會做爲構建上下文內容發送給Docker Daemon。html
docker build --no-cache -t helloapp:v2 -f dockerfiles/Dockerfile context |
如上 –no-cache 表示鏡像構建時不使用緩存,-f 指定Dockerfile文件位置, context 指定build context目錄。python
將一些非必要的文件包含到build context中,會致使build context過大,從而致使鏡像過大,會增長鏡像構建、推送及拉取的時間,以及容器運行時的大小。mysql
執行docker build時會顯示build context的大小,nginx
Sending build context to Docker daemon 187.8MB |
最佳實踐建議git
使用.dockerignore來排除不須要加入到build context中的文件,相似於.gitignoregithub
不要安裝沒必要要的包,全部包含的東西都是鏡像必須的,非必須的不要包含。web
解耦應用,若是應用有分層,解耦應用到多個容器,便於橫向擴展,如web應用程序棧包含web服務應用,數據庫,緩存等。redis
最少化鏡像層數:只有RUN、COPY、ADD指令會建立鏡像層,其它指令建立臨時的中間鏡像,不會增大鏡像構建的大小sql
若是可能,儘量使用多階段構建,只複製你須要的組件到最終鏡像,這使得你能夠在中間構建階段包含工具與debug信息,同時又不會增大最終鏡像的大小。
排序多行參數:將參數按字母排序,有利於避免包重複,及後續的維護與提升易讀性
做用
FROM指定基礎鏡像,每個定製鏡像,必須以一個現有鏡像爲基礎。所以一個Dockerfile中FROM是必須的指令,而且必須是第一條。使用格式,
FROM <image>:<tag> |
最佳實踐建議
若是不想以任何鏡像爲基礎,則可使用FROM scratch
儘可能使用官方鏡像做爲基礎鏡像
推薦使用Alpine鏡像,由於它足夠輕量級(小於5MB),但麻雀雖小五臟俱全,基本具備Linux的基礎功能
做用
用來執行命令行命令,是最經常使用的指令之一。使用格式,
# shell格式,跟直接在命令行輸入命令一行 |
RUN指令建立的中間鏡像會被緩存,並會在下次構建中使用。若是不想使用這些緩存鏡像,能夠在構建指令中指定–no-cache參數,如:docker build --no-cache
最佳實踐建議
將比較長的複雜的指令經過 \ 分爲多行,讓Dockerfile文件可讀性、可理解性、可維護性更高,將多個指令經過 && 鏈接,減小鏡像的層數
確保每一層只添加必需的東西,任何無關的東西都應該清理掉,如全部下載、展開的文件,apt 緩存文件等,以儘量減小鏡像各層的大小
將RUN apt-get update
與 RUN apt-get install
組合成一條RUN指令(將apt-get update單獨做爲一條指令會由於緩存問題致使後續的apt-get install 指令失敗)
好比先按以下Dockerfile建立了一個鏡像
FROM ubuntu:18.04 |
一段時間後,再按如下Dockerfile建立另外一個鏡像
FROM ubuntu:18.04 |
由於RUN指令建立的鏡像層會被緩存,因此下面鏡像的RUN apt-get update
並不會執行,直接使用了前面構建的鏡像層,這樣,curl、nginx就可能安裝已通過時的版本。
所以 在 apt-get update
以後當即接 && apt-get install -y
,這叫作「 cache busting」(緩存破壞),也能夠經過指定包的版本,來達到一樣的目的,這叫「 version pinning」 (版本指定)示例:
RUN apt-get update && apt-get install -y \ |
使用管道(pipes)。一些RUN指令依賴於從一個指令管道輸出到另外一個,如
RUN wget -O - https://some.site | wc -l > /number |
Docker使用/bin/sh -c 解釋器來執行這些指令,只會評估管道最後一條命令的退出碼來肯定是否成功,如上例中只要wc -l成功了就算wget失敗,也會認爲是成功的。
若是要使管道命令的任何一步報錯都致使指令失敗,則可經過加 set -o pipefile &&
來實現,如
RUN set -o pipefail && wget -O - https://some.site | wc -l > /number |
不是全部的shell都支持-o pipefail
選項,若是不支持的話可使用以下形式,顯式地指定一個支持的shell
RUN ["/bin/bash", "-c", "set -o pipefail && wget -O - https://some.site | wc -l > /number"] |
做用
COPY從構建上下文的目錄中複製文件/目錄到鏡像層的目標路徑。使用格式,
COPY [--chown=<user>:<group>] <源路徑>... <目標路徑> |
同RUN同樣,也有兩種格式。源文件能夠多個,甚至能夠是通配符,目標路徑是容器的絕對路徑,能夠是相對工做目錄(WORKDIR指定)的相對路徑,目標路徑不存在時會自動建立。使用--chown=<user>:<group>
來改變文件的所屬用戶與組。
ADD與COPY的使用格式與性質差很少,但功能更豐富,如源路徑能夠是URL(下載後放到目標路徑下,文件權限爲600),也能夠爲tar壓縮包,壓縮格式爲gzip,bzip2及xz的狀況下,ADD 指令將會自動解壓縮這個壓縮文件到目標路徑去
最佳實踐建議
若是在Dockerfile中有多處須要使用不一樣的文件,分別使用COPY,而不是一次性COPY全部的,這能夠保證每一步的構建緩存只會在對應文件改變時,纔會失效。好比
COPY requirements.txt /tmp/ |
若是把COPY . /tmp/
放在RUN上面,將使RUN層鏡像緩存失效的場景更多——由於 . 目錄(當前目錄)中任何一個文件的改變都會致使緩存失效。
由於鏡像大小的緣由, 使用ADD來獲取遠程包是很是不推薦的,應該使用curl或wget,這種方式能夠在再也不須要使用時刪除對應文件,而不須要增長額外的層,如,應避免以下用法
ADD http://example.com/big.tar.xz /usr/src/things/ |
而應使用
RUN mkdir -p /usr/src/things \ |
若是不須要使用ADD的自動解壓特性,儘可能使用COPY(語義更清晰)
做用
CMD指定容器的啓動命令。容器實質就是進程,進程就須要啓動命令及參數,CMD指令就是用於指定默認的容器主進程的啓動命令的。使用格式
# shell格式 |
在容器運行時能夠指定新的命令來覆蓋Dockerfile中設置的這個默認命令
最佳實踐建議
服務類鏡像建議:CMD ["apache2","-DFOREGROUND"]
,CMD ["nginx", "-g", "daemon off;"]
容器進程都應之前臺運行,不能之後臺服務的形式運行,不然啓動就退出了。
其它鏡像,建議給一個交互式的shell,如bash,python,perl等:CMD ["python"]
, CMD ["php", "-a"]
做用
ENTRYPOINT的目的和CMD同樣,都是在指定容器啓動時要運行的程序及參數。ENTRYPOINT在運行時也能夠替代,不過比CMD要略顯繁瑣,須要經過docker run的參數 –entrypoint 來指定。若是指定了ENTRYPOINT,則CMD將只是提供參數,傳遞給ENTRYPOINT。使用ENTRYPOINT能夠在容器運行時直接爲默認啓動程序添加參數。與RUN指令格式同樣,ENTRYPOINT也分爲exec格式和shell格式。
最佳實踐建議
ENTRYPOINT可用來指定鏡像的主命令,容許鏡像能像命令同樣運行,可使用CMD來做爲默認的標誌(參數),如
ENTRYPOINT ["s3cmd"] |
直接run時,至關於執行了s3cmd --help
。也可使用shell腳本,在腳本中作一些預處理的工做,如
COPY ./docker-entrypoint.sh / |
做用
爲鏡像添加label以方便組織鏡像,記錄licensce信息,幫助自動化實現等等。字符串中包含空格須要轉義或包含在引號中, 如
# Set one or more individual labels |
做用
ENV設置環境變量,不管是後面的其它指令,如 RUN(使用 $環境變量key 的形式) ,仍是運行時的應用,均可以直接使用這裏定義的環境變量。使用格式有兩種,
#只能設置一個key value |
除了RUN,還有這些指令能夠引用環境變量:ADD 、 COPY 、 ENV 、 EXPOSE 、 LABEL 、 USER 、 WORKDIR 、 VOLUME 、STOPSIGNAL 、 ONBUILD
最佳實踐建議
定義環境變量,更新PATH環境變量,如要使 CMD [「nginx」] 運行,可設置環境變量 ENV PATH /usr/local/nginx/bin:$PATH
ENV也能夠用於定義常量,便於維護
做用
ARG設置構建參數,即docker build命令時傳入的參數。和ENV的效果差很少,都是設置環境變量,不一樣的是,ARG設置的是構建環境的環境變量,在容器運行時是不會存在這些環境變量的。
Dockerfile中的ARG指令是定義參數名稱,以及默認值(可選)。該默認值能夠在執行構建命令docker build時用 –build-arg <參數名>=<值> 來覆蓋。使用格式,
ARG <參數名>[=<默認值>] |
如 VOLUME /data
, 任何向/data目錄寫入的數據都會寫入匿名卷。能夠運行容器時覆蓋這個掛載設置 docker run -d -v host-path:/data xxxx
最佳實踐建議
VOLUME應該被用來暴露全部的數據存儲,配置存儲,或者被容器建立的文件、目錄
若是數據動態變化,強烈建議使用VOLUME
做用
EXPOSE指令是聲明運行時容器提供的服務端口,也只是一個聲明,在容器運行時並不會由於這個聲明應用就必定會開啓這個端口的服務,容器啓動時,仍是須要經過 -p host-port:container-port
來實現映射。EXPOSE主要是幫助鏡像使用者瞭解這個鏡像服務的監聽端口,以方便進行映射配置,另外一個用處是在運行時若是是使用隨機端口映射,也就是經過 docker run -P
的形式時,會自動隨機映射EXPOSE聲明的端口。使用格式,
EXPOSE <端口1> [<端口2>...] |
最佳實踐建議
若是一個服務不須要權限也能運行,則使用USER來切換到非root用戶,如RUN groupadd -r postgres && useradd --no-log-init -r -g postgres postgres
避免使用sudo,由於可能存在一些不可預見的TTY與信號轉發行爲致使問題,若是實在須要,考慮使用「gosu」。爲了減小鏡像層數,應避免不斷切換USER
使用gosu示例
# 創建 redis 用戶,並使用 gosu 換另外一個用戶執行命令 |
做用
HEALTHCHECK用於檢查容器的健康狀態,Docker可經過健康狀態來決定是否對容器進行從新調度。使用格式
HEALTHCHECK [選項] CMD <命令> |
支持的選項爲
–interval=<間隔> :兩次健康檢查的間隔,默認爲30秒
–timeout=<時長> :執行健康檢查命令的超時時間,若是超時,則本次健康檢查就被視爲失敗,默認30秒
–retries=<次數> :當連續失敗指定的次數後,將容器狀態置爲unhealthy ,默認3次
命令的返回值決定了該次健康檢查的成功與否—— 0 :成功;1 :失敗;2 :保留(不要使用這個值),如:
FROM nginx |
它後面跟的是其它指令,好比 RUN , COPY 等,這些指令在當前鏡像構建時並不會被執行。
ONBUILD命令在本鏡像的子鏡像中執行,把ONBUILD想象爲父鏡像爲子鏡像聲明的一條指令,Docker會在子鏡像全部命令以前執行ONBUILD指令。
最佳實踐建議
當在ONBUILD指令中使用ADD或COPY時要注意,若是build context中沒有指定的資源,可能致使災難性的錯誤。
Docker在構建鏡像時會複用緩存中已經存在的鏡像,若是明確不使用緩存,則可加參數docker build --no-cache=true
使用緩存鏡像的規則
從一個已存在於緩存的父鏡像開始構建,則會將當前鏡像的下一行指令與全部繼承於那個父鏡像的子鏡像比較,若是其中沒有一個是使用相同的指令構建的,則緩存失效
大部分狀況下,將Dockerfile中的指令與其中一個子鏡像簡單比較就夠了,可是某些指令須要更多的檢查與說明:對於ADD,COPY指令,文件內容會被檢查,會計算每個文件的checksum,checksum中不會考慮最後修改及最後訪問時間,在緩存中查找時,checksum會與已經存在的鏡像進行比較,若是文件中有修改,則緩存失效。除了ADD,COPY命令,緩存檢查不會查看容器中的文件來決定緩存匹配,如處理RUN apt-get -y update
命令時,容器中文件的更新不會進行檢查來肯定緩存是否命中, 這種狀況下, 只會檢查指令字符串自己是否匹配。
一旦緩存失效,全部後續的指令都會產生新的鏡像,不會再使用緩存。
經過標準輸入來生成Dockerfile構建,不會發送build context(從stdin讀取build context,只包含Dockerfile),適用於一次性構建,不須要寫Dockerfile
# 將會構建一個名稱與tag均爲none的鏡像 |
連字符 - 做爲文件名告訴Docker從stdin讀取Dockerfile
使用stdin來生成Dockerfile, 可是使用當前目錄做爲build context
# build an image using the current directory as context, and a Dockerfile passed through stdin |
使用遠程git倉庫構建鏡像,從stdin生成Dockerfile
docker build -t myimage:latest -f - https://github.com/docker-library/hello-world.git <<EOF |
END
相關閱讀
Docker筆記(一):什麼是DockerDocker筆記(二):Docker管理的對象
Docker筆記(三):Docker安裝與配置
Docker筆記(四):Docker鏡像管理
Docker筆記(五):整一個本身的鏡像
Docker筆記(六):容器管理Docker筆記(七):經常使用服務安裝——Nginx、MySql、Redis
Docker筆記(九):網絡管理
Docker筆記(十):使用Docker來搭建一套ELK日誌分析系統
做者:空山新雨
歡迎關注個人微信公衆號:jboost-ksxy
原文出處:https://www.cnblogs.com/spec-dog/p/11570394.html