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目前支持以下兩個解析器指令:
syntaxescape
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容器安全實踐
