【轉】Dockerfile


1. 關於docker build

docker build可以基於Dockerfile和context打包出一個鏡像,其中context是一系列在PATHURL中指定的位置中的文件(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
2
# ~/docker/test1/Dockerfil指定Dockerfile位置,~/docker/指定context一定是Dockerfile所在目錄或父目錄
docker build -f ~/docker/test1/Dockerfile ~/docker/

 此外還可以指定如果鏡像構建成功存放的倉庫和標簽(即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中表現為\),同時指令解釋器后應該空一行。總結一下,應該遵循如下的規則:

  1. #開頭,指令解釋器小寫,如果有位於Dockerfile的第一行;
  2. 不支持行繼續操作行為,即\
  3. 解釋器后應留一個空白行;

下面是一些反例:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 反例1:使用了行繼續 \
# direc \
tive=value

# 反例2:只會識別第一個,value2不應該出現會被當成注釋
# directive=value1
# directive=value2

FROM ImageName

# 反例3:指令解釋器非首行
FROM ImageName
# directive=value

注:指令解釋器允許使用空格符,比如下面的幾種方式都是一樣有效的:

1
2
3
4
#directive=value
# directive =value
# directive= value
# directive = value

 Dockerfile支持的指令解釋器有:syntaxescape。其中syntax僅支持下一代構建工具BuildKit,這里暫不進行搗鼓,直接看escape,它定義的是Dockerfile中的轉義字符,有2種定義方式:

1
2
3
4
5
# escape=\

# 或者

# escape=`

如果不指定默認轉義字符為\。轉義字符既用於轉義一行中的字符,也用於轉義換行符。這使得Dockerfile指令可以跨越多行,無論轉義分析器指令是否包含在Dockerfile中,轉義都不會在RUN命令中執行,除非在行尾執行。在windows上,\ 是目錄之間的分隔符,將轉義字符設置為 ` 是非常有用的,它和 PowerShell 一致,比如在windows的powerShell中:

1
2
3
FROM microsoft/nanoserver
COPY testfile.txt c:\\
RUN dir c:\

沒有指定指令解釋器,默認為\,那此時第二行中COPY testfile.txt c:\\就出現了轉義字符(c:\\中的第一個\),實際意思就是COPY testfile.txt c:\,那剩下的那個\就變成了行繼續的操作,所以第二行和第三行是一行實際為:COPY testfile.txt c:\RUN dir c:第三行的轉義字符在行末屬於可執行范圍。在powershell中,為了避免這個問題可以使用 ` 作為轉義字符就不會出現上述的問題:

1
2
3
4
5
# escape=`

FROM microsoft/nanoserver
COPY testfile.txt c:\\
RUN dir c:\

上述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的值。

 環境變量支持如下的指令:ADDCOPYENVEXPOSEFROMLABELSTOPSIGNALUSERVOLUMEWORKDIRONBUILD(1.4版本之后該指令只有在和上述其他指令結合使用時才能使用環境變量)。下面是一個示例(解析的指令在#后已經標出):

1
2
3
4
5
6
7
8
9
FROM busybox
# 聲明環境變量foo,表示的值為/bar
ENV foo /bar
# 等同於 WORKDIR /bar
WORKDIR ${foo}
# 等同於 ADD . /bar
ADD . $foo
# 等同於 COPY $foo /quux
COPY \$foo /quux

 環境變量的替換將在整個指令中對每個變量使用相同的值,比如下面的示例:

1
2
3
ENV abc=hello
ENV abc=bye def=$abc
ENV ghi=$abc

上述的定義過程中,def的值為hello,而不是byeghi的值為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並通過ADDCOPY將這些文件加入到鏡像中。同樣的.dockerignore文件中也是允許注釋的,行首用#標注即可,比如:

1
2
3
4
# comment
*/temp*
*/*/temp*
temp?

上述的# comment是一個注釋,CLI將不會管它;*/temp*表示CLI將排除context根目錄中的任何一級子目錄中名稱以temp開頭的文件或目錄;*/*/temp*表示CLI將從context根目錄下任何兩級子目錄中排除以temp開頭的文件或目錄(如/somedir/subdir/temporary.txt);temp?表示CLI將排除context根目錄中名為temp的單字符擴展名的文件和目錄(比如/tempa)。此外它還支持**通配符(匹配0或多個目錄,注意是用於匹配目錄的!!),如**/*.go就是匹配context目錄(包括它所有的子孫目錄)下所有以.go結尾的文件。

 如果行首是!可以用於排除例外的情況,示例如下:

1
2
*.md
!README.md

上述就是排除所有以.md結尾的文件,但README.md除外不用排除。

!的位置會影響.dockerignore文件中與特定文件匹配的屬性決定它是包含的還是排除的最后一行,下面是2個示例(有點懵懂):

1
2
3
4
5
6
7
8
9
# 示例1:除了README-secret.md之外,排除上下文中含任何其他以.md結尾的文件。
*.md
!README*.md
README-secret.md

# 示例2:所有README文件都包含在內,中間的 README-secret.md 沒有效果,因為 !readme*.md 與 readme-secret.md 匹配卻在最后
*.md
README-secret.md
!README*.md

 在.dockerignore中甚至可以將Dockerfile.dockerignore排除掉,但它們仍然會被發送到daemon因為docker需要這些進行job,但ADDCOPY指令不會將它們復制到鏡像中。

2.4 具體指令(只包含18.09版本最新指令)

2.4.1 FROM

FROM指令初始化一個新的構建階段,它為后續的指令設置了一個基本鏡像,因此它必須是在所有指令的最前面定義的(除指令解釋器),這個基本鏡像可以是任何一個合法的鏡像。語法如下:

1
2
3
4
5
6
7
8
# 方式1
FROM <image> [AS <name>]

# 方式2
FROM <image>[:<tag>] [AS <name>]

# 方式3
FROM <image>[@<digest>] [AS <name>]

FROM可以在一個Dockerfile文件中出現多次,創建多個鏡像或使用一個生成階段作為另一個的依賴。只需在每條新的FROM指令之前記錄提交所輸出的最后一個鏡像的ID,每個FROM指令會清除之前其他指令創建的狀態。name是可選的,可以通過在FROM指令中添加AS name給一個新的構建階段起一個名字,這個名字可以在后來的FROM指令和COPY --from=<name|index>指令中引用對應的鏡像。同樣tagdigest也是可選的,如果不指定,默認為latest

2.4.2 ARG

ARG是唯一一個可以在FROM指令之前的,FROM指令是支持在第一個FROM之前由ARG指令聲明的變量,比如:

1
2
3
4
5
6
7
8
9
# 聲明變量 CODE_VERSION,值為latest
ARG CODE_VERSION=latest
# 在FROM指令中使用由ARG聲明的變量
FROM base:${CODE_VERSION}
CMD /code/run-app

# 在FROM指令中使用由ARG聲明的變量
FROM extras:${CODE_VERSION}
CMD /code/run-extras

FROM指令之前ARG變量的聲明是屬於構建階段之外的,因此不能在FROM指令之后的其他指令中使用。如果要使用第一個From之前聲明的ARG值,需要在構建階段內使用沒有指定值的ARG指令,比如:

1
2
3
4
5
ARG VERSION=latest
FROM busybox:$VERSION
# 聲明一個沒有默認值的變量 ARG 指令
ARG VERSION
RUN echo $VERSION > image_version
2.4.3 RUN

RUN指令將會在當前鏡像的頂部的一個新層layer中執行命令並提交結果,提交鏡像的結果將用於Dockerfile文件中的下一步驟。分層RUN並生成新的新的結果符合Docker的核心思想(即提交的成本很低且可以從鏡像歷史的任何一點創建容器,很像源代碼管理)。語法如下:

1
2
3
4
5
# 形式1:shell的形式,命令運行在shell中,Linux中默認運行 /bin/sh -c,win中默認運行 cmd /S /C
RUN <command>

# 形式2:exec的形式
RUN ["executable", "param1", "param2"]

exec形式可以避免對shell字符串進行munging,可以在不包含指定可執行shell的基本鏡像中運行RUN指令。shell形式默認的shell可以使用SHELL命令更改,比如要使用除/bin/sh之外的shell,就需要使用傳入所需shell的exec形式:

1
RUN ["/bin/bash", "-c", "echo hello"]

在shell形式中可以使用\以繼續運下一行的 RUN 指令(即使用\作為行繼續操作符,可以跨行運行),比如:

1
2
RUN /bin/bash -c 'source $HOME/.bashrc; \
echo $HOME'

上述使用的是行繼續操作符,實際是一個指令,等同於: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-cacheRUN指令的緩存也可以通過ADD指令失效。

2.4.4 CMD

 一個Dockerfile文件中僅可以只有一個CMD指令,如果有多個CMD,僅有最有一個CMD奏效,它的主要目的是為正在執行的容器提供默認值,這些默認值可以包括可執行文件,也可以省略可執行文件(但此時必須指定一個ENTRYPOINT指令),主要語法如下:

1
2
3
4
5
6
7
8
# 形式1:exec形式,這是首選形式
CMD ["executable","param1","param2"]

# 作為ENTRYPOINT默認參數
CMD ["param1","param2"]

# shell形式
CMD command param1 param2

如果CMD用於為ENTRYPOINT指令提供默認參數(即上述第2種形式),那CMDENTRYPOINT指令都應該用JSON格式指定(雙引號")。當在shell或exec形式,CMD指令設置在運行鏡像時要執行的命令,如果使用shell形式的CMD<command>應該是/bin/sh -c

1
2
FROM ubuntu
CMD echo "This is a test." | wc -

如果不想在shell中運行<command>,那就必須使用JSON數組的形式並使用可執行文件的全路徑。數組形式是CMD的首選形式,任何額外的參數必須在數組中做獨立表達,如:

1
2
FROM ubuntu
CMD ["/usr/bin/wc","--help"]

如果想每次容器都執行相同的可執行文件,應該考慮使用ENTRYPOINTCMD的配合使用。如果指定docker run的參數,那么它們將覆蓋在CMD中指定的默認值。

注:RUNCMD可能有些像,主要區別是:RUN是運行一個命令然后提交結果,而CMD在構建鏡像的過程中不會干任何事,但指定鏡像的預期命令。

2.4.5 LABEL

LABEL指令用於向鏡像添加元數據,它是一個鍵值對,如果key值或value值中包含空格,需要使用雙引號和反斜杠,一個鏡像可以有多個標簽,下面是示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 有空格用引號包裹
LABEL "com.example.vendor"="ACME Incorporated"

#
LABEL com.example.label-with-value="foo"

#
LABEL version="1.0"

# 使用行繼續操作符跨行
LABEL description="This text illustrates \
that label-values can span multiple lines."

# 一個LABEL中設置多個標簽
LABEL multi.label1="value1" multi.label2="value2" other="value3"

# 一個LABEL中設置多個標簽
LABEL multi.label1="value1" \
multi.label2="value2" \
other="value3"

FROM指令中指定的基本鏡像中的標簽會被當前構建的鏡像繼承過來,這些標簽如果在當前構建的鏡像指定相同的key值但不同的value值時是會被覆蓋的,使用docker inspect xxx可以查看鏡像xxx的標簽,下面是openjdk部分標簽:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
docker inspect 3675b9f543c5

# 結果
[
{
"Id": "sha256:3675b9f543c5db0d8d554f8e103a4cb98db26be5e5c88019cbe49dcd4fea4685",
"RepoTags": [
"openjdk:8-jdk-alpine"
],
"RepoDigests": [
"openjdk@sha256:2a52fedf1d4ab53323e16a032cadca89aac47024a8228dea7f862dbccf169e1e"
],
"Parent": "",
"Comment": "",
"Created": "2019-04-10T01:52:39.548813341Z",
"Container": "2d38510bb479e015e7990ac60a6af8855e1c9f7bf6b6a66bde2c8625355eccd9",
"ContainerConfig": {
...
},
...
"Metadata": {
"LastTagTime": "0001-01-01T00:00:00Z"
}
}
]
2.4.6 EXPOSE

EXPOSE指令表示了docker容器運行時監聽的特定網絡端口,可以指定監聽TCP(默認)或UDP,在實際運行容器docker run時可以使用-p標識去發布並映射到一個或多個端口。比如默認監聽TCP,現在改成UDP:

1
EXPOSE 80/udp

也可以同時監聽TCP和UDP的80端口:

1
2
EXPOSE 80/tcp
EXPOSE 80/udp

如果在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
2
3
4
5
6
7
8
9
10
11
12
# 形式1:設置單個變量值,在第一個空格后的字符串就是value(可能包含空格)
ENV <key> <value>
# 形式1示例,定義了3個環境變量
ENV myName John Doe
ENV myDog Rex The Dog
ENV myCat fluffy

# 形式2:允許同時設置多個環境變量,可以使用雙引號 和行繼續操作符 \
ENV <key>=<value> ...
# 形式2示例,定義了2個環境變量
ENV myName="John Doe" myDog=Rex\ The\ Dog \
myCat=fluffy

使用ENV設置的環境變量將從生成鏡像到鏡像容器運行保持不變,使用docker inspect可以看到這些環境變量,在運行容器時可以使用docker run --env <key>=<value>來改變環境變量。如果為單個指令設置值的話,盡量使用RUN <key>=<value> <command>

2.4.8 ADD

ADD指令可以復制新的文件、目錄或者遠程URL中的<src>,將它們添加到鏡像的文件系統中(具體是鏡像的<dest>目錄),具體語法有2種:

1
2
3
4
5
# 形式1:將context中 src 復制到鏡像中的 dest 目錄
ADD [--chown=<user>:<group>] <src>... <dest>

# 形式2:和形式1一樣,就多了雙引號,主要用於src和dest路徑中存在空格的情況
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]

<src>是相對於context的相對路徑,可以有多個,而且它可以包含通配符,比如:

1
2
3
4
# 將所有以hom開頭的為文件復制進去
ADD hom* /mydir/
# ? 匹配單個字符,如"home.txt"
ADD hom?.txt /mydir/

<dest>是鏡像中的一個路徑,可以是絕對路徑,也可以是相對於WORKDIR的一個相對目錄(以/開頭就是絕對路徑),如下:

1
2
3
4
5
# 將 test 復制到 `WORKDIR`/relativeDir/ 的相對路徑
ADD test relativeDir/

# 將 test 復制到絕對路徑 /absoluteDir/
ADD test /absoluteDir/

當復制包含特殊字符名字(比如[])的文件或目錄時,需要做一些處理防止被誤認為通配符,比如復制arr[0].txt文件:

1
2
# 將 arr[0].txt文件復制到鏡像中的 /mydir/ 目錄
ADD 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
2
3
4
ADD --chown=55:mygroup files* /somedir/
ADD --chown=bin files* /somedir/
ADD --chown=1 files* /somedir/
ADD --chown=10:11 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
2
COPY [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]
2.4.10 ENTRYPOINT

ENTRYPOINT允許配置容器,語法為:

1
2
3
4
5
# 形式1:exec形式,優先
ENTRYPOINT ["executable", "param1", "param2"]

# 形式2:shell形式
ENTRYPOINT command param1 param2

比如以默認內容啟動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形式禁止使用任何CMDrun命令行參數,缺點是ENTRYPOINT將作為/bin/sh-c的子命令啟動,不會傳遞信號,即可執行文件經不會成為容器的PID 1且不會收到 Unix 信號,因此可執行文件將收不到docker stop <container>發的SIGTERM。Dockerfile中最后一條ENTRYPOINT指令才有效。

【exec形式的ENTRYPOINT

 可以使用exec形式的ENTRYPOINT設置相當穩定的默認命令和參數,然后使用任意一種形式的CMD設置可能改變的額外默認值,如:

1
2
3
FROM ubuntu
ENTRYPOINT ["top", "-b"]
CMD ["-c"]

在運行該鏡像容器時,top是唯一的進程。在命令行中可以使用--entrypoint選項來覆蓋Dockerfile中ENTRYPOINT指令。

注:

  1. exec形式必須使用雙引號""(最終是被編譯成JSON);
  2. exec形式不會對shell命令中的變量做替換(如$HOME不會被替換成用戶根目錄,比如/root),如有需要必須指定一個shell目錄,如ENTRYPOINT [ "sh", "-c", "echo $HOME" ]

【shell形式的ENTRYPOINT

 shell形式可以直接指定字符串,默認就會在/bin/sh -c中執行,理所當然的可以進行變量替換。

【總結】

CMDENTRYPOINT指令定義了在運行容器時什么命令會執行,規則如下:

  1. Dockerfile應當指定至少一個CMDENTRYPOINT指令;
  2. 在將容器用作可執行文件時應當定義ENTRYPOINT
  3. CMD指令應當作為ENTRYPOINT指令指定默認參數的一種方式,或者在容器中執行特殊命令;
  4. CMD指令可以在運行容器時指定被重寫;
  5. 如果CMD在基礎鏡像上定義了,那在當前鏡像中如果設置ENTRYPOINT將會重置上述的CMD的值(變為空值),此時必須在當前鏡像中定義CMD的值;
2.4.11 VOLUME

VOLUME指令用於創建指定名字的掛載點,並將其標記為保存來自本地主機或其他容器的外部裝入的卷。形式有JSON和純字符串的:

1
2
3
4
5
6
7
# 形式1(JSON)
VOLUME ["/var/log/"]

# 形式2
VOLUME /var/log
# 可以指定多個
VOLUME /var/log /var/db

比如:

1
2
3
4
FROM ubuntu
RUN mkdir /myvol
RUN echo "hello world" > /myvol/greeting
VOLUME /myvol

將會創建一個新的掛載點/myvol/greeting並將greeting文件復制到上述新建的存儲卷。使用VOLUME有如下的注意點:

  1. 基於win平台存儲卷的容器:容器內部存儲卷的目的地必須為空目錄或者不存在的目錄(此時docker會自動創建),此外指定目錄不能為C盤;
  2. Dockerfile中更改存儲卷:如果任何生成步驟在聲明卷后然后又更改卷內的數據,則這些更改將被丟棄(即存儲卷的使用周期只有當前容器);
  3. 由於JSON,注意雙引號;
  4. 在主機目錄在容器運行時聲明:主機目錄(即掛載點)是天然依賴主機的,為了保持映像的可移植性,因為不能保證給定的主機目錄在所有主機上都可用,因此不能在Dockerfile中掛載主機目錄,VOLUME指令不支持指定host-dir參數,在創建或者運行容器時必須指定掛載點;
2.4.12 USER

USER指令用於在運行鏡像中的RUNCMDENTRYPOINT指令時設置用戶名(或UID)以及用戶組(可選,或者GID),主要形式如下:

1
2
3
4
5
# 形式1
USER <user>[:<group>]

# 形式2
USER <UID>[:<GID>]

注:

  1. 當用戶沒有用戶組時,鏡像將會運行在root用戶組下;
  2. win中,如果用戶不是內置的賬戶,那必須先進行創建,可以使用net user來創建,下面是示例:
1
2
3
4
5
FROM microsoft/windowsservercore
# 在容器中先創建win用戶
RUN net user /add patrick
# Set it for subsequent commands
USER patrick
2.4.13 WORKDIR

WORKDIR指令用於設置RUNCMDENTRYPOINTCOPYADD指令的工作目錄,如果WORKDIR指定的目錄不存在,就會被創建(即使后續指令沒有使用該目錄),該目錄可以在Dockerfile中使用多次,基本語法為:

1
2
WORKDIR /path/to/workdir
`

如果提供了相對路徑,那它是先前WORKDIR指定目錄的相對路徑,下面是示例:

1
2
3
4
5
6
7
# 示例
# 絕對路徑
WORKDIR /a
# 相對路徑
WORKDIR b
WORKDIR c
RUN pwd

最終的路徑為/a/b/c。此外WORKDIR指令可以解析之前使用ENV設置的環境變量,如:

1
2
3
4
ENV DIRPATH /path
WORKDIR $DIRPATH/$DIRNAME
# 輸出應為 /path/$DIRNAME
RUN pwd
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
2
3
4
# 注:默認值是可省的
FROM busybox
ARG user1
ARG buildno

注:不建議將密碼這類的信息作為構建參數,因為任何用戶都可以使用docker history <images>查看到構建參數。

【作用域】

ARG變量定義從dockerfile中定義它的地方開始生效,而不是從參數在命令行或其他地方的使用開始生效,比如:

1
2
3
4
FROM busybox
USER ${user:-some_user}
ARG user
USER $user

構建時使用docker build --build-arg user=what_user .,實際在Dockerfile中的第2行中定義了USER,並將變量user的值假設為some_user,第4行的USER指令定義了變量user是什么(將會從命令行中傳過來)。在用ARG指令定義變量之前,任何變量的使用都是空字符串(那是,沒定義就使用不報錯就不錯了)。ARG指令的作用域只在定義ARG的階段生效,如果需要在多個階段使用arg,每個階段必須都包含ARG指令,如:

1
2
3
4
5
6
7
FROM busybox
ARG SETTINGS
RUN ./run/setup $SETTINGS

FROM busybox
ARG SETTINGS
RUN ./run/other $SETTINGS

【使用】

可以使用ARGENV指令指定RUN指令中指定的變量,使用ENV指令定義的環境變量會覆蓋掉ARG指令中同名的變量,比如:

1
2
3
4
FROM ubuntu
ARG CONT_IMG_VER
ENV CONT_IMG_VER v1.0.0
RUN echo $CONT_IMG_VER

構建鏡像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
2
3
FROM ubuntu
ARG HTTP_PROXY
RUN echo "Hello World"

【構建緩存的影響】

ARG變量不會像ENV變量一樣將其持久化到鏡像中,但它也以類似的方式影響着構建緩存。如果Dockerfile中定義了和之前構建定義的不同ARG變量值,在第一階段就會出現“cache miss”,尤其是在所有的RUN指令都是用了之前ARG定義的變量就會造成上述的緩存未命中。所有預定義的ARG變量並不會被緩存除非在Dockerfile中覆蓋過(換而言之,所有Dokcerfile中使用ARG定義的變量都會被緩存)。下面是示例:

1
2
3
4
5
6
7
8
9
# Dockerfile1
FROM ubuntu
ARG CONT_IMG_VER
RUN echo $CONT_IMG_VER

# Dockerfile2
FROM ubuntu
ARG CONT_IMG_VER
RUN echo hello

在構建時指定--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指令之后立即插入的。流程如下:

  1. 當看到ONBUILD時,構建器會向當前正在構建的鏡像的 metadata 中添加 trigger,該指令不會影響當前構建;
  2. 在構建過程結束時,一系列的 trigger 將會存儲到鏡像的manifest中的OnBuild字段(一個數組對象)中,可以使用docker inspect查看這個字段;
  3. 然后這個鏡像可能用作其他鏡像構建的基礎鏡像,作為FROM指令的一部分,下游的構建器將會尋找ONBUILD trigger並按它們注冊的順序執行。如果其中某一個trigger執行失敗了,FROM指令將被中止,從而導致構建失敗;
  4. 觸發器在執行后從最終鏡像中清除,它們不是由“孫子”構建繼承;

下面是示例:

1
2
3
4
[...]
ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build --dir /app/src
[...]

注:

  • ONBUILD指令禁止嵌套使用,即類似於ONBUILD ONBUILD
  • ONBUILD指令可能不會觸發FROMMAINTAINER指令(即盡量不要在ONBUILD指令中注冊FROMMAINTAINER指令);
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 將變成unhealthyHEALTHCHECK的基本語法為:

1
2
3
4
5
# 形式1:在容器內運行指定命令進行容器體檢
HEALTHCHECK [OPTIONS] CMD command

# 形式2:禁用從基映像繼承的任何運行狀況檢查
HEALTHCHECK NONE

上述形式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
2
HEALTHCHECK --interval=5m --timeout=3s \
CMD curl -f http://localhost/ || exit 1

從前至后,參數分別為:檢測間隔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:cmdpowershell,包含可選的shell:sh

SHELL指令可以出現多次,每個SHELL指令都重寫之前的SHELL指令,並影響所有的后續指令,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
FROM microsoft/windowsservercore

# Executed as cmd /S /C echo default
RUN echo default

# Executed as cmd /S /C powershell -command Write-Host default
RUN powershell -command Write-Host default

# Executed as powershell -command Write-Host hello
SHELL ["powershell", "-command"]
RUN Write-Host hello

# Executed as cmd /S /C echo hello
SHELL ["cmd", "/S", "/C"]
RUN echo hello

當在Dockerfile中的RUNCMDENTRYPOINT指令中使用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種方式:

  1. 直接使用RUN指令的JSON形式:RUN ["powershell", "-command", "Execute-MyCmdlet", "-param1 \"c:\\foo.txt\""]
  2. 使用SHELL指令,配合使用shell形式,對於win用戶是更加是更加自然的語法,特別是結合使用escape指定編譯指令時,如:
1
2
3
4
5
6
7
# escape=`

FROM microsoft/nanoserver
SHELL ["powershell","-command"]
RUN New-Item -ItemType Directory C:\Example
ADD Execute-MyCmdlet.ps1 c:\example\
RUN c:\example\Execute-MyCmdlet -sample 'hello world'

SHELL指令還可以用於修改shell的執行方式,如在win上使用SHELL cmd /S /C /V:ON|OFF,可以修改延遲的環境變量擴展語義。

附:

下面是整章Dockerfile最終的綜合示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# 示例1
# Nginx
#
# VERSION 0.0.1

FROM ubuntu
LABEL Description="This image is used to start the foobar executable" Vendor="ACME Products" Version="1.0"
RUN apt-get update && apt-get install -y inotify-tools nginx apache2 openssh-server


# 示例2
# Firefox over VNC
#
# VERSION 0.3

FROM ubuntu

# Install vnc, xvfb in order to create a 'fake' display and firefox
RUN apt-get update && apt-get install -y x11vnc xvfb firefox
RUN mkdir ~/.vnc
# Setup a password
RUN x11vnc -storepasswd 1234 ~/.vnc/passwd
# Autostart firefox (might not be the best way, but it does the trick)
RUN bash -c 'echo "firefox" >> /.bashrc'

EXPOSE 5900
CMD ["x11vnc", "-forever", "-usepw", "-create"]


# 示例3
# Multiple images example
#
# VERSION 0.1

FROM ubuntu
RUN echo foo > bar
# Will output something like ===> 907ad6c2736f

FROM ubuntu
RUN echo moo > oink
# Will output something like ===> 695d7793cbe4

# You'll now have two images, 907ad6c2736f with /bar, and 695d7793cbe4 with

轉自:https://blog.wgl.wiki/Dockerfile/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM