docker公司在容器技術發展中提出了映象分層的理念,可以說也是這個革命性的理念讓原本只不過是整合linux核心特性的容器,開始野蠻生長。
docker通過UnionFS聯合檔案系統將映象的分層實現合併,關於映象相關知識有興趣的同學可參考我們之前文章docker容器技術基礎之聯合檔案系統OverlayFS
本文是對docker官方文件Dockerfile reference學習與實踐,在學習docker容器相關技術的同學別光收藏,你要動起來!實踐起來!
提示:沒有人比docker公司更懂docker,本小作文含部分自己的理解,有英文閱讀習慣的同學,建議直接閱讀官方文件哈。
docker build
Dockerfile是一個映象構建命令集合的文字檔案,下面是我們最常見的Dockerfile構建,假如我們目錄下有一個檔案Dockerfile
[root@localhost nginx_project]# ls
Dockerfile
[root@localhost nginx_project]# docker build -t nginx:v1 .
通過build指定了目標映象的標籤為nginx:v1,以及Dockerfile的上下文context .
什麼是docker上下文?
一個面向服務端的目錄夾結構,除了Dockerfile,你的一切構建資源都應該在這個目錄(指定的上下文)中。
上下文是遞迴處理的。因此, 如果是PATH
則包含任何子目錄,如果是一個URL
則包含儲存庫及其子模組。
關鍵點,構建是由 Docker 守護程式執行,而不是由 CLI 執行,所以docker會把上下文資源打包傳輸給守護程式進行構建,為了減少不必要的臃腫,最好從一個空目錄作為上下文開始,並將 Dockerfile 儲存在該目錄中。僅新增構建 Dockerfile 所需的檔案。
我們可以使用-f
選項指定dockerfile
[root@localhost folder]# docker build -f ../Dockerfile -t nginx:v1 .
使用多個-t
選項保持多個tag
[root@localhost folder]# docker build -t nginx:v1 -t dockerhub.com/nginx:v2 .
Sending build context to Docker daemon 1.583kB
Step 1/2 : FROM nginx
---> 08b152afcfae
Step 2/2 : run echo 123
---> Using cache
---> 3b636c79fbfa
Successfully built 3b636c79fbfa
Successfully tagged nginx:v1
Successfully tagged dockerhub.com/nginx:v2
這樣就構建兩個不同tag的同一ID映象
[root@localhost folder]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
dockerhub.com/nginx v2 3b636c79fbfa 23 minutes ago 133MB
nginx v1 3b636c79fbfa 23 minutes ago 133MB
BuildKit
buildkit將 Dockerfile 變成了 Docker 映象。它不只是構建 Docker 映象;它可以構建 OCI 影像和其他幾種輸出格式。
從版本18.09開始,Docker支援由moby / buildkit專案提供的用於執行構建的新後端。與舊的實現相比,BuildKit後端提供了許多好處。例如,BuildKit可以:
- 檢測並跳過執行未使用的構建階段。
- 平行構建獨立的構建階段。
- 在不同的構建過程中,只增加傳輸構建上下文中的更改檔案。
- 在構建上下文中檢測並跳過傳輸未使用的檔案。
- 使用外部Dockerfile實現許多新功能。
- 避免與API的其他部分(中間映象和容器)產生副作用。
- 優先處理您的構建快取,以便自動修剪。
要使用BuildKit後端,只需要在呼叫 DOCKER_BUILDKIT=1
docker build
之前在CLI上設定環境變數DOCKER_BUILDKIT = 1。或者配置/etc/docker/daemon.json啟用。
[root@localhost folder]# DOCKER_BUILDKIT=1 docker build -f ../Dockerfile -t nginx:v1 -t dockerhub.com/nginx:v2 .
[+] Building 5.2s (6/6) FINISHED
=> [internal] load build definition from Dockerfile 0.7s
=> => transferring dockerfile: 118B 0.0s
=> [internal] load .dockerignore 0.6s
=> => transferring context: 2B 0.0s
=> [internal] load metadata for docker.io/library/nginx:latest 0.0s
=> [1/2] FROM docker.io/library/nginx 2.2s
=> [2/2] RUN echo 123 1.3s
=> exporting to image 0.5s
=> => exporting layers 0.2s
=> => writing image sha256:813b09c58322dce98ee28e717baeb9f3593ce3e46a032488949250f761004495 0.0s
=> => naming to docker.io/library/nginx:v1 0.0s
=> => naming to dockerhub.com/nginx:v2
dockerfile格式
1、註釋
一個標準的dockerfile,註釋是必須的。
#這是dockerfile註釋,dockerfile中指令以"CMD args"格式出現
CMD args
CMD args
...
一個Dockerfile
第一個指令必須是FROM
指令,用於指定基礎映象,那麼基礎映象的父映象從哪裡來?答案是scratch
帶有該FROM scratch
指令的 Dockerfile會建立一個基本映像。
2.解析器指令
解析器指令是可選的,會影響 aDockerfile
中後續行的處理方式。解析器指令不會向構建新增層,也不會顯示為構建步驟,單個指令只能使用一次。
dockerfile目前支援以下兩個解析器指令:
syntax
escape
2.1syntax
此功能僅在使用BuildKit後端時可用,在使用經典構建器後端時會被忽略。
我們可以在dockerfile檔案開頭指定此dockerfile語法解析器,如下:
# syntax=docker/dockerfile:1
# syntax=docker.io/docker/dockerfile:1
# syntax=example.com/user/repo:tag@sha256:abcdef...
通過syntax自定義 Dockerfile 語法解析器可以實現如下:
- 在不更新 Docker 守護程式的情況下自動修復錯誤
- 確保所有使用者都使用相同的解析器來構建您的 Dockerfile
- 無需更新 Docker 守護程式即可使用最新功能
- 在將新功能或第三方功能整合到 Docker 守護程式之前試用它們
- 使用替代的構建定義,或建立自己的定義
官方dockerfile解析器:
docker/dockerfile:1
不斷更新最新的1.x.x
次要和補丁版本docker/dockerfile:1.2
保持更新最新的1.2.x
補丁版本,一旦版本1.3.0
釋出就停止接收更新。docker/dockerfile:1.2.1
不可變:從不更新1.2版本
比如我們使用1.2最新補丁版本,我們的Dockerfile如下:
#syntax=docker/dockerfile:1.2
FROM busybox
run echo 123
我們啟用buildkit構建
# DOCKER_BUILDKIT=1 docker build -t busybox:v1 .
[+] Building 5.8s (8/8) FINISHED
=> [internal] load build definition from Dockerfile 0.3s
=> => transferring dockerfile: 150B 0.0s
=> [internal] load .dockerignore 0.4s
=> => transferring context: 2B 0.0s
=> resolve image config for docker.io/docker/dockerfile:1.2 2.6s
=> CACHED docker-image://docker.io/docker/dockerfile:1.2@sha256:e2a8561e419ab1ba6b2fe6cbdf49fd92b95 0.0s
=> [internal] load metadata for docker.io/library/busybox:latest 0.0s
=> [1/2] FROM docker.io/library/busybox 0.3s
=> [2/2] RUN echo 123 1.1s
=> exporting to image 0.3s
=> => exporting layers 0.3s
=> => writing image sha256:bd66a3db9598d942b68450a7ac08117830b4d66b68180b6e9d63599d01bc8a04 0.0s
=> => naming to docker.io/library/busybox:v1
2.2 escape
通過escape定義dockerfile的換行拼接轉義符
# escape=\
如果要構建一個window映象就有大用處了,我們看下面dockerfile
FROM microsoft/nanoserver
COPY testfile.txt c:\\
RUN dir c:\
由於預設轉義符為\
,則在構建的第二步step2會是這樣COPY testfile.txt c:\RUN dir c:
顯然與我們的預期不符。
我們把轉義符換成`號即可
# escape=`
FROM microsoft/nanoserver
COPY testfile.txt c:\ `
RUN dir c:\
3.類bash的環境變數
FROM busybox
ENV FOO=/bar
WORKDIR ${FOO} # WORKDIR /bar
ADD . $FOO # ADD . /bar
COPY \$FOO /quux # COPY $FOO /quux
${variable_name}
語法還支援bash
指定的一些標準修飾符:
${variable:-word}
表示如果variable
變數被設定(存在),則結果將是該值。如果variable
未設定,word
則將是結果。${variable:+word}
表示如果variable
被設定則為word
結果,否則為空字串。
4. .dockerignore
.dockerignore用於忽略CLI傳送到docker守護程式的檔案或目錄。以下是一個.dockerignore
檔案
#.dockeringre可以有註釋
*.md
!README.md
temp?
*/temp*
*/*/temp*
規則 | 行為 |
---|---|
*/temp* |
排除名稱以temp 根目錄的任何直接子目錄開頭的檔案和目錄。例如,純檔案/somedir/temporary.txt 被排除在外,目錄/somedir/temp . |
*/*/temp* |
排除temp 從根目錄下兩級的任何子目錄開始的檔案和目錄。例如,/somedir/subdir/temporary.txt 被排除在外。 |
temp? |
排除根目錄中名稱為一個字元副檔名的檔案和目錄temp 。例如,/tempa 和/tempb 被排除在外。 |
! | 不排除到檔案 |
dockerfile命令
1.FROM
指定基礎映象。一般格式如下,[]
括號內容可省略:
FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]
特別需要注意的是FROM
在一個dockerfile中可以多次出現,以實現多階段構建。並且可以和ARG 引數互動。如下:
ARG CODE_VERSION=latest
FROM base:${CODE_VERSION}
CMD /code/run-app
FROM extras:${CODE_VERSION}
CMD /code/run-extras
我們載入了兩個通過arg引數指定的不同版本基礎映象。
2.RUN
RUN的兩種形式
- RUN
首選, (命令在shell中執行,即預設為/bin/sh -c ) - RUN ["exec",param1,param2]
RUN命令主要是在映象構建時執行,形成新層。比如我們經常會看到在構建映象時安裝相關軟體。
RUN yum install -y gcc
當我們不想使用預設shell是可以採用exec形式實現
RUN ["/bin/bash","-c","yum install -y gcc"]
當然,exec形式可以不使用shell
RUN ["yum","install","-y","gcc"]
EXEC形式被解析為一個JSON陣列,所以必須使用雙引號
3.CMD
CMD
指令有三種形式:
CMD ["executable","param1","param2"]
(exec形式,這是首選形式)CMD ["param1","param2"]
(作為ENTRYPOINT 的預設引數)CMD command param1 param2
(shell形式)
一個dockerfile中,應該只寫一個CMD,如果有多個只有最後一個生效。在實際編寫dockerfie時CMD命令常常用於為ENTRYPOINT提供預設值,後面我們會講到。
與RUN相比,CMD在構建時不會執行任何操作,主要用於指定映象的啟動命令。CMD的啟動命令可以被docker run 引數代替。
我們在dockerfile中新增如下CMD命令
CMD echo hello
構建映象後,docker run 不新增引數,啟動容器
[root@localhost dockerfiles]# docker run centos:v1
hello
當我們在docker run 新增引數後
[root@localhost dockerfiles]# docker run centos_env:v1 echo container
container
顯然我們CMD命令echo hello已被docker run中的引數echo container取代。
4. LABEL
label用於新增映象的後設資料,採用key-value的形式。
LABEL <key>=<value>
比如我們新增如下LABEL
LABEL "miantainer"="iqsing.github.io"
LABEL "version"="v1.2"
LABEL "author"="waterman&&iqsing"
為了防止建立三層,我們最好通過一個標籤來寫。
LABEL "miantainer"="iqsing.github.io" \
"version"="v1.2" \
"author"="waterman&&iqsing"
我們通過docker inspect 來檢視映象label資訊
#docker inspect centos_labels:v1
"Labels": {
"author": "waterman&&iqsing",
"miantainer": "iqsing.github.io",
"org.label-schema.build-date": "20201204",
"org.label-schema.license": "GPLv2",
"org.label-schema.name": "CentOS Base Image",
"org.label-schema.schema-version": "1.0",
"org.label-schema.vendor": "CentOS",
"version": "v1.2"
}
5.EXPOSE
EXPOSE 80/tcp
EXPOSE 161/udp
注意,EXPOSE只是告訴dockerfile的閱讀者,我們構建的映象需要暴露哪些埠,只是一個資訊。在容器中還是需要通過-p
選項來暴露埠。
6.ENV
ENV <key>=<value> ... 首先方式
或
ENV <key> <value>
通過ENV指定環境變數,將作用於在構建階段的所有後續指令的環境中。
ENV username="iqsing"
這樣當我們啟動這個容器後可以檢視到容器資訊已經附帶了ENV
環境變數
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"username=iqsing"
],
當然我們也可以在啟動容器時新增環境變數
docker run --env <key>=<value>
另外如果只需要在映象構建期間使用環境變數,更好的選擇是使用ARG
引數來處理
7.ADD && COPY
ADD和COPY格式相似,有兩種形式,包含空格的路徑需要後一種形式:
ADD [--chown=<user>:<group>] <src>... <dest>
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]
COPY [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]
在linux平臺中可以對新增到遠端目錄或檔案設定所屬使用者和組。
<SRC>
指複製新檔案、目錄或遠端檔案 URL,每<src>
可以包含萬用字元,如下:
ADD hom* /mydir/
ADD hom?.txt /mydir/
一般使用中,ADD、COPY都遵守以下規則:
-
<src>
路徑必須是內部語境的構建; 你不能COPY ../something /something
,因為docker build
是將上下文目錄(和子目錄)傳送到 docker 守護程式。 -
如果
<src>
是目錄,則複製目錄的全部內容,包括檔案系統後設資料。 -
如果
<src>
是任何其他型別的檔案,則將其與其後設資料一起單獨複製。在這種情況下,如果<dest>
以斜槓結尾/
,它將被視為一個目錄,其內容<src>
將被寫入<dest>/base(<src>)
。 -
如果
<src>
直接指定了多個資源,或者由於使用了萬用字元,則<dest>
必須是目錄,並且必須以斜槓結尾/
。 -
如果
<dest>
不以斜槓結尾,則將其視為常規檔案,並將其內容<src>
寫入<dest>
. -
如果
<dest>
不存在,則在其路徑中建立所有丟失的目錄。
特別的,當
8.ENTRYPOINT
exec首選和shell形式:
ENTRYPOINT ["executable", "param1", "param2"]
ENTRYPOINT command param1 param2
ENTRYPOINT 和CMD很相似,都是指定啟動命令,不同之處在於ENTRYPOINT 指定的命令無法被docker run 引數取代。
我們在dockerfile中新增ENTRYPOINT
ENTRYPOINT echo hello container
構建映象並啟動容器,可以看到docker run 中的引數並未取代ENTRYPOINT
[root@localhost dockerfiles]# docker run centos_entrtpoint:v1 echo hello docker
hello container
這指令優秀的另一個地方在於可以和CMD指令做互動。讓容器以應用或者服務執行。
經典操作:ENTRYPOINT
+ CMD
= 預設容器命令引數
ENTRYPOINT是dockerfile中非常重要的指令,有必要另寫一篇小作文深入學習一下這東西。
9.VOLUME
VOLUME ["/data"]
volume指令可以用於建立儲存卷,我來看一下例項:
FROM centos
RUN mkdir /volume
RUN echo "hello world" > /volume/greeting
VOLUME /volume
構建映象後,建立一個容器
[root@localhost dockerfiles]# docker create --name centos_volume centos_volue:v1
[root@localhost dockerfiles]# docker inspect centos_volume
"Mounts": [
{
"Type": "volume",
"Name": "494cdb193984680045c36a16bbc2b759cf568b55c7e9b0852ccf6dff8bf79c46",
"Source": "/var/lib/docker/volumes/494cdb193984680045c36a16bbc2b759cf568b55c7e9b0852ccf6dff8bf79c46/_data",
"Destination": "/volume",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
],
這樣我們就通過VOLUME指令建立一個儲存卷,你可以通過--volumes-from
共享這個容器,可參考我之前的小作文《docker容器儲存》
10.USER
指定指令集所屬使用者和組。組預設為root。可以作用於RUN
,CMD
和 ENTRYPOINT
它們後面的指令。
USER <user>[:<group>]
或
USER <UID>[:<GID>]
11.WORKDIR
指定指令集所在的工作目錄,若目錄不存在將會自動建立。可作用於RUN
,CMD
, ENTRYPOINT
,COPY
和ADD
WORKDIR /path/to/workdir
12.ARG
ARG <name>[=<default value>]
ARG
指令定義了一個變數,我們可以在docker build
通過使用--build-arg <varname>=<value>
標誌的命令將其傳遞給構建器。
-
如果
ARG
指令具有預設值並且在構建時沒有傳遞任何值,則構建器使用預設值。 -
在多階段構建應該新增多個ARG
-
ENV變數會覆蓋ARG變數
-
與ENV變數相比,ARG變數多用於構建,無法駐留在映象中。
13.STOPSIGNAL
配置容器退出時的系統呼叫
STOPSIGNAL signal
14.HEALTHCHECK
HEALTHCHECK
指令有兩種形式:
HEALTHCHECK [OPTIONS] CMD command
(通過在容器內執行命令來檢查容器健康狀況)HEALTHCHECK NONE
(禁用從基礎映象繼承的任何健康檢查)
OPTIONS支援如下引數:
--interval=DURATION
(預設值:30s
)--timeout=DURATION
(預設值:30s
)--start-period=DURATION
(預設值:0s
)--retries=N
(預設值:3
)
比如我們可以新增如下引數用於檢查web服務:
HEALTHCHECK --interval=5m --timeout=3s \
CMD curl -f http://localhost/ || exit 1
每五分鐘左右檢查一次web伺服器能否在3s內響應。如果失敗則返回狀態碼1
命令的退出狀態指示容器的健康狀態。可能的值為:
- 0:成功 - 容器執行良好,可以使用
- 1:不健康 - 容器無法正常工作
- 2:reserved - 不要使用這個退出程式碼
編寫一個優質的Dockerfile並不容易,你需要考慮所構建映象的迭代、服務穩定執行、啟動與停止、安全等等問題,希望這篇小作文可以幫助你對Dockerfile有多一點了解。
您可以隨意轉載、修改、釋出本文章,無需經過本人同意。 個人blog:iqsing.github.io
NEXT
- Dockerfile 理解ENTRYPOINT與CMD結合
- Dockerfile 多階段構建實踐
- Dockerfile 與docker容器安全實踐