iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 18
0
Cloud Native

30 天準備 LPI DevOps Tools Engineer 證照系列 第 18

[Day 18] Docker (4)

前兩天跟著文件中 Get Started 的內容大致瞭解了使用 Docker 作為應用程式服務的開發流程,從單一容器的應用程式開始,到部署於單一節點的 swarm 服務,最後是多個節點、多個服務的堆疊。這幾天研究並操作了不少 Docker 相關工具,但 Docker 本身的架構又是如何呢?Docker 引擎是一個 client-server 架構的應用程式,它由三個部分組成:

  • server,是一個 daemon process,名為 dockerd
  • REST API,一個用來和 dockerd 溝通並告訴它要做什麼的介面。
  • CLI (command line interface),client,也就是 docker 指令。

這三層的關係,請參考下圖。
https://docs.docker.com/engine/images/engine-components-flow.png
(圖片出自 https://docs.docker.com/engine/docker-overview/#docker-engine)

這幾天介紹的指令,都是最外層的 client,它透過中間層的 Docker API 和 server 溝通。將來開發上有需要,可以直接使用 API 及 SDK,API 文件請參考 https://docs.docker.com/develop/sdk/#choose-the-sdk-or-api-version-to-use

我覺得 Docker 初學的重點應該是關於映像檔和容器的操作,所以今天先來看看官方文件中關於映像檔的說明。

映像檔

Docker 的映像檔由多個唯讀層 (read-only layer) 所組成,每一層代表了 Dockerfile 中的一個指令,映像檔由這些唯讀層堆疊而成,每一層都是和前一層變化的差異 (delta of changes from the previous layer)。當映像檔被執行時會產生容器,並在唯讀層之上加上一可寫層 (writable layer) 或稱容器層 (container layer),所有對執行中的容器作出的改變,像是寫入新檔案,修改已存在的檔案,刪除檔案,都是寫入這一可寫層。

Dockerfile 包含了使用者在命令列中可以呼叫的指令,用來組成 Docker 所使用的映像檔。透過 docker build 指令可依序讀取 Dockerfile 中的指令以自動建立映像檔。除了 Dockerfile 之外,docker build 在建立映像檔時還需要指定一 build context,context 是指在指定目錄或 Git 儲存庫中的一組檔案。docker build 是由 Docker daemon 所運行的,而不是 CLI。build 過程第一件事是將整個 context 內容送到 daemon。這裡不是很懂文件的意思,我直覺上認為將 context 送到 daemon,可能是指將 context 中的檔案複製到容器中,但 Dockerfile 中又有 COPYADD 的指令可以指定要加到容器中的檔案。預設 Dockerfile 會位於 build context 中,也可以指定其路徑。如果要排除 context 中和 build 無關的檔案,可以使用 .dockerignore,語法和 .gitignore 類似。

如同先前的例子中所看到的,Dockerfile 的基本格式是指令加上參數,註解則以 # 開頭,慣例上會使用大寫字母來編寫指令。例如:

# Comment
INSTRUCTION arguments

接下來介紹 Dockerfile 中可用的指令,文件中這裡分為建議 (recommendation) 及參考 (reference) 兩個部分,詳細的說明請參考 https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#dockerfile-instructionshttps://docs.docker.com/engine/reference/builder/,以下僅列出簡要的用途和格式,並舉例說明。

FROM

Dockerfile 原則上會從 FROM 指令開始,用來指定這個映像檔所使用的基礎映像檔 (base image) 以讓後續的指令使用,基本格式是

* FROM <image> [AS <name>]
* FROM <image>[:<tag>] [AS <name>]
* FROM <image>[@<digest>] [AS <name>]

FROM ubuntu:14.04

RUN

每一個 RUN 指令會在現有映像檔之上加入新的一層,指令於該層被執行並提交結果。有兩種格式,第一種是 shell 形式,會透過 shell 來執行,第二種是 exec 形式,請參考以下格式。

* RUN <command> (shell form)
* RUN ["executable", "param1", "param2"] (exec form)

RUN /bin/bash -c 'source $HOME/.bashrc; echo $HOME'
RUN ["/bin/bash", "-c", "echo hello"]

CMD

一個 Dockerfile 中只能有一個 CMD 指令,若出現多次則最後一次會產生作用。它的作用是為執行中的容器提供預設行為 (provide defaults)。和 CMD 不同之處在於 RUN 是在建立 (build) 映像檔的過程中會執行的指令,CMD 則是容器運行時所執行的指令,格式如下:

* CMD ["executable","param1","param2"] (exec form, this is the preferred form)
* CMD ["param1","param2"] (as default parameters to ENTRYPOINT)
* CMD command param1 param2 (shell form)

CMD echo "This is a test." | wc -
CMD ["/usr/bin/wc","--help"]

LABEL

為映像檔添加元資料 (metadata),格式如下:

* LABEL <key>=<value> <key>=<value> <key>=<value> ...

LABEL "com.example.vendor"="ACME Incorporated"
LABEL com.example.label-with-value="foo"

EXPOSE

用來指定此容器在執行時要揭露的 port。若要從容器外部連接此 port,需要在 docker run 執行此容器時使用 -p 旗標,讓它對應本機的 port。格式如下:

* EXPOSE <port> [<port>/<protocol>...]

EXPOSE 80/tcp
EXPOSE 80/udp

ENV

用來設置環境變數,可於 build 階段於後續的指令中使用,或者是在容器執行時作為環境變數,格式如下:

* ENV <key> <value>
* ENV <key>=<value> ...

ENV PG_MAJOR 9.3
ENV PG_VERSION="9.3.4"

ADD

複製指定的檔案、目錄或遠端檔案 URL,將其加入映像檔檔案系統中的指定位置。來源為 build context 的相對路徑,且必須為位於 build context 之中 ,目的則為絕對路徑或 WORKDIR 的相對路徑。若來源檔案為本機的 tar 壓縮檔,會解開成目錄複製到目的位置。格式範例如下:

* * ADD [--chown=<user>:<group>] <src>... <dest>
* ADD [--chown=<user>:<group>] ["<src>",... "<dest>"] (this form is required for paths containing whitespace)

ADD hom* /mydir/                          # adds all files starting with "hom"
ADD test relativeDir/                     # adds "test" to `WORKDIR`/relativeDir/
ADD test /absoluteDir/                    # adds "test" to /absoluteDir/
ADD --chown=55:mygroup files* /somedir/

COPY

用途和 ADD 類似,但只是單純的複製,沒有解開本地 tar 檔複製或支援遠端 URL 的功能。如果是一般的複製,文件建議使用 COPY 指令。

* COPY [--chown=<user>:<group>] <src>... <dest>
* COPY [--chown=<user>:<group>] ["<src>",... "<dest>"] (this form is required for paths containing whitespace)

ENTRYPOINT

ENTRYPOINT 的用途和 CMD 類似,都是用來在容器中執行指令。

* ENTRYPOINT ["executable", "param1", "param2"] (exec form, preferred)
* ENTRYPOINT command param1 param2 (shell form)

文件中說 ENTRYPOINT 的使用時機是希望將容器當作一個執行檔來使用,就可以使用 ENTRYPOINT 來指定這個執行檔應該執行的命令,在這種情況下,CMD 可用來提供預設的旗標,例如:

ENTRYPOINT ["s3cmd"]
CMD ["--help"]

執行 docker run sc3md,就會顯示指令的幫助說明,或者以 docker run s3cmd ls s3://mybucket 來執行指令。關於 ENTRYPOINTCMD 的差異及搭配使用方式,文件有進一步的說明如下:

  1. Dockerfile 中至少要有一個 CMDENTRYPOINT 指令。
  2. 若要以 container 作為執行檔,應使用 ENTRYPOINT 指令。
  3. CMD 應以作為 ENTRYPOINT 的預設參數,或在容器中執行任意 (ad-hoc) 指令的方式使用。
  4. 運行容器時若加上額外的參數,CMD 值會被覆寫。

VOLUME

用來建立一個掛載點,以掛載本機或其他容器的卷宗。要被掛載的卷宗或檔案路徑要在執行容器時指定。範例如下,可用JSON 陣列指定多個掛載點。

VOLUME ["/data"]

USER

用來指定後續 RUNCMDENTRYPOINT 指令或容器中的使用者名稱、群組名稱。格式及範例如下:

* USER <user>[:<group>]
* USER <UID>[:<GID>]

FROM microsoft/windowsservercore
# Create Windows user in the container
RUN net user /add patrick
# Set it for subsequent commands
USER patrick

WORKDIR

用來設定在 RUNCMDENTRYPOINTCOPYADD 指令中的工作目錄。若工作目錄不存在會被建立,格式如下:

* WORKDIR /path/to/workdir

ARG

用來設定在 build 時期會用到的變數,可在 docker build 指令使用 --build-arg <name>=<value> 的形式傳入,也可在 Dockerfile 中指定變數預設值。格式如下:

* ARG <name>[=<default value>]

FROM busybox
ARG user1
ARG buildno=2

ONBUILD

用來指定當此映像檔要被用來建立其他映像檔時,必須先執行的指令。它會加在某個指示的開頭(ONBUILD 除外),格式範例如下:

* ONBUILD [INSTRUCTION]

ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build --dir /app/src

SHELL

用來指定後續指令預設使用的 shell 類型,格式範列如下:

* SHELL ["executable", "parameters"]

# Executed as powershell -command Write-Host hello
SHELL ["powershell", "-command"]
RUN Write-Host hello

除了介紹編寫 Dockerfile 中會用到的指令外,文件也說明了編寫 Dockerfile 的原則和最佳實踐 (best practice)。一般來說會建立一個空的目錄作為 build context,在其中編寫 Dockerfile,只把有需要的檔案複製至該目錄,不要在映像檔中安裝不必要的套件。其他的最佳實踐方式請參考文件說明。


上一篇
[Day 17] Docker (3)
下一篇
[Day 19] Docker (5)
系列文
30 天準備 LPI DevOps Tools Engineer 證照30

尚未有邦友留言

立即登入留言