我們可以把剛才的對容器的所有操作命令都記錄到一個文件里,就像寫更腳本程序。
之后用 docker build 命令以此文件為基礎制作一個鏡像,并會自動提交到本地倉庫。
這樣的話鏡像的構(gòu)建會變的透明化,對鏡像的維護起來也更加簡單,只修改這個文件即可。
同時分享也更加簡單快捷,因為只要分享這個文件即可。
Dokcerfile 是一個普通的文本文件,文件名一般叫 Dockerfile
其中包含了一系列的指令(Instruction), 每一條指令都會構(gòu)建一層,就是描述該層是如何創(chuàng)建的。
小試牛刀
示例:
- 編輯 Dockerfile 文件
[root@localhost ~]# mkdir centos_dockerfile
[root@localhost ~]# cd centos_dockerfile/
[root@localhost centos_dockerfile]# vi Dockerfile
FROM centos:latest
LABEL maintainer="yangge <yangge@qf.com>" description="Install tree vim*"
RUN rpm -qa | grep tree || yum install -y tree vim*
指令介紹:
- FORM 定義一個基礎鏡像
- LABEL 定義一些元數(shù)據(jù)信息,比如作者、版本、關于鏡像的描述信息
- RUN 執(zhí)行命令行的命令
編輯完,保存退出
2.開始構(gòu)建鏡像
命令語法格式:
docker bulid -t 倉庫名/鏡像名:tag .
docker build [選項] <上下文路徑/URL/->
示例:
[root@localhost centos_dockerfile]# docker build -t centos:1.20 .
Sending build context to Docker daemon 2.048kB
Step 1/3 : FROM centos:latest
---> e934aafc2206
Step 2/3 : LABEL maintainer="shark<dockerhub@163.com>" description="Install tree vim*"
---> Using cache
---> 1207b2848015
Step 3/3 : RUN rpm -qa | grep tree || yum install -y tree vim*
---> Running in 33d321b249d7
Loaded plugins: fastestmirror, ovl
Determining fastest mirrors
...略...
Complete!
Removing intermediate container 33d321b249d7
---> adc30981bc84
Successfully built adc30981bc84 # 表示構(gòu)建成功
Successfully tagged centos:1.20 # TAG 標簽
[root@localhost centos_dockerfile]#
構(gòu)建鏡像的上下文(context)
這個 . 表示當前目錄,這實際上是在指定上下文的目錄是當前目錄,docker build 命令會將該目錄下的內(nèi)容打包交給 Docker 引擎以幫助構(gòu)建鏡像。
docker build 命令得知這個路徑后,會將路徑下的所有內(nèi)容打包,然后上傳給 Docker 引擎。這樣 Docker 引擎收到這個上下文包后,展開就會獲得構(gòu)建鏡像所需的一切文件。
最佳實戰(zhàn)
一般來說,應該會將 Dockerfile 置于一個空目錄下,或者項目根目錄下。如果該目錄下沒有所需文件,那么應該把所需文件復制一份過來。如果目錄下有些東西確實不希望構(gòu)建時傳給 Docker 引擎,那么可以用 .gitignore 一樣的語法寫一個 .dockerignore,該文件是用于剔除不需要作為上下文傳遞給 Docker 引擎的
Dockerfile 的文件名并不要求必須為 Dockerfile,而且并不要求必須位于上下文目錄中,比如可以用 -f ../Dockerfile.qf 參數(shù)指定某個文件作為 Dockerfile。
一般大家習慣性的會使用默認的文件名 Dockerfile,以及會將其置于鏡像構(gòu)建上下文目錄中。
[root@localhost dockerfile_qf_ignore]# tree .
.
├── Dockerfile.qf
└── test
├── a.txt
├── b.txt
└── test.qf
[root@localhost dockerfile_qf_ignore]# cat Dockerfile.qf
FROM alpine
COPY ./test.qf /root/test.qf
[root@localhost dockerfile_qf_ignore]# docker build -f ../Dockerfile.qf -t alpine:test.qf .
[root@localhost dockerfile_qf_ignore]# docker run -it alpine:test.qf /bin/sh
Dockerfile 詳解
FROM 指令
主要作用是指定一個鏡像作為構(gòu)建自定義鏡像的基礎鏡像,在這個基礎鏡像之上進行修改定制。
這個指令是 Dockerfile 中的必備指令,同時也必須是第一條指令。
在 Docker Store 上有很多高質(zhì)量的官方鏡像,可以直接作為我們的基礎鏡像。
除了一些現(xiàn)有的鏡像,Docker 還有一個特殊的鏡像 scratch
這個鏡像是虛擬的,表示空白鏡像
FORM scratch
...
這以為著這將不以任何鏡像為基礎鏡像。
可以把可執(zhí)行的二進制文件復制到鏡像中直接執(zhí)行,容器本身就是和宿主機共享 Linux內(nèi)核的。
使用 Go 語言開發(fā)的應用很多會使用這種方式來制作鏡像,這也是為什么有人認為 Go 是特別適合容器微服務架構(gòu)的語言的原因之一。
制作自己的 Hello world
-
在任意一臺 Linux 機器上,安裝 gcc
查看有沒有安裝
[root@localhost hello_qf]# rpm -qa gcc glibc-static
glibc-static-2.17-222.el7.x86_64
gcc-4.8.5-11.el7.x86_64
沒有的話,進行安裝即可
[root@localhost hello_qf]# yum install gcc glibc-static
- 編輯 C 源代碼文件
[root@localhost docker]# cat hello.c
#include <stdio.h>
int main()
{
printf("Hello, Yangge! \n");
return 0;
}
- 將源代碼文件編譯為可執(zhí)行的二進制文件
[root@localhost hello_qf]# gcc --static hello.c -o hello
編譯好后,測試一下
[root@localhost hello_qf]# ls
hello hello.c
# hello 是我們編譯好的二進制文件
# hello.c 是 C 的源碼文件
[root@localhost hello_qf]# ./hello
Hello Yangge # 輸出結(jié)果,說明編譯成功
- 編輯 Dockerfile
在有 hello 二進制的文件目錄下,編譯 Dockerfile 文件,內(nèi)容如下:
[root@localhost hello_qf]# ls
Dockerfile hello hello.c
[root@localhost hello_qf]# cat Dockerfile
FROM scratch
ADD hello /
CMD ["/hello"]
ADD 是把當前目錄下的 hello 文檔拷貝到 容器中的根目錄下
CMD 執(zhí)行根目錄下的 hello 文件
- 構(gòu)建新的鏡像
注意命令的最后有個 .
[root@ hello_qf]# docker build -t xiguatian/hello-yangge .
Sending build context to Docker daemon 868.9kB
Step 1/3 : FROM scratch
--->
Step 2/3 : ADD hello /
---> 63ed3c13b7fd
Step 3/3 : CMD ["/hello"]
---> Running in a26622affa68
Removing intermediate container a26622affa68
---> dfadd4a86525
Successfully built dfadd4a86525
Successfully tagged xiguatian/hello-yange:latest
- 查看本地倉庫驗證
[root@localhost hello_qf]# docker image ls xiguatian/hello-yange
REPOSITORY TAG IMAGE ID CREATED SIZE
xiguatian/hello-yange latest dfadd4a86525 3 minutes ago 865kB
可以看到鏡像很小
- 利用新的鏡像運行一個容器
[root@localhost hello_qf]# docker run --rm qf/hello-yange
Hello Yangge
關于 Alpine
官網(wǎng):https://alpinelinux.org/
WIKI https://wiki.alpinelinux.org/wiki/Main_Page
Alpine Linux是一款獨立的非商業(yè)性通用Linux發(fā)行版,專為那些了解安全性,簡單性和資源效率的高級用戶而設計。
小
Alpine Linux圍繞musl libc和busybox構(gòu)建。這使得它比傳統(tǒng)的GNU / Linux發(fā)行版更小,更節(jié)省資源。一個容器需要不超過8 MB的空間,而對磁盤的最小安裝需要大約130 MB的存儲空間。您不僅可以獲得完整的Linux環(huán)境,還可以從存儲庫中選擇大量的軟件包。
二進制軟件包被縮減和拆分,使您可以更好地控制安裝的內(nèi)容,從而使您的環(huán)境盡可能地小巧高效。
簡單
Alpine Linux是一個非常簡單的發(fā)行版,它會盡量避免使用。它使用自己的包管理器,稱為apk,OpenRC init系統(tǒng),腳本驅(qū)動的設置,就是這樣!這為您提供了一個簡單,清晰的Linux環(huán)境,沒有任何噪音。然后,您可以添加項目所需的軟件包,因此無論是構(gòu)建家用PVR還是iSCSI存儲控制器,薄型郵件服務器容器或堅如磐石的嵌入式交換機,其他都不會擋道。
安全
Alpine Linux的設計考慮到了安全性。內(nèi)核修補了一個非官方的grsecurity / PaX端口,并且所有的用戶級二進制文件被編譯為位置獨立可執(zhí)行文件(PIE)和堆棧粉碎保護。這些主動安全功能可防止利用整個類別的零日等漏洞。
LABEL 指令
LABEL 指令用于指定一個鏡像的描述信息
該LABEL指令將元數(shù)據(jù)添加到鏡像中。
LABEL是一個鍵值對。
要在LABEL值中包含空格,請像在命令行解析中一樣使用引號和續(xù)行符\。
幾個用法示例:
LABEL maintainer="yangge@qf.com"
LABEL "com.example.vendor"="ACME Incorporated"
LABEL com.example.label-with-value="foo"
LABEL version="1.0"
LABEL description="This text illustrates \
that label-values can span multiple lines."
一個鏡像可以有多個LABEL標簽。您可以在一行中指定多個標簽。并且目前的版本不再會影響到鏡像的大小了。
但是仍然可以把他們寫在一行或用反斜線進行續(xù)航
LABEL multi.label1="value1" multi.label2="value2" other="value3"
LABEL multi.label1="value1" \
multi.label2="value2" \
other="value3"
有繼承關系的鏡像,標簽也會有面向?qū)ο缶幊讨欣^承的關系和特性
要查看鏡像的 LABEL 信息,請使用該docker inspect命令。
ENV 指令
用于設置環(huán)境變量
格式有兩種:
ENV <key> <value>ENV <key1>=<value1> <key2>=<value2>...
示例:
推薦的方式,易讀
ENV VERSION=1.0 DEBUG=on \
NAME="Happy Feet"
不推薦都方式,不易讀
ENV NODE_VERSION 7.2.0
其他指令使用:
RUN echo $NODE_VERSION
...
下列指令可以支持環(huán)境變量: ADD、COPY、ENV、EXPOSE、LABEL、USER、WORKDIR、VOLUME、STOPSIGNAL、ONBUILD。
RUN 指令
RUN 指令是在容器內(nèi)執(zhí)行 shell 命令,默認會是用 /bin/sh -c 的方式執(zhí)行。
執(zhí)行命令的兩種方式
RUN <command>(shell形式,該命令在shell中運行)RUN ["executable", "param1", "param2"](exec形式)
之前說過,Dockerfile 中每一個指令都會建立一層,RUN 也不例外。每一個 RUN 的行為,就和剛才我們手工建立鏡像的過程一樣:新建立一層,在其上執(zhí)行這些命令,執(zhí)行結(jié)束后,commit 這一層的修改,構(gòu)成新的鏡像。
注意:Union FS 是有最大層數(shù)限制的,比如 AUFS,曾經(jīng)是最大不得超過 42 層,現(xiàn)在是不得超過 127 層。
所以,在使用 shell 方式,盡量多的使用續(xù)行符\
RUN /bin/bash -c 'source $HOME/.bashrc; \
echo $HOME'
寫 Dockerfile 的時候,要經(jīng)常提醒自己,這并不是在寫 Shell 腳本,而是在定義每一層該如何構(gòu)建。
注意當使用 exec 方式時,需要明確指定 shell 路徑,否則變量可能不會生效
FROM centos
ENV name="yangge"
RUN ["/bin/echo", "$name"]

可以看到
$name被作為普通的字符串輸出了,因為$name是 shell 中的用法,而這里里并沒有 使用到 shell
下面是正確的做法
FROM alpine
ENV name="yangge"
RUN ["/bin/sh", "-c", "/bin/echo $name"]
注意: exec的方式下,列表中的內(nèi)容會被解析為JSON數(shù)組,這意味著您必須在單詞周圍使用雙引號(“) 而非單引號(')。

CMD 指令
Dockerfile 中只能有一條CMD指令。如果列出多個,CMD 則只有最后一個CMD會生效。
CMD 主要目的是為運行容器時提供默認值
Docker 不是虛擬機,容器就是進程,CMD 指令就是用于指定默認的容器主進程的啟動命令的。在啟動(運行)一個容器時可以指定新的命令來替代鏡像設置中的這個默認命令。
可以包含可執(zhí)行文件,當然也可以省略。
CMD 指令的格式和 RUN 相似,也是兩種格式:
-
shell格式:CMD <命令> -
exec格式:CMD ["可執(zhí)行文件", "參數(shù)1", "參數(shù)2"...] - 參數(shù)列表格式:
CMD ["參數(shù)1", "參數(shù)2"...]。在指定了ENTRYPOINT指令后,用CMD指定具體的參數(shù)。
注意:不要混淆
RUN和CMD。RUN實際上運行一個命令并提交結(jié)果;CMD在構(gòu)建時不執(zhí)行任何操作,但指定鏡像的默認命令。
Docker 不是虛擬機,容器內(nèi)沒有后臺服務的概念。
不要期望這樣啟動一個程序到后臺:
CMD systemctl start nginx
這行被 Docker 理解為:
CMD ["sh" "-c" "systemctl start nginx"]
對于容器而言,其啟動程序就是容器的應用進程,容器就是為了主進程而存在的,主進程退出,容器就失去了存在的意義,從而退出,其它輔助進程不是它需要關心的東西。
就像上面的示例中,主進程是 sh , 那么當 service nginx start 命令結(jié)束后,sh 也就結(jié)束了,sh 作為主進程退出了,自然就會使容器退出。
正確的做法是直接執(zhí)行 nginx 這個可執(zhí)行文件,并且關閉后臺守護的方式,使程序在前臺運行。
CMD ["nginx", "-g", "daemon off;"]
ENTRYPOINT 指令
ENTRYPOINT 的目的和 CMD 一樣,都是在指定容器的啟動程序及參數(shù)。
ENTRYPOINT 在運行時也可以被替代,不過比 CMD 要略顯繁瑣,需要通過 docker run 的參數(shù) --entrypoint 來指定。
ENTRYPOINT 的格式和 RUN 指令格式一樣,也分為 exec 格式和 shell 格式。
當指定了 ENTRYPOINT 后,CMD 的含義就發(fā)生了改變,不再是直接的運行其命令,而是將 CMD 的內(nèi)容作為參數(shù)傳給 ENTRYPOINT 指令,也就是實際執(zhí)行時,將變?yōu)椋?/p>
<ENTRYPOINT> "<CMD>"
有了 CMD 后,為什么還要有 ENTRYPOINT 呢?
這種 <ENTRYPOINT> "<CMD>" 給我們帶來了什么好處么?
讓我們來看幾個場景。
-
場景一:讓鏡像變成像命令一樣使用
CMD 方式
FROM centos
RUN yum update \
&& yum install -y curl
CMD [ "curl", "-s", "http://ip.cn" ]
構(gòu)建鏡像后, 運行容器
# docker run --rm centos-echo-ip-cmd
執(zhí)行下面命令會報錯
# docker run --rm centos-echo-ip-cmd -i
我們可以看到報錯,executable file not found。之前我們說過,跟在鏡像名后面的是 command,運行時會替換 CMD 的默認值。因此這里的 -i 并不是添加在原來的 curl -s http://ip.cn 后面。
而是替換了原來的 CMD,變成了 CMD ["-i"],而 -i 根本不是命令,所以報了可執(zhí)行文件找不到。
所以應該使用 ENTRYPOINT 方式
FROM centos
RUN yum install -y curl
ENTRYPOINT ["curl", "-s", "http://ip.cn"]
再次構(gòu)建鏡像后, 運行容器
# docker run --rm centos-echo-ip-entrypoint
# docker run --rm centos-echo-ip-entrypoint -i
這樣的話, 最終的指令就變成 ENTRYPOINT ["curl", "-s", "http://ip.cn", "-i"]
- 場景二:應用運行前的準備工作
啟動容器就是啟動主進程,但有些時候,啟動主進程前,需要一些準備工作。
官方鏡像 redis 中的示例:
FROM alpine:3.4
...
RUN addgroup -S redis && adduser -S -G redis redis
...
ENTRYPOINT ["docker-entrypoint.sh"]
EXPOSE 6379
CMD [ "redis-server" ]
可以看到其中為 redis 服務創(chuàng)建了 redis 用戶,并在最后指定了 ENTRYPOINT 為 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 gosu redis "$0" "$@"
fi
exec "$@"
該腳本的內(nèi)容就是根據(jù) CMD 的內(nèi)容來判斷,如果是 redis-server 的話,則切換到 redis 用戶身份啟動服務器,否則依舊使用 root 身份執(zhí)行。比如:
$ docker run -it redis id
uid=0(root) gid=0(root) groups=0(root)
還有 ENTRYPOINT 指令不會被 RUN 指令覆蓋,而 CMD 指令會被 RUN 指令覆蓋
WORKDIR 指令
用于聲明當前的工作目錄,以后各層的當前目錄就被改為指定的目錄。
格式為 WORKDIR <工作目錄路徑>。
如該目錄不存在,WORKDIR 會幫你建立目錄。
再次強調(diào)!不要以為編寫 Dockerfiel 是在寫 shell 腳本。
下面是一個錯誤示例:
RUN cd /app
RUN echo "hello" > world.txt
如果將這個 Dockerfile 進行構(gòu)建鏡像運行后,會發(fā)現(xiàn)找不到 /app/world.txt 文件,或者其內(nèi)容不是 hello。
原因其實很簡單,這兩行 RUN 命令的執(zhí)行環(huán)境根本不同,是兩個完全不同的容器。這就是對 Dockerfile 構(gòu)建分層存儲的概念不了解所導致的錯誤。
之前說過每一個 RUN 都是啟動一個容器、執(zhí)行命令、然后提交存儲層文件變更。
兩行 RUN 分別構(gòu)建了并啟動了各自全新的容器。
因此如果需要改變以后各層的工作目錄的位置,那么應該使用 WORKDIR 指令。
FROM alpine
WORKDIR /a/b
RUN touch a_b_f.txt
WORKDIR /a
RUN touch a_f.txt
[root@localhost workdir]# docker run -it alpine:workdir /bin/sh
/a # ls
a_f.txt b
/a # cd b
/a/b # ls
a_b_f.txt
COPY 指令
格式:
COPY <源路徑>... <目標路徑>-
COPY ["<源路徑1>",... "<目標路徑>"]和
RUN指令一樣,也有兩種格式,一種類似于命令行,一種類似于函數(shù)調(diào)用。
<目標路徑> 可以是容器內(nèi)的絕對路徑,也可以是相對于 WORKDIR 指定的工作目錄的相對路徑。目標路徑不需要事先創(chuàng)建,如果目錄不存在會在復制文件前先被創(chuàng)建。
COPY 指令將會從構(gòu)建的上下文目錄中,把源路徑的文件或目錄復制到新的一層的鏡像內(nèi)的 <目標路徑> 位置。比如:
COPY qf.json /usr/src/app/
注意下面是錯誤的
COPY qf.json /usr/src/app
這樣會把 qf.json 拷貝成為 /usr/src/ 目錄下的 app 文件
<源路徑> 可以是多個,支持通配符,如:
COPY qf* /app/
COPY q?.txt /app/
使用 COPY 指令,源文件的各種元數(shù)據(jù)都會保留。
比如讀、寫、執(zhí)行權限、文件變更時間等。
COPY 命令的源如果是文件夾,復制的是文件夾的內(nèi)容而不是其本身
ADD 指令
ADD 指令和 COPY 的格式和性質(zhì)基本一致。但是在 COPY 基礎上增加了一些功能。
支持自動解壓縮,壓縮格式支持: gzip, bzip2 以及 xz
官方推薦使用 COPY 進行文件的復制。
ADD 指定會使構(gòu)建鏡像時的緩存失效,導致構(gòu)建鏡像的速度很慢。
COPY 和 ADD 指令中選擇的原則,所有的文件復制均使用 COPY 指令,僅在需要自動解壓縮的場合使用 ADD。
ADD qf.tar.gz /
USER 指令
USER 則是改變執(zhí)行 RUN, CMD 以及 ENTRYPOINT 這類命令的身份。
這個用戶必須是事先在容器內(nèi)存在(建立好)的,否則無法切換。
如果以 root 執(zhí)行的腳本,在執(zhí)行期間希望改變身份,比如希望以某個已經(jīng)建立好的用戶來運行某個服務進程,不要使用 su 或者 sudo,這些都需要比較麻煩的配置,而且在 TTY 缺失的環(huán)境下經(jīng)常出錯。建議使用 gosu。
# 建立 redis 用戶,并使用 gosu 換另一個用戶執(zhí)行命令
RUN groupadd -r redis && useradd -r -g redis redis
# 下載 gosu
RUN wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/1.7/gosu-amd64" \
&& chmod +x /usr/local/bin/gosu \
&& gosu nobody true
# 設置 CMD,并以另外的用戶執(zhí)行
CMD [ "exec", "gosu", "redis", "redis-server" ]
HEALTHCHECK 健康檢查指令
格式:
-
HEALTHCHECK [選項] CMD <命令>:設置檢查容器健康狀況的命令 -
HEALTHCHECK NONE:如果基礎鏡像有健康檢查指令,使用這行可以屏蔽掉其健康檢查指令
HEALTHCHECK 指令是告訴 Docker 應該如何進行判斷容器的狀態(tài)是否正常,這是 Docker 1.12 引入的新指令。
通過該指令指定一行命令,用這行命令來判斷容器主進程的服務狀態(tài)是否還正常,從而比較真實的反應容器實際狀態(tài)。
當在一個鏡像指定了 HEALTHCHECK 指令后,用其啟動容器后的狀態(tài)變化會是下面的演變過程:
初始狀態(tài)會為 starting
在 HEALTHCHECK 指令檢查成功后變?yōu)?healthy
如果連續(xù)一定次數(shù)失敗,則會變?yōu)?unhealthy。
HEALTHCHECK 支持下列選項:
-
--interval=<間隔>:兩次健康檢查的間隔,默認為 30 秒; -
--timeout=<時長>:健康檢查命令運行超時時間,如果超過這個時間,本次健康檢查就被視為失敗,默認 30 秒; -
--retries=<次數(shù)>:當連續(xù)失敗指定次數(shù)后,則將容器狀態(tài)視為unhealthy,默認 3 次。 -
--start-period=<時長>: 容器的初始化實長,默認0秒,不計入健康檢測時間內(nèi)。
和 CMD, ENTRYPOINT 一樣,HEALTHCHECK 在 Dockerfile 中只可以出現(xiàn)一次,如果寫了多個,只有最后一個生效。
后面的命令同樣支持 shell 方式和 exec 方式。
命令的返回值決定了該次健康檢查的成功與否:
0:成功;1:失敗。
示例:
使用 curl 命令來判斷 nginx 提供的 web 服務是否正常。
其 Dockerfile 的 HEALTHCHECK 可以這么寫:
FROM centos
RUN rpm -ivh \
http://mirrors.aliyun.com/epel/epel-release-latest-7.noarch.rpm && yum install nginx \
curl -y
ADD index.html /usr/share/nginx/html/index.html
HEALTHCHECK --interval=5s --timeout=3s CMD curl -fs \
http://localhost/ || exit 1
CMD ["nginx", "-g", "daemon off;"]
EXPOSE 80
這里設置了每 5 秒檢查一次(這里為了試驗所以間隔非常短,實際應該相對較長),如果健康檢查命令超過 3 秒沒響應就視為失敗,并且使用 curl -fs http://localhost/ || exit 1 作為健康檢查命令。
構(gòu)建鏡像后, 啟動容器,并觀察容器的狀態(tài)變化
# docker build -t ali_nginx .
# docker run -d ali_nginx
[root@localhost ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
09a8b90b0f67 ali_nginx "nginx -g 'daemon of…" 4 seconds ago Up 3 seconds (health: starting) 80/tcp vigorous_jang
[root@localhost ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
09a8b90b0f67 ali_nginx "nginx -g 'daemon of…" 19 seconds ago Up 18 seconds (healthy) 80/tcp vigorous_jang
利用元數(shù)據(jù)查看容器的健康狀態(tài)
docker inspect --format '{{json .State.Health}}' vigorous_jang | python -m json.tool
ONBILUD 指令
ONBILUD 指令用于當其他 Dockerfile 以自己為基礎鏡像時將會運行的命令。
格式:ONBUILD <其它指令>。
其他指令可以是: 比如 RUN, COPY 等。
基礎應用場景
假如有兩個項目 A 和 B
兩個項目想分別有不同的文件
A 項目下的文件:
[root@docker onbulid]# tree A/
A/
├── a1.txt
└── a.txt
0 directories, 2 files
B 項目下的文件
[root@docker onbulid]# tree B
B
├── b1.txt
├── b2.txt
└── b.txt
0 directories, 3 files
現(xiàn)在任意的空目錄下創(chuàng)建一個 Dockerfile
文件內(nèi)容:
[root@docker onbulid]# cat Dockerfile
FROM alpine
ONBUILD COPY . /root/
接著用這個 Dockerfile 來構(gòu)建一個所有項目都要使用的一個基礎鏡像
鏡像名字: alpine-base
[root@docker onbulid]# docker build -t alpine-base .
Sending build context to Docker daemon 6.144kB
Step 1/2 : FROM alpine
---> 3fd9065eaf02
Step 2/2 : ONBUILD COPY . /root/
---> Running in 4d6fad2809be
Removing intermediate container 4d6fad2809be
---> 804bfc0b47be
Successfully built 804bfc0b47be
Successfully tagged alpine-base:latest
當使用這個鏡像去運行容器的時候。查看 /root 目錄下,可發(fā)現(xiàn)并沒有任何東西,
說明 COPY . /root/ 并沒有此次構(gòu)建鏡像的過程中去執(zhí)行。
[root@docker onbulid]# docker run --rm alpine-base:latest ls /root/
[root@docker onbulid]#
現(xiàn)在我們在使用剛才構(gòu)建的鏡像為項目 A 的基礎鏡像,來構(gòu)建 A 項目的鏡像
想看看目前 A 項目下的文件:
[root@docker onbulid]# cd A
[root@docker A]# ls
a1.txt a.txt
在項目的 A 目錄下編寫 Dockerfile 文件內(nèi)容如下:
[root@docker A]# cat Dockerfile
FROM alpine-base:latest
是的只需要這一行即可
現(xiàn)在讓我們來構(gòu)建 A 項目的鏡像
[root@docker A]# docker build -t alpine-a .
Sending build context to Docker daemon 3.072kB
Step 1/1 : FROM alpine-base:latest
# Executing 1 build trigger
---> 5a003e1dc65f
Successfully built 5a003e1dc65f
Successfully tagged alpine-a:latest
接著運行以這個鏡像alpine-a:latest為基礎鏡像而運行的容器中的 /root/ 目錄下會有 A 項目目錄下的所有文件:
[root@docker A]# docker run --rm alpine-a:latest ls /root/
Dockerfile
a.txt
a1.txt
B 項目的 Dockerfile 的內(nèi)容:
[root@docker B]# ls
b1.txt b2.txt b.txt
[root@docker B]# cat Dockerfile
FROM alpine-base:latest
同樣構(gòu)建 B 項目的 鏡像,運行容器后可以看到 /root/ 目錄下會有 B 項目目錄下的所有文件
[root@docker B]# docker build -t alpine-b .
Sending build context to Docker daemon 3.584kB
Step 1/1 : FROM alpine-base:latest
# Executing 1 build trigger
---> e66b6ee561a9
Successfully built e66b6ee561a9
Successfully tagged alpine-b:latest
[root@docker B]# docker run --rm alpine-b:latest ls /root/
Dockerfile
b.txt
b1.txt
b2.txt
可以看出,ONBUILD 指令后面內(nèi)容會在,其他鏡像以此鏡像為基礎鏡像構(gòu)建的時候執(zhí)行。
高級應用場景
python 項目都有自己的依賴包,通常會放在項目根目錄下的一個文件,這個文件名叫:requirements.txt
此文件可以通過如下命令得到:
(django) [root@localhost ~]# pip3 freeze > requirements.txt
內(nèi)容一般為:
(django) [root@localhost ~]# head -3 requirement.txt
Django==1.11
PyMySQL==0.8.1
可以使用如下命令來安裝這些項目的依賴模塊。
pip3 install -r requirement.txt
現(xiàn)在假設公司有多個 python3 的項目,每個項目都有自己不同的依賴模塊。需要為每個項目制定一個 Dockerfile 或者鏡像嗎?
比如有兩個項目: CMDB 和 SUPERMAN
下面我們使用 ONBUILD 指令來構(gòu)建一個基礎 python鏡像,
之后兩個項目可以不必修改原來的 Dockerfile 就可以部署自己的環(huán)境依賴包了。
CMDB 的 Dockerfile
CMDB
[root@docker onbulid]# tree CMDB/
CMDB/
├── requirments.txt
└── run.py
[root@docker onbulid]# cat CMDB/requirmants.txt
django==1.11
[root@docker onbulid]# cat CMDB/run.py
import django
print(django.VERSION)
SUPERMAN
[root@docker onbulid]# tree SUPERMAN/
SUPERMAN/
├── requirments.txt
└── run.py
[root@docker onbulid]# cat SUPERMAN/requirmants.txt
django==1.11
[root@docker onbulid]# cat SUPERMAN/run.py
import django
print(django.VERSION)
使用 ONBUILD 指令構(gòu)建 Python 基礎鏡像
[root@docker onbulid]# cat Dockerfile
FROM python
ONBUILD COPY . /opt/
ONBUILD RUN pip3 install -r /opt/requirments.txt
ONBUILD CMD ["python3", "/opt/run.py"]
[root@docker onbulid]# docker build -t python3-base .
之后分別在各自的項目目錄下創(chuàng)建自己的 Dockerfile
CMDB 的 Dockerfile
FROM python3-base
SUPERMAN 的 Dockerfile
FROM python3-base
這樣就可以很簡單的實現(xiàn)不同的項目只需要創(chuàng)建一個同樣內(nèi)容的鏡像,而會得到自己的環(huán)境了。
另外下面的是在 shell 中的執(zhí)行 python 的命令:
FROM python
ONBUILD COPY ./requirement.txt /
ONBUILD RUN pip install -r /requirement.txt
ONBUILD CMD ["python", "-c" "import django;print(django.VERSION)"]
把這個構(gòu)建成所有項目的基礎鏡像,名字為: python-onbuild:v1.0
# docker build -t python-onbuild:v1.0 .
其他 python 項目再使用此鏡像為基礎鏡像時,Dockerfile 中只需一行即可:
FROM python-onbuild:v1.0
更多參考官方 Docker Demo 和官網(wǎng)
- doker demo