程式語言初學者 Docker 入門第八章 —— 使用 Dockerfile 建立映像檔

 Dockerfile 是一個文字格式的設定檔,使用者可以使用 Dockerfile 快速建立自訂的映像檔

1. 基本結構

以行為單位的命語句所組成,並且支援以 # 開頭標示為註解行。

由 4 部分組成

  1. 基礎印象檔資訊
    第一行必需指定基於的基礎映像檔,若欲建立基礎映像檔則可使用 scratch 空白印象檔
    FROM ubuntu

  2. 維護者資訊

    docker_user <docker_user at email.com> (@docker_user)

    MAINTAINER docker_user docker_user@email.com

  3. 映像檔操作指令

    RUN echo "deb http://archive.ubuntu.com/ubuntu/ raring main universe" >> /etc/apt/sources.list

    RUN apt-get update && apt-get install -y nginx

    RUN echo "\ndaemon off;" >> /etc/nginx/nginx.conf

  4. 容器啟動時需執行指令

    CMD /usr/sbni/nginx

# This dockerfile uses the ubuntu image 
# Version 2 - EDITION 1
# Author: docker_user
# Command format: Instruction [arguments / command] ..

2. 指令說明

1. FROM

所有 Dockerfile 中的第一道指令

建立多個映像檔,可以使用多個 FROM

FROM <image>

FROM <image>:<tag>

FROM <image>@<digest>

2. MAINTAINER

MAINTAINER <name>

MAINTAINER image_creator@docker.com

該資訊會寫入產程映像檔的 Author 名稱屬性中

3. RUN

運行指定命令

格式為 RUN <command> or RUN ["executable", "param1", "param2"]

後面的指令會被解析為 JSON,必需用雙引號

前者預設在 shell 終端中執行命令 /bin/sh -c; 後者使用 exec 執行,不會啟動 shell 環境

指定使用其他終端類型可以透過第二種方式實現

RUN ["/bin/bash", "-c", "echo hello"]

每條 RUN 指令將在目前映像檔之基礎上執行指定命令,並交付成為新的映像檔,當命令較長時,可以使用 \ 來換行。

RUN apt-get update \\
    && apt-get install -y libsnappy-dev zlib1g-dev libbz2-dev \\
    && rm -rf /var/cache/apt

4. CMD

指定啟動容器試射執行的命令。他支援三種格式

  1. 推薦:CMD ["executable", "param1", "param2"] 使用 exec 執行
  2. CMD command param1 param2 會在 /bin/sh 中執行,提供給需要交互作用的應用程式
  3. CMD ["param1", "param2"] 提供給 ENTRYPOINT 的預設參數

每個 Dockerfile 只能有一條 CMD 命令。如果指定多條命令,只有最後一個會被執行

如果使用者在啟動容器時手動指定了欲執行之命令 (docker run 時的參數),則會覆蓋掉 CMD 指定的命令

5. LABEL

LABEL 指令用來指定產程映像檔的中繼資料標籤資訊。

格式 LABEL <key>=<value> <key>=<value> <key>=<value> ...。

例如:

LABEL version="1.0"

LABEL description="This text illustrates \ that label-values can span multiple lines."

6. EXPOSE

宣告映像檔內服務所監聽的連接埠

EXPOSE <port> [<port>...] (HOSTPORT)

EXPOSE 22 80 8443

該指令只是達到宣告作用,不會自動完成連接埠對應

在啟動容器時需要使用 -P ,Docker 主機會自動分配一個 Host 主機的臨時連接埠轉發到容器內指定的連接埠; -p 則可以具體指定 Host 主機的哪個連接埠來對應。

7. ENV

指定環境變數,在映像檔產生過程中會被後續 RUN 指令所使用,以此印象檔啟動的容器中也會存在。

格式: ENV<key><value> 或 ENV<key>=<value>...

ENV PG_MAJOR 9.3
ENV PG_VERSION 9.3.4
RUN curl -SL <http://example.com/postfres-$PG_VERSION.tar.xz> | tar -xJC /usr/src/postgress && ...
ENV PATH /usr/local/postgres-$PG_MAJOR/bin:$PATH

指令所指定的環境變數在執行時仍可以被覆蓋掉: docker run --env <key≥<value> built_image

8. ADD

格式: ADD<src> <dest>

<src>: 本地端主機 Dockerfile 所在目錄的相對路徑,

  • 檔案或目錄。不能使用 ../something 和絕對路徑。
  • URL
  • tar 壓縮檔,會自動姐壓縮到 <dest> 路徑下

<dest>: 映像檔的絕對路徑,或者相對於於 WORKDIR 的相對路徑

支援萬用符格式:ADD *.c /code/

9. COPY

格式: COPY<src> <dest>

<src>: 本地端主機 Dockerfile 所在目錄的相對路徑,檔案或目錄

<dest>: src 要複製到映像檔中的路徑,不存在會新增

當使用本地目錄為原始來源時,推薦使用 COPY

10. ENTRYPOINT

指定映像檔的預設進入點,該進入點另命會在啟動容器時成為唯一另另執行,所有傳入值皆為該命令的參數

  • exec 方式執行

    ENTRYPOINT ["executable", "param1", "param2"]

  • shell 中執行

    ENTRYPOINT command param1 param2

11. VOLUME

建立資料卷掛載點,多為資料庫和需要持久保存的資料

格式: VOLUME ["/data"]

來源:本機或其他容器可掛載的資料卷

12. USER

13. WORKDIR

為後續 RUN, CMD, ENTRYPOINT 指令設定執行的工作目錄

格式: WORKDIR /path/to/workdir

WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd

最終路徑會是 /a/b/c

14. ARG

指定映像檔內使用的參數

15. ONBUILD

配置當所建立的映像檔成為其他映像檔之基礎映像檔時,所必定執行的建置操作指令

格式: ONBUILD [INSTRUCTION]

例如 Dockerfile 使用如下的內容建立了映像檔 image-A

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

如果基於 image-A 建立新的映像檔時,新的 Dockerfile 中使用 FROM image-A 指定為基礎映像檔,並會自動執行 ONBUILD 指令的內容

FROM image-A

以下指令會自動執行

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

有使用此指令的映像檔,建議在標籤中註明

ex: ruby:1.9-onbuild

16. STOPSIGNAL

17. HEALTHCHECK

設置所啟動容器該如何進行狀態檢查

18. SHELL

格式:SHELL ["executable", "parameters"]

預設為 ["/bin/bash", "-c"]

3. 建立映像檔

編寫完 Dockerfile 後可以透過 docker build 來建立映像檔

格式: docker build [選項] 內容路徑

$ docker build -t build_repi/first_image /tmp/docker_builder
                  <映像檔標籤>             <Dockerfile 所在路徑>

指定 Dockerfile 所在路徑為 /tmp/docker_builder/,並且希望產生映像檔標籤為 build_repo/first_image

$ docker build -t docker_build/first_image /home/seraphine/workspace/ccclub-api/
Sending build context to Docker daemon  976.9kB
Step 1/13 : FROM python:3.7-alpine
 ---> 078bbc5dc0e6
Step 2/13 : WORKDIR /usr/src/app
 ---> Using cache
 ---> 7591691636db
Step 3/13 : ENV PYTHONDONTWRITEBYTECODE 1
 ---> Using cache
 ---> 43e64a551189
Step 4/13 : ENV PYTHONUNBUFFERED 1
 ---> Using cache
 ---> 1b0c1b79d3f0
Step 5/13 : RUN apk update     && apk add postgresql-dev gcc python3-dev musl-dev jpeg-dev zlib-dev
 ---> Using cache
 ---> e953b47702be
Step 6/13 : RUN pip install --upgrade pip
 ---> Using cache
 ---> 3ac60eac560c
Step 7/13 : COPY ./requirements.txt /usr/src/app/requirements.txt
 ---> Using cache
 ---> 778a99f44a6e
Step 8/13 : RUN pip install -r requirements.txt
 ---> Using cache
 ---> f200dbd01d70
Step 9/13 : COPY . /usr/src/app/
 ---> Using cache
 ---> 4d2ea928202c
Step 10/13 : COPY ./entrypoint.sh /usr/src/app/entrypoint.sh
 ---> Using cache
 ---> 851525a35a14
Step 11/13 : RUN chmod +x /usr/src/app/entrypoint.sh
 ---> Using cache
 ---> e56669cb2d69
Step 12/13 : EXPOSE 80
 ---> Using cache
 ---> 0cdb6a5f9865
Step 13/13 : ENTRYPOINT ["/usr/src/app/entrypoint.sh"]
 ---> Using cache
 ---> 6b4acf541a79
Successfully built 6b4acf541a79
Successfully tagged docker_build/first_image:latest
$ docker images
REPOSITORY                          TAG          IMAGE ID       CREATED         SIZE
docker_build/first_image            latest       6b4acf541a79   2 minutes ago   579MB

4. 使用 .dockerignore 檔

可以藉由此檔案,每行新增一行排除模式 exclusion patterns 來讓 Docker 忽略比對模式路徑下的目錄和檔案

*/timp*
*/*/temp*
tmp?
~*

5. 從映像檔產生 Dockerfile

範例:CenturyLinkLabs 釋出 dockerfile-from-image 的工具,可以逆向工程用 image 建立出 Dockerfile

$ docker run -v /run/docker.sock:/run/docker.sock centurylink/dockerfile-from-image
Usage: dockerfile-from-image.rb [options] <image_id>
    -f, --full-tree                  Generate Dockerfile for all parent layers
    -h, --help                       Show this message

docker run -v /var/run/docker.sock:/var/run/docker.sock --rm alpine/dfimage -sV=1.36 nginx:latest

$ docker run -v /var/run/docker.sock:/var/run/docker.sock --rm alpine/dfimage -sV=1.36 nginx:latest
Unable to find image 'alpine/dfimage:latest' locally
latest: Pulling from alpine/dfimage
df20fa9351a1: Pull complete 
820dbffe2156: Pull complete 
Digest: sha256:4a271e763d51b7f3cca72eac9bf508502c032665dde0e4c8d5fcf6376600f64a
Status: Downloaded newer image for alpine/dfimage:latest
Analyzing nginx:latest
Docker Version: 19.03.12
GraphDriver: overlay2
Environment Variables
|PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|NGINX_VERSION=1.21.0
|NJS_VERSION=0.5.3
|PKG_RELEASE=1~buster

Open Ports
|80

Image user
|User is root

Potential secrets:
Dockerfile:
CMD ["bash"]
LABEL maintainer=NGINX Docker Maintainers <docker-maint@nginx.com>
ENV NGINX_VERSION=1.21.0
ENV NJS_VERSION=0.5.3
ENV PKG_RELEASE=1~buster
RUN set -x  \\
	&& addgroup --system --gid 101 nginx  \\
	&& adduser --system --disabled-login --ingroup nginx --no-create-home --home /nonexistent --gecos "nginx user" --shell /bin/false --uid 101 nginx  \\
	&& apt-get update  \\
	&& apt-get install --no-install-recommends --no-install-suggests -y gnupg1 ca-certificates  \\
	&& NGINX_GPGKEY=573BFD6B3D8FBC641079A6ABABF5BD827BD9BF62; found=''; for server in ha.pool.sks-keyservers.net hkp://keyserver.ubuntu.com:80 hkp://p80.pool.sks-keyservers.net:80 pgp.mit.edu ; do echo "Fetching GPG key $NGINX_GPGKEY from $server"; apt-key adv --keyserver "$server" --keyserver-options timeout=10 --recv-keys "$NGINX_GPGKEY"  \\
	&& found=yes  \\
	&& break; done; test -z "$found"  \\
	&& echo >&2 "error: failed to fetch GPG key $NGINX_GPGKEY"  \\
	&& exit 1; apt-get remove --purge --auto-remove -y gnupg1  \\
	&& rm -rf /var/lib/apt/lists/*  \\
	&& dpkgArch="$(dpkg --print-architecture)"  \\
	&& nginxPackages=" nginx=${NGINX_VERSION}-${PKG_RELEASE} nginx-module-xslt=${NGINX_VERSION}-${PKG_RELEASE} nginx-module-geoip=${NGINX_VERSION}-${PKG_RELEASE} nginx-module-image-filter=${NGINX_VERSION}-${PKG_RELEASE} nginx-module-njs=${NGINX_VERSION}+${NJS_VERSION}-${PKG_RELEASE} "  \\
	&& case "$dpkgArch" in amd64|i386|arm64) echo "deb <https://nginx.org/packages/mainline/debian/> buster nginx" >> /etc/apt/sources.list.d/nginx.list  \\
	&& apt-get update ;; *) echo "deb-src <https://nginx.org/packages/mainline/debian/> buster nginx" >> /etc/apt/sources.list.d/nginx.list  \\
	&& tempDir="$(mktemp -d)"  \\
	&& chmod 777 "$tempDir"  \\
	&& savedAptMark="$(apt-mark showmanual)"  \\
	&& apt-get update  \\
	&& apt-get build-dep -y $nginxPackages  \\
	&& ( cd "$tempDir"  \\
	&& DEB_BUILD_OPTIONS="nocheck parallel=$(nproc)" apt-get source --compile $nginxPackages )  \\
	&& apt-mark showmanual | xargs apt-mark auto > /dev/null  \\
	&& { [ -z "$savedAptMark" ] || apt-mark manual $savedAptMark; }  \\
	&& ls -lAFh "$tempDir"  \\
	&& ( cd "$tempDir"  \\
	&& dpkg-scanpackages . > Packages )  \\
	&& grep '^Package: ' "$tempDir/Packages"  \\
	&& echo "deb [ trusted=yes ] file://$tempDir ./" > /etc/apt/sources.list.d/temp.list  \\
	&& apt-get -o Acquire::GzipIndexes=false update ;; esac  \\
	&& apt-get install --no-install-recommends --no-install-suggests -y $nginxPackages gettext-base curl  \\
	&& apt-get remove --purge --auto-remove -y  \\
	&& rm -rf /var/lib/apt/lists/* /etc/apt/sources.list.d/nginx.list  \\
	&& if [ -n "$tempDir" ]; then apt-get purge -y --auto-remove  \\
	&& rm -rf "$tempDir" /etc/apt/sources.list.d/temp.list; fi  \\
	&& ln -sf /dev/stdout /var/log/nginx/access.log  \\
	&& ln -sf /dev/stderr /var/log/nginx/error.log  \\
	&& mkdir /docker-entrypoint.d
COPY file:65504f71f5855ca017fb64d502ce873a31b2e0decd75297a8fb0a287f97acf92 in /
	docker-entrypoint.sh

COPY file:0b866ff3fc1ef5b03c4e6c8c513ae014f691fb05d530257dfffd07035c1b75da in /docker-entrypoint.d
	docker-entrypoint.d/
	docker-entrypoint.d/10-listen-on-ipv6-by-default.sh

COPY file:0fd5fca330dcd6a7de297435e32af634f29f7132ed0550d342cad9fd20158258 in /docker-entrypoint.d
	docker-entrypoint.d/
	docker-entrypoint.d/20-envsubst-on-templates.sh

COPY file:09a214a3e07c919af2fb2d7c749ccbc446b8c10eb217366e5a65640ee9edcc25 in /docker-entrypoint.d
	docker-entrypoint.d/
	docker-entrypoint.d/30-tune-worker-processes.sh

ENTRYPOINT ["/docker-entrypoint.sh"]
EXPOSE 80
STOPSIGNAL SIGQUIT
CMD ["nginx" "-g" "daemon off;"]

實作時間

可以參考 Github Repo 試著把 flask app build 成 image 再 run 起來
好處:本地完全不用裝 flask 套件,很難裝!!
恭喜你會使用 Docker run app 拉!

Comments

Popular posts from this blog

《 Imgproxy 使用分析一:圖片下載速度優化分析:Akamai CDN vs Imgproxy 效能比較》

《 Akamai + S3 與 CloudFront + Imgproxy + S3 使用分析二:壓縮圖片設計流程:檔案大小 vs 載入時間的權衡》

程式語言初學者 Docker 入門第二章 —— 安裝與驗證 (Mac)