docker(六):Dockerfile詳解


一、Dockerfile Introduction

  前面的docker鏡像管理章節有說到,構建鏡像的方式有兩種:一種是基於容器制作,另一種就是通過Dockerfile。Dockerfile其實就是我們用來構建Docker鏡像的源碼,當然這不是所謂的編程源碼,而是一些命令的組合,只要理解它的邏輯和語法格式,就可以編寫Dockerfile了。

  簡要概括Dockerfile的作用:它可以讓用戶個性化定制Docker鏡像。因為工作環境中的需求各式各樣,網絡上的鏡像很難滿足實際的需求。

二、Dockerfile Format

  1. Dockerfile整體就兩類語句組成:

    • # Comment 注釋信息
    • Instruction arguments 指令 參數,一行一個指令。
  2. Dockerfile文件名首字母必須大寫。

  3. Dockerfile指令不區分大小寫,但是為方便和參數做區分,通常指令使用大寫字母。

  4. Dockerfile中指令按順序從上至下依次執行。

  5. Dockerfile中第一個非注釋行必須是FROM指令,用來指定制作當前鏡像依據的是哪個基礎鏡像。

  6. Dockerfile中需要調用的文件必須跟Dockerfile文件在同一目錄下,或者在其子目錄下,父目錄或者其它路徑無效。

三、Dockerfile Instructions

FROM

  • Introduction

    • FROM指令必須為Dockerfile文件開篇的第一個非注釋行,用於指定構建鏡像所使用的基礎鏡像,后續的指令運行都要依靠此基礎鏡像所提供的的環境(簡單說就是假如Dockerfile中所引用的基礎鏡像里面沒有mkdir命令,那后續的指令是沒法使用mkdir參數的。)
    • 實際使用中,如果沒有指定倉庫,docker build會先從本機查找是否有此基礎鏡像,如果沒有會默認去Docker Hub Registry上拉取,再找不到就會報錯。
  • Syntax

    • FROM <Repository>[:<Tag>]

    • FROM <Repository>@<Digest>

      • Digest:鏡像的哈希碼,防止鏡像被冒名頂替。

MAINTAINER(deprecated)

  • Introduction

    • 用於讓Dockerfile的作者提供個人的信息
    • Dockerfile並不限制MAINTAINER指令的位置,但是建議放在FROM指令之后
    • 在較新的docker版本中,已經被LABEL替代。
  • Syntax

    • MAINTAINER "merle@example.com"

LABEL

  • Introduction

    • 同docker run -l
    • 讓用戶為鏡像指定各種元數據(鍵值對的格式)。
  • Syntax

    • LABEL <key>=<value> <key>=<value>

COPY

  • Introduction

    • 復制宿主機上的文件到目標鏡像中
  • Syntax

    • COPY <src>... <dest>

    • COPY ["<src>",... "<dest>"]

      • <src>:要復制的源文件或者目錄,支持通配符
      • <dest>:目標路徑,即正創建的鏡像的文件系統路徑,建議使用絕對路徑,否則,COPY指令會以WORKDIR為其起始路徑。
      • 如果路徑中如果包含空白字符,建議使用第二種格式用引號引起來,否則會被當成兩個文件。
  • Rules

    • <src>必須是build上下文中的目錄,不能是其父目錄中的文件。
    • 如果<src>是目錄,則其內部的文件或則子目錄會被遞歸復制,但<src>目錄本身不會被復制。
    • 如果指定了多個<src>,或者<src>中使用通配符,則<dest>必須是一個目錄,且必須以 / 結尾。
    • 如果<dest>事先不存在,它將會被自動創建,包括其父目錄路徑。

ADD

  • Introduction

    • ADD指令跟COPY類似,不過它還支持使用tar文件和URL路徑。

      • 當拷貝的源文件是tar文件時,會自動展開為一個目錄並拷貝進新的鏡像中;然而通過URL獲取到的tar文件不會自動展開。
      • 主機可以聯網的情況下,docker build可以將網絡上的某文件引用下載並打包到新的鏡像中。
  • Syntax

    • ADD <src>... <dest>
    • ADD ["<src>",... "<dest>"]

WORKDIR

  • Introduction

    • 同docker run -w
    • 指定工作目錄,可以指多個,每個WORKDIR只影響他下面的指令,直到遇見下一個WORKDIR為止。
    • WORKDIR也可以調用由ENV指令定義的變量。
  • Syntax

    • WORKDIR 相對路徑或者絕對路徑

      • 相對路徑是相對於上一個WORKDIR指令的路徑,如果上面沒有WORKDIR指令,那就是當前Dockerfile文件的目錄。

VOLUME

  • Introduction

    • docker run -v簡化版
    • 用於在鏡像中創建一個掛載點目錄。上一章中有提到Volume有兩種類型:綁定掛載卷和docker管理的卷。在dockerfile中只支持docker管理的卷,也就是說只能指定容器內的路徑,不能指定宿主機的路徑。
  • Syntax

    • VOLUME <mountpoint>
    • VOLUME ["<mountpoint>"]

EXPOSE

  • Introduction

    • 同docker run --expose
    • 指定容器中待暴露的端口。比如容器提供的是一個https服務且需要對外提供訪問,那就需要指定待暴露443端口,然后在使用此鏡像啟動容器時搭配 -P 的參數才能將待暴露的狀態轉換為真正暴露的狀態,轉換的同時443也會轉換成一個隨機端口,跟 -p :443一個意思。
    • EXPOSE指令可以一次指定多個端口,例如:EXPOSE 11111/udp 11112/tcp
  • Syntax

    • EXPOSE <port>[/<protocol>] [<port>[/<protocol>] ...]
    • <protocol>用於指定協議類型,如果不指定,默認TCP協議。

ENV

  • Introduction

    • 同docker run -e
    • 為鏡像定義所需的環境變量,並可被ENV指令后面的其它指令所調用。調用格式為$variable_name或者${variable_name}
    • 使用docker run啟動容器的時候加上 -e 的參數為variable_name賦值,可以覆蓋Dockerfile中ENV指令指定的此variable_name的值。但是不會影響到dockerfile中已經引用過此變量的文件名。下面有舉例說明:
  • Syntax

    • ENV <key> <value>

    • ENV <key>=<value> ...

      • 第一種格式一次只能定義一個變量,<key>之后所有內容都會被視為<value>的組成部分
      • 第二種格式一次可以定義多個變量,每個變量為一個" = "的鍵值對,如果<value>中包含空格,可以用反斜線 \ 進行轉義,也可以為<value>加引號,另外參數過長時可用反斜線做續行。
      • 定義多個變量時,建議使用第二種方式,因為Dockerfile中每一行都是一個鏡像層,構建起來比較吃資源。
  • Example

# 基於busybox啟動一個鏡像,將test文件拷貝至容器內的/usr/local/aaa/目錄下。
[root@docker1 docker]# pwd
/server/docker--rm 
[root@docker1 docker]# echo 1111 >test
[root@docker1 docker]# vim Dockerfile 
# Description: test image
FROM busybox
ENV file=aaa
ADD ./test /usr/local/$file/
[root@docker1 docker]# docker build -t busy:v1 ./
# 根據此鏡像啟動容器並查看文件是否拷貝成功,並且查看file變量的值
[root@docker1 docker]# docker run --name busy02 --rm busy:v1 ls /usr/local/aaa
test
[root@docker1 docker]# docker run --name busy02 --rm busy:v1 printenv
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=57111b7b246c
file=aaa
HOME=/root
# 接下來我們在啟動容器的時候加上-e參數為file變量指定一個新值,並且查看file變量的值
[root@docker1 docker]# docker run --name busy02 -e file=bbb --rm busy:v1 printenv
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=787ad8585fc0
file=bbb
HOME=/root
# 此時再看test的文件,依然是在aaa的目錄下的
[root@docker1 docker]# docker run --name busy02 -e file=bbb --rm busy:v1 ls /usr/local/aaa
test
# 這是因為docker build屬於第一階段,而docker run屬於第二階段。第一階段定義file變量的值aaa已經被引用了,生米已經煮成熟飯了,后續階段再改file變量的值也影響不了aaa。

RUN

  • Introduction

    • 用於指定docker build過程中運行的程序,可以是任何命令。
    • RUN指令后所執行的命令必須在FROM指令后的基礎鏡像中存在才行。
  • Syntax

    • RUN <command>

    • RUN ["executable", "param1", "param2"]

      • <command>通常是一個shell命令,系統默認會把后面的命令作為shell的子進程來運行,以"/bin/sh -c"來運行它,也就意味着此進程在容器中的PID一定不為1,如果是1完事就結束了哇。
      • 第二種格式的參數是一個JSON格式的數組,其中"executable"為要運行的命令,后面的"paramN"為傳遞給命令的選項或參數。此格式指定的命令不會以"/bin/sh -c"來發起,也就是直接由內核創建,因此不具備shell特性,類似於RUN [ "echo", "$HOME" ],是無法識別 $ 的;如果想要依賴shell特性,可以替換命令為這樣的格式[ "/bin/sh", "-c", "echo $HOME" ]。

CMD

  • Introduction

    • 指定啟動容器的默認要運行的程序,也就是PID為1的進程命令,且其運行結束后容器也會終止。如果不指定,默認是bash。

    • CMD指令指定的默認程序會被docker run命令行指定的參數所覆蓋。

    • Dockerfile中可以存在多個CMD指令,但僅最后一個生效。因為一個docker容器只能運行一個PID為1的進程。

    • 類似於RUN指令,也可以運行任意命令或程序,但是兩者的運行時間點不同

      • RUN指令運行在docker build的過程中,而CMD指令運行在基於新鏡像啟動容器(docker run)時。
  • Syntax

    • CMD command param1 param2

    • CMD ["executable","param1","param2"]

    • CMD ["param1","param2"]

      • 前兩種語法格式同RUN指令。第一種用法對於CMD指令基本沒有意義,因為它運行的程序PID不為1。
      • 第三種則需要結合ENTRYPOINT指令使用,CMD指令后面的命令作為ENTRYPOINT指令的默認參數。如果docker run命令行結尾有參數指定,那CMD后面的參數不生效。

ENTRYPOINT

  • Introduction

    • 類似CMD指令的功能,用於為容器指定默認運行程序。
    • Dockerfile中可以存在多個ENTRYPOINT指令,但僅最后一個生效
    • 與CMD區別在於,由ENTRYPOINT啟動的程序不會被docker run命令行指定的參數所覆蓋,而且這些命令行參數會被當做參數傳遞給ENTRYPOINT指令指定的程序。
    • 不過,docker run的--entrypoint選項的參數可覆蓋ENTRYPOINT指定的默認程序。示例如下:
  • Syntax

    • ENTRYPOINT command param1 param2
    • ENTRYPOINT ["executable", "param1", "param2"]
  • Example

# 還是以httpd服務做舉例,先以CMD指令開始
[root@docker1 docker]# vim Dockerfile
# Description: test image
FROM busybox
LABEL maintainer="merle <merle@freeit.com>" app="httpd"
ENV WEBDIR="/data/web/html"
RUN mkdir -p ${WEBDIR} && \
    echo 'this is a test web' > ${WEBDIR}/index.html
CMD [ "sh","-c","/bin/httpd -f -h ${WEBDIR}" ]
[root@docker1 docker]# docker build -t httpd:v1 ./
[root@docker1 docker]# docker run --name web01 -it --rm httpd:v1
# 此時為前台運行,復制一個窗口,kill掉容器,然后開始docker run結尾傳入新的指令
[root@docker1 ~]# docker kill web01 
web01
[root@docker1 docker]# docker run --name web01 -it --rm httpd:v1 ls /data/web/html
index.html
# 可以看出命令行的參數已經替代了原本的CMD指令指定的程序。下面我們再用ENTRYPOINT指令做測試。
-------------------------------------------------------------------------------------------------------------
[root@docker1 docker]# vim Dockerfile
# Description: test image
FROM busybox
LABEL maintainer="merle <merle@freeit.com>" app="httpd"
ENV WEBDIR="/data/web/html"
RUN mkdir -p ${WEBDIR} && \
    echo 'this is a test web' > ${WEBDIR}/index.html
ENTRYPOINT [ "sh","-c","/bin/httpd -f -h ${WEBDIR}" ]
[root@docker1 docker]# docker build -t httpd:v2 ./
[root@docker1 docker]# docker run --name web01 -it --rm httpd:v2
# 也是前台啟動,復制一個窗口,kill掉容器,然后開始docker run結尾傳入新的指令
[root@docker1 docker]# docker run --name web01 -it --rm httpd:v2 ls /data/web/html
# 可以看到沒有反應,這種情況其實是吧ls /data/web/html當做參數傳給了/bin/httpd -f -h ${WEBDIR}程序。只是httpd不識別罷了。我們kill掉容器。加上--entrypoint參數再試一下
[root@docker1 docker]# docker run --name web01 -it --rm --entrypoint="" httpd:v2 ls /data/web/html
index.html
# 使用--entrypoint參數替換命令成功。
# 再測試下CMD的第三種語法,CMD指令的后面的命令作為參數傳給ENTRYPOINT指令后的命令
[root@docker1 docker]# vim Dockerfile 
# Description: test image
FROM busybox
LABEL maintainer="merle <merle@freeit.com>" app="httpd"
ENV WEBDIR="/data/web/html"
RUN mkdir -p ${WEBDIR} && \
    echo 'this is a test web' > ${WEBDIR}/index.html
CMD [ "/bin/httpd -f -h ${WEBDIR}" ]
ENTRYPOINT [ "sh","-c" ]
[root@docker1 docker]# docker build -t httpd:v3 ./
[root@docker1 docker]# docker run --name web01 -it --rm httpd:v3
# OK的,前面有說過:指定ENTRYPOINT的情況下,如果docker run命令行結尾有參數指定,那CMD后面的參數不生效,下面咱再試試,還用v3的鏡像。
[root@docker1 docker]# docker run --name web01 -it --rm httpd:v3 "ls /data/web/html"
index.html

USER

  • Introduction

    • 用於指定docker build過程中任何RUN、CMD等指令的用戶名或者UID。
    • 默認情況下容器的運行用戶為root。
  • Syntax

    • USER <user>[:<group>]

    • USER <UID>[:<GID>]

      • 實踐中UID需要是/etc/passwd中某用戶的有效UID,否則docker run命令將運行失敗。

HEALTHCHECK

  • Introduction

    • 顧名思義,健康檢查。此指令的就是告訴docker如果檢查容器是否正常工作。拿nginx舉例,即便進程運行,服務也不一定正常,因為萬一root指錯了呢?
  • Syntax

    • HEALTHCHECK [OPTIONS] CMD command

    • HEALTHCHECK NONE

      • HEALTHCHECK指令讓我們去定義一個CMD,在CMD后面編寫一條命令去判斷我們的服務運行是否正常。檢查肯定不是一次性的,所以OPTIONS就是指定檢查的頻率等等。

        • --interval=DURATION(默認值:30s):每隔多久檢查一次,默認30s

        • --timeout=DURATION(默認值:30s):超時時長,默認30s

        • --start-period=DURATION(默認值:0s):啟動健康檢查的等待時間。因為容器啟動成功時,進程不一定立馬就啟動成功,那過早開始檢查就會返回不健康。

        • --retries=N(默認值:3):如果檢查一次失敗就返回不健康未免太武斷,所以默認三次機會。

        • CMD健康檢測命令發出時,返回值有三種情況

          • 0:成功
          • 1:不健康
          • 2:保留,無實際意義。
      • HEALTHCHECK NONE就是不做健康檢查

  • Example

HEALTHCHECK --interval=5m --timeout=3s \
  CMD curl -f http://localhost/ || exit 1

SHELL

  • Introduction

    • 用來指定運行程序默認要使用的shell類型,因為windows環境默認是powershell。此指令一般不會使用。
  • Syntax

    • SHELL ["executable", "parameters"]

STOPSIGNAL

  • Introduction

    • 指定發送使容器退出的系統調用信號。docker stop之所以能停止容器,就是發送了15的信號給容器內PID為1的進程。此指令一般不會使用。
  • Syntax

    • STOPSIGNAL signal

ARG

  • Introduction

    • ARG命令同EVN類似,也是指定一個變量,但不同的是,ENV指令配合-e參數可以在docker run過程中傳參,而使用ARG指令配合--build-arg參數可以在docker build過程中傳參,這方便了我們為不同場景構建不同鏡像。
  • Syntax

    • ARG <name>[=<default value>]
  • Example

[root@docker1 docker]# vim Dockerfile 

FROM nginx:1.14-alpine
ARG AUTHOR="merle <merle@freeit.com>"    # 指定默認值
LABEL maintainer=$AUTHOR
ENV NGXDIR='/data/web/html/'
ADD index.html $NGXDIR
ADD entrypoint.sh /bin/
CMD ["nginx", "-g", "daemon off;"]
ENTRYPOINT ["/bin/entrypoint.sh"]
[root@docker1 docker]# docker build --build-arg AUTHOR="Jtt <Jtt@freeit.com>" -t nginx:v4 ./
[root@docker1 docker]# docker image inspect nginx:v4 | grep maintainer
                "maintainer": "Jtt <Jtt@freeit.com>"
# 上面只是以maintainer舉例,實踐環境中可以修改為不同jar包的名字構建不同java程序鏡像。

ONBUILD

  • Introduction

    • 用於在Dockerfile中定義一個觸發器。
    • ONBUILD后面指定的指令在docker build時是不會執行,構建完的鏡像在被另一個Dockerfile文件中FROM指令所引用的時才會觸發執行。
  • Syntax

    • ONBUILD [INSTRUCTION]

      • 幾乎任何指令都可以成為觸發器指令,但ONBUILD不能自我嵌套,且不會觸發FROM和MAINTAINER指令,多數情況是使用RUN或者ADD。
      • 另外在使用COPY指令時,應該注意后續引用該鏡像的Dockerfile的同級目錄下是否有被拷貝的文件。
  • Example

[root@docker1 docker]# vim Dockerfile 
FROM nginx:1.14-alpine
LABEL maintainer="merle <merle@freeit.com>"
ENV NGXDIR='/data/web/html/'
ADD index.html $NGXDIR
ADD entrypoint.sh /bin/
ONBUILD add http://nginx.org/download/nginx-1.14.0.tar.gz /usr/local/
CMD ["nginx", "-g", "daemon off;"]
ENTRYPOINT ["/bin/entrypoint.sh"]
[root@docker1 docker]# docker build -t nginx:v5 ./
[root@docker1 docker]# docker run -it --name web01 --rm nginx:v5 ls /usr/local/
bin    lib    share
# 上面的結果或者docker build的過程都可以看出並沒有執行ONBUILD后面的指令。我們修改Dockerfile中FROM指定的基礎鏡像為上面構建完的nginx:v5,然后刪除ONBUILD指令。
[root@docker1 docker]# vim Dockerfile 
FROM nginx:v5
LABEL maintainer="merle <merle@freeit.com>"
ENV NGXDIR='/data/web/html/'
ADD index.html $NGXDIR
ADD entrypoint.sh /bin/
CMD ["nginx", "-g", "daemon off;"]
ENTRYPOINT ["/bin/entrypoint.sh"]
[root@docker1 docker]# docker build -t nginx:v6 ./
Sending build context to Docker daemon  6.656kB
Step 1/7 : FROM nginx:v5
# Executing 1 build trigger
Downloading  [=======
# 這里查看構建過程就會發現從第一層構建就開始執行上面的ONBUILD后面的指令了。我們啟動容器並查看下/usr/local目錄下的文件。
[root@docker1 docker]# docker run -it --name web01 --rm nginx:v6 ls /usr/local
bin                  nginx-1.14.0.tar.gz
lib                  share


寫作不易,轉載請注明出處,謝謝~~


免責聲明!

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



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