成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

關(guān)于Dockerfile的最佳實(shí)踐技巧

開(kāi)發(fā) 前端
Dockerfile的語(yǔ)法非常簡(jiǎn)單,然而如何加快鏡像構(gòu)建速度,如何減少Docker鏡像的大小卻不是那么直觀,需要積累實(shí)踐經(jīng)驗(yàn)。這篇文章可以幫助你快速掌握編寫Dockerfile的技巧。

Dockerfile的語(yǔ)法非常簡(jiǎn)單,然而如何加快鏡像構(gòu)建速度,如何減少Docker鏡像的大小卻不是那么直觀,需要積累實(shí)踐經(jīng)驗(yàn)。這篇文章可以幫助你快速掌握編寫Dockerfile的技巧。

目標(biāo)

  • 更快的構(gòu)建速度
  • 更小的Docker鏡像大小
  • 更少的Docker鏡像層
  • 充分利用鏡像緩存
  • 增加Dockerfile可讀性
  • 讓Docker容器使用起來(lái)更簡(jiǎn)單

總結(jié)

  • 編寫.dockerignore文件
  • 容器只運(yùn)行單個(gè)應(yīng)用
  • 將多個(gè)RUN指令合并為一個(gè)
  • 基礎(chǔ)鏡像的標(biāo)簽不要用latest
  • 每個(gè)RUN指令后刪除多余文件
  • 選擇合適的基礎(chǔ)鏡像(alpine版本最好)
  • 設(shè)置WORKDIR和CMD
  • 使用ENTRYPOINT (可選)
  • 在entrypoint腳本中使用exec
  • COPY與ADD優(yōu)先使用前者
  • 合理調(diào)整COPY與RUN的順序
  • 設(shè)置默認(rèn)的環(huán)境變量,映射端口和數(shù)據(jù)卷
  • 使用LABEL設(shè)置鏡像元數(shù)據(jù)
  • 添加HEALTHCHECK
  • 多階段構(gòu)建

示例

示例Dockerfile犯了幾乎所有的錯(cuò)(當(dāng)然我是故意的)。接下來(lái),我會(huì)一步步優(yōu)化它。假設(shè)我們需要使用Docker運(yùn)行一個(gè)Node.js應(yīng)用,下面就是它的Dockerfile(CMD指令太復(fù)雜了,所以我簡(jiǎn)化了,它是錯(cuò)誤的,僅供參考)。

FROM ubuntu
ADD . /app
RUN apt-get update  
RUN apt-get upgrade -y  
RUN apt-get install -y nodejs ssh mysql  
RUN cd /app && npm install
# this should start three processes, mysql and ssh
# in the background and node app in foreground
# isn't it beautifully terrible? <3
CMD mysql & sshd & npm start

構(gòu)建鏡像:

你能發(fā)現(xiàn)上面Dockerfile所有的錯(cuò)誤嗎? 不能? 那接下來(lái)讓我們一步一步完善它。

優(yōu)化

1. 編寫.dockerignore文件

構(gòu)建鏡像時(shí),Docker需要先準(zhǔn)備context ,將所有需要的文件收集到進(jìn)程中。默認(rèn)的context包含Dockerfile目錄中的所有文件,但是實(shí)際上,我們并不需要.git目錄,node_modules目錄等內(nèi)容。 .dockerignore 的作用和語(yǔ)法類似于 .gitignore,可以忽略一些不需要的文件,這樣可以有效加快鏡像構(gòu)建時(shí)間,同時(shí)減少Docker鏡像的大小。示例如下:

2. 容器只運(yùn)行單個(gè)應(yīng)用

從技術(shù)角度講,你可以在Docker容器中運(yùn)行多個(gè)進(jìn)程。你可以將數(shù)據(jù)庫(kù),前端,后端,ssh,supervisor都運(yùn)行在同一個(gè)Docker容器中。但是,這會(huì)讓你非常痛苦:

  • 非常長(zhǎng)的構(gòu)建時(shí)間(修改前端之后,整個(gè)后端也需要重新構(gòu)建)
  • 非常大的鏡像大小
  • 多個(gè)應(yīng)用的日志難以處理(不能直接使用stdout,否則多個(gè)應(yīng)用的日志會(huì)混合到一起)
  • 橫向擴(kuò)展時(shí)非常浪費(fèi)資源(不同的應(yīng)用需要運(yùn)行的容器數(shù)并不相同)
  • 僵尸進(jìn)程問(wèn)題 - 你需要選擇合適的init進(jìn)程

因此,建議大家為每個(gè)應(yīng)用構(gòu)建單獨(dú)的Docker鏡像,然后使用 Docker Compose 運(yùn)行多個(gè)Docker容器。現(xiàn)在,我從Dockerfile中刪除一些不需要的安裝包,另外,SSH可以用docker exec替代。示例如下:

FROM ubuntu
ADD . /app
RUN apt-get update  
RUN apt-get upgrade -y
# we should remove ssh and mysql, and use
# separate container for database 
RUN apt-get install -y nodejs  # ssh mysql  
RUN cd /app && npm install
CMD npm start

3. 將多個(gè)RUN指令合并為一個(gè)

Docker鏡像是分層的,下面這些知識(shí)點(diǎn)非常重要:

  • Dockerfile中的每個(gè)指令都會(huì)創(chuàng)建一個(gè)新的鏡像層。
  • 鏡像層將被緩存和復(fù)用
  • 當(dāng)Dockerfile的指令修改了,復(fù)制的文件變化了,或者構(gòu)建鏡像時(shí)指定的變量不同了,對(duì)應(yīng)的鏡像層緩存就會(huì)失效
  • 某一層的鏡像緩存失效之后,它之后的鏡像層緩存都會(huì)失效
  • 鏡像層是不可變的,如果我們?cè)倌骋粚又刑砑右粋€(gè)文件,然后在下一層中刪除它,則鏡像中依然會(huì)包含該文件(只是這個(gè)文件在Docker容器中不可見(jiàn)了)。

Docker鏡像類似于洋蔥。它們都有很多層。為了修改內(nèi)層,則需要將外面的層都刪掉。記住這一點(diǎn)的話,其他內(nèi)容就很好理解了。現(xiàn)在,我們將所有的RUN指令合并為一個(gè)。同時(shí)把a(bǔ)pt-get upgrade刪除,因?yàn)樗鼤?huì)使得鏡像構(gòu)建非常不確定(我們只需要依賴基礎(chǔ)鏡像的更新就好了)。

FROM ubuntu
ADD . /app
RUN apt-get update \  
    && apt-get install -y nodejs \
    && cd /app \
    && npm install
CMD npm start

記住一點(diǎn),我們只能將變化頻率一樣的指令合并在一起。將node.js安裝與npm模塊安裝放在一起的話,則每次修改源代碼,都需要重新安裝node.js,這顯然不合適。因此,正確的寫法是這樣的:

FROM ubuntu
RUN apt-get update && apt-get install -y nodejs  
ADD . /app  
RUN cd /app && npm install
CMD npm start

4. 基礎(chǔ)鏡像的標(biāo)簽不要用latest

當(dāng)鏡像沒(méi)有指定標(biāo)簽時(shí),將默認(rèn)使用latest 標(biāo)簽。因此, FROM ubuntu 指令等同于FROM ubuntu:latest。當(dāng)時(shí),當(dāng)鏡像更新時(shí),latest標(biāo)簽會(huì)指向不同的鏡像,這時(shí)構(gòu)建鏡像有可能失敗。如果你的確需要使用最新版的基礎(chǔ)鏡像,可以使用latest標(biāo)簽,否則的話,最好指定確定的鏡像標(biāo)簽。示例Dockerfile應(yīng)該使用16.04作為標(biāo)簽。

FROM ubuntu:16.04  # it's that easy!
RUN apt-get update && apt-get install -y nodejs  
ADD . /app  
RUN cd /app && npm install
CMD npm start

5. 每個(gè)RUN指令后刪除多余文件

假設(shè)我們更新了apt-get源,下載,解壓并安裝了一些軟件包,它們都保存在/var/lib/apt/lists/目錄中。但是,運(yùn)行應(yīng)用時(shí)Docker鏡像中并不需要這些文件。我們最好將它們刪除,因?yàn)樗鼤?huì)使Docker鏡像變大。示例Dockerfile中,我們可以刪除/var/lib/apt/lists/目錄中的文件(它們是由apt-get update生成的)。

FROM ubuntu:16.04
RUN apt-get update \  
    && apt-get install -y nodejs \
    # added lines
    && rm -rf /var/lib/apt/lists/*
ADD . /app  
RUN cd /app && npm install
CMD npm start

6. 選擇合適的基礎(chǔ)鏡像(alpine版本最好)

在示例中,我們選擇了ubuntu作為基礎(chǔ)鏡像。但是我們只需要運(yùn)行node程序,有必要使用一個(gè)通用的基礎(chǔ)鏡像嗎?node鏡像應(yīng)該是更好的選擇。

FROM node
ADD . /app  
# we don't need to install node 
# anymore and use apt-get
RUN cd /app && npm install
CMD npm start

更好的選擇是alpine版本的node鏡像。alpine是一個(gè)極小化的Linux發(fā)行版,只有4MB,這讓它非常適合作為基礎(chǔ)鏡像。

FROM node:7-alpine
ADD . /app  
RUN cd /app && npm install
CMD npm start

apk是Alpine的包管理工具。它與apt-get有些不同,但是非常容易上手。另外,它還有一些非常有用的特性,比如no-cache和 --virtual選項(xiàng),它們都可以幫助我們減少鏡像的大小。

7. 設(shè)置WORKDIR和 CMD

WORKDIR指令可以設(shè)置默認(rèn)目錄,也就是運(yùn)行RUN / CMD / ENTRYPOINT指令的地方。CMD指令可以設(shè)置容器創(chuàng)建是執(zhí)行的默認(rèn)命令。另外,你應(yīng)該講命令寫在一個(gè)數(shù)組中,數(shù)組中每個(gè)元素為命令的每個(gè)單詞(參考官方文檔)。

FROM node:7-alpine
WORKDIR /app  
ADD . /app  
RUN npm install
CMD ["npm", "start"]

8. 使用ENTRYPOINT (可選)

ENTRYPOINT指令并不是必須的,因?yàn)樗鼤?huì)增加復(fù)雜度。ENTRYPOINT是一個(gè)腳本,它會(huì)默認(rèn)執(zhí)行,并且將指定的命令當(dāng)成參數(shù)接收。它通常用于構(gòu)建可執(zhí)行的Docker鏡像。entrypoint.sh如下:

示例Dockerfile:

FROM node:7-alpine
WORKDIR /app  
ADD . /app  
RUN npm install
ENTRYPOINT ["./entrypoint.sh"]  
CMD ["start"]

可以使用如下命令運(yùn)行該鏡像:

9. 在entrypoint腳本中使用exec

在前文的entrypoint腳本中,我使用了exec命令運(yùn)行node應(yīng)用。不使用exec的話,我們則不能順利地關(guān)閉容器,因?yàn)镾IGTERM信號(hào)會(huì)被bash腳本進(jìn)程吞沒(méi)。exec命令啟動(dòng)的進(jìn)程可以取代腳本進(jìn)程,因此所有的信號(hào)都會(huì)正常工作。這里擴(kuò)展介紹一下docker容器的停止過(guò)程:(1). 對(duì)于容器來(lái)說(shuō),init 系統(tǒng)不是必須的,當(dāng)你通過(guò)命令 docker stop mycontainer 來(lái)停止容器時(shí),docker CLI 會(huì)將 TERM 信號(hào)發(fā)送給 mycontainer 的 PID 為 1 的進(jìn)程。

  • 如果 PID 1 是 init 進(jìn)程 - 那么 PID 1 會(huì)將 TERM 信號(hào)轉(zhuǎn)發(fā)給子進(jìn)程,然后子進(jìn)程開(kāi)始關(guān)閉,最后容器終止。
  • 如果沒(méi)有 init 進(jìn)程- 那么容器中的應(yīng)用進(jìn)程(Dockerfile 中的ENTRYPOINT或CMD指定的應(yīng)用)就是 PID 1,應(yīng)用進(jìn)程直接負(fù)責(zé)響應(yīng)TERM信號(hào)。這時(shí)又分為兩種情況:

應(yīng)用不處理 SIGTERM - 如果應(yīng)用沒(méi)有監(jiān)聽(tīng) SIGTERM 信號(hào),或者應(yīng)用中沒(méi)有實(shí)現(xiàn)處理 SIGTERM 信號(hào)的邏輯,應(yīng)用就不會(huì)停止,容器也不會(huì)終止。

容器停止時(shí)間很長(zhǎng) - 運(yùn)行命令 docker stop mycontainer 之后,Docker 會(huì)等待 10s,如果 10s 后容器還沒(méi)有終止,Docker 就會(huì)繞過(guò)容器應(yīng)用直接向內(nèi)核發(fā)送 SIGKILL,內(nèi)核會(huì)強(qiáng)行殺死應(yīng)用,從而終止容器。

(2).如果容器中的進(jìn)程沒(méi)有收到 SIGTERM 信號(hào),很有可能是因?yàn)閼?yīng)用進(jìn)程不是 PID 1,PID 1 是 shell,而應(yīng)用進(jìn)程只是 shell 的子進(jìn)程。而 shell 不具備 init 系統(tǒng)的功能,也就不會(huì)將操作系統(tǒng)的信號(hào)轉(zhuǎn)發(fā)到子進(jìn)程上,這也是容器中的應(yīng)用沒(méi)有收到 SIGTERM 信號(hào)的常見(jiàn)原因。問(wèn)題的根源就來(lái)自 Dockerfile,例如:

FROM alpine:3.7
COPY popcorn.sh .
RUN chmod +x popcorn.sh
ENTRYPOINT ./popcorn.sh
CMD ["start"]

ENTRYPOINT 指令使用的是 **shell 模式**,這樣 Docker 就會(huì)把應(yīng)用放到 shell 中運(yùn)行,因此 shell 是 PID 1。解決方案有以下幾種:

方案 1:使用 exec 模式的 ENTRYPOINT 指令

與其使用 shell 模式,不如使用 exec 模式,例如:

FROM alpine:3.7
COPY popcorn.sh .
RUN chmod +x popcorn.sh
ENTRYPOINT ["./popcorn.sh"]

這樣 PID 1 就是 ./popcorn.sh,它將負(fù)責(zé)響應(yīng)所有發(fā)送到容器的信號(hào),至于 ./popcorn.sh 是否真的能捕捉到系統(tǒng)信號(hào),那是另一回事。舉個(gè)例子,假設(shè)使用上面的 Dockerfile 來(lái)構(gòu)建鏡像,popcorn.sh 腳本每過(guò)一秒打印一次日期:

#!/bin/sh

while true
do
    date
    sleep 1
done

構(gòu)建鏡像并創(chuàng)建容器:

docker build -t truek8s/popcorn .
docker run -it --name corny --rm truek8s/popcorn

打開(kāi)另外一個(gè)終端執(zhí)行停止容器的命令,并計(jì)時(shí):

time docker stop corny

因?yàn)?nbsp;popcorn.sh 并沒(méi)有實(shí)現(xiàn)捕獲和處理 SIGTERM 信號(hào)的邏輯,所以需要 10s 左右才能停止容器。要想解決這個(gè)問(wèn)題,就要往腳本中添加信號(hào)處理代碼,讓它捕獲到 SIGTERM 信號(hào)時(shí)就終止進(jìn)程:

#!/bin/sh
# catch the TERM signal and then exit
trap "exit" TERM
while true
do
    date
    sleep 1
done

注意:下面這條指令與 shell 模式的 ENTRYPOINT 指令是等效的:

ENTRYPOINT ["/bin/sh", "./popcorn.sh"]

方案 2:直接使用 exec 命令

如果你就想使用 shell 模式的 ENTRYPOINT 指令,也不是不可以,只需將啟動(dòng)命令追加到 exec 后面即可,例如:

FROM alpine:3.7
COPY popcorn.sh .
RUN chmod +x popcorn.sh
ENTRYPOINT exec ./popcorn.sh

這樣 exec 就會(huì)將 shell 進(jìn)程替換為 ./popcorn.sh 進(jìn)程,PID 1 仍然是 ./popcorn.sh。

方案 3:使用 init 系統(tǒng)

如果容器中的應(yīng)用默認(rèn)無(wú)法處理 SIGTERM 信號(hào),又不能修改代碼,這時(shí)候方案 1 和 2 都行不通了,只能在容器中添加一個(gè) init 系統(tǒng)。init 系統(tǒng)有很多種,這里推薦使用 tini,它是專用于容器的輕量級(jí) init 系統(tǒng),使用方法也很簡(jiǎn)單:

  1. 安裝 tini
  2. 將 tini 設(shè)為容器的默認(rèn)應(yīng)用
  3. 將 popcorn.sh 作為 tini 的參數(shù)

具體的 Dockerfile 如下:

FROM alpine:3.7
COPY popcorn.sh .
RUN chmod +x popcorn.sh
RUN apk add --no-cache tini
ENTRYPOINT ["/sbin/tini", "--", "./popcorn.sh"]

現(xiàn)在 ``` tini

就是 PID 1,它會(huì)將收到的系統(tǒng)信號(hào)轉(zhuǎn)發(fā)給子進(jìn)程 ```
popcorn.sh

10. COPY與ADD優(yōu)先使用前者

COPY指令非常簡(jiǎn)單,僅用于將文件拷貝到鏡像中。ADD相對(duì)來(lái)講復(fù)雜一些,可以用于下載遠(yuǎn)程文件以及解壓壓縮包(參考官方文檔)。

FROM node:7-alpine
WORKDIR /app
COPY . /app  
RUN npm install
ENTRYPOINT ["./entrypoint.sh"]  
CMD ["start"]

11. 合理調(diào)整COPY與RUN的順序

我們應(yīng)該把變化最少的部分放在Dockerfile的前面,這樣可以充分利用鏡像緩存。在構(gòu)建鏡像的時(shí)候,docker 會(huì)按照dockerfile中的指令順序來(lái)一次執(zhí)行。每一個(gè)指令被執(zhí)行的時(shí)候 docker 都會(huì)去緩存中檢查是否有已經(jīng)存在的鏡像可以復(fù)用,而不是去創(chuàng)建一個(gè)新的鏡像復(fù)制。如果不想使用構(gòu)建緩存,可以使用docker build參數(shù)選項(xiàng)--no-cache=true來(lái)禁用構(gòu)建緩存。在使用鏡像緩存時(shí),要弄清楚緩存合適生效,何時(shí)失效。構(gòu)建緩存最基本規(guī)則如下:

  • 如果引用的父鏡像在構(gòu)建緩存中,下一個(gè)命令將會(huì)和所有從該父進(jìn)程派生的子鏡像做比較,如果有子鏡像使用相同的命令,那么緩存命中,否則緩存失效。
  • 在大部分情況下,通過(guò)比較Dockerfile中的指令和子鏡像已經(jīng)足夠了。但是有些指令需要進(jìn)一步的檢查。
  • 對(duì)于ADD和COPY指令, 文件的內(nèi)容會(huì)被檢查,并且會(huì)計(jì)算每一個(gè)文件的校驗(yàn)碼。但是文件最近一次的修改和訪問(wèn)時(shí)間不在校驗(yàn)碼的考慮范圍內(nèi)。在構(gòu)建過(guò)程中,docker 會(huì)比對(duì)已經(jīng)存在的鏡像,只要有文件內(nèi)容和元數(shù)據(jù)發(fā)生變動(dòng),那么緩存就會(huì)失效。
  • 除了ADD和COPY指令,鏡像緩存不會(huì)檢查容器中文件來(lái)判斷是否命中緩存。例如,在處理RUN apt-get -y update命令時(shí),不會(huì)檢查容器中的更新文件以確定是否命中緩存,這種情況下只會(huì)檢查命令字符串是否相同。

示例中,源代碼會(huì)經(jīng)常變化,則每次構(gòu)建鏡像時(shí)都需要重新安裝NPM模塊,這顯然不是我們希望看到的。因此我們可以先拷貝package.json,然后安裝NPM模塊,最后才拷貝其余的源代碼。這樣的話,即使源代碼變化,也不需要重新安裝NPM模塊。

FROM node:7-alpine
WORKDIR /app
COPY package.json /app  
RUN npm install  
COPY . /app
ENTRYPOINT ["./entrypoint.sh"]  
CMD ["start"]

同樣舉一反三,Python項(xiàng)目的時(shí)候,我們同樣可以先拷貝requerements.txt,然后進(jìn)行pip install requerements.txt,最后再進(jìn)行COPY 代碼。

ROM python:3.6
# 創(chuàng)建 app 目錄
WORKDIR /app
# 安裝 app 依賴
COPY src/requirements.txt ./
RUN pip install -r requirements.txt
# 打包 app 源碼
COPY src /app
EXPOSE 8080
CMD [ "python", "server.py" ]

## 12. 設(shè)置默認(rèn)的環(huán)境變量,映射端口和數(shù)據(jù)卷 運(yùn)行Docker容器時(shí)很可能需要一些環(huán)境變量。在Dockerfile設(shè)置默認(rèn)的環(huán)境變量是一種很好的方式。另外,我們應(yīng)該在Dockerfile中設(shè)置映射端口和數(shù)據(jù)卷。示例如下: ```dockerfile FROM node:7-alpine ENV PROJECT_DIR=/app WORKDIRPROJECT_DIR   RUN npm install   COPY .MEDIA_DIR   EXPOSE $APP_PORT ENTRYPOINT ["./entrypoint.sh"]   CMD ["start"] ``` [ENV](https://docs.docker.com/engine/reference/builder/#env)指令指定的環(huán)境變量在容器中可以使用。如果你只是需要指定構(gòu)建鏡像時(shí)的變量,你可以使用[ARG](https://docs.docker.com/engine/reference/builder/#arg)指令。

13. 使用LABEL設(shè)置鏡像元數(shù)據(jù)

使用LABEL指令,可以為鏡像設(shè)置元數(shù)據(jù),例如鏡像創(chuàng)建者或者鏡像說(shuō)明。舊版的Dockerfile語(yǔ)法使用MAINTAINER指令指定鏡像創(chuàng)建者,但是它已經(jīng)被棄用了。有時(shí),一些外部程序需要用到鏡像的元數(shù)據(jù),例如nvidia-docker需要用到com.nvidia.volumes.needed。示例如下:

FROM node:7-alpine  
LABEL maintainer "jakub.skalecki@example.com"  
...

14. 添加HEALTHCHECK

運(yùn)行容器時(shí),可以指定--restart always選項(xiàng)。這樣的話,容器崩潰時(shí),Docker守護(hù)進(jìn)程(docker daemon)會(huì)重啟容器。對(duì)于需要長(zhǎng)時(shí)間運(yùn)行的容器,這個(gè)選項(xiàng)非常有用。但是,如果容器的確在運(yùn)行,但是不可(陷入死循環(huán),配置錯(cuò)誤)用怎么辦?使用HEALTHCHECK指令可以讓Docker周期性的檢查容器的健康狀況。我們只需要指定一個(gè)命令,如果一切正常的話返回0,否則返回1。對(duì)HEALTHCHECK感興趣的話,可以參考這篇博客。示例如下:

FROM node:7-alpine  
LABEL maintainer "jakub.skalecki@example.com"
ENV PROJECT_DIR=/app  
WORKDIR $PROJECT_DIR
COPY package.json $PROJECT_DIR  
RUN npm install  
COPY . $PROJECT_DIR
ENV MEDIA_DIR=/media \  
    NODE_ENV=production \
    APP_PORT=3000
VOLUME $MEDIA_DIR  
EXPOSE $APP_PORT  
HEALTHCHECK CMD curl --fail http://localhost:$APP_PORT || exit 1
ENTRYPOINT ["./entrypoint.sh"]  
CMD ["start"]

當(dāng)請(qǐng)求失敗時(shí),curl --fail 命令返回非0狀態(tài)。

15. 多階段構(gòu)建

參考文檔《https://docs.docker.com/develop/develop-images/multistage-build/》在docker不支持多階段構(gòu)建的年代,我們構(gòu)建docker鏡像時(shí)通常會(huì)采用如下兩種方法:方法A.將所有的構(gòu)建過(guò)程編寫在同一個(gè)Dockerfile中,包括項(xiàng)目及其依賴庫(kù)的編譯、測(cè)試、打包等流程,可能會(huì)有如下問(wèn)題:

  • Dockerfile可能會(huì)特別臃腫
  • 鏡像層次特別深
  • 存在源碼泄露的風(fēng)險(xiǎn)

方法B.事先在外部將項(xiàng)目及其依賴庫(kù)編譯測(cè)試打包好后,再將其拷貝到構(gòu)建目錄中執(zhí)行構(gòu)建鏡像。方法B較方法A略顯優(yōu)雅一些,而且可以很好地規(guī)避方法A存在的風(fēng)險(xiǎn)點(diǎn),但仍需要我們編寫兩套或多套Dockerfile或者一些腳本才能將其兩個(gè)階段自動(dòng)整合起來(lái),例如有多個(gè)項(xiàng)目彼此關(guān)聯(lián)和依賴,就需要我們維護(hù)多個(gè)Dockerfile,或者需要編寫更復(fù)雜的腳本,導(dǎo)致后期維護(hù)成本很高。為解決以上問(wèn)題,**Docker v17.05 開(kāi)始支持多階段構(gòu)建 (multistage builds)**。使用多階段構(gòu)建我們就可以很容易解決前面提到的問(wèn)題,并且只需要編寫一個(gè) Dockerfile。你可以在一個(gè) Dockerfile 中使用多個(gè) FROM 語(yǔ)句。每個(gè) FROM 指令都可以使用不同的基礎(chǔ)鏡像,并表示開(kāi)始一個(gè)新的構(gòu)建階段。你可以很方便的將一個(gè)階段的文件復(fù)制到另外一個(gè)階段,在最終的鏡像中保留下你需要的內(nèi)容即可。默認(rèn)情況下,構(gòu)建階段是沒(méi)有命令的,我們可以通過(guò)它們的索引來(lái)引用它們,第一個(gè) FROM 指令從0開(kāi)始,我們也可以用AS指令為構(gòu)建階段命名。

案例1

FROM golang:1.7.3
WORKDIR /go/src/github.com/alexellis/href-counter/
RUN go get -d -v golang.org/x/net/html
COPY app.go .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .

FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=0 /go/src/github.com/alexellis/href-counter/app .
CMD ["./app"]

通過(guò) docker build 構(gòu)建后,最終結(jié)果是產(chǎn)生與之前相同大小的 Image,但復(fù)雜性顯著降低。您不需要?jiǎng)?chuàng)建任何中間 Image,也不需要將任何編譯結(jié)果臨時(shí)提取到本地系統(tǒng)。哪它是如何工作的呢?關(guān)鍵就在 COPY --from=0 這個(gè)指令上。Dockerfile 中第二個(gè) FROM 指令以 alpine:latest 為基礎(chǔ)鏡像開(kāi)始了一個(gè)新的構(gòu)建階段,并通過(guò) COPY --from=0 僅將前一階段的構(gòu)建文件復(fù)制到此階段。前一構(gòu)建階段中產(chǎn)生的 Go SDK 和任何中間層都會(huì)在此階段中被舍棄,而不是保存在最終 Image 中。使用多階段構(gòu)建一個(gè)python應(yīng)用。

案例2

默認(rèn)情況下,構(gòu)建階段是未命名的。您可以通過(guò)一個(gè)整數(shù)值來(lái)引用它們,默認(rèn)是從第 0 個(gè) FROM 指令開(kāi)始的。 為了方便管理,您也可以通過(guò)向 FROM 指令添加 as NAME 來(lái)命名您的各個(gè)構(gòu)建階段。下面的示例就通過(guò)命名各個(gè)構(gòu)建階段并在 COPY 指令中使用名稱來(lái)訪問(wèn)指定的構(gòu)建階段。這樣做的好處就是即使稍后重新排序 Dockerfile 中的指令,COPY 指令一樣能找到對(duì)應(yīng)的構(gòu)建階段。

FROM golang:1.7.3 as builder
WORKDIR /go/src/github.com/alexellis/href-counter/
RUN go get -d -v golang.org/x/net/html
COPY app.go    .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .

FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /go/src/github.com/alexellis/href-counter/app .
CMD ["./app"]

案例3

停在特定的構(gòu)建階段構(gòu)建鏡像時(shí),不一定需要構(gòu)建整個(gè) Dockerfile 中每個(gè)階段,您也可以指定需要構(gòu)建的階段。比如:您只構(gòu)建 Dockerfile 中名為 builder 的階段

$ docker build --target builder -t alexellis2/href-counter:latest .

此功能適合以下場(chǎng)景:

  • 調(diào)試特定的構(gòu)建階段。
  • 在 Debug 階段,啟用所有程序調(diào)試模式或調(diào)試工具,而在生產(chǎn)階段盡量精簡(jiǎn)。
  • 在 Testing 階段,您的應(yīng)用程序使用測(cè)試數(shù)據(jù),但在生產(chǎn)階段則使用生產(chǎn)數(shù)據(jù)。

案例4

使用外部鏡像作為構(gòu)建階段使用多階段構(gòu)建時(shí),您不僅可以從 Dockerfile 中創(chuàng)建的鏡像中進(jìn)行復(fù)制。您還可以使用 COPY --from 指令從單獨(dú)的 Image 中復(fù)制,支持使用本地 Image 名稱、本地或 Docker 注冊(cè)中心可用的標(biāo)記或標(biāo)記 ID。

COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf

案例5

把前一個(gè)階段作為一個(gè)新的階段在使用 FROM 指令時(shí),您可以通過(guò)引用前一階段停止的地方來(lái)繼續(xù)。同樣,采用此方式也可以方便一個(gè)團(tuán)隊(duì)中的不同角色,如何使用類似流水線的方式,一級(jí)一級(jí)提供基礎(chǔ)鏡像,同樣更方便快速的復(fù)用團(tuán)隊(duì)其他人的基礎(chǔ)鏡像。例如:

FROM alpine:latest as builder
RUN apk --no-cache add build-base
FROM builder as build1
COPY source1.cpp source.cpp
RUN g++ -o /binary source.cpp
FROM builder as build2
COPY source2.cpp source.cpp
RUN g++ -o /binary source.cpp
# ---- 基礎(chǔ) python 鏡像 ----
FROM python:3.6 AS base
# 創(chuàng)建 app 目錄
WORKDIR /app
# ---- 依賴 ----
FROM base AS dependencies  
COPY gunicorn_app/requirements.txt ./
# 安裝 app 依賴
RUN pip install -r requirements.txt
# ---- 復(fù)制文件并 build ----
FROM dependencies AS build  
WORKDIR /app
COPY . /app
# 在需要時(shí)進(jìn)行 Build 或 Compile
# --- 使用 Alpine 發(fā)布 ----
FROM python:3.6-alpine3.7 AS release  
# 創(chuàng)建 app 目錄
WORKDIR /app
COPY --from=dependencies /app/requirements.txt ./
COPY --from=dependencies /root/.cache /root/.cache
# 安裝 app 依賴
RUN pip install -r requirements.txt
COPY --from=build /app/ ./
CMD ["gunicorn", "--config", "./gunicorn_app/conf/gunicorn_config.py", "gunicorn_app:app"]


責(zé)任編輯:武曉燕 來(lái)源: 運(yùn)維開(kāi)發(fā)故事
相關(guān)推薦

2023-11-28 07:57:04

Dockerfile指令

2017-03-30 22:16:21

DevOpsIT應(yīng)用程序

2013-05-17 11:43:55

主數(shù)據(jù)數(shù)據(jù)管理

2011-12-31 10:18:33

響應(yīng)設(shè)計(jì)

2013-04-01 09:39:06

JavaJava異常

2013-12-31 09:26:31

JavaScript技巧

2015-03-16 16:16:15

JavaJava異常處理Java最佳實(shí)踐

2023-11-12 11:54:55

UX性能widget

2013-05-28 09:47:36

異常處理Javay異常

2012-03-29 09:35:32

WEBCSS

2012-03-19 09:55:38

CSS

2015-01-09 11:29:45

DockerDockerFile創(chuàng)建鏡像

2023-09-13 08:00:00

JavaScript循環(huán)語(yǔ)句

2015-09-15 16:01:40

混合IT私有云IT架構(gòu)

2011-08-18 11:05:21

jQuery

2023-07-21 01:12:30

Reactfalse?變量

2016-12-27 08:49:55

API設(shè)計(jì)策略

2024-04-10 08:24:29

2024-01-09 07:25:31

2024-04-07 00:06:00

Rust編程技巧
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 精品日韩一区二区 | 亚洲人在线| 日韩久久综合网 | 日本天堂一区 | 日韩欧美国产精品一区二区 | 在线观看av免费 | 一区二区在线不卡 | 国产精品国产成人国产三级 | www.国产精 | 天天拍夜夜爽 | 中文字幕一区在线观看视频 | 日韩精品亚洲专区在线观看 | 国产一区二区三区在线免费观看 | aaa国产大片| 九久久| 久久久久久91 | a毛片 | 亚洲精品二区 | 国产欧美视频一区二区 | 日日操视频 | 日日干夜夜操天天操 | 国产精品美女久久久久aⅴ国产馆 | 精品1区 | 四虎伊人 | 在线观看中文视频 | 一区二区三区免费 | 在线中文视频 | 精品国产乱码久久久久久88av | 国产精品69久久久久水密桃 | 亚洲国产一区视频 | 精品欧美一区二区三区久久久 | 国产精品毛片无码 | 久草新在线 | 天天操天天射综合网 | 久久精品av麻豆的观看方式 | 精品久久久久一区二区国产 | 欧美性高潮 | 久久精品久久久久久 | 三级av在线| 国产三级国产精品 | 欧美中文字幕在线观看 |