docker容器dockerfile詳解


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


免責聲明!

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



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