真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

怎么使用DockerDockerfile定制鏡像

這篇“怎么使用Docker Dockerfile定制鏡像”文章的知識(shí)點(diǎn)大部分人都不太理解,所以小編給大家總結(jié)了以下內(nèi)容,內(nèi)容詳細(xì),步驟清晰,具有一定的借鑒價(jià)值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來(lái)看看這篇“怎么使用Docker Dockerfile定制鏡像”文章吧。

創(chuàng)新互聯(lián)建站是一家專(zhuān)業(yè)提供高昌企業(yè)網(wǎng)站建設(shè),專(zhuān)注與網(wǎng)站設(shè)計(jì)制作、做網(wǎng)站、H5開(kāi)發(fā)、小程序制作等業(yè)務(wù)。10年已為高昌眾多企業(yè)、政府機(jī)構(gòu)等服務(wù)。創(chuàng)新互聯(lián)專(zhuān)業(yè)網(wǎng)絡(luò)公司優(yōu)惠進(jìn)行中。

使用 dockerfile 定制鏡像

鏡像的定制實(shí)際上就是定制每一層所添加的配置、文件。如果我們可以把每一層修改、安裝、構(gòu)建、操作的命令都寫(xiě)入一個(gè)腳本,用這個(gè)腳本來(lái)構(gòu)建、定制鏡像,那么無(wú)法重復(fù)的問(wèn)題、鏡像構(gòu)建透明性的問(wèn)題、體積的問(wèn)題就都會(huì)解決。這個(gè)腳本就是 dockerfile。

dockerfile 是一個(gè)文本文件,其內(nèi)包含了一條條的指令(instruction),每一條指令構(gòu)建一層,因此每一條指令的內(nèi)容,就是描述該層應(yīng)當(dāng)如何構(gòu)建。

此處以定制 nginx 鏡像為例,使用 dockerfile 來(lái)定制。

在一個(gè)空白目錄中,建立一個(gè)文本文件,并命名為 dockerfile :

$ mkdir mynginx
$ cd mynginx
$ touch dockerfile

其內(nèi)容為:

from nginx
run echo '

hello, docker!

' > /usr/share/nginx/html/index.html

這個(gè) dockerfile 很簡(jiǎn)單,一共就兩行。涉及到了兩條指令, from 和 run 。

dockerfile 指令詳解

from 指定基礎(chǔ)鏡像

所謂定制鏡像,那一定是以一個(gè)鏡像為基礎(chǔ),在其上進(jìn)行定制。而 from 就是指定基礎(chǔ)鏡像,因此一個(gè) dockerfile 中 from 是必備的指令,并且必須是第一條指令。

在 docker store 上有非常多的高質(zhì)量的官方鏡像,有可以直接拿來(lái)使用的服務(wù)類(lèi)的鏡像,如nginx 、 redis 、 mongo 、MySQL 等;也有一些方便開(kāi)發(fā)、構(gòu)建、運(yùn)行各種語(yǔ)言應(yīng)用的鏡像,如 node 、 openjdk 、 python 等??梢栽谄渲袑ふ乙粋€(gè)最符合我們最終目標(biāo)的鏡像為基礎(chǔ)鏡像進(jìn)行定制。

如果沒(méi)有找到對(duì)應(yīng)服務(wù)的鏡像,官方鏡像中還提供了一些更為基礎(chǔ)的操作系統(tǒng)鏡像,如ubuntu 、 debian 、 centos 等,這些操作系統(tǒng)的軟件庫(kù)為我們提供了更廣闊的擴(kuò)展空間。

除了選擇現(xiàn)有鏡像為基礎(chǔ)鏡像外,docker 還存在一個(gè)特殊的鏡像,名為 scratch 。這個(gè)鏡像是虛擬的概念,并不實(shí)際存在,它表示一個(gè)空白的鏡像。

from scratch
...

如果你以 scratch 為基礎(chǔ)鏡像的話,意味著你不以任何鏡像為基礎(chǔ),接下來(lái)所寫(xiě)的指令將作為鏡像第一層開(kāi)始存在。

不以任何系統(tǒng)為基礎(chǔ),直接將可執(zhí)行文件復(fù)制進(jìn)鏡像的做法并不罕見(jiàn),比如 swarm 、 coreos/etcd 。對(duì)于 linux 下靜態(tài)編譯的程序來(lái)說(shuō),并不需要有操作系統(tǒng)提供運(yùn)行時(shí)支持,所需的一切庫(kù)都已經(jīng)在可執(zhí)行文件里了,因此直接 from scratch 會(huì)讓鏡像體積更加小巧。使用 go 語(yǔ)言 開(kāi)發(fā)的應(yīng)用很多會(huì)使用這種方式來(lái)制作鏡像,這也是為什么有人認(rèn)為 go是特別適合容器微服務(wù)架構(gòu)的語(yǔ)言的原因之一。

run 執(zhí)行命令

run 指令是用來(lái)執(zhí)行命令行命令的。由于命令行的強(qiáng)大能力, run 指令在定制鏡像時(shí)是最常用的指令之一。其格式有兩種:

shell 格式: run <命令> ,就像直接在命令行中輸入的命令一樣。剛才寫(xiě)的 dockerfile 中的 run 指令就是這種格式。

run echo '

hello, docker!

' > /usr/share/nginx/html/index.html

exec 格式: run ["可執(zhí)行文件", "參數(shù)1", "參數(shù)2"],這更像是函數(shù)調(diào)用中的格式。
既然 run 就像 shell 腳本一樣可以執(zhí)行命令,那么我們是否就可以像 shell 腳本一樣把每個(gè)命令對(duì)應(yīng)一個(gè) run 呢?比如這樣:

from debian:jessie
run apt-get update
run apt-get install -y gcc libc6-dev make
run wget -o redis.tar.gz "http://download.redis.io/releases/redis-3.2.5.tar.gz"
run mkdir -p /usr/src/redis
run tar -xzf redis.tar.gz -c /usr/src/redis --strip-components=1
run make -c /usr/src/redis
run make -c /usr/src/redis install

之前說(shuō)過(guò),dockerfile 中每一個(gè)指令都會(huì)建立一層, run 也不例外。每一個(gè) run 的行為,就和剛才我們手工建立鏡像的過(guò)程一樣:新建立一層,在其上執(zhí)行這些命令,執(zhí)行結(jié)束后, commit 這一層的修改,構(gòu)成新的鏡像。

而上面的這種寫(xiě)法,創(chuàng)建了 7 層鏡像。這是完全沒(méi)有意義的,而且很多運(yùn)行時(shí)不需要的東西,都被裝進(jìn)了鏡像里,比如編譯環(huán)境、更新的軟件包等等。結(jié)果就是產(chǎn)生非常臃腫、非常多層的鏡像,不僅僅增加了構(gòu)建部署的時(shí)間,也很容易出錯(cuò)。 這是很多初學(xué) docker 的人常犯的一個(gè)錯(cuò)誤(我也不能原諒自己ε=(´ο`*)))唉)。

union fs 是有最大層數(shù)限制的,比如 aufs,曾經(jīng)是最大不得超過(guò) 42 層,現(xiàn)在是不得超過(guò)127 層。

上面的 dockerfile 正確的寫(xiě)法應(yīng)該是這樣:

from debian:jessie
run builddeps='gcc libc6-dev make' \
  && apt-get update \
  && apt-get install -y $builddeps \
  && wget -o redis.tar.gz "http://download.redis.io/releases/redis-3.2.5.tar.gz" \
  && mkdir -p /usr/src/redis \
  && tar -xzf redis.tar.gz -c /usr/src/redis --strip-components=1 \
  && make -c /usr/src/redis \
  && make -c /usr/src/redis install \
  && rm -rf /var/lib/apt/lists/* \
  && rm redis.tar.gz \
  && rm -r /usr/src/redis \
  && apt-get purge -y --auto-remove $builddeps

首先,之前所有的命令只有一個(gè)目的,就是編譯、安裝 redis 可執(zhí)行文件。因此沒(méi)有必要建立很多層,這只是一層的事情。因此,這里沒(méi)有使用很多個(gè) run 對(duì)一一對(duì)應(yīng)不同的命令,而是僅僅使用一個(gè) run 指令,并使用 && 將各個(gè)所需命令串聯(lián)起來(lái)。將之前的 7 層,簡(jiǎn)化為了1 層。在撰寫(xiě) dockerfile 的時(shí)候,要經(jīng)常提醒自己,這并不是在寫(xiě) shell 腳本,而是在定義每一層該如何構(gòu)建。

并且,這里為了格式化還進(jìn)行了換行。dockerfile 支持 shell 類(lèi)的行尾添加 \ 的命令換行方式,以及行首 # 進(jìn)行注釋的格式。良好的格式,比如換行、縮進(jìn)、注釋等,會(huì)讓維護(hù)、排障更為容易,這是一個(gè)比較好的習(xí)慣。

此外,還可以看到這一組命令的最后添加了清理工作的命令,刪除了為了編譯構(gòu)建所需要的軟件,清理了所有下載、展開(kāi)的文件,并且還清理了 apt 緩存文件。這是很重要的一步,之前有說(shuō)過(guò),鏡像是多層存儲(chǔ),每一層的東西并不會(huì)在下一層被刪除,會(huì)一直跟隨著鏡像。因此鏡像構(gòu)建時(shí),一定要確保每一層只添加真正需要添加的東西,任何無(wú)關(guān)的東西都應(yīng)該清理掉。

很多人初學(xué) docker 制作出了很臃腫的鏡像的原因之一,就是忘記了每一層構(gòu)建的最后一定要清理掉無(wú)關(guān)文件。

構(gòu)建鏡像

好了,讓我們?cè)倩氐街岸ㄖ频?nginx 鏡像的 dockerfile 來(lái)。現(xiàn)在我們明白了這個(gè) dockerfile的內(nèi)容,那么讓我們來(lái)構(gòu)建這個(gè)鏡像吧。

在 dockerfile 文件所在目錄執(zhí)行:

$ docker build -t nginx:v3 .
sending build context to docker daemon 2.048 kb
step 1 : from nginx
---> e43d811ce2f4
step 2 : run echo '

hello, docker!

' > /usr/share/nginx/html/index.html ---> running in 9cdc27646c7b ---> 44aa4490ce2c removing intermediate container 9cdc27646c7b successfully built 44aa4490ce2c

從命令的輸出結(jié)果中,我們可以清晰的看到鏡像的構(gòu)建過(guò)程。在 step 2 中,如同我們之前所說(shuō)的那樣, run 指令啟動(dòng)了一個(gè)容器 9cdc27646c7b ,執(zhí)行了所要求的命令,并最后提交了這一層 44aa4490ce2c ,隨后刪除了所用到的這個(gè)容器 9cdc27646c7b 。

這里我們使用了 docker build 命令進(jìn)行鏡像構(gòu)建。其格式為:

docker build [選項(xiàng)] <上下文路徑/url/->

在這里我們指定了最終鏡像的名稱(chēng) -t nginx:v3 ,構(gòu)建成功后,我們可以直接運(yùn)行這個(gè)鏡像,其結(jié)果就是我們的主頁(yè)被改變成了hello, docker!。

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

如果注意,會(huì)看到 docker build 命令最后有一個(gè) . 。 . 表示當(dāng)前目錄,而 dockerfile就在當(dāng)前目錄,因此不少初學(xué)者以為這個(gè)路徑是在指定 dockerfile 所在路徑,這么理解其實(shí)是不準(zhǔn)確的。如果對(duì)應(yīng)上面的命令格式,你可能會(huì)發(fā)現(xiàn),這是在指定上下文路徑。那么什么是上下文呢?

首先我們要理解 docker build 的工作原理。docker 在運(yùn)行時(shí)分為 docker 引擎(也就是服務(wù)端守護(hù)進(jìn)程)和客戶端工具。docker 的引擎提供了一組 rest api,被稱(chēng)為 dockerremote api,而如 docker 命令這樣的客戶端工具,則是通過(guò)這組 api 與 docker 引擎交互,從而完成各種功能。因此,雖然表面上我們好像是在本機(jī)執(zhí)行各種 docker 功能,但實(shí)際上,一切都是使用的遠(yuǎn)程調(diào)用形式在服務(wù)端(docker 引擎)完成。也因?yàn)檫@種 c/s 設(shè)計(jì),讓我們操作遠(yuǎn)程服務(wù)器的 docker 引擎變得輕而易舉。

當(dāng)我們進(jìn)行鏡像構(gòu)建的時(shí)候,并非所有定制都會(huì)通過(guò) run 指令完成,經(jīng)常會(huì)需要將一些本地文件復(fù)制進(jìn)鏡像,比如通過(guò) copy 指令、 add 指令等。而 docker build 命令構(gòu)建鏡像,其實(shí)并非在本地構(gòu)建,而是在服務(wù)端,也就是 docker 引擎中構(gòu)建的。那么在這種客戶端/服務(wù)端的架構(gòu)中,如何才能讓服務(wù)端獲得本地文件呢?

這就引入了上下文的概念。當(dāng)構(gòu)建的時(shí)候,用戶會(huì)指定構(gòu)建鏡像上下文的路徑, docker build 命令得知這個(gè)路徑后,會(huì)將路徑下的所有內(nèi)容打包,然后上傳給 docker 引擎。這樣docker 引擎收到這個(gè)上下文包后,展開(kāi)就會(huì)獲得構(gòu)建鏡像所需的一切文件。

如果在 dockerfile 中這么寫(xiě):

copy ./package.json /app/

這并不是要復(fù)制執(zhí)行 docker build 命令所在的目錄下的 package.json ,也不是復(fù)制 dockerfile 所在目錄下的 package.json ,而是復(fù)制 上下文(context) 目錄下的 package.json 。

因此, copy 這類(lèi)指令中的源文件的路徑都是相對(duì)路徑。這也是初學(xué)者經(jīng)常會(huì)問(wèn)的為什么 copy ../package.json /app 或者 copy /opt/xxxx /app 無(wú)法工作的原因,因?yàn)檫@些路徑已經(jīng)超出了上下文的范圍,docker 引擎無(wú)法獲得這些位置的文件。如果真的需要那些文件,應(yīng)該將它們復(fù)制到上下文目錄中去。

現(xiàn)在就可以理解剛才的命令 docker build -t nginx:v3 . 中的這個(gè) . ,實(shí)際上是在指定上下文的目錄, docker build 命令會(huì)將該目錄下的內(nèi)容打包交給 docker 引擎以幫助構(gòu)建鏡像。

如果觀察 docker build 輸出,我們其實(shí)已經(jīng)看到了這個(gè)發(fā)送上下文的過(guò)程:

$ docker build -t nginx:v3 .
sending build context to docker daemon 2.048 kb
...

理解構(gòu)建上下文對(duì)于鏡像構(gòu)建是很重要的,避免犯一些不應(yīng)該的錯(cuò)誤。比如有些初學(xué)者在發(fā)現(xiàn) copy /opt/xxxx /app 不工作后,于是干脆將 dockerfile 放到了硬盤(pán)根目錄去構(gòu)建,結(jié)果發(fā)現(xiàn) docker build 執(zhí)行后,在發(fā)送一個(gè)幾十 gb 的東西,極為緩慢而且很容易構(gòu)建失敗。那是因?yàn)檫@種做法是在讓 docker build 打包整個(gè)硬盤(pán),這顯然是使用錯(cuò)誤。

一般來(lái)說(shuō),應(yīng)該會(huì)將 dockerfile 置于一個(gè)空目錄下,或者項(xiàng)目根目錄下。如果該目錄下沒(méi)有所需文件,那么應(yīng)該把所需文件復(fù)制一份過(guò)來(lái)。如果目錄下有些東西確實(shí)不希望構(gòu)建時(shí)傳給 docker 引擎,那么可以用 .gitignore 一樣的語(yǔ)法寫(xiě)一個(gè) .dockerignore ,該文件是用于剔除不需要作為上下文傳遞給 docker 引擎的。

那么為什么會(huì)有人誤以為 . 是指定 dockerfile 所在目錄呢?這是因?yàn)樵谀J(rèn)情況下,如果不額外指定 dockerfile 的話,會(huì)將上下文目錄下的名為 dockerfile 的文件作為 dockerfile。

這只是默認(rèn)行為,實(shí)際上 dockerfile 的文件名并不要求必須為 dockerfile ,而且并不要求必須位于上下文目錄中,比如可以用 -f ../dockerfile.php 參數(shù)指定某個(gè)文件作為 dockerfile 。

當(dāng)然,一般大家習(xí)慣性的會(huì)使用默認(rèn)的文件名 dockerfile ,以及會(huì)將其置于鏡像構(gòu)建上下文目錄中。

其它 docker build 的用法

直接用 git repo 進(jìn)行構(gòu)建

或許你已經(jīng)注意到了, docker build 還支持從 url 構(gòu)建,比如可以直接從 git repo 中構(gòu)建:

$ docker build https://github.com/twang2218/gitlab-ce-zh.git#:8.14
docker build https://github.com/twang2218/gitlab-ce-zh.git\#:8.14
sending build context to docker daemon 2.048 kb
step 1 : from gitlab/gitlab-ce:8.14.0-ce.0
8.14.0-ce.0: pulling from gitlab/gitlab-ce
aed15891ba52: already exists
773ae8583d14: already exists
...

這行命令指定了構(gòu)建所需的 git repo,并且指定默認(rèn)的 master 分支,構(gòu)建目錄為 /8.14/ ,然后 docker 就會(huì)自己去 git clone 這個(gè)項(xiàng)目、切換到指定分支、并進(jìn)入到指定目錄后開(kāi)始構(gòu)建。

用給定的 tar 壓縮包構(gòu)建

$ docker build http://server/context.tar.gz

如果所給出的 url 不是個(gè) git repo,而是個(gè) tar 壓縮包,那么 docker 引擎會(huì)下載這個(gè)包,并自動(dòng)解壓縮,以其作為上下文,開(kāi)始構(gòu)建。

從標(biāo)準(zhǔn)輸入中讀取 dockerfile 進(jìn)行構(gòu)建

docker build - < dockerfile

cat dockerfile | docker build -

如果標(biāo)準(zhǔn)輸入傳入的是文本文件,則將其視為 dockerfile ,并開(kāi)始構(gòu)建。這種形式由于直接從標(biāo)準(zhǔn)輸入中讀取 dockerfile 的內(nèi)容,它沒(méi)有上下文,因此不可以像其他方法那樣可以將本地文件 copy 進(jìn)鏡像之類(lèi)的事情。

從標(biāo)準(zhǔn)輸入中讀取上下文壓縮包進(jìn)行構(gòu)建

$ docker build - < context.tar.gz

如果發(fā)現(xiàn)標(biāo)準(zhǔn)輸入的文件格式是 gzip 、 bzip2 以及 xz 的話,將會(huì)使其為上下文壓縮包,直接將其展開(kāi),將里面視為上下文,并開(kāi)始構(gòu)建。

copy 復(fù)制文件
格式:

  • copy <源路徑>... <目標(biāo)路徑>

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

和 run 指令一樣,也有兩種格式,一種類(lèi)似于命令行,一種類(lèi)似于函數(shù)調(diào)用。copy 指令將從構(gòu)建上下文目錄中 <源路徑> 的文件/目錄復(fù)制到新的一層的鏡像內(nèi)的 <目標(biāo)路徑> 位置。比如:

copy package.json /usr/src/app/

<源路徑> 可以是多個(gè),甚至可以是通配符,其通配符規(guī)則要滿足 go 的 filepath.match 規(guī)則,如:

copy hom* /mydir/
copy hom?.txt /mydir/

<目標(biāo)路徑> 可以是容器內(nèi)的絕對(duì)路徑,也可以是相對(duì)于工作目錄的相對(duì)路徑(工作目錄可以用 workdir 指令來(lái)指定)。目標(biāo)路徑不需要事先創(chuàng)建,如果目錄不存在會(huì)在復(fù)制文件前先行創(chuàng)建缺失目錄。

此外,還需要注意一點(diǎn),使用 copy 指令,源文件的各種元數(shù)據(jù)都會(huì)保留。比如讀、寫(xiě)、執(zhí)行權(quán)限、文件變更時(shí)間等。這個(gè)特性對(duì)于鏡像定制很有用。特別是構(gòu)建相關(guān)文件都在使用 git進(jìn)行管理的時(shí)候。

add 更高級(jí)的復(fù)制文件

add 指令和 copy 的格式和性質(zhì)基本一致。但是在 copy 基礎(chǔ)上增加了一些功能。比如 <源路徑> 可以是一個(gè) url ,這種情況下,docker 引擎會(huì)試圖去下載這個(gè)鏈接的文件放到 <目標(biāo)路徑> 去。下載后的文件權(quán)限自動(dòng)設(shè)置為 600 ,如果這并不是想要的權(quán)限,那么還需要增加額外的一層 run 進(jìn)行權(quán)限調(diào)整,另外,如果下載的是個(gè)壓縮包,需要解壓縮,也一樣還需要額外的一層 run 指令進(jìn)行解壓縮。所以不如直接使用 run 指令,然后使用 wget 或者 curl 工具下載,處理權(quán)限、解壓縮、然后清理無(wú)用文件更合理。因此,這個(gè)功能其實(shí)并不實(shí)用,而且不推薦使用。

如果 <源路徑> 為一個(gè) tar 壓縮文件的話,壓縮格式為 gzip , bzip2 以及 xz 的情況下, add 指令將會(huì)自動(dòng)解壓縮這個(gè)壓縮文件到 <目標(biāo)路徑> 去。

在某些情況下,這個(gè)自動(dòng)解壓縮的功能非常有用,比如官方鏡像 ubuntu 中:

from scratch
add ubuntu-xenial-core-cloudimg-amd64-root.tar.gz /
...

但在某些情況下,如果我們真的是希望復(fù)制個(gè)壓縮文件進(jìn)去,而不解壓縮,這時(shí)就不可以使用 add 命令了。

在 docker 官方的 dockerfile 最佳實(shí)踐文檔 中要求,盡可能的使用 copy ,因?yàn)?copy 的語(yǔ)義很明確,就是復(fù)制文件而已,而 add 則包含了更復(fù)雜的功能,其行為也不一定很清晰。最適合使用 add 的場(chǎng)合,就是所提及的需要自動(dòng)解壓縮的場(chǎng)合。

另外需要注意的是, add 指令會(huì)令鏡像構(gòu)建緩存失效,從而可能會(huì)令鏡像構(gòu)建變得比較緩慢。

因此在 copy 和 add 指令中選擇的時(shí)候,可以遵循這樣的原則,所有的文件復(fù)制均使用 copy 指令,僅在需要自動(dòng)解壓縮的場(chǎng)合使用 add 。

cmd 容器啟動(dòng)命令

cmd 指令的格式和 run 相似,也是兩種格式:

  • shell 格式: cmd <命令>

  • exec 格式: cmd ["可執(zhí)行文件", "參數(shù)1", "參數(shù)2"...]

  • 參數(shù)列表格式: cmd ["參數(shù)1", "參數(shù)2"...] 。在指定了 entrypoint 指令后,用 cmd 指定具體的參數(shù)。

之前介紹容器的時(shí)候曾經(jīng)說(shuō)過(guò),docker 不是虛擬機(jī),容器就是進(jìn)程。既然是進(jìn)程,那么在啟動(dòng)容器的時(shí)候,需要指定所運(yùn)行的程序及參數(shù)。 cmd 指令就是用于指定默認(rèn)的容器主進(jìn)程的啟動(dòng)命令的。

在運(yùn)行時(shí)可以指定新的命令來(lái)替代鏡像設(shè)置中的這個(gè)默認(rèn)命令,比如, ubuntu 鏡像默認(rèn)的cmd 是 /bin/bash ,如果我們直接 docker run -it ubuntu 的話,會(huì)直接進(jìn)入 bash 。我們也可以在運(yùn)行時(shí)指定運(yùn)行別的命令,如 docker run -it ubuntu cat /etc/os-release 。這就是用 cat /etc/os-release 命令替換了默認(rèn)的 /bin/bash 命令了,輸出了系統(tǒng)版本信息。

在指令格式上,一般推薦使用 exec 格式,這類(lèi)格式在解析時(shí)會(huì)被解析為 json 數(shù)組,因此一定要使用雙引號(hào) " ,而不要使用單引號(hào)。

如果使用 shell 格式的話,實(shí)際的命令會(huì)被包裝為 sh -c 的參數(shù)的形式進(jìn)行執(zhí)行。比如:

cmd echo $home

在實(shí)際執(zhí)行中,會(huì)將其變更為:

cmd [ "sh", "-c", "echo $home" ]

這就是為什么我們可以使用環(huán)境變量的原因,因?yàn)檫@些環(huán)境變量會(huì)被 shell 進(jìn)行解析處理。提到 cmd 就不得不提容器中應(yīng)用在前臺(tái)執(zhí)行和后臺(tái)執(zhí)行的問(wèn)題。這是初學(xué)者常出現(xiàn)的一個(gè)混淆。

docker 不是虛擬機(jī),容器中的應(yīng)用都應(yīng)該以前臺(tái)執(zhí)行,而不是像虛擬機(jī)、物理機(jī)里面那樣,用 upstart/systemd 去啟動(dòng)后臺(tái)服務(wù),容器內(nèi)沒(méi)有后臺(tái)服務(wù)的概念。

初學(xué)者一般將 cmd 寫(xiě)為:

cmd service nginx start

然后發(fā)現(xiàn)容器執(zhí)行后就立即退出了。甚至在容器內(nèi)去使用 systemctl 命令結(jié)果卻發(fā)現(xiàn)根本執(zhí)行不了。這就是因?yàn)闆](méi)有搞明白前臺(tái)、后臺(tái)的概念,沒(méi)有區(qū)分容器和虛擬機(jī)的差異,依舊在以傳統(tǒng)虛擬機(jī)的角度去理解容器。

對(duì)于容器而言,其啟動(dòng)程序就是容器應(yīng)用進(jìn)程,容器就是為了主進(jìn)程而存在的,主進(jìn)程退出,容器就失去了存在的意義,從而退出,其它輔助進(jìn)程不是它需要關(guān)心的東西。

而使用 service nginx start 命令,則是希望 systemd 來(lái)以后臺(tái)守護(hù)進(jìn)程形式啟動(dòng) nginx 服務(wù)。而剛才說(shuō)了 cmd service nginx start 會(huì)被理解為 cmd [ “sh”, “-c”, “service nginxstart”] ,因此主進(jìn)程實(shí)際上是 sh 。那么當(dāng) service nginx start 命令結(jié)束后, sh 也就結(jié)束了, sh 作為主進(jìn)程退出了,自然就會(huì)令容器退出。

正確的做法是直接執(zhí)行 nginx 可執(zhí)行文件,并且要求以前臺(tái)形式運(yùn)行。比如:

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

entrypoint 入口點(diǎn)

entrypoint 的格式和 run 指令格式一樣,分為 exec 格式和 shell 格式。

entrypoint 的目的和 cmd 一樣,都是在指定容器啟動(dòng)程序及參數(shù)。entrypoint 在運(yùn)行時(shí)也可以替代,不過(guò)比 cmd 要略顯繁瑣,需要通過(guò) docker run 的參數(shù) –entrypoint 來(lái)指定。

當(dāng)指定了 entrypoint 后, cmd 的含義就發(fā)生了改變,不再是直接的運(yùn)行其命令,而是將cmd 的內(nèi)容作為參數(shù)傳給 entrypoint 指令,換句話說(shuō)實(shí)際執(zhí)行時(shí),將變?yōu)椋?/p>

 ""

那么有了 cmd 后,為什么還要有 entrypoint 呢?這種 "" 有什么好處么?讓我們來(lái)看幾個(gè)場(chǎng)景。

場(chǎng)景一:讓鏡像變成像命令一樣使用

假設(shè)我們需要一個(gè)得知自己當(dāng)前公網(wǎng) ip 的鏡像,那么可以先用 cmd 來(lái)實(shí)現(xiàn):

from ubuntu:16.04
run apt-get update \
&& apt-get install -y curl \
&& rm -rf /var/lib/apt/lists/*
cmd [ "curl", "-s", "http://ip.cn" ]

假如我們使用 docker build -t myip . 來(lái)構(gòu)建鏡像的話,如果我們需要查詢當(dāng)前公網(wǎng) ip,只需要執(zhí)行:

$ docker run myip

當(dāng)前 ip:61.148.226.66 來(lái)自:北京市 聯(lián)通

嗯,這么看起來(lái)好像可以直接把鏡像當(dāng)做命令使用了,不過(guò)命令總有參數(shù),如果我們希望加參數(shù)呢?比如從上面的 cmd 中可以看到實(shí)質(zhì)的命令是 curl ,那么如果我們希望顯示 http頭信息,就需要加上 -i 參數(shù)。那么我們可以直接加 -i 參數(shù)給 docker run myip 么?

$ docker run myip -i
docker: error response from daemon: invalid header field value "oci runtime error: con
tainer_linux.go:247: starting container process caused \"exec: \\\"-i\\\": executable
file not found in $path\"\n".

我們可以看到可執(zhí)行文件找不到的報(bào)錯(cuò), executable file not found 。之前我們說(shuō)過(guò),跟在鏡像名后面的是 command ,運(yùn)行時(shí)會(huì)替換 cmd 的默認(rèn)值。因此這里的 -i 替換了原來(lái)的 cmd ,而不是添加在原來(lái)的 curl -s http://ip.cn 后面。而 -i 根本不是命令,所以自然找不到。

那么如果我們希望加入 -i 這參數(shù),我們就必須重新完整的輸入這個(gè)命令:

$ docker run myip curl -s http://ip.cn -i

這顯然不是很好的解決方案,而使用 entrypoint 就可以解決這個(gè)問(wèn)題?,F(xiàn)在我們重新用 entrypoint 來(lái)實(shí)現(xiàn)這個(gè)鏡像:

from ubuntu:16.04
run apt-get update \
  && apt-get install -y curl \
  && rm -rf /var/lib/apt/lists/*
entrypoint [ "curl", "-s", "http://ip.cn" ]

這次我們?cè)賮?lái)嘗試直接使用 docker run myip -i :

$ docker run myip

當(dāng)前 ip:61.148.226.66 來(lái)自:北京市 聯(lián)通

$ docker run myip -i
http/1.1 200 ok
server: nginx/1.8.0
date: tue, 22 nov 2016 05:12:40 gmt
content-type: text/html; charset=utf-8
vary: accept-encoding
x-powered-by: php/5.6.24-1~dotdeb+7.1
x-cache: miss from cache-2
x-cache-lookup: miss from cache-2:80
x-cache: miss from proxy-2_6
transfer-encoding: chunked
via: 1.1 cache-2:80, 1.1 proxy-2_6:8006
connection: keep-alive

當(dāng)前 ip:61.148.226.66 來(lái)自:北京市 聯(lián)通

可以看到,這次成功了。這是因?yàn)楫?dāng)存在 entrypoint 后, cmd 的內(nèi)容將會(huì)作為參數(shù)傳給 entrypoint ,而這里 -i 就是新的 cmd ,因此會(huì)作為參數(shù)傳給 curl ,從而達(dá)到了我們預(yù)期的效果。

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

啟動(dòng)容器就是啟動(dòng)主進(jìn)程,但有些時(shí)候,啟動(dòng)主進(jìn)程前,需要一些準(zhǔn)備工作。比如 mysql 類(lèi)的數(shù)據(jù)庫(kù),可能需要一些數(shù)據(jù)庫(kù)配置、初始化的工作,這些工作要在最終的 mysql 服務(wù)器運(yùn)行之前解決。

此外,可能希望避免使用 root 用戶去啟動(dòng)服務(wù),從而提高安全性,而在啟動(dòng)服務(wù)前還需要以 root 身份執(zhí)行一些必要的準(zhǔn)備工作,最后切換到服務(wù)用戶身份啟動(dòng)服務(wù)?;蛘叱朔?wù)外,其它命令依舊可以使用 root 身份執(zhí)行,方便調(diào)試等。

這些準(zhǔn)備工作是和容器 cmd 無(wú)關(guān)的,無(wú)論 cmd 為什么,都需要事先進(jìn)行一個(gè)預(yù)處理的工作。這種情況下,可以寫(xiě)一個(gè)腳本,然后放入 entrypoint 中去執(zhí)行,而這個(gè)腳本會(huì)將接到的參數(shù)(也就是 )作為命令,在腳本最后執(zhí)行。比如官方鏡像 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 用戶,并在最后指定了 entrypoint 為 dockerentrypoint.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 "$@"

該腳本的內(nèi)容就是根據(jù) cmd 的內(nèi)容來(lái)判斷,如果是 redis-server 的話,則切換到 redis 用戶身份啟動(dòng)服務(wù)器,否則依舊使用 root 身份執(zhí)行。比如:

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

env 設(shè)置環(huán)境變量

格式有兩種:

  1. env

  2. env = =...

這個(gè)指令很簡(jiǎn)單,就是設(shè)置環(huán)境變量而已,無(wú)論是后面的其它指令,如 run ,還是運(yùn)行時(shí)的應(yīng)用,都可以直接使用這里定義的環(huán)境變量。

env version=1.0 debug=on \
  name="happy feet"

這個(gè)例子中演示了如何換行,以及對(duì)含有空格的值用雙引號(hào)括起來(lái)的辦法,這和 shell 下的行為是一致的。

定義了環(huán)境變量,那么在后續(xù)的指令中,就可以使用這個(gè)環(huán)境變量。比如在官方 node 鏡像 dockerfile 中,就有類(lèi)似這樣的代碼:

env node_version 7.2.0
run curl -slo "https://nodejs.org/dist/v$node_version/node-v$node_version-linux-x64.ta
r.xz" \
  && curl -slo "https://nodejs.org/dist/v$node_version/shasums256.txt.asc" \
  && gpg --batch --decrypt --output shasums256.txt shasums256.txt.asc \
  && grep " node-v$node_version-linux-x64.tar.xz\$" shasums256.txt | sha256sum -c - \
  && tar -xjf "node-v$node_version-linux-x64.tar.xz" -c /usr/local --strip-components=1 \
  && rm "node-v$node_version-linux-x64.tar.xz" shasums256.txt.asc shasums256.txt \
  && ln -s /usr/local/bin/node /usr/local/bin/nodejs

在這里先定義了環(huán)境變量 node_version ,其后的 run 這層里,多次使用 $node_version 來(lái)進(jìn)行操作定制。可以看到,將來(lái)升級(jí)鏡像構(gòu)建版本的時(shí)候,只需要更新 7.2.0 即可, dockerfile 構(gòu)建維護(hù)變得更輕松了。

下列指令可以支持環(huán)境變量展開(kāi):
add 、 copy 、 env 、 expose 、 label 、 user 、 workdir 、 volume 、 stopsignal 、 onbuild 。

可以從這個(gè)指令列表里感覺(jué)到,環(huán)境變量可以使用的地方很多,很強(qiáng)大。通過(guò)環(huán)境變量,我們可以讓一份 dockerfile 制作更多的鏡像,只需使用不同的環(huán)境變量即可。

arg 構(gòu)建參數(shù)

格式: arg <參數(shù)名>[=<默認(rèn)值>]

構(gòu)建參數(shù)和 env 的效果一樣,都是設(shè)置環(huán)境變量。所不同的是, arg 所設(shè)置的構(gòu)建環(huán)境的環(huán)境變量,在將來(lái)容器運(yùn)行時(shí)是不會(huì)存在這些環(huán)境變量的。但是不要因此就使用 arg 保存密碼之類(lèi)的信息,因?yàn)?docker history 還是可以看到所有值的。

dockerfile 中的 arg 指令是定義參數(shù)名稱(chēng),以及定義其默認(rèn)值。該默認(rèn)值可以在構(gòu)建命令 docker build 中用 --build-arg <參數(shù)名>=<值> 來(lái)覆蓋。

在 1.13 之前的版本,要求 –build-arg 中的參數(shù)名,必須在 dockerfile 中用 arg 定義過(guò)了,換句話說(shuō),就是 –build-arg 指定的參數(shù),必須在 dockerfile 中使用了。如果對(duì)應(yīng)參數(shù)沒(méi)有被使用,則會(huì)報(bào)錯(cuò)退出構(gòu)建。從 1.13 開(kāi)始,這種嚴(yán)格的限制被放開(kāi),不再報(bào)錯(cuò)退出,而是顯示警告信息,并繼續(xù)構(gòu)建。這對(duì)于使用 ci 系統(tǒng),用同樣的構(gòu)建流程構(gòu)建不同的 dockerfile 的時(shí)候比較有幫助,避免構(gòu)建命令必須根據(jù)每個(gè) dockerfile 的內(nèi)容修改。

volume 定義匿名卷

格式為:

  • volume ["<路徑1>", "<路徑2>"...]

  • volume <路徑>

之前我們說(shuō)過(guò),容器運(yùn)行時(shí)應(yīng)該盡量保持容器存儲(chǔ)層不發(fā)生寫(xiě)操作,對(duì)于數(shù)據(jù)庫(kù)類(lèi)需要保存動(dòng)態(tài)數(shù)據(jù)的應(yīng)用,其數(shù)據(jù)庫(kù)文件應(yīng)該保存于卷(volume)中。為了防止運(yùn)行時(shí)用戶忘記將動(dòng)態(tài)文件所保存目錄掛載為卷,在 dockerfile 中,我們可以事先指定某些目錄掛載為匿名卷,這樣在運(yùn)行時(shí)如果用戶不指定掛載,其應(yīng)用也可以正常運(yùn)行,不會(huì)向容器存儲(chǔ)層寫(xiě)入大量數(shù)據(jù)。

volume /data

這里的 /data 目錄就會(huì)在運(yùn)行時(shí)自動(dòng)掛載為匿名卷,任何向 /data 中寫(xiě)入的信息都不會(huì)記錄進(jìn)容器存儲(chǔ)層,從而保證了容器存儲(chǔ)層的無(wú)狀態(tài)化。當(dāng)然,運(yùn)行時(shí)可以覆蓋這個(gè)掛載設(shè)置。比如:

docker run -d -v mydata:/data xxxx

在這行命令中,就使用了 mydata 這個(gè)命名卷掛載到了 /data 這個(gè)位置,替代了 dockerfile 中定義的匿名卷的掛載配置。

expose 聲明端口

格式為 expose <端口1> [<端口2>...]。

expose 指令是聲明運(yùn)行時(shí)容器提供服務(wù)端口,這只是一個(gè)聲明,在運(yùn)行時(shí)并不會(huì)因?yàn)檫@個(gè)聲明應(yīng)用就會(huì)開(kāi)啟這個(gè)端口的服務(wù)。在 dockerfile 中寫(xiě)入這樣的聲明有兩個(gè)好處,一個(gè)是幫助鏡像使用者理解這個(gè)鏡像服務(wù)的守護(hù)端口,以方便配置映射;另一個(gè)用處則是在運(yùn)行時(shí)使用隨機(jī)端口映射時(shí),也就是 docker run -p 時(shí),會(huì)自動(dòng)隨機(jī)映射 expose 的端口。

此外,在早期 docker 版本中還有一個(gè)特殊的用處。以前所有容器都運(yùn)行于默認(rèn)橋接網(wǎng)絡(luò)中,因此所有容器互相之間都可以直接訪問(wèn),這樣存在一定的安全性問(wèn)題。于是有了一個(gè) docker 引擎參數(shù) --icc=false ,當(dāng)指定該參數(shù)后,容器間將默認(rèn)無(wú)法互訪,除非互相間使用了 --links 參數(shù)的容器才可以互通,并且只有鏡像中 expose 所聲明的端口才可以被訪問(wèn)。這個(gè) --icc=false 的用法,在引入了 docker network 后已經(jīng)基本不用了,通過(guò)自定義網(wǎng)絡(luò)可以很輕松的實(shí)現(xiàn)容器間的互聯(lián)與隔離。

要將 expose 和在運(yùn)行時(shí)使用 -p <宿主端口>:<容器端口> 區(qū)分開(kāi)來(lái)。 -p ,是映射宿主端口和容器端口,換句話說(shuō),就是將容器的對(duì)應(yīng)端口服務(wù)公開(kāi)給外界訪問(wèn),而 expose 僅僅是聲明容器打算使用什么端口而已,并不會(huì)自動(dòng)在宿主進(jìn)行端口映射。

workdir 指定工作目錄

格式為 workdir <工作目錄路徑> 。

使用 workdir 指令可以來(lái)指定工作目錄(或者稱(chēng)為當(dāng)前目錄),以后各層的當(dāng)前目錄就被改為指定的目錄,如該目錄不存在, workdir 會(huì)幫你建立目錄。

之前提到一些初學(xué)者常犯的錯(cuò)誤是把 dockerfile 等同于 shell 腳本來(lái)書(shū)寫(xiě),這種錯(cuò)誤的理解還可能會(huì)導(dǎo)致出現(xiàn)下面這樣的錯(cuò)誤:

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

如果將這個(gè) dockerfile 進(jìn)行構(gòu)建鏡像運(yùn)行后,會(huì)發(fā)現(xiàn)找不到 /app/world.txt 文件,或者其內(nèi)容不是 hello 。原因其實(shí)很簡(jiǎn)單,在 shell 中,連續(xù)兩行是同一個(gè)進(jìn)程執(zhí)行環(huán)境,因此前一個(gè)命令修改的內(nèi)存狀態(tài),會(huì)直接影響后一個(gè)命令;而在 dockerfile 中,這兩行 run 命令的執(zhí)行環(huán)境根本不同,是兩個(gè)完全不同的容器。這就是對(duì) dockerfile 構(gòu)建分層存儲(chǔ)的概念不了解所導(dǎo)致的錯(cuò)誤。

之前說(shuō)過(guò)每一個(gè) run 都是啟動(dòng)一個(gè)容器、執(zhí)行命令、然后提交存儲(chǔ)層文件變更。第一層 runcd /app 的執(zhí)行僅僅是當(dāng)前進(jìn)程的工作目錄變更,一個(gè)內(nèi)存上的變化而已,其結(jié)果不會(huì)造成任何文件變更。而到第二層的時(shí)候,啟動(dòng)的是一個(gè)全新的容器,跟第一層的容器更完全沒(méi)關(guān)系,自然不可能繼承前一層構(gòu)建過(guò)程中的內(nèi)存變化。

因此如果需要改變以后各層的工作目錄的位置,那么應(yīng)該使用 workdir 指令。

user 指定當(dāng)前用戶

格式: user <用戶名>

user 指令和 workdir 相似,都是改變環(huán)境狀態(tài)并影響以后的層。 workdir 是改變工作目錄, user 則是改變之后層的執(zhí)行 run , cmd 以及 entrypoint 這類(lèi)命令的身份。當(dāng)然,和 workdir 一樣, user 只是幫助你切換到指定用戶而已,這個(gè)用戶必須是事先建立好的,否則無(wú)法切換。

run groupadd -r redis && useradd -r -g redis redis
user redis
run [ "redis-server" ]

如果以 root 執(zhí)行的腳本,在執(zhí)行期間希望改變身份,比如希望以某個(gè)已經(jīng)建立好的用戶來(lái)運(yùn)行某個(gè)服務(wù)進(jìn)程,不要使用 su 或者 sudo ,這些都需要比較麻煩的配置,而且在 tty 缺失的環(huán)境下經(jīng)常出錯(cuò)。建議使用 gosu 。

# 建立 redis 用戶,并使用 gosu 換另一個(gè)用戶執(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 引入的新指令。

在沒(méi)有 healthcheck 指令前,docker 引擎只可以通過(guò)容器內(nèi)主進(jìn)程是否退出來(lái)判斷容器是否狀態(tài)異常。很多情況下這沒(méi)問(wèn)題,但是如果程序進(jìn)入死鎖狀態(tài),或者死循環(huán)狀態(tài),應(yīng)用進(jìn)程并不退出,但是該容器已經(jīng)無(wú)法提供服務(wù)了。在 1.12 以前,docker 不會(huì)檢測(cè)到容器的這種狀態(tài),從而不會(huì)重新調(diào)度,導(dǎo)致可能會(huì)有部分容器已經(jīng)無(wú)法提供服務(wù)了卻還在接受用戶請(qǐng)求。

而自 1.12 之后,docker 提供了 healthcheck 指令,通過(guò)該指令指定一行命令,用這行命令來(lái)判斷容器主進(jìn)程的服務(wù)狀態(tài)是否還正常,從而比較真實(shí)的反應(yīng)容器實(shí)際狀態(tài)。

當(dāng)在一個(gè)鏡像指定了 healthcheck 指令后,用其啟動(dòng)容器,初始狀態(tài)會(huì)為 starting ,在 healthcheck 指令檢查成功后變?yōu)?healthy ,如果連續(xù)一定次數(shù)失敗,則會(huì)變?yōu)?unhealthy 。

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

  • interval=<間隔> :兩次健康檢查的間隔,默認(rèn)為 30 秒;

  • timeout=<時(shí)長(zhǎng)> :健康檢查命令運(yùn)行超時(shí)時(shí)間,如果超過(guò)這個(gè)時(shí)間,本次健康檢查就被視為失敗,默認(rèn) 30 秒;

  • retries=<次數(shù)> :當(dāng)連續(xù)失敗指定次數(shù)后,則將容器狀態(tài)視為 unhealthy ,默認(rèn) 3 次。

和 cmd , entrypoint 一樣, healthcheck 只可以出現(xiàn)一次,如果寫(xiě)了多個(gè),只有最后一個(gè)生效。

在 healthcheck [選項(xiàng)] cmd 后面的命令,格式和 entrypoint 一樣,分為 shell 格式,和 exec 格式。命令的返回值決定了該次健康檢查的成功與否: 0 :成功; 1 :失??; 2 :保留,不要使用這個(gè)值。

假設(shè)我們有個(gè)鏡像是個(gè)最簡(jiǎn)單的 web 服務(wù),我們希望增加健康檢查來(lái)判斷其 web 服務(wù)是否在正常工作,我們可以用 curl 來(lái)幫助判斷,其 dockerfile 的 healthcheck 可以這么寫(xiě):

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

這里我們?cè)O(shè)置了每 5 秒檢查一次(這里為了試驗(yàn)所以間隔非常短,實(shí)際應(yīng)該相對(duì)較長(zhǎng)),如果健康檢查命令超過(guò) 3 秒沒(méi)響應(yīng)就視為失敗,并且使用 curl -fs  || exit 1 作為健康檢查命令。

使用 docker build 來(lái)構(gòu)建這個(gè)鏡像:

$ docker build -t myweb:v1 .

構(gòu)建好了后,我們啟動(dòng)一個(gè)容器:

$ docker run -d --name web -p 80:80 myweb:v1

當(dāng)運(yùn)行該鏡像后,可以通過(guò) docker container ls 看到最初的狀態(tài)為 (health: starting) :

$ docker container ls
container id  image      command         created     status                 ports          names
03e28eb00bd0  myweb:v1    "nginx -g 'daemon off" 3 seconds ago  up 2 seconds (health: starting)     80/tcp, 443/tcp     web

在等待幾秒鐘后,再次 docker container ls ,就會(huì)看到健康狀態(tài)變化為了 (healthy) :

$ docker container ls
container id  image      command         created     status                 ports          names
03e28eb00bd0  myweb:v1    "nginx -g 'daemon off" 18 seconds ago  up 16 seconds (health: healthy)     80/tcp, 443/tcp     web

如果健康檢查連續(xù)失敗超過(guò)了重試次數(shù),狀態(tài)就會(huì)變?yōu)?(unhealthy) 。

為了幫助排障,健康檢查命令的輸出(包括 stdout 以及 stderr )都會(huì)被存儲(chǔ)于健康狀態(tài)里,可以用 docker inspect 來(lái)查看。

$ docker inspect --format '{{json .state.health}}' upbeat_allen | python -m json.tool
{
  "failingstreak": 0,
  "log": [
    {
      "end": "2018-06-14t04:55:37.477730277-04:00",
      "exitcode": 0,
      "output": "\n\n\nwelcome to nginx!\n\n\n\n

welcome to nginx!

\n

if you see this page, the nginx web server is successfully installed and\nworking. further configuration is required.

\n\n

for online documentation and support please refer to\nnginx.org.
\ncommercial support is available at\nnginx.com.

\n\n

thank you for using nginx.

\n\n\n",       "start": "2018-06-14t04:55:37.408045977-04:00"     },     {       "end": "2018-06-14t04:55:42.553816257-04:00",       "exitcode": 0,       "output": "\n\n\nwelcome to nginx!\n\n\n\n

welcome to nginx!

\n

if you see this page, the nginx web server is successfully installed and\nworking. further configuration is required.

\n\n

for online documentation and support please refer to\nnginx.org.
\ncommercial support is available at\nnginx.com.

\n\n

thank you for using nginx.

\n\n\n",       "start": "2018-06-14t04:55:42.480940888-04:00"     },     {       "end": "2018-06-14t04:55:47.631694051-04:00",       "exitcode": 0,       "output": "\n\n\nwelcome to nginx!\n\n\n\n

welcome to nginx!

\n

if you see this page, the nginx web server is successfully installed and\nworking. further configuration is required.

\n\n

for online documentation and support please refer to\nnginx.org.
\ncommercial support is available at\nnginx.com.

\n\n

thank you for using nginx.

\n\n\n",       "start": "2018-06-14t04:55:47.557214953-04:00"     },     {       "end": "2018-06-14t04:55:52.708195002-04:00",       "exitcode": 0,       "output": "\n\n\nwelcome to nginx!\n\n\n\n

welcome to nginx!

\n

if you see this page, the nginx web server is successfully installed and\nworking. further configuration is required.

\n\n

for online documentation and support please refer to\nnginx.org.
\ncommercial support is available at\nnginx.com.

\n\n

thank you for using nginx.

\n\n\n",       "start": "2018-06-14t04:55:52.63499573-04:00"     },     {       "end": "2018-06-14t04:55:57.795117794-04:00",       "exitcode": 0,       "output": "\n\n\nwelcome to nginx!\n\n\n\n

welcome to nginx!

\n

if you see this page, the nginx web server is successfully installed and\nworking. further configuration is required.

\n\n

for online documentation and support please refer to\nnginx.org.
\ncommercial support is available at\nnginx.com.

\n\n

thank you for using nginx.

\n\n\n",       "start": "2018-06-14t04:55:57.714289056-04:00"     }   ],   "status": "healthy" }

onbuild 為他人做嫁衣裳

格式: onbuild <其它指令>。

onbuild 是一個(gè)特殊的指令,它后面跟的是其它指令,比如 run , copy 等,而這些指令,在當(dāng)前鏡像構(gòu)建時(shí)并不會(huì)被執(zhí)行。只有當(dāng)以當(dāng)前鏡像為基礎(chǔ)鏡像,去構(gòu)建下一級(jí)鏡像的時(shí)候才會(huì)被執(zhí)行。

dockerfile 中的其它指令都是為了定制當(dāng)前鏡像而準(zhǔn)備的,唯有 onbuild 是為了幫助別人定制自己而準(zhǔn)備的。

假設(shè)我們要制作 node.js 所寫(xiě)的應(yīng)用的鏡像。我們都知道 node.js 使用 npm 進(jìn)行包管理,所有依賴(lài)、配置、啟動(dòng)信息等會(huì)放到 package.json 文件里。在拿到程序代碼后,需要先進(jìn)行 npm install 才可以獲得所有需要的依賴(lài)。然后就可以通過(guò) npm start 來(lái)啟動(dòng)應(yīng)用。因此,一般來(lái)說(shuō)會(huì)這樣寫(xiě) dockerfile :

from node:slim
run mkdir /app
workdir /app
copy ./package.json /app
run [ "npm", "install" ]
copy . /app/
cmd [ "npm", "start" ]

把這個(gè) dockerfile 放到 node.js 項(xiàng)目的根目錄,構(gòu)建好鏡像后,就可以直接拿來(lái)啟動(dòng)容器運(yùn)行。但是如果我們還有第二個(gè) node.js 項(xiàng)目也差不多呢?好吧,那就再把這個(gè) dockerfile 復(fù)制到第二個(gè)項(xiàng)目里。那如果有第三個(gè)項(xiàng)目呢?再?gòu)?fù)制么?文件的副本越多,版本控制就越困難,讓我們繼續(xù)看這樣的場(chǎng)景維護(hù)的問(wèn)題。

如果第一個(gè) node.js 項(xiàng)目在開(kāi)發(fā)過(guò)程中,發(fā)現(xiàn)這個(gè) dockerfile 里存在問(wèn)題,比如敲錯(cuò)字了、或者需要安裝額外的包,然后開(kāi)發(fā)人員修復(fù)了這個(gè) dockerfile ,再次構(gòu)建,問(wèn)題解決。第一個(gè)項(xiàng)目沒(méi)問(wèn)題了,但是第二個(gè)項(xiàng)目呢?雖然最初 dockerfile 是復(fù)制、粘貼自第一個(gè)項(xiàng)目的,但是并不會(huì)因?yàn)榈谝粋€(gè)項(xiàng)目修復(fù)了他們的 dockerfile ,而第二個(gè)項(xiàng)目的 dockerfile 就會(huì)被自動(dòng)修復(fù)。

那么我們可不可以做一個(gè)基礎(chǔ)鏡像,然后各個(gè)項(xiàng)目使用這個(gè)基礎(chǔ)鏡像呢?這樣基礎(chǔ)鏡像更新,各個(gè)項(xiàng)目不用同步 dockerfile 的變化,重新構(gòu)建后就繼承了基礎(chǔ)鏡像的更新?好吧,可以,讓我們看看這樣的結(jié)果。那么上面的這個(gè) dockerfile 就會(huì)變?yōu)椋?/p>

from node:slim
run mkdir /app
workdir /app
cmd [ "npm", "start" ]

這里我們把項(xiàng)目相關(guān)的構(gòu)建指令拿出來(lái),放到子項(xiàng)目里去。假設(shè)這個(gè)基礎(chǔ)鏡像的名字為 mynode 的話,各個(gè)項(xiàng)目?jī)?nèi)的自己的 dockerfile 就變?yōu)椋?/p>

from my-node
copy ./package.json /app
run [ "npm", "install" ]
copy . /app/

基礎(chǔ)鏡像變化后,各個(gè)項(xiàng)目都用這個(gè) dockerfile 重新構(gòu)建鏡像,會(huì)繼承基礎(chǔ)鏡像的更新。

那么,問(wèn)題解決了么?沒(méi)有。準(zhǔn)確說(shuō),只解決了一半。如果這個(gè) dockerfile 里面有些東西需要調(diào)整呢?比如 npm install 都需要加一些參數(shù),那怎么辦?這一行 run 是不可能放入基礎(chǔ)鏡像的,因?yàn)樯婕暗搅水?dāng)前項(xiàng)目的 ./package.json ,難道又要一個(gè)個(gè)修改么?所以說(shuō),這樣制作基礎(chǔ)鏡像,只解決了原來(lái)的 dockerfile 的前4條指令的變化問(wèn)題,而后面三條指令的變化則完全沒(méi)辦法處理。

onbuild 可以解決這個(gè)問(wèn)題。讓我們用 onbuild 重新寫(xiě)一下基礎(chǔ)鏡像的 dockerfile :

from node:slim
run mkdir /app
workdir /app
onbuild copy ./package.json /app
onbuild run [ "npm", "install" ]
onbuild copy . /app/
cmd [ "npm", "start" ]

 這次我們回到原始的 dockerfile ,但是這次將項(xiàng)目相關(guān)的指令加上 onbuild ,這樣在構(gòu)建基礎(chǔ)鏡像的時(shí)候,這三行并不會(huì)被執(zhí)行。然后各個(gè)項(xiàng)目的 dockerfile 就變成了簡(jiǎn)單地:

from my-node

 是的,只有這么一行。當(dāng)在各個(gè)項(xiàng)目目錄中,用這個(gè)只有一行的 dockerfile 構(gòu)建鏡像時(shí),之前基礎(chǔ)鏡像的那三行 onbuild 就會(huì)開(kāi)始執(zhí)行,成功的將當(dāng)前項(xiàng)目的代碼復(fù)制進(jìn)鏡像、并且針對(duì)本項(xiàng)目執(zhí)行 npm install ,生成應(yīng)用鏡像。

以上就是關(guān)于“怎么使用Docker Dockerfile定制鏡像”這篇文章的內(nèi)容,相信大家都有了一定的了解,希望小編分享的內(nèi)容對(duì)大家有幫助,若想了解更多相關(guān)的知識(shí)內(nèi)容,請(qǐng)關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。


分享名稱(chēng):怎么使用DockerDockerfile定制鏡像
URL鏈接:http://weahome.cn/article/gcpjhg.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部