Dockerfile 是一個文本格式的配置文件,風格卻像是一段代碼,用戶可以通過編寫 Dockerfile 來創建自定義鏡像。
本文將從 Dockerfile 的基本格式入手,羅列其支持的指令并進行介紹,并說明如何通過這些指令來構建我們想要的鏡像。最后還會講下一些編寫 Dockerfile 的經驗。
1. 基礎結構
Docker 由一行行命令語句和以 # 開頭的注釋語句構成。主體內容分為四個部分:基礎鏡像信息、維護者信息、鏡像操作指令和容器啟動時執行指令。
下面從一個簡單的 Dockerfile 例子開始說明:
# escape=\ (backslash)
# This dockerfile uses the ubuntu:xenial image
# VERSION 2 - EDITION 1
# Author: docker_user
# Command format: Instruction [arguments / command] ..
# Base image to use, this must be set as the first line
FROM ubuntu:xenial
# Maintainer: docker_user <docker_user at email.com> (@docker_user)
LABEL maintainer docker_user<docker_user@email.com>
# Command to update the image
RUN echo "deb http://archive.ubuntu.com/ubuntu/ xenial main universe" >> /etc/apt/sources.list
RUN apt-get update && apt-get install -y nginx
RUN echo "\ndaemon off;" >> /etc/nginx/nginx.conf
# Command when creating a new container
CMD /usr/sbin/nginx
1.1 解析器指令
這個例子中,第一行是解析器命令,而不是普通的注釋,它指定了這個 Dockerfile 文本的轉義字符。
# escape=\ (backslash)
或者 # escape=` (backtick)
,默認轉義字符是 \ 。
轉義字符既用于轉義一行中的字符,也用于轉義換行符。這使得一條 Dockerfile 指令可以跨越多行。注意,無論解析器指令 escape 是否包含在 Dockerfile 中,轉義都不會在 RUN 指令中執行,除非要轉義的字符出現在行末尾。
在Windows環境下,將轉義字符設置為反引號而不是 \ 將非常有用,這樣,\ 就可以保持其作為目錄路徑分隔符的原有的含義,與 Windows PowerShell 中保持一致。
考慮下面的這個例子,它在Windows上可能會非常詭異地執行失敗。第二行末尾的第二個 \ 將被視為用來轉義換行符的轉義字符,而不是第一個 \ 的轉義目標。類似地,假設第三行末尾的 \ 本來是要作為一條指令進行處理的,但是它卻將被視為行延續字符。這個 Dockerfile 的結果是,第二行和第三行被認為是一條指令:
FROM microsoft/nanoserver
COPY testfile.txt c:\\
RUN dir c:\
解決方式就是指定轉義字符為 `
。
更多關于解析器指令的資料可以參考 Docker 官方文檔
1.2 主體部分
一般而言,大部分鏡像會包含一個 FROM
標簽指定基礎鏡像信息,即要構建的新鏡像是基于哪個舊鏡像構建的。接下來是使用 LABEL 指令說明維護者信息,后面則是鏡像操作指令,需要注意的是每運行一條 RUN 指令,鏡像添加新的一層,并提交,最后是 CMD 指令,指定容器啟動時的操作命令。
2. 指令說明
Dockerfile 中指令的一般格式是 INSTRUCTION arguments,類型有配置指令(配置鏡像信息)和操作指令(具體執行操作)。
2.1 配置指令
2.1.1 ARG
定義了創建鏡像過程中的變量。
格式為 ARG <name>[=<default value>]。
在執行 docker build 時,可以通過 -build-arg[=] 來為變量賦值。當鏡像編譯成功后, ARG 變量將不再存在(ENV 變量將一直保留在環境變量中)。
Docker 中內置了一些鏡像創建變量,可以直接使用(無需區分大小寫):HTTP_PROXY、hTTPS_PROXY、FTP_PROXY、NO_PROXY。
2.1.2 FROM
指定所創建鏡像的基礎鏡像。
格式為 FROM <image> [AS <name>] 或 FROM <image>:<tag> [AS <name>] 或 FROM <image>@<digest> [AS <name>]。
任何 Dockerfile 的第一條指令(不算解析器指令),必須是 FROM 指令。如果在同一個 Dockerfile 中有多個鏡像,可以使用多個 FROM 指令。
例子為
ARG VERSION=9.3
FROM debian:${VERSION}
2.1.3 LABEL
LABEL 指令可以為生成的鏡像添加元數據標簽信息。之后可以使用 docker ps -f lable key=value
快速過濾出特定鏡像。
格式為 LABEL <key>=<value> <key>=<value> <key>=<value>....
例如:
LABEL author="wean" date="2019-1-16"
LABEL description="This is a mult-line \
text"
2.1.4 EXPOSE
聲明鏡像內服務監聽的端口。
格式為 EXPOSE <port> [<port>/<protocol>...]
例如:
EXPOSE 22 80 443
需要注意的是,該指令只是起到聲明的作用,并不會自動完成端口映射。
如果要映射端口出來,在啟動容器時可以使用 -P 或 -p 參數,可以參考 操作 Docker 容器,查看端口映射的具體操作。
2.1.5 ENV
指定環境變量,在編譯鏡像過程中會被后續 RUN 指令使用,在鏡像啟動后也會存在于環境變量中。
例如:
ENV APP_VERSION=1.0.0
ENV PATH $PATH:/usr/local/bin
指令指定的環境變量在啟動時也可以覆蓋掉:docker run --env <key>=<value> built_image
注意當一條 ENV 指令中同時為多個環境變量賦值并且值也是從環境變量讀取時;會為變量先賦值后更新。如下面指令,最終結果是 key1=value1 key2=value2
ENV key1=value2
ENV key1=value1 key2=${key1}
2.1.6 ENTRYPOINT
指定鏡像的默認入口命令,該命令會在啟動容器時作為跟命令執行,所有傳入值作為該命令的參數。
支持兩種格式:
- ENTRYPOINT ["executable", "param1", "param2"]: exec 調用執行
- ENTRYPOINT command param1 param2: shell 調用執行。
此時,若是指定了 CMD 指令或者 docker run 指定了啟動命令,則會作為 ENTRYPOINT 根命令的參數。
每個 Dockerfile 中只能有一個 ENTRYPOINT,當指定多個時,只有最后一個生效。
在運行時,可以被 --entrypoint 參數覆蓋掉。
關于 ENRTYPOINT 和 CMD 的更多差異可以參考 https://www.cnblogs.com/lienhua34/p/5170335.html。
2.1.7 VOLUME
創建數據卷掛載點,掛載到本地的一個隨機目錄下。
格式為 VOLUME ["/data"]。
2.1.8 USER
指定運行容器時的用戶名或者 UID,后續的 RUN 指令也會默認使用特定的用戶身份。
格式為 USER daemon。
當服務不需要管理員權限時,可以在 Dockerfile 中創建需要所需要的用戶,并通過 USER 命令指定該運行用戶。例如:
RUN groupadd -r postgres && useradd --no-log-init -r -g postgress postgress
后續需要獲取臨時管理員權限可以使用 gosu 命令。
2.1.9 WORKDIR
為后續的 RUN、CMD、ENTRYPOINT 指令配置工作目錄。
格式為 WORKDIR /path/to/workdir
可以使用多個 WORKDIR 指令,但是后續指令如果是相對路徑,基于之前的指令指定的路徑。
例如:
WORKDIR /a
WORKDIR b
結果是路徑 /a/b,所以推薦要是需要寫多個 WORKDIR,則每個 WORKDIR 都寫絕對路徑。
2.1.10 ONBUILD
指定當基于所生成的鏡像創建子鏡像時,自動執行的操作指令。
格式為 ONBUILD [INSTRUCTION]
由由于這個命令是隱式執行的,子鏡像創建過程中察覺不到,所以應該在鏡像標簽中進行標注,例如 ruby:2.1-onbuild。
這個指令在創建專門用于自動編譯、檢查等操作的基礎鏡像時十分有用。
2.1.11 STOPSIGNAL
默認的stop-signal是SIGTERM,在docker stop的時候會給容器內PID為1的進程發送這個signal,通過--stop-signal可以設置自己需要的signal,主要的目的是為了讓容器內的應用程序在接收到signal之后可以先做一些事情,實現容器的平滑退出,如果不做任何處理,容器將在一段時間之后強制退出,會造成業務的強制中斷,這個時間默認是10s。
格式為 STOPSIGNAL signal
2.1.12 HEALTHCHECK
配置所啟動容器如何進行健康檢查(如何判斷健康與否)。格式有兩種:
- HEALTHCHECK [OPTIONS] CMD command:根據所執行命令返回值是否為 0 來判斷
- HEALTHCHECK NONE: 禁止基礎鏡像中的健康檢查
OPTIONS 支持如下參數:
- -interval=DURATION (default: 30s): 過多久檢查一次。
- -timeout=DURATION (default: 30s): 每次檢查的超時時間。
- retries=N (default: 3): 如果失敗了,重試幾次才最終確定失敗。
2.1.13 SHELL
指定其他命令使用 shell 時的默認 shell 類型。
SHELL ["executable", "parameters"]。
默認值是 ["/bin/sh", "-c"]。
2.2 操作指令
2.2.1 RUN
運行指定命令。
格式為 RUN <command> 或 RUN ["executable", "param1", "param2"]。命令過長可以使用轉義字符換行,比如默認的
每條 RUN 指令會生成一個新的鏡像層。
2.2.2 CMD
CMD 容器用來指定容器啟動時默認執行的命令。支持三種格式:
- CMD ["executable", "param1", "param2"]: 相當于執行 executable param1 param2,推薦方式
- CMD command parame1 param2: 在默認的 shell 中執行,提供給需要交互的應用
- CMD ["param1", "param2"]: 提供給 ENTRYPOINT 的默認參數
每個鏡像只能有一個 CMD,多條會覆蓋,可以在運行 (run) 容器時通過參數覆蓋
2.2.3 ADD
添加內容到鏡像。格式為 ADD <src> <dest>。
該命令復制指定的 <src> 下的內容到容器中的 <dest> 路徑下。
其中 <src> 可以是 Dockerfile 所在目錄的一個相對路徑(文件或目錄);也可以是一個 URL;還可以是一個 tar 文件(會自動解壓為目錄)。<dest> 可以是鏡像內的絕對路徑,或者相對于工作目錄 WORKDIR 下的相對路徑。且路徑支持正則格式。
2.2.4 COPY
COPY 指令與 ADD 相似,語義上 CPOY 更貼切,推薦使用 COPY。
3. 創建鏡像
編寫完 Dockerfile 后就可以通過 docker [image] build [OPTIONS] PATH|URL|-
命令來創建鏡像。
要注意的點有:
- 碰到 ADD、COPY 和 RUN 指令會生成一層新的鏡像
- 執行命令的過程中會把 Dockerfile 文件所在的上下文都發給服務端。因此應該避免無關的文件存在上下文,或者使用 .dockerignore 文件。
- 非上下文路徑的 Dockerfile 使用 -f 選項來指定其路徑
- 要指定生成鏡像的標簽信息,可以使用 -t 選項。該選項可以重復使用多次來添加多個標簽。
3.1 OPTIONS 命令選項
選項 | 說明 |
---|---|
-add-host list | 添加自定義的主機名到 IP 的映射 |
-build-arg list | 添加創建時的變量 |
-cache-from strings | 使用指定鏡像作為緩存源 |
-cgroup-parent string | 繼承上層 cgroup |
-compress | 使用 gzip 來壓縮創建上下文數據 |
-cpu-period int | 分配的 CFS 調度器時長 |
-cpu-quota int | CPU 調度器總份額 |
-c, -cpu-shares int | CPU 權重 |
-cpuset-cpus string | 多 CPU 允許使用的 CPU |
-cpuset-mems string | 多 CPU 允許使用的內存 |
-disable-content-trust | 不進行鏡像校驗,默認為真 |
-f,-file string | Dockerfile 名稱 |
-force-rm | 總是刪除中間過程的容器 |
-iidfile string | 將鏡像 ID 寫入到文件 |
-isolation string | 容器的隔離機制 |
-label list | 配置鏡像的元數據 |
-m,-memory bytes | 限制使用內存量 |
-memory-swap bytes | 限制內存和緩存的總量 |
-network string | 指定 RUN 命令時的網絡模式 |
-no-cache | 創建鏡像時不適用緩存 |
-platform string | 指定平臺類型 |
-pull | 總是嘗試獲取鏡像的最新版本 |
-q,-quiet | 不打印創建過程中的日志信息 |
-rm | 創建成功后自動刪除中間過程容器,默認為真 |
-security-opt strings | 指定安全相關的選項 |
-shm-size bytes | /dev/shm 的大小 |
-squash | 將新創建的多層擠壓放到一層中 |
-stream | 持續獲取創建的上下文 |
-t,-tag list | 指定鏡像的標簽列表 |
-target string | 指定創建的目標階段 |
-ulimit ulimit | 指定 ulimit 變量 |
3.2 選擇父鏡像
從這個鏡像的繼承圖來看,我們可以選擇兩種鏡像作為父鏡像,一種是基礎鏡像(灰色),另一種是普通鏡像(白色)。
3.3 使用 .dockerignore 文件
從前面我們可以知道,Dockerfile build 的過程中會把上下文一起發給服務端,如果有不想發送的文件(加快傳輸過程),可以使用一下模式忽略文件:
- /temp
- //temp*
- tmp?
- ~*
- Dockerfile
- !README.md
其中 * 表示任意多的字符, ? 代表單個字符, ! 代表不匹配(即不忽略指定的路徑或文件)。
3.4 多步驟創建
從 17.05 版本開始,Docker 支持多步驟鏡像創建,用來精簡最終生成的鏡像的大小。
舉個簡單的例子,制作一個 Java 程序鏡像,分成兩個步驟創建,第一步用包含 maven 庫及 JDK 的編譯鏡像編譯程序,第二部把生成的 jar 文件復制到運行鏡像中,打包運行鏡像就得到精簡的鏡像。
以 Go 語言為例,提供一個例子:
package main
import (
"fmt"
)
func main() {
fmt.Println("Hello, Docker")
}
FROM golang:1.9 as builder
RUN mkdir -p /go/src/test
WORKDIR /go/src/test
COPY main.go .
RUN CGO_ENABLED=0 GOOS=linux go build -o app .
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR/root/
COPY --from=builder /go/src/test/app .
CMD ["./app"]
編譯鏡像,可以看到最終大小只有 6.83 MB
>docker build -t wean2018/test-multistage:latest .
> docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
wean2018/test-multistage latest 2e2eed03a664 53 seconds ago 6.83MB