docker容器dockerfile詳解

頂級飲水機管理員發表於2021-08-19

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>不存在,則在其路徑中建立所有丟失的目錄。

特別的,當是可識別的壓縮包如gzip、bzip2等tar包時,首先會將包新增到映象中,然後自動解壓。這可以說是與COPY命令在使用中的最大的區別。

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。可以作用於RUNCMDENTRYPOINT它們後面的指令。

USER <user>[:<group>]
或
USER <UID>[:<GID>]
11.WORKDIR

指定指令集所在的工作目錄,若目錄不存在將會自動建立。可作用於RUNCMDENTRYPOINTCOPYADD

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容器安全實踐

相關文章