目錄
定制鏡像
建立Dockerfile檔案
建構鏡像
上下文路徑
Dockerfile 指令詳解
FROM
RUN
COPY
ADD
CMD
ENTRYPOINT
ENV
ARG
VOLUME
EXPOSE
WORKDIR
USER
HEALTHCHECK
ONBUILD
定制鏡像
鏡像是多層存儲,每一層是在前一層的基礎上進行的修改;而容器同樣也是多層存儲,是在以鏡像為基礎層,在其基礎上加一層作為容器運作時的存儲層。
Dockerfile 是一個文本檔案,其内包含了一條條的指令(Instruction),每一條指令建構一層,是以每一條指令的内容,就是描述該層應當如何建構。
建立Dockerfile檔案
在一個空白目錄中,建立一個文本檔案,并命名為 Dockerfile:
# mkdir mynginx
# cd mynginx
# touch Dockerfile
# vi Dockerfile
Dockerfile内容:
FROM nginx
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
建構鏡像
在 Dockerfile 檔案所在目錄執行:
docker build [選項] <上下文路徑/URL/>
[[email protected] mynginx]# docker build -t nginx:v2 .
Sending build context to Docker daemon 2.048kB
Step 1/2 : FROM nginx
---> 540a289bab6c
Step 2/2 : RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
---> Running in 491354cbdd17
Removing intermediate container 491354cbdd17
---> 552965e05275
Successfully built 552965e05275
Successfully tagged nginx:v2
從指令的輸出結果中,可以清晰的看到鏡像的建構過程。在 Step 2 中, RUN 指令啟動了一個容器 d764dfad4bd2,執行了所要求的指令,并最後送出了這一層 c7f7f5c2a0b1,随後删除了所用到的這個容器d764dfad4bd2 。
[[email protected] mynginx]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx v2 552965e05275 4 seconds ago 126MB
nginx latest 540a289bab6c 4 weeks ago 126MB
建構成功後,最終鏡像的名稱 nginx:v2 ,可以正常運作使用。
上下文路徑
上下文路徑,是指 docker 在建構鏡像,有時候想要使用到本機的檔案(比如複制),docker build 指令得知這個路徑後,會将路徑下的所有内容打包。
由于 docker 的運作模式是 C/S。我們本機是 C,docker 引擎是 S。實際的建構過程是在 docker 引擎下完成的,是以這個時候無法用到我們本機的檔案。這就需要把我們本機的指定目錄下的檔案一起打包提供給 docker 引擎使用。
如果未說明最後一個參數,那麼預設上下文路徑就是 Dockerfile 所在的位置。
上下文路徑下不要放無用的檔案,因為會一起打包發送給 docker 引擎,如果檔案過多會造成過程緩慢。
是以一般會建立空白檔案夾。
Dockerfile 指令詳解
FROM
所謂定制鏡像,那一定是以一個鏡像為基礎,在其上進行定制。而 FROM 就是指定基礎鏡像,是以一個 Dockerfile 中 FROM 是必備的指令,并且必須是第一條指令。
格式:
FROM scratch
...
如果是以 scratch 為基礎鏡像的話,意味着你不以任何鏡像為基礎,接下來所寫的指令将作 為鏡像第一層開始存在。
RUN
執行後面跟着的指令行指令。是在鏡像建構的過程中執行的。
- shell 格式:
RUN <指令行指令>
# <指令行指令> 等同于,在終端操作的 shell 指令。
示例:
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
- exec 格式:RUN ["可執行檔案", "參數1", "參數2"] ; 像是函數調用中的格式。
RUN ["可執行檔案", "參數1", "參數2"]
示例:
RUN ["./test.php", "dev", "offline"]
# 等價于 RUN ./test.php dev offline
Dockerfile 中每一個指令都會建立一層。是以過多無意義的層,會造成鏡像膨脹過大。
例如:
以下執行會建立 3 層鏡像
FROM centos
RUN yum install wget
RUN wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz"
RUN tar -xvf redis.tar.gz
可簡化為:
FROM centos
RUN yum install wget\
&& wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz"\
&& tar -xvf redis.tar.gz
以 && 符号連接配接指令,這樣執行後,隻會建立 1 層鏡像。
Dockerfile 支援 Shell 類的行尾添加 \ 的指令換行方 式,以及行首 # 進行注釋的格式。
COPY
複制,從上下文目錄中複制檔案或者目錄到容器裡指定路徑。
格式:
COPY [--chown=<user>:<group>] <源路徑>... <目标路徑>
COPY [--chown=<user>:<group>] ["<源路徑1>",... "<目标路徑>"]
示例:
COPY package.json /usr/src/app/
[--chown=<user>:<group>]:可選參數,使用者改變複制到容器内檔案的擁有者和屬組。
<源路徑> :可以是多個,甚至可以是通配符,其通配符規則要滿足 Go 的 filepath.Match 規則,如:
COPY hom* /mydir/
COPY hom?.txt /mydir/
<目标路徑> :可以是容器内的絕對路徑,也可以是相對于工作目錄的相對路徑(工作目錄可以用 WORKDIR 指令來指定)。如果目錄不存在會在複制檔案前先行建立缺失目錄。
使用 COPY 指令,源檔案的各種中繼資料都會保留。比如讀、寫、執行權限、檔案變更時間等。
ADD
ADD 指令和 COPY 的格式和性質基本一緻。但是在 COPY 基礎上增加了一些功能。(官方推薦使用 COPY)
優點:
在執行 <源檔案> 為 tar 壓縮檔案的話,壓縮格式為 gzip, bzip2 以及 xz 的情況下,會自動複制并解壓到 <目标路徑>。
缺點:
在不解壓的前提下,無法複制 tar 壓縮檔案。會令鏡像建構緩存失效,進而可能會令鏡像建構變得比較緩慢。具體是否使用,可以根據是否需要自動解壓來決定。
如果 <源路徑> 為一個 tar 壓縮檔案,壓縮格式為 gzip , bzip2 以及 xz 的情況下, ADD 指令将會自動解壓縮這個壓縮檔案到 <目标路徑>去。 在某些情況下,這個自動解壓縮的功能非常有用,比如官方鏡像 ubuntu 中:
FROM scratch
ADD ubuntu-xenial-core-cloudimg-amd64-root.tar.gz /
...
在 COPY 和 ADD 指令中選擇的時候,可以遵循這樣的原則,所有的檔案複制均使用 COPY 指令,僅在需要自動解壓縮的場合使用 ADD 。
CMD
類似于 RUN ,CMD可以用于執行特定的指令。
二者運作的時間點不同:
CMD 在docker run 時運作。
RUN 是在 docker build。
格式:
CMD <shell 指令>
CMD ["<可執行檔案或指令>","<param1>","<param2>",...] # (推薦使用)
CMD ["<param1>","<param2>",...] # 該寫法是為 ENTRYPOINT 指令指定的程式提供預設參數,如果docker run指令行結尾有參數指定,那CMD後面的參數不生效
為啟動的容器指定預設要運作的程式,程式運作結束,容器也就結束;
CMD 指令指定的程式可被 docker run 指令行參數中指定要運作的程式所覆寫;
如果 Dockerfile 中如果存在多個 CMD 指令,僅最後一個生效。
示例:
[[email protected] ~]# docker run -it ubuntu:16.04
[email protected]:/# exit
exit
[[email protected] ~]# docker run -it ubuntu:16.04 cat /etc/os-release
NAME="Ubuntu"
VERSION="16.04.6 LTS (Xenial Xerus)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 16.04.6 LTS"
VERSION_ID="16.04"
HOME_URL="http://www.ubuntu.com/"
SUPPORT_URL="http://help.ubuntu.com/"
BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"
VERSION_CODENAME=xenial
UBUNTU_CODENAME=xenial
[[email protected] ~]#
如果使用 shell 格式的話,實際的指令會被包裝為 sh -c 的參數的形式進行執行。比如:
CMD echo $HOME
在實際執行中,會将其變更為:
CMD [ "sh", "-c", "echo $HOME" ]
這就是為什麼可以使用環境變量的原因,因為這些環境變量會被 shell 進行解析處理。
ENTRYPOINT
類似于 CMD ,但其不會被 docker run 的指令行參數指定的指令所覆寫,而且這些指令行參數會被當作參數送給 ENTRYPOINT 指令指定的程式。
格式:
ENTRYPOINT ["<executeable>","<param1>","<param2>",...]
但是, 如果運作 docker run 時使用了 --entrypoint 選項,此選項的參數可當作要運作的程式覆寫 ENTRYPOINT 指令指定的程式。
當指定了 ENTRYPOINT 後, CMD 的含義就發生了改變,不再是直接的運作其指令,而是将 CMD 的内容作為參數傳給 ENTRYPOINT 指令,換句話說實際執行時,将變為:
<ENTRYPOINT> "<CMD>"
示例:
假設已認證 Dockerfile 建構了 nginx:test 鏡像:
FROM nginx
ENTRYPOINT ["nginx", "-c"] # 定參
CMD ["/etc/nginx/nginx.conf"] # 變參
1、不傳參運作
$ docker run nginx:test
# 容器内會預設運作以下指令,啟動主程序
nginx -c /etc/nginx/nginx.conf
2、傳參運作
$ docker run nginx:test -c /etc/nginx/new.conf
# 容器内會預設運作以下指令,啟動主程序(/etc/nginx/new.conf:假設容器内已有此檔案)
nginx -c /etc/nginx/new.conf
有些時候,啟動主程序前,需要一些準備工作
比如 mysql 類的資料庫,可能需要一些資料庫配置、初始化的工作,這些工作要在最終的 mysql 伺服器運作之前解決。
此外,可能希望避免使用 root 使用者去啟動服務,進而提高安全性,而在啟動服務前還需要以 root 身份執行一些必要的準備工作,最後切換到服務使用者身份啟動服務。或者除了服務外,其它指令依舊可以使用 root 身份執行,友善調試等。
這些準備工作是和容器 CMD 無關的,無論 CMD 為什麼,都需要事先進行一個預處理的工作。這種情況下,可以寫一個腳本,然後放入 ENTRYPOINT 中去執行,而這個腳本會将接到的參數(也就是<CMD>)作為指令,在腳本最後執行。比如官方鏡像 redis 中就是這麼做的:
FROM alpine:3.4
...
RUN addgroup -S redis && adduser -S -G redis redis
...
ENTRYPOINT ["docker-entrypoint.sh"]
EXPOSE 6379
CMD [ "redis-server" ]
"docker-entrypoint.sh"
#!/bin/sh
...
# allow the container to be started with `--user`
if [ "$1" = 'redis-server' -a "$(id -u)" = '0' ]; then
chown -R redis .
exec su-exec redis "$0" "$@"
fi
exec "$@"
可以看到其中為了 redis 服務建立了 redis 使用者,并在最後指定了 ENTRYPOINT 為 dock-erentrypoint.sh 腳本。
該腳本的内容就是根據 CMD 的内容來判斷,如果是 redis-server 的話,則切換到 redis 使用者身份啟動伺服器,否則依舊使用 root 身份執行。比如:
$ docker run -it redis id
uid=0(root) gid=0(root) groups=0(root)
ENV
設定環境變量,定義了環境變量,在後續的指令中,可以使用這個環境變量。
格式:
ENV <key> <value>
ENV <key1>=<value1> <key2>=<value2>...
示例:
ENV NODE_VERSION 7.2.0
RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz"\
&& curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc"
ARG
建構參數,與 ENV 作用一至。不過作用域不一樣。ARG 設定的環境變量僅對 Dockerfile 内有效,也就是說隻有 docker build 的過程中有效,建構好的鏡像内不存在此環境變量。
建構指令 docker build 中可以用 --build-arg <參數名>=<值> 來覆寫。
格式:
ARG <參數名>[=<預設值>]
VOLUME
定義匿名資料卷。在啟動容器時忘記挂載資料卷,會自動挂載到匿名卷。
避免重要的資料,因容器重新開機而丢失;避免容器不斷變大。
格式:
VOLUME ["<路徑1>", "<路徑2>"...]
VOLUME <路徑>
在啟動容器 docker run 的時候,可以通過 -v 參數修改挂載點。
示例:
docker run -d -v mydata:/data xxxx
EXPOSE
隻是聲明端口。
幫助鏡像使用者了解這個鏡像服務的守護端口,以友善配置映射;在運作時使用随機端口映射時,也就是 docker run -P 時,會自動随機映射 EXPOSE 的端口。
格式:
EXPOSE <端口1> [<端口2>...]
EXPOSE 指令是聲明運作時容器提供服務端口,這隻是一個聲明,在運作時并不會因為這個聲明應用就會開啟這個端口的服務。
WORKDIR
指定工作目錄。用 WORKDIR 指定的工作目錄,以後各層的目前目錄就被改為指定的目錄,如該目錄不存在, WORKDIR 會幫你建立目錄。
格式:
WORKDIR <工作目錄路徑>
每一個 RUN 都是啟動一個容器、執行指令、然後送出存儲層檔案變更。第一層的執行僅僅是目前程序的工作目錄變更,一個記憶體上的變化而已,其結果不會造成任何檔案變更。而到第二層的時候,啟動的是一個全新的容器,跟第一層的容器更完全沒關系,不可能繼承前一層建構過程中的記憶體變化。
docker build 建構鏡像過程中的,每一個 RUN 指令都是建立的一層。隻有通過 WORKDIR 建立的目錄才會一直存在。
USER
用于指定執行後續指令的使用者和使用者組,這邊隻是切換後續指令執行的使用者(使用者和使用者組必須提前已經存在)。
格式:
USER <使用者名>
HEALTHCHECK
用于指定某個程式或者指令來監控 docker 容器服務的運作狀态。
格式:
# 設定檢查容器健康狀況的指令
HEALTHCHECK [選項] CMD <指令>
# 如果基礎鏡像有健康檢查指令,使用這行可以屏蔽掉其健康檢查指令
HEALTHCHECK NONE
# 這邊 CMD 後面跟随的指令使用,可以參考 CMD 的用法
HEALTHCHECK [選項] CMD <指令>
HEALTHCHECK 支援選項:
- --interval=<間隔> :兩次健康檢查的間隔,預設為 30 秒
- --timeout=<時長> :健康檢查指令運作逾時時間,如果超過這個時間,本次健康檢查就被視為失敗,預設 30 秒
- --retries=<次數> :當連續失敗指定次數後,則将容器狀态視為 unhealthy ,預設 3 次
示例:
FROM nginx
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*HEALTHCHECK --interval=5s --timeout=3s\
CMD curl -fs http://localhost/ || exit 1
這裡設定了每 5 秒檢查一次,如果健康檢查指令超過 3 秒沒響應就視為失敗,并且使用 curl -fs http://localhost/ || exit 1 作為健康檢查指令。
容器狀态:health: starting、healthy、unhealthy
ONBUILD
用于延遲建構指令的執行。就是 Dockerfile 裡用 ONBUILD 指定的指令,在本次建構鏡像的過程中不會執行(假設鏡像為 test-build)。當有新的 Dockerfile 使用了之前建構的鏡像 FROM test-build ,這是執行新鏡像的 Dockerfile 建構時候,會執行 test-build 的 Dockerfile 裡的 ONBUILD 指定的指令。
它後面跟的是其它指令,比如 RUN , COPY 等,而這些指令,在目前鏡像建構時并不會被執行。隻有當以目前鏡像為基礎鏡像,去建構下一級鏡像的時候才會被執行。
格式:
ONBUILD <其它指令>
示例:
FROM node:slim
RUN mkdir /app
WORKDIR /app
ONBUILD COPY ./package.json /app
ONBUILD RUN [ "npm", "install" ]
ONBUILD COPY . /app/
CMD [ "npm", "start" ]
假設用上面示例建構的鏡像為 my-node,那麼之後以 my-node 為基礎建構的鏡像,之前的 ONBUILD 後的指令就會開始執行
FROM my-node
...
參考:
https://github.com/yeasy/docker_practice
https://www.runoob.com/docker/docker-dockerfile.html