1. 關於docker build
docker build
可以基於Dockerfile和context打包出一個鏡像,其中context是一系列在PATH
或URL
中指定的位置中的文件(context是遞歸的,包含子目錄下的文件,build時會將context中的全部內容傳遞給docker daemon)。其中PATH
是指本地文件系統,URL
則是git倉庫的地址。比如在當前目錄下創建鏡像:docker build .
,此時context就是當前目錄.
。build的運行是由docker daemon操作的,並不是CLI(Command-Lin Interface)。通常build時強烈建議創建一個空目錄,然后將Dockerfile文件放入,最后只將build需要用到的文件放到該目錄下。注:千萬不要用root或者/
作為PATH,docker daemon會將整個目錄下的文件讀取。為了盡可能提高Docker的build性能,和git一樣可以在上述目錄中添加.dockerignore
文件排除一些不要的文件,比如正常打包時都會排除掉Dockerfile:
1 |
./Dockerfile* |
默認build時會基於context的根目錄下Dockerfile文件打包(如果不存在會報錯),當然可以通過-f DockerfilePath
的方式指定任意位置的Dockerfile位置,但后面的context必須在Dockerfile所在位置的目錄或者父目錄(指定為其他目錄會報錯,比如指定為~/k8s/
),比如:
1 |
# ~/docker/test1/Dockerfil指定Dockerfile位置,~/docker/指定context一定是Dockerfile所在目錄或父目錄 |
此外還可以指定如果鏡像構建成功存放的倉庫和標簽(即repository和tag),如構建時打上阿里雲的倉庫標簽:docker build -t registry.cn-shanghai.aliyuncs.com/hhu/redis:4.0-alpine3.9 .
。注:這里可以不斷追加-t 標簽
的方式打上多個倉庫倉庫標簽,比如:docker build -t registry.cn-shanghai.aliyuncs.com/hhu/redis:4.0-alpine3.9 -t test1/redis:4.0-alpine3.9 -t test2/redis:4.0-alpine3.9 .
,上述栗子一下就會出現3個標簽的鏡像,但它們的ID是相同的,僅僅是標簽不同。
和大部分的應用類似,docker daemon會在運行Dockerfile中的指令時會先驗證文件的可行性,比如語法錯誤就會返回error。Dockerfile中的每條指令都是獨立運行的(比如RUN cd /tmp
並不會影響下一條指定的執行),並不會創建新的鏡像。不論何時,docker都會盡可能重復應用緩存的中間鏡像以便加快build的過程,所以有時在build時可能會在控制台看到Using cache
的字樣。可以使用的緩存鏡像只有在本地具備父鏈才能使用(即緩存的鏡像先前被創建過或者整個鏡像鏈都使用docker load
加載過)。如果在build時希望使用某個特定的鏡像緩存可以使用--cache-from
選項,注:使用--cache-from
時不需要具備本地父鏈,可能會從其他的注冊中心拉下來。
2. 語法
Dockerfile的語法為INSTRUCTION arguments
,不區分大小寫,但為了區分指令和參數,約定指令全部大寫、參數盡量小寫。docker會按序執行Dockerfile中的指令,Dockerfile文件必須以FROM
指令開頭(除指令解釋器外),它指定了當前構建鏡像的基礎鏡像。通常Dockerfile中也允許注釋,注釋行以#
開頭,但指令解釋器除外,比如。
2.1 指令解釋器
指令解釋器是可選的,它會影響下面指令的處理方式,但不會在增加層layer,也不會作為構建的步驟展示出來。docker一旦處理了注釋、空行或生成器指令,就不會再看是它否分析器指令了,就算下面還有指令解釋器,docker也會將其視為注釋(都是以#
開頭),所以Dockerfile中只要有指令解釋器,請務必盡可能的靠前聲明(約定解釋器為小寫),應該Dockerfile的第一行。指令解釋器不支持行繼續的行為(Linux中表現為\
),同時指令解釋器后應該空一行。總結一下,應該遵循如下的規則:
#
開頭,指令解釋器小寫,如果有位於Dockerfile的第一行;- 不支持行繼續操作行為,即
\
; - 解釋器后應留一個空白行;
下面是一些反例:
1 |
# 反例1:使用了行繼續 \ |
注:指令解釋器允許使用空格符,比如下面的幾種方式都是一樣有效的:
1 |
#directive=value |
Dockerfile支持的指令解釋器有:syntax
和escape
。其中syntax
僅支持下一代構建工具BuildKit,這里暫不進行搗鼓,直接看escape
,它定義的是Dockerfile中的轉義字符,有2種定義方式:
1 |
# escape=\ |
如果不指定默認轉義字符為\
。轉義字符既用於轉義一行中的字符,也用於轉義換行符。這使得Dockerfile指令可以跨越多行,無論轉義分析器指令是否包含在Dockerfile中,轉義都不會在RUN
命令中執行,除非在行尾執行。在windows上,\ 是目錄之間的分隔符,將轉義字符設置為 ` 是非常有用的,它和 PowerShell 一致,比如在windows的powerShell中:
1 |
FROM microsoft/nanoserver |
沒有指定指令解釋器,默認為\
,那此時第二行中COPY testfile.txt c:\\
就出現了轉義字符(c:\\
中的第一個\
),實際意思就是COPY testfile.txt c:\
,那剩下的那個\
就變成了行繼續的操作,所以第二行和第三行是一行實際為:COPY testfile.txt c:\RUN dir c:
第三行的轉義字符在行末屬於可執行范圍。在powershell中,為了避免這個問題可以使用 ` 作為轉義字符就不會出現上述的問題:
1 |
# escape=` |
上述Dockerfile拆解成了3個指令(除去指令解釋器,之前是2條指令)。
2.2 環境替換
docker中可以使用ENV
為特定的指令聲明環境變量,轉義字符也被處理為將類似變量的語法包含到字面上的語句中。環境變量在Dockerfile中使用$variable_name
或${variable_name}
標注,上述2標注方式是一樣的,第2種{xx}
的方式通常用於解決變量名沒有空格的問題(不是很理解),比如${foo}_bar
,此外這種大括號的方式還支持一些標准的bash
修飾符,比如(word
只是隨意取的值):
${variable:-word}
表示如果variable
設置了,那它的值就是設置的值;如果variable
沒有設置,那word
就是它的值(有點三目運算的意思)。${variable:+word}
表示如果variable
設置了,那word
就是它的值;如果variable
沒有設置,那它的值就是空字符串。
注:變量上也是可以使用轉義字符的,比如使用的轉義字符是默認的\
,那在變量中\$foo
或\${foo}
,那就表示它就是個普通的字符串$foo
或${foo}
,而不是對應foo的值。
環境變量支持如下的指令:ADD
、COPY
、ENV
、EXPOSE
、FROM
、LABEL
、STOPSIGNAL
、USER
、VOLUME
、WORKDIR
、ONBUILD
(1.4版本之后該指令只有在和上述其他指令結合使用時才能使用環境變量)。下面是一個示例(解析的指令在#
后已經標出):
1 |
FROM busybox |
環境變量的替換將在整個指令中對每個變量使用相同的值,比如下面的示例:
1 |
ENV abc=hello |
上述的定義過程中,def
的值為hello
,而不是bye
;ghi
的值為bye
,因為它不是將abc
設置為bye
那條指令的一部分。可能有點繞人,但只要記住一點就行了,在使用變量時,變量的值永遠都是前面最靠近使用該變量的指令的定義(但不包含本身),如第2條指令使用def=$abc
,向前尋找變量abc
,排除自身,發現最靠近的abc
定義在第一條指令中abc=hello
,所以def=hello
;同樣第3條指令使用ghi=$abc
,向前尋找變量abc
,發現第2條指令最靠近它,雖然第1條指令也有abc
的聲明,但沒有第2條指令靠近,所以取第2條指令之中abc
的聲明bye
,所以ghi=bye
。
2.3 .DOCKERIGNORE
文件
和.gitignore
類似,在CLI(Command-Lin Interface)將context發送給docker daemon時,它會關注一下context根目錄下的.dockerignore
文件,如果存在,CLI將修改context以排除與該文件中匹配的文件和目錄。這樣可以避免將一些不必要的大文件和敏感發送給docker daemon並通過ADD
或COPY
將這些文件加入到鏡像中。同樣的.dockerignore
文件中也是允許注釋的,行首用#
標注即可,比如:
1 |
# comment |
上述的# comment
是一個注釋,CLI將不會管它;*/temp*
表示CLI將排除context根目錄中的任何一級子目錄中名稱以temp
開頭的文件或目錄;*/*/temp*
表示CLI將從context根目錄下任何兩級子目錄中排除以temp
開頭的文件或目錄(如/somedir/subdir/temporary.txt
);temp?
表示CLI將排除context根目錄中名為temp
的單字符擴展名的文件和目錄(比如/tempa
)。此外它還支持**
通配符(匹配0或多個目錄,注意是用於匹配目錄的!!),如**/*.go
就是匹配context目錄(包括它所有的子孫目錄)下所有以.go
結尾的文件。
如果行首是!
可以用於排除例外的情況,示例如下:
1 |
*.md |
上述就是排除所有以.md
結尾的文件,但README.md
除外不用排除。
!
的位置會影響.dockerignore
文件中與特定文件匹配的屬性決定它是包含的還是排除的最后一行,下面是2個示例(有點懵懂):
1 |
# 示例1:除了README-secret.md之外,排除上下文中含任何其他以.md結尾的文件。 |
在.dockerignore
中甚至可以將Dockerfile
和.dockerignore
排除掉,但它們仍然會被發送到daemon因為docker需要這些進行job,但ADD
和COPY
指令不會將它們復制到鏡像中。
2.4 具體指令(只包含18.09版本最新指令)
2.4.1 FROM
FROM
指令初始化一個新的構建階段,它為后續的指令設置了一個基本鏡像,因此它必須是在所有指令的最前面定義的(除指令解釋器),這個基本鏡像可以是任何一個合法的鏡像。語法如下:
1 |
# 方式1 |
FROM
可以在一個Dockerfile
文件中出現多次,創建多個鏡像或使用一個生成階段作為另一個的依賴。只需在每條新的FROM
指令之前記錄提交所輸出的最后一個鏡像的ID,每個FROM
指令會清除之前其他指令創建的狀態。name
是可選的,可以通過在FROM
指令中添加AS name
給一個新的構建階段起一個名字,這個名字可以在后來的FROM
指令和COPY --from=<name|index>
指令中引用對應的鏡像。同樣tag
和digest
也是可選的,如果不指定,默認為latest
。
2.4.2 ARG
ARG
是唯一一個可以在FROM
指令之前的,FROM
指令是支持在第一個FROM
之前由ARG
指令聲明的變量,比如:
1 |
# 聲明變量 CODE_VERSION,值為latest |
在FROM
指令之前ARG
變量的聲明是屬於構建階段之外的,因此不能在FROM
指令之后的其他指令中使用。如果要使用第一個From
之前聲明的ARG
值,需要在構建階段內使用沒有指定值的ARG
指令,比如:
1 |
ARG VERSION=latest |
2.4.3 RUN
RUN
指令將會在當前鏡像的頂部的一個新層layer中執行命令並提交結果,提交鏡像的結果將用於Dockerfile文件中的下一步驟。分層RUN
並生成新的新的結果符合Docker的核心思想(即提交的成本很低且可以從鏡像歷史的任何一點創建容器,很像源代碼管理)。語法如下:
1 |
# 形式1:shell的形式,命令運行在shell中,Linux中默認運行 /bin/sh -c,win中默認運行 cmd /S /C |
exec形式可以避免對shell字符串進行munging,可以在不包含指定可執行shell的基本鏡像中運行RUN
指令。shell形式默認的shell可以使用SHELL
命令更改,比如要使用除/bin/sh
之外的shell,就需要使用傳入所需shell的exec形式:
1 |
RUN ["/bin/bash", "-c", "echo hello"] |
在shell形式中可以使用\
以繼續運下一行的 RUN 指令(即使用\
作為行繼續操作符,可以跨行運行),比如:
1 |
RUN /bin/bash -c 'source $HOME/.bashrc; \ |
上述使用的是行繼續操作符,實際是一個指令,等同於:RUN /bin/bash -c 'source $HOME/.bashrc; echo $HOME'
。exec形式會被編譯成JSON形式,所以需要使用雙引號"
而不是單引號'
,此外在exec形式中應該避免\
,如RUN ["c:\windows\system32\tasklist.exe"]
會出錯,正確的寫法應該為RUN ["c:\\windows\\system32\\tasklist.exe"]
。
和shell形式不同,exec形式不會調用shell,所以平常在shell正常處理的在exec形式中可能無法處理,比如:RUN [ "echo", "$HOME" ]
不會替換變量$HOME
,如果要用shell處理,要么直接用shell形式RUN <command>
,要么直接執行shell,如:RUN [ "sh", "-c", "echo $HOME" ]
。RUN
指令的緩存在下一個構建階段不會失效,比如RUN apt-get dist-upgrade -y
將會在下一個階段重復使用,當然也可以使用--no-cache
參數來讓RUN
指令失效,比如:docker build --no-cache
,RUN
指令的緩存也可以通過ADD
指令失效。
2.4.4 CMD
一個Dockerfile文件中僅可以只有一個CMD
指令,如果有多個CMD
,僅有最有一個CMD
奏效,它的主要目的是為正在執行的容器提供默認值,這些默認值可以包括可執行文件,也可以省略可執行文件(但此時必須指定一個ENTRYPOINT
指令),主要語法如下:
1 |
# 形式1:exec形式,這是首選形式 |
如果CMD
用於為ENTRYPOINT
指令提供默認參數(即上述第2種形式),那CMD
和ENTRYPOINT
指令都應該用JSON格式指定(雙引號"
)。當在shell或exec形式,CMD
指令設置在運行鏡像時要執行的命令,如果使用shell形式的CMD
,<command>
應該是/bin/sh -c
:
1 |
FROM ubuntu |
如果不想在shell中運行<command>
,那就必須使用JSON數組的形式並使用可執行文件的全路徑。數組形式是CMD
的首選形式,任何額外的參數必須在數組中做獨立表達,如:
1 |
FROM ubuntu |
如果想每次容器都執行相同的可執行文件,應該考慮使用ENTRYPOINT
和CMD
的配合使用。如果指定docker run
的參數,那么它們將覆蓋在CMD
中指定的默認值。
注:RUN
和CMD
可能有些像,主要區別是:RUN
是運行一個命令然后提交結果,而CMD
在構建鏡像的過程中不會干任何事,但指定鏡像的預期命令。
2.4.5 LABEL
LABEL
指令用於向鏡像添加元數據,它是一個鍵值對,如果key值或value值中包含空格,需要使用雙引號和反斜杠,一個鏡像可以有多個標簽,下面是示例:
1 |
# 有空格用引號包裹 |
FROM
指令中指定的基本鏡像中的標簽會被當前構建的鏡像繼承過來,這些標簽如果在當前構建的鏡像指定相同的key值但不同的value值時是會被覆蓋的,使用docker inspect xxx
可以查看鏡像xxx的標簽,下面是openjdk部分標簽:
1 |
docker inspect 3675b9f543c5 |
2.4.6 EXPOSE
EXPOSE
指令表示了docker容器運行時監聽的特定網絡端口,可以指定監聽TCP(默認)或UDP,在實際運行容器docker run
時可以使用-p
標識去發布並映射到一個或多個端口。比如默認監聽TCP,現在改成UDP:
1 |
EXPOSE 80/udp |
也可以同時監聽TCP和UDP的80端口:
1 |
EXPOSE 80/tcp |
如果在docker run
時指定-p
,那端口將為TCP和UDP各暴露一次。-p
在主機上使用了一個短暫的高階主機端口,因此TCP和UDP的端口將不相同(不懂)。當然就算在Dockerfile中配置過端口,在docker run
時可以通過-p
來覆蓋這里定義的,如:
1 |
docker run -p 80:80/tcp -p 80:80/udp ... |
docker network
可以在容器之間創建網絡進行交流,不需要暴露或發布特定端口,因為連接到網絡的容器可以通過任何端口相互通信。
2.4.7 ENV
ENV
指令用於設置環境變量,將環境變量設置<key>
設置為<value>
,這里設置的環境變量將會存在於所有后續指令的環境變量中,可以在命令行進行替換,可以使用docker inspect xxx
查看環境變量的值。ENV
有如下形式:
1 |
# 形式1:設置單個變量值,在第一個空格后的字符串就是value(可能包含空格) |
使用ENV
設置的環境變量將從生成鏡像到鏡像容器運行保持不變,使用docker inspect
可以看到這些環境變量,在運行容器時可以使用docker run --env <key>=<value>
來改變環境變量。如果為單個指令設置值的話,盡量使用RUN <key>=<value> <command>
。
2.4.8 ADD
ADD
指令可以復制新的文件、目錄或者遠程URL中的<src>
,將它們添加到鏡像的文件系統中(具體是鏡像的<dest>
目錄),具體語法有2種:
1 |
# 形式1:將context中 src 復制到鏡像中的 dest 目錄 |
<src>
是相對於context的相對路徑,可以有多個,而且它可以包含通配符,比如:
1 |
# 將所有以hom開頭的為文件復制進去 |
<dest>
是鏡像中的一個路徑,可以是絕對路徑,也可以是相對於WORKDIR
的一個相對目錄(以/
開頭就是絕對路徑),如下:
1 |
# 將 test 復制到 `WORKDIR`/relativeDir/ 的相對路徑 |
當復制包含特殊字符名字(比如[
和]
)的文件或目錄時,需要做一些處理防止被誤認為通配符,比如復制arr[0].txt
文件:
1 |
# 將 arr[0].txt文件復制到鏡像中的 /mydir/ 目錄 |
注:其中--chown
是僅支持建立再Linux容器上的Dockerfile,win上無效。所有新文件或目錄都是使用UID和GID為0開頭(Linux中UID和GID即表示UserId和GroupId,0是超級用戶,500起則是普通用戶,表示的一種權限)被創建,除非使用--chown
選項指定用戶、用戶組(或用UID和GID,2種方式可以混用),如果指定了用戶卻不指定用戶組(或指定了UID卻不指定GID),那將會使用和UID一樣的GID。下面是示例:
1 |
ADD --chown=55:mygroup files* /somedir/ |
ADD
遵循下面的規則:
<src>
必須在context目錄中,不能出現context之外的目錄,CLI只將context發送給daemon,docker是無法讀取context之外目錄的文件、目錄的;<src>
如果是一個URL:- 如果
<dest>
不以斜杠/
結尾,文件將會從URL下載並被復制到<dest>
; - 如果
<dest>
以斜杠/
結尾,那將從URL推斷文件名並將文件下載到<dest>/<filename>
,如ADD http://example.com/foobar /
將會創建/foobar
文件;
- 如果
- 如果
<src>
是一個目錄,那整個目錄的內容(只是它內部的內容但不包含目錄本身)將會被復制,包括文件系統的元信息; - 如果
<src>
是本地context目錄下的壓縮包,那它將會被解壓成一個目錄(前提是可識別的壓縮格式:gzip, bzip2 or xz),但僅限於context是本地目錄的情況,如果<src>
是URL資源就不會自動解壓了。 - 如果
<src>
有多個,那<dest>
必須只能是一個目錄(即必須/
結尾); - 如果
<dest>
不以/
結尾,那將會被認為是一個文件(不是目錄),那<src>
的內容將會被寫入到該文件中; - 如果
<dest>
不存在,那將會創建(丟失的目錄也會隨之被創建);
2.4.9 COPY
COPY
和上述的ADD
指令功能一樣,但COPY
的<src>
不能是URL,也不能對壓縮包進行解壓,語法類似:
1 |
COPY [--chown=<user>:<group>] <src>... <dest> |
2.4.10 ENTRYPOINT
ENTRYPOINT
允許配置容器,語法為:
1 |
# 形式1:exec形式,優先 |
比如以默認內容啟動nginx,監聽80端口:docker run -i -t --rm -p 80:80 nginx
。運行docker run xxx
時指定命令行參數將會追加到exec形式的ENTRYPOINT
指令中,且覆蓋使用CMD
指令指定的所有元素,允許將這些參數傳給entry point,-d
(即docker run <image> -d
)即可將將這些參數傳給entry point,甚至可以使用docker run --entrypoint
選項覆蓋Dockerfile中的ENTRYPOINT
指令。shell形式禁止使用任何CMD
或run
命令行參數,缺點是ENTRYPOINT
將作為/bin/sh-c
的子命令啟動,不會傳遞信號,即可執行文件經不會成為容器的PID 1
且不會收到 Unix 信號,因此可執行文件將收不到docker stop <container>
發的SIGTERM
。Dockerfile中最后一條ENTRYPOINT
指令才有效。
【exec形式的ENTRYPOINT
】
可以使用exec形式的ENTRYPOINT
設置相當穩定的默認命令和參數,然后使用任意一種形式的CMD
設置可能改變的額外默認值,如:
1 |
FROM ubuntu |
在運行該鏡像容器時,top
是唯一的進程。在命令行中可以使用--entrypoint
選項來覆蓋Dockerfile中ENTRYPOINT
指令。
注:
- exec形式必須使用雙引號
""
(最終是被編譯成JSON); - exec形式不會對shell命令中的變量做替換(如
$HOME
不會被替換成用戶根目錄,比如/root
),如有需要必須指定一個shell目錄,如ENTRYPOINT [ "sh", "-c", "echo $HOME" ]
;
【shell形式的ENTRYPOINT
】
shell形式可以直接指定字符串,默認就會在/bin/sh -c
中執行,理所當然的可以進行變量替換。
【總結】
CMD
和ENTRYPOINT
指令定義了在運行容器時什么命令會執行,規則如下:
- Dockerfile應當指定至少一個
CMD
或ENTRYPOINT
指令; - 在將容器用作可執行文件時應當定義
ENTRYPOINT
; CMD
指令應當作為ENTRYPOINT
指令指定默認參數的一種方式,或者在容器中執行特殊命令;CMD
指令可以在運行容器時指定被重寫;- 如果
CMD
在基礎鏡像上定義了,那在當前鏡像中如果設置ENTRYPOINT
將會重置上述的CMD
的值(變為空值),此時必須在當前鏡像中定義CMD
的值;
2.4.11 VOLUME
VOLUME
指令用於創建指定名字的掛載點,並將其標記為保存來自本地主機或其他容器的外部裝入的卷。形式有JSON和純字符串的:
1 |
# 形式1(JSON) |
比如:
1 |
FROM ubuntu |
將會創建一個新的掛載點/myvol/greeting
並將greeting
文件復制到上述新建的存儲卷。使用VOLUME
有如下的注意點:
- 基於win平台存儲卷的容器:容器內部存儲卷的目的地必須為空目錄或者不存在的目錄(此時docker會自動創建),此外指定目錄不能為C盤;
- Dockerfile中更改存儲卷:如果任何生成步驟在聲明卷后然后又更改卷內的數據,則這些更改將被丟棄(即存儲卷的使用周期只有當前容器);
- 由於JSON,注意雙引號;
- 在主機目錄在容器運行時聲明:主機目錄(即掛載點)是天然依賴主機的,為了保持映像的可移植性,因為不能保證給定的主機目錄在所有主機上都可用,因此不能在Dockerfile中掛載主機目錄,
VOLUME
指令不支持指定host-dir
參數,在創建或者運行容器時必須指定掛載點;
2.4.12 USER
USER
指令用於在運行鏡像中的RUN
、CMD
和ENTRYPOINT
指令時設置用戶名(或UID
)以及用戶組(可選,或者GID
),主要形式如下:
1 |
# 形式1 |
注:
- 當用戶沒有用戶組時,鏡像將會運行在
root
用戶組下; - win中,如果用戶不是內置的賬戶,那必須先進行創建,可以使用
net user
來創建,下面是示例:
1 |
FROM microsoft/windowsservercore |
2.4.13 WORKDIR
WORKDIR
指令用於設置RUN
、CMD
、ENTRYPOINT
、COPY
、ADD
指令的工作目錄,如果WORKDIR
指定的目錄不存在,就會被創建(即使后續指令沒有使用該目錄),該目錄可以在Dockerfile中使用多次,基本語法為:
1 |
WORKDIR /path/to/workdir |
如果提供了相對路徑,那它是先前WORKDIR
指定目錄的相對路徑,下面是示例:
1 |
# 示例 |
最終的路徑為/a/b/c
。此外WORKDIR
指令可以解析之前使用ENV
設置的環境變量,如:
1 |
ENV DIRPATH /path |
2.4.14 構建時參數ARG
ARG
指令用於用戶在創建鏡像時定義的一些變量,語法:
1 |
ARG <name>[=<default value>] |
其中默認值是可選的,如果在構建時不指定構建參數將會使用默認值。可以在docker build
時使用--build-arg <varname>=<value>
指定這些變量的具體值,如果用戶指定了Dockerfile中未定義的構建參數,構建時將會出現下面的日志:
1 |
[Warning] One or more build-args [foo] were not consumed. |
可以定義多個ARG
指令,比如:
1 |
# 注:默認值是可省的 |
注:不建議將密碼這類的信息作為構建參數,因為任何用戶都可以使用docker history <images>
查看到構建參數。
【作用域】
ARG
變量定義從dockerfile中定義它的地方開始生效,而不是從參數在命令行或其他地方的使用開始生效,比如:
1 |
FROM busybox |
構建時使用docker build --build-arg user=what_user .
,實際在Dockerfile中的第2行中定義了USER
,並將變量user
的值假設為some_user
,第4行的USER
指令定義了變量user
是什么(將會從命令行中傳過來)。在用ARG
指令定義變量之前,任何變量的使用都是空字符串(那是,沒定義就使用不報錯就不錯了)。ARG
指令的作用域只在定義ARG
的階段生效,如果需要在多個階段使用arg,每個階段必須都包含ARG
指令,如:
1 |
FROM busybox |
【使用】
可以使用ARG
和ENV
指令指定RUN
指令中指定的變量,使用ENV
指令定義的環境變量會覆蓋掉ARG
指令中同名的變量,比如:
1 |
FROM ubuntu |
構建鏡像docker build --build-arg CONT_IMG_VER=v2.0.1 .
最終的CONT_IMG_VER
變量輸出為v1.0.0
。
【預定義的ARG】
Docker中有一些預定義好的參數ARG
,這些參數可以直接在build時指定,而無需在Dockerfile中先進行定義,預定義的參數有:
HTTP_PROXY
http_proxy
HTTPS_PROXY
https_proxy
FTP_PROXY
ftp_proxy
NO_PROXY
no_proxy
使用時直接通過--build-arg <varname>=<value>
傳值進去即可。默認預定義的參數是無法使用docker history
看到的,如果要覆蓋默認行為(即預定義參數無法通過docker history
看到),可以在Dockerfile中定義一個同名參數覆蓋即可,如:
1 |
FROM ubuntu |
【構建緩存的影響】
ARG
變量不會像ENV
變量一樣將其持久化到鏡像中,但它也以類似的方式影響着構建緩存。如果Dockerfile中定義了和之前構建定義的不同ARG
變量值,在第一階段就會出現“cache miss”,尤其是在所有的RUN
指令都是用了之前ARG
定義的變量就會造成上述的緩存未命中。所有預定義的ARG
變量並不會被緩存除非在Dockerfile中覆蓋過(換而言之,所有Dokcerfile中使用ARG
定義的變量都會被緩存)。下面是示例:
1 |
# Dockerfile1 |
在構建時指定--build-arg CONT_IMG_VER=<value>
,在上述2個Dockerfile的構建過程中,第3行會出現 cache miss 的提示(但我操作過程中進行的很順利啊,並沒有出現這個提示啊( ̄ ‘i  ̄;),我是誰,我在哪……)。
2.4.15 ONBUILD
當一個鏡像A被用於另一個鏡像B的基本鏡像時(即FROM
),ONBUILD
指令用於向一個鏡像中添加一個 trigger,以便可以在一段時間后執行。觸發器將在下游(即當前構建的鏡像B)生成的context中執行,只要它是在下游Dockerfile中FROM
指令之后立即插入的。流程如下:
- 當看到
ONBUILD
時,構建器會向當前正在構建的鏡像的 metadata 中添加 trigger,該指令不會影響當前構建; - 在構建過程結束時,一系列的 trigger 將會存儲到鏡像的manifest中的
OnBuild
字段(一個數組對象)中,可以使用docker inspect
查看這個字段; - 然后這個鏡像可能用作其他鏡像構建的基礎鏡像,作為
FROM
指令的一部分,下游的構建器將會尋找ONBUILD
trigger並按它們注冊的順序執行。如果其中某一個trigger執行失敗了,FROM
指令將被中止,從而導致構建失敗; - 觸發器在執行后從最終鏡像中清除,它們不是由“孫子”構建繼承;
下面是示例:
1 |
[...] |
注:
ONBUILD
指令禁止嵌套使用,即類似於ONBUILD ONBUILD
;ONBUILD
指令可能不會觸發FROM
或MAINTAINER
指令(即盡量不要在ONBUILD
指令中注冊FROM
或MAINTAINER
指令);
2.4.16 STOPSIGNAL
STOPSIGNAL
指令用於設置系統發送到容器中、目的用於退出的命令,這個信號可以是內核的 syscall 表中有效的無符號數字(比如9
),或格式為SIGNAME
的信號名字(比如SIGKILL
)。基本語法如下:
1 |
STOPSIGNAL signal |
2.4.17 HEALTHCHECK
HEALTHCHECK
指令用於告訴Docker如何檢測容器是否處於健康(工作)狀態,有點類似於服務檢測,這個功能可以檢測到一些情況,如Web服務器卡在無限循環中,無法處理新連接,即使服務器進程仍在運行。當一個容器指定了一個健康檢測,那該容器除了它本身的正常狀態外,還會額外有一個“健康狀態”health status(字段為health_status
),健康狀態初始值為starting
,只有當健康檢查(可以稱作容器體檢)通過了,它才會變成healthy
,在連續次數(這個次數可以指定)的體檢結果都不通過那 health status 將變成unhealthy
。HEALTHCHECK
的基本語法為:
1 |
# 形式1:在容器內運行指定命令進行容器體檢 |
上述形式1中的OPTIONS
可用選項有:
--interval=DURATION
:表示容器體檢的時間間隔,通常在容器首次進行體檢(此時可以理解為delay)和體檢失敗后再次體檢時用到,默認是30s;--timeout=DURATION
:表示容器體檢的超時時間,一次體檢時長超過此時間則認為本次體檢失敗,置為默認為30s;--start-period=DURATION
:表示容器的啟動時長,在這個時間段內體檢失敗的結果不作數(即不算作retries
),但是如果在這段時間內,容器體檢成功了,從此刻(即使此刻還在start-period
內)開始所有之后的檢查就開始生效,retries
開始統計,就認為容器已經啟動成功,默認是0s;--retries=N
:表示容器體檢重試的最大次數,即第一次體檢失敗並不意味着容器不健康,只有失敗時嘗試指定次數都是失敗,該容器此時的health status
才會置為unhealthy
,默認是3次;
形式1中CMD
后可以是shell命令,如HEALTHCHECK CMD /bin/check-running
,也可以是exec數組(和之前類似),這些命令的執行完成后的狀態就可以推測出容器的health status,下面是多數命令的常用退出狀態碼:
0
:success - 容器體檢健康,可以正常使用;1
:unhealthy - 容器體檢不健康,不能進行正常工作;2
:reserved - 不要使用此退出代碼;
比如:
1 |
HEALTHCHECK --interval=5m --timeout=3s \ |
從前至后,參數分別為:檢測間隔5分鍾,檢測超時時間為3s,檢測行為指令curl -f http://localhost/ || exit 1
。為了調式失敗的探測failing probes
,使用stdout和stderr命令輸出的內容(只能存儲最近的4096byte)都將會存儲到健康狀態中,這些輸出都可以使用docker inspect
命令查看到。
2.4.18 SHELL
SHELL
指令允許在shell形式中使用的默認shell可以被重寫,Linux中默認的Shell為["/bin/sh", "-c"]
,win中默認的shell為["cmd", "/S", "/C"]
。Dockerfile中的SHELL
指令必須使用JSON格式。SHELL
指令在win上是非常有用的,因為win上有兩種常用的shell:cmd
和powershell
,包含可選的shell:sh
。
SHELL
指令可以出現多次,每個SHELL
指令都重寫之前的SHELL
指令,並影響所有的后續指令,如:
1 |
FROM microsoft/windowsservercore |
當在Dockerfile中的RUN
、CMD
、ENTRYPOINT
指令中使用shell形式會被SHELL
指令影響。下面的示例是win上的通用模式,它可以使用SHELL
指令簡化,如:
1 |
RUN powershell -command Execute-MyCmdlet -param1 "c:\foo.txt" |
docker最終調用的命令為:
1 |
cmd /S /C powershell -command Execute-MyCmdlet -param1 "c:\foo.txt" |
但這樣效率較低,因為存在一個不必要的命令處理器正在被調用(即shell),而且RUN
指令以shell形式給出需要一個額外的powershell -command
前綴。為了提高效率,有2種方式:
- 直接使用
RUN
指令的JSON形式:RUN ["powershell", "-command", "Execute-MyCmdlet", "-param1 \"c:\\foo.txt\""]
; - 使用
SHELL
指令,配合使用shell形式,對於win用戶是更加是更加自然的語法,特別是結合使用escape
指定編譯指令時,如:
1 |
# escape=` |
SHELL
指令還可以用於修改shell的執行方式,如在win上使用SHELL cmd /S /C /V:ON|OFF
,可以修改延遲的環境變量擴展語義。
附:
下面是整章Dockerfile最終的綜合示例:
1 |
# 示例1 |