Dockerfile 的詳解

我們可以把剛才的對容器的所有操作命令都記錄到一個文件里竟痰,就像寫更腳本程序签钩。

之后用 docker build 命令以此文件為基礎(chǔ)制作一個鏡像掏呼,并會自動提交到本地倉庫坏快。

這樣的話鏡像的構(gòu)建會變的透明化,對鏡像的維護(hù)起來也更加簡單憎夷,只修改這個文件即可莽鸿。

同時分享也更加簡單快捷,因?yàn)橹灰窒磉@個文件即可拾给。

Dokcerfile 是一個普通的文本文件祥得,文件名一般叫 Dockerfile

其中包含了一系列的指令(Instruction), 每一條指令都會構(gòu)建一層蒋得,就是描述該層是如何創(chuàng)建的级及。

小試牛刀

示例:

  1. 編輯 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 定義一個基礎(chǔ)鏡像
  • LABEL 定義一些元數(shù)據(jù)信息,比如作者额衙、版本饮焦、關(guān)于鏡像的描述信息
  • RUN 執(zhí)行命令行的命令

編輯完,保存退出

2.開始構(gòu)建鏡像

命令語法格式:

docker bulid -t 倉庫名/鏡像名:tag .

docker build [選項(xiàng)] <上下文路徑/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 標(biāo)簽
[root@localhost centos_dockerfile]#

構(gòu)建鏡像的上下文(context)

這個 . 表示當(dāng)前目錄窍侧,這實(shí)際上是在指定上下文的目錄是當(dāng)前目錄县踢,docker build 命令會將該目錄下的內(nèi)容打包交給 Docker 引擎以幫助構(gòu)建鏡像。

docker build 命令得知這個路徑后伟件,會將路徑下的所有內(nèi)容打包硼啤,然后上傳給 Docker 引擎。這樣 Docker 引擎收到這個上下文包后斧账,展開就會獲得構(gòu)建鏡像所需的一切文件谴返。

最佳實(shí)戰(zhàn)

一般來說,應(yīng)該會將 Dockerfile 置于一個空目錄下咧织,或者項(xiàng)目根目錄下亏镰。如果該目錄下沒有所需文件,那么應(yīng)該把所需文件復(fù)制一份過來拯爽。如果目錄下有些東西確實(shí)不希望構(gòu)建時傳給 Docker 引擎索抓,那么可以用 .gitignore 一樣的語法寫一個 .dockerignore,該文件是用于剔除不需要作為上下文傳遞給 Docker 引擎的

Dockerfile 的文件名并不要求必須為 Dockerfile,而且并不要求必須位于上下文目錄中逼肯,比如可以用 -f ../Dockerfile.qf 參數(shù)指定某個文件作為 Dockerfile耸黑。

一般大家習(xí)慣性的會使用默認(rèn)的文件名 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)建自定義鏡像的基礎(chǔ)鏡像大刊,在這個基礎(chǔ)鏡像之上進(jìn)行修改定制。

這個指令是 Dockerfile 中的必備指令三椿,同時也必須是第一條指令缺菌。

Docker Store 上有很多高質(zhì)量的官方鏡像,可以直接作為我們的基礎(chǔ)鏡像搜锰。

作為服務(wù)類的伴郁,如 Nginx Mongo

用于開發(fā)的, 如 Python golang

操作系統(tǒng)類蛋叼, 如 Centos ubuntu

除了一些現(xiàn)有的鏡像焊傅,Docker 還有一個特殊的鏡像 scratch

這個鏡像是虛擬的,表示空白鏡像

FORM scratch
...

這以為著這將不以任何鏡像為基礎(chǔ)鏡像狈涮。

可以把可執(zhí)行的二進(jìn)制文件復(fù)制到鏡像中直接執(zhí)行狐胎,容器本身就是和宿主機(jī)共享 Linux內(nèi)核的。

使用 Go 語言開發(fā)的應(yīng)用很多會使用這種方式來制作鏡像歌馍,這也是為什么有人認(rèn)為 Go 是特別適合容器微服務(wù)架構(gòu)的語言的原因之一握巢。

制作自己的 Hello world
  1. 在任意一臺 Linux 機(jī)器上,安裝 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   

沒有的話松却,進(jìn)行安裝即可

[root@localhost hello_qf]# yum install gcc glibc-static 
  1. 編輯 C 源代碼文件
[root@localhost docker]# cat hello.c
#include <stdio.h>

int main()
{
   printf("Hello, Yangge! \n");
   return 0;
}
  1. 將源代碼文件編譯為可執(zhí)行的二進(jìn)制文件
[root@localhost hello_qf]# gcc --static hello.c -o hello

編譯好后暴浦,測試一下

[root@localhost hello_qf]# ls
hello  hello.c
# hello   是我們編譯好的二進(jìn)制文件
# hello.c 是 C 的源碼文件
[root@localhost hello_qf]# ./hello
Hello Yangge     # 輸出結(jié)果,說明編譯成功
  1. 編輯 Dockerfile

在有 hello 二進(jìn)制的文件目錄下玻褪,編譯 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 是把當(dāng)前目錄下的 hello 文檔拷貝到 容器中的根目錄下

  • CMD 執(zhí)行根目錄下的 hello 文件

  1. 構(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
  1. 查看本地倉庫驗(yàn)證
[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

可以看到鏡像很小

  1. 利用新的鏡像運(yùn)行一個容器
[root@localhost hello_qf]# docker run --rm qf/hello-yange
Hello Yangge
關(guān)于 Alpine

官網(wǎng):https://alpinelinux.org/

WIKI https://wiki.alpinelinux.org/wiki/Main_Page

Alpine Linux是一款獨(dú)立的非商業(yè)性通用Linux發(fā)行版,專為那些了解安全性带射,簡單性和資源效率的高級用戶而設(shè)計(jì)始绍。

Alpine Linux圍繞musl libc和busybox構(gòu)建也物。這使得它比傳統(tǒng)的GNU / Linux發(fā)行版更小,更節(jié)省資源。一個容器需要不超過8 MB的空間倔喂,而對磁盤的最小安裝需要大約130 MB的存儲空間捷凄。您不僅可以獲得完整的Linux環(huán)境凿叠,還可以從存儲庫中選擇大量的軟件包俯逾。

二進(jìn)制軟件包被縮減和拆分,使您可以更好地控制安裝的內(nèi)容匣吊,從而使您的環(huán)境盡可能地小巧高效儒拂。

簡單

Alpine Linux是一個非常簡單的發(fā)行版寸潦,它會盡量避免使用。它使用自己的包管理器社痛,稱為apk见转,OpenRC init系統(tǒng),腳本驅(qū)動的設(shè)置蒜哀,就是這樣斩箫!這為您提供了一個簡單,清晰的Linux環(huán)境撵儿,沒有任何噪音乘客。然后,您可以添加項(xiàng)目所需的軟件包淀歇,因此無論是構(gòu)建家用PVR還是iSCSI存儲控制器易核,薄型郵件服務(wù)器容器或堅(jiān)如磐石的嵌入式交換機(jī),其他都不會擋道房匆。

安全

Alpine Linux的設(shè)計(jì)考慮到了安全性耸成。內(nèi)核修補(bǔ)了一個非官方的grsecurity / PaX端口报亩,并且所有的用戶級二進(jìn)制文件被編譯為位置獨(dú)立可執(zhí)行文件(PIE)和堆棧粉碎保護(hù)浴鸿。這些主動安全功能可防止利用整個類別的零日等漏洞。

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標(biāo)簽掸哑。您可以在一行中指定多個標(biāo)簽。并且目前的版本不再會影響到鏡像的大小了零远。

但是仍然可以把他們寫在一行或用反斜線進(jìn)行續(xù)航

LABEL multi.label1="value1" multi.label2="value2" other="value3"
LABEL multi.label1="value1" \
      multi.label2="value2" \
      other="value3"

有繼承關(guān)系的鏡像苗分,標(biāo)簽也會有面向?qū)ο缶幊讨欣^承的關(guān)系和特性

要查看鏡像的 LABEL 信息,請使用該docker inspect命令牵辣。

ENV 指令

用于設(shè)置環(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择浊、ENVEXPOSE逾条、LABEL琢岩、USERWORKDIR师脂、VOLUME担孔、STOPSIGNAL江锨、ONBUILD

RUN 指令

RUN 指令是在容器內(nèi)執(zhí)行 shell 命令糕篇,默認(rèn)會是用 /bin/sh -c 的方式執(zhí)行泳桦。

執(zhí)行命令的兩種方式
  • RUN <command>shell形式,該命令在shell中運(yùn)行)

  • 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)建挟憔。

注意當(dāng)使用 exec 方式時钟些,需要明確指定 shell 路徑,否則變量可能不會生效

FROM centos
ENV name="yangge"
RUN ["/bin/echo", "$name"]
無效的變量

可以看到 $name 被作為普通的字符串輸出了绊谭,因?yàn)?$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 主要目的是為運(yùn)行容器時提供默認(rèn)值

Docker 不是虛擬機(jī),容器就是進(jìn)程逊朽,CMD 指令就是用于指定默認(rèn)的容器主進(jìn)程的啟動命令的罕伯。在啟動(運(yùn)行)一個容器時可以指定新的命令來替代鏡像設(shè)置中的這個默認(rèn)命令。

可以包含可執(zhí)行文件叽讳,當(dāng)然也可以省略追他。

CMD 指令的格式和 RUN 相似坟募,也是兩種格式:

  • shell 格式:CMD <命令>
  • exec 格式:CMD ["可執(zhí)行文件", "參數(shù)1", "參數(shù)2"...]
  • 參數(shù)列表格式:CMD ["參數(shù)1", "參數(shù)2"...]。在指定了 ENTRYPOINT 指令后邑狸,用 CMD 指定具體的參數(shù)懈糯。

注意:不要混淆RUNCMDRUN實(shí)際上運(yùn)行一個命令并提交結(jié)果; CMD在構(gòu)建時不執(zhí)行任何操作单雾,但指定鏡像的默認(rèn)命令赚哗。

Docker 不是虛擬機(jī),容器內(nèi)沒有后臺服務(wù)的概念硅堆。

不要期望這樣啟動一個程序到后臺:

CMD systemctl start nginx

這行被 Docker 理解為:

CMD ["sh" "-c" "systemctl start nginx"]

對于容器而言屿储,其啟動程序就是容器的應(yīng)用進(jìn)程,容器就是為了主進(jìn)程而存在的渐逃,主進(jìn)程退出够掠,容器就失去了存在的意義,從而退出茄菊,其它輔助進(jìn)程不是它需要關(guān)心的東西疯潭。

就像上面的示例中,主進(jìn)程是 sh , 那么當(dāng) service nginx start 命令結(jié)束后面殖,sh 也就結(jié)束了竖哩,sh 作為主進(jìn)程退出了,自然就會使容器退出畜普。

正確的做法是直接執(zhí)行 nginx 這個可執(zhí)行文件期丰,并且關(guān)閉后臺守護(hù)的方式群叶,使程序在前臺運(yùn)行吃挑。

CMD ["nginx", "-g", "daemon off;"]

ENTRYPOINT 指令

ENTRYPOINT 的目的和 CMD 一樣,都是在指定容器的啟動程序及參數(shù)街立。

ENTRYPOINT 在運(yùn)行時也可以被替代舶衬,不過比 CMD 要略顯繁瑣,需要通過 docker run 的參數(shù) --entrypoint 來指定赎离。

ENTRYPOINT 的格式和 RUN 指令格式一樣逛犹,也分為 exec 格式和 shell 格式。

當(dāng)指定了 ENTRYPOINT 后梁剔,CMD 的含義就發(fā)生了改變虽画,不再是直接的運(yùn)行其命令,而是將 CMD 的內(nèi)容作為參數(shù)傳給 ENTRYPOINT 指令荣病,也就是實(shí)際執(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)建鏡像后, 運(yùn)行容器

# docker run --rm centos-echo-ip-cmd

執(zhí)行下面命令會報錯

# docker run --rm centos-echo-ip-cmd -i

我們可以看到報錯,executable file not found柴梆。之前我們說過陨溅,跟在鏡像名后面的是 command,運(yùn)行時會替換 CMD 的默認(rèn)值绍在。因此這里的 -i 并不是添加在原來的 curl -s http://ip.cn 后面门扇。
而是替換了原來的 CMD,變成了 CMD ["-i"]偿渡,而 -i 根本不是命令悯嗓,所以報了可執(zhí)行文件找不到

所以應(yīng)該使用 ENTRYPOINT 方式

FROM centos
RUN yum install -y curl
ENTRYPOINT ["curl", "-s", "http://ip.cn"]

再次構(gòu)建鏡像后, 運(yùn)行容器

# docker run --rm centos-echo-ip-entrypoint
# docker run --rm centos-echo-ip-entrypoint -i

這樣的話, 最終的指令就變成 ENTRYPOINT ["curl", "-s", "http://ip.cn", "-i"]

  • 場景二:應(yīng)用運(yùn)行前的準(zhǔn)備工作

啟動容器就是啟動主進(jìn)程卸察,但有些時候脯厨,啟動主進(jìn)程前,需要一些準(zhǔn)備工作坑质。

官方鏡像 redis 中的示例:

FROM alpine:3.4
...
RUN addgroup -S redis && adduser -S -G redis redis
...
ENTRYPOINT ["docker-entrypoint.sh"]

EXPOSE 6379
CMD [ "redis-server" ]

可以看到其中為 redis 服務(wù)創(chuàng)建了 redis 用戶合武,并在最后指定了 ENTRYPOINTdocker-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 用戶身份啟動服務(wù)器,否則依舊使用 root 身份執(zhí)行吃沪。比如:

$ docker run -it redis id
uid=0(root) gid=0(root) groups=0(root)

還有 ENTRYPOINT 指令不會被 RUN 指令覆蓋汤善,而 CMD 指令會被 RUN 指令覆蓋

WORKDIR 指令

用于聲明當(dāng)前的工作目錄,以后各層的當(dāng)前目錄就被改為指定的目錄票彪。

格式為 WORKDIR <工作目錄路徑>红淡。

如該目錄不存在,WORKDIR 會幫你建立目錄降铸。

再次強(qiáng)調(diào)在旱!不要以為編寫 Dockerfiel 是在寫 shell 腳本。

下面是一個錯誤示例:

RUN cd /app
RUN echo "hello" > world.txt

如果將這個 Dockerfile 進(jìn)行構(gòu)建鏡像運(yùn)行后推掸,會發(fā)現(xiàn)找不到 /app/world.txt 文件桶蝎,或者其內(nèi)容不是 hello

原因其實(shí)很簡單谅畅,這兩行 RUN 命令的執(zhí)行環(huán)境根本不同登渣,是兩個完全不同的容器。這就是對 Dockerfile 構(gòu)建分層存儲的概念不了解所導(dǎo)致的錯誤毡泻。

之前說過每一個 RUN 都是啟動一個容器胜茧、執(zhí)行命令、然后提交存儲層文件變更牙捉。

兩行 RUN 分別構(gòu)建了并啟動了各自全新的容器竹揍。

因此如果需要改變以后各層的工作目錄的位置敬飒,那么應(yīng)該使用 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 <源路徑>... <目標(biāo)路徑>

  • COPY ["<源路徑1>",... "<目標(biāo)路徑>"]

    RUN 指令一樣芬位,也有兩種格式无拗,一種類似于命令行,一種類似于函數(shù)調(diào)用昧碉。

<目標(biāo)路徑> 可以是容器內(nèi)的絕對路徑英染,也可以是相對于 WORKDIR 指定的工作目錄的相對路徑。目標(biāo)路徑不需要事先創(chuàng)建被饿,如果目錄不存在會在復(fù)制文件前先被創(chuàng)建四康。

COPY 指令將會從構(gòu)建的上下文目錄中,把源路徑的文件或目錄復(fù)制到新的一層的鏡像內(nèi)的 <目標(biāo)路徑> 位置狭握。比如:

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í)行權(quán)限漏设、文件變更時間等。

COPY 命令的源如果是文件夾今妄,復(fù)制的是文件夾的內(nèi)容而不是其本身

ADD 指令

ADD 指令和 COPY 的格式和性質(zhì)基本一致郑口。但是在 COPY 基礎(chǔ)上增加了一些功能。

支持自動解壓縮盾鳞,壓縮格式支持: gzip, bzip2 以及 xz

官方推薦使用 COPY 進(jìn)行文件的復(fù)制犬性。

ADD 指定會使構(gòu)建鏡像時的緩存失效,導(dǎo)致構(gòu)建鏡像的速度很慢雁仲。

COPYADD 指令中選擇的原則仔夺,所有的文件復(fù)制均使用 COPY 指令,僅在需要自動解壓縮的場合使用 ADD攒砖。

ADD qf.tar.gz  /

USER 指令

USER 則是改變執(zhí)行 RUN, CMD 以及 ENTRYPOINT 這類命令的身份。

這個用戶必須是事先在容器內(nèi)存在(建立好)的日裙,否則無法切換吹艇。

如果以 root 執(zhí)行的腳本,在執(zhí)行期間希望改變身份昂拂,比如希望以某個已經(jīng)建立好的用戶來運(yùn)行某個服務(wù)進(jìn)程受神,不要使用 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
# 設(shè)置 CMD撑碴,并以另外的用戶執(zhí)行
CMD [ "exec", "gosu", "redis", "redis-server" ]

HEALTHCHECK 健康檢查指令

格式:

  • HEALTHCHECK [選項(xiàng)] CMD <命令>:設(shè)置檢查容器健康狀況的命令
  • HEALTHCHECK NONE:如果基礎(chǔ)鏡像有健康檢查指令撑教,使用這行可以屏蔽掉其健康檢查指令

HEALTHCHECK 指令是告訴 Docker 應(yīng)該如何進(jìn)行判斷容器的狀態(tài)是否正常,這是 Docker 1.12 引入的新指令醉拓。

通過該指令指定一行命令伟姐,用這行命令來判斷容器主進(jìn)程的服務(wù)狀態(tài)是否還正常,從而比較真實(shí)的反應(yīng)容器實(shí)際狀態(tài)亿卤。

當(dāng)在一個鏡像指定了 HEALTHCHECK 指令后愤兵,用其啟動容器后的狀態(tài)變化會是下面的演變過程:

初始狀態(tài)會為 starting

HEALTHCHECK 指令檢查成功后變?yōu)?healthy

如果連續(xù)一定次數(shù)失敗,則會變?yōu)?unhealthy排吴。

HEALTHCHECK 支持下列選項(xiàng):

  • --interval=<間隔>:兩次健康檢查的間隔秆乳,默認(rèn)為 30 秒;
  • --timeout=<時長>:健康檢查命令運(yùn)行超時時間钻哩,如果超過這個時間矫夷,本次健康檢查就被視為失敗,默認(rèn) 30 秒憋槐;
  • --retries=<次數(shù)>:當(dāng)連續(xù)失敗指定次數(shù)后双藕,則將容器狀態(tài)視為 unhealthy,默認(rèn) 3 次阳仔。
  • --start-period=<時長>: 容器的初始化實(shí)長忧陪,默認(rèn)0秒,不計(jì)入健康檢測時間內(nèi)近范。

CMD, ENTRYPOINT 一樣嘶摊,HEALTHCHECK 在 Dockerfile 中只可以出現(xiàn)一次,如果寫了多個评矩,只有最后一個生效叶堆。

后面的命令同樣支持 shell 方式和 exec 方式。

命令的返回值決定了該次健康檢查的成功與否:

0:成功斥杜;1:失敗虱颗。

示例:

使用 curl 命令來判斷 nginx 提供的 web 服務(wù)是否正常。

DockerfileHEALTHCHECK 可以這么寫:

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

這里設(shè)置了每 5 秒檢查一次(這里為了試驗(yàn)所以間隔非常短蔗喂,實(shí)際應(yīng)該相對較長)忘渔,如果健康檢查命令超過 3 秒沒響應(yīng)就視為失敗,并且使用 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 指令用于當(dāng)其他 Dockerfile 以自己為基礎(chǔ)鏡像時將會運(yùn)行的命令。

格式:ONBUILD <其它指令>宣赔。

其他指令可以是: 比如 RUN, COPY 等预麸。

基礎(chǔ)應(yīng)用場景

假如有兩個項(xiàng)目 A 和 B

兩個項(xiàng)目想分別有不同的文件

A 項(xiàng)目下的文件:

[root@docker onbulid]# tree A/
A/
├── a1.txt
└── a.txt

0 directories, 2 files

B 項(xiàng)目下的文件

[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)建一個所有項(xiàng)目都要使用的一個基礎(chǔ)鏡像

鏡像名字: 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

當(dāng)使用這個鏡像去運(yùn)行容器的時候。查看 /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)建的鏡像為項(xiàng)目 A 的基礎(chǔ)鏡像椅棺,來構(gòu)建 A 項(xiàng)目的鏡像

想看看目前 A 項(xiàng)目下的文件:

[root@docker onbulid]# cd A
[root@docker A]# ls
a1.txt  a.txt

在項(xiàng)目的 A 目錄下編寫 Dockerfile 文件內(nèi)容如下:

[root@docker A]# cat Dockerfile
FROM alpine-base:latest

是的只需要這一行即可

現(xiàn)在讓我們來構(gòu)建 A 項(xiàng)目的鏡像

[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

接著運(yùn)行以這個鏡像alpine-a:latest為基礎(chǔ)鏡像而運(yùn)行的容器中的 /root/ 目錄下會有 A 項(xiàng)目目錄下的所有文件:

[root@docker A]# docker run --rm alpine-a:latest ls /root/
Dockerfile
a.txt
a1.txt

B 項(xiàng)目的 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 項(xiàng)目的 鏡像犁罩,運(yùn)行容器后可以看到 /root/ 目錄下會有 B 項(xiàng)目目錄下的所有文件

[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)容會在两疚,其他鏡像以此鏡像為基礎(chǔ)鏡像構(gòu)建的時候執(zhí)行床估。

高級應(yīng)用場景

python 項(xiàng)目都有自己的依賴包,通常會放在項(xiàng)目根目錄下的一個文件诱渤,這個文件名叫: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

可以使用如下命令來安裝這些項(xiàng)目的依賴模塊丐巫。

pip3 install -r requirement.txt

現(xiàn)在假設(shè)公司有多個 python3 的項(xiàng)目,每個項(xiàng)目都有自己不同的依賴模塊勺美。需要為每個項(xiàng)目制定一個 Dockerfile 或者鏡像嗎递胧?

比如有兩個項(xiàng)目: CMDB 和 SUPERMAN

下面我們使用 ONBUILD 指令來構(gòu)建一個基礎(chǔ) python鏡像,

之后兩個項(xiàng)目可以不必修改原來的 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 基礎(chǔ)鏡像

[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 .

之后分別在各自的項(xiàng)目目錄下創(chuàng)建自己的 Dockerfile

CMDB 的 Dockerfile

FROM python3-base

SUPERMAN 的 Dockerfile

FROM python3-base

這樣就可以很簡單的實(shí)現(xiàn)不同的項(xiàng)目只需要創(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)建成所有項(xiàng)目的基礎(chǔ)鏡像占卧,名字為: python-onbuild:v1.0

# docker build -t python-onbuild:v1.0 .

其他 python 項(xiàng)目再使用此鏡像為基礎(chǔ)鏡像時遗菠,Dockerfile 中只需一行即可:

FROM python-onbuild:v1.0

更多參考官方 Docker Demo 和官網(wǎng)

  1. doker demo

https://github.com/docker-library

  1. 官網(wǎng)

    https://docs.docker.com/engine/reference/builder/

    https://docs.docker.com/develop/develop-images/dockerfile_best-practices/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市华蜒,隨后出現(xiàn)的幾起案子辙纬,更是在濱河造成了極大的恐慌,老刑警劉巖叭喜,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件贺拣,死亡現(xiàn)場離奇詭異,居然都是意外死亡捂蕴,警方通過查閱死者的電腦和手機(jī)譬涡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來启绰,“玉大人昂儒,你說我怎么就攤上這事∥桑” “怎么了?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長着倾。 經(jīng)常有香客問我拾酝,道長,這世上最難降的妖魔是什么卡者? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任蒿囤,我火速辦了婚禮,結(jié)果婚禮上崇决,老公的妹妹穿的比我還像新娘材诽。我一直安慰自己,他們只是感情好恒傻,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布脸侥。 她就那樣靜靜地躺著,像睡著了一般盈厘。 火紅的嫁衣襯著肌膚如雪睁枕。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天沸手,我揣著相機(jī)與錄音外遇,去河邊找鬼。 笑死契吉,一個胖子當(dāng)著我的面吹牛跳仿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播捐晶,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼菲语,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了租悄?” 一聲冷哼從身側(cè)響起谨究,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎泣棋,沒想到半個月后胶哲,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡潭辈,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年鸯屿,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片把敢。...
    茶點(diǎn)故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡寄摆,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出修赞,到底是詐尸還是另有隱情婶恼,我是刑警寧澤桑阶,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站勾邦,受9級特大地震影響蚣录,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜眷篇,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一萎河、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蕉饼,春花似錦虐杯、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至慨飘,卻和暖如春确憨,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背瓤的。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工休弃, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人圈膏。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓塔猾,卻偏偏與公主長得像,于是被迫代替她去往敵國和親稽坤。 傳聞我的和親對象是個殘疾皇子丈甸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內(nèi)容