摘要:將安裝與模塊安裝放在一起的話,則每次修改源代碼,都需要重新安裝,這顯然不合適。使用設置鏡像元數(shù)據(jù)使用指令,可以為鏡像設置元數(shù)據(jù),例如鏡像創(chuàng)建者或者鏡像說明。自從年雙十一正式上線,累計處理了億錯誤事件,付費客戶有金山軟件百姓網(wǎng)等眾多品牌企業(yè)。
譯者按: Dockerfile 的語法非常簡單,然而如何加快鏡像構建速度,如何減少 Docker 鏡像的大小卻不是那么直觀,需要積累實踐經(jīng)驗。這篇博客可以幫助你快速掌握編寫 Dockerfile 的技巧。
原文: How to write excellent Dockerfiles
譯者: Fundebug
本文采用意譯,版權歸原作者所有
我已經(jīng)使用 Docker 有一段時間了,其中編寫 Dockerfile 是非常重要的一部分工作。在這篇博客中,我打算分享一些建議,幫助大家編寫更好的 Dockerfile。
目標:更快的構建速度
更小的 Docker 鏡像大小
更少的 Docker 鏡像層
充分利用鏡像緩存
增加 Dockerfile 可讀性
讓 Docker 容器使用起來更簡單
總結編寫.dockerignore 文件
容器只運行單個應用
將多個 RUN 指令合并為一個
基礎鏡像的標簽不要用 latest
每個 RUN 指令后刪除多余文件
選擇合適的基礎鏡像(alpine 版本最好)
設置 WORKDIR 和 CMD
使用 ENTRYPOINT (可選)
在 entrypoint 腳本中使用 exec
COPY 與 ADD 優(yōu)先使用前者
合理調(diào)整 COPY 與 RUN 的順序
設置默認的環(huán)境變量,映射端口和數(shù)據(jù)卷
使用 LABEL 設置鏡像元數(shù)據(jù)
添加 HEALTHCHECK
示例示例 Dockerfile 犯了幾乎所有的錯(當然我是故意的)。接下來,我會一步步優(yōu)化它。假設我們需要使用 Docker 運行一個 Node.js 應用,下面就是它的 Dockerfile(CMD 指令太復雜了,所以我簡化了,它是錯誤的,僅供參考)。
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
構建鏡像:
docker build -t wtf .1. 編寫.dockerignore 文件
構建鏡像時,Docker 需要先準備context ,將所有需要的文件收集到進程中。默認的context包含 Dockerfile 目錄中的所有文件,但是實際上,我們并不需要.git 目錄,node_modules 目錄等內(nèi)容。 .dockerignore 的作用和語法類似于 .gitignore,可以忽略一些不需要的文件,這樣可以有效加快鏡像構建時間,同時減少 Docker 鏡像的大小。示例如下:
.git/ node_modules/2. 容器只運行單個應用
從技術角度講,你可以在 Docker 容器中運行多個進程。你可以將數(shù)據(jù)庫,前端,后端,ssh,supervisor 都運行在同一個 Docker 容器中。但是,這會讓你非常痛苦:
非常長的構建時間(修改前端之后,整個后端也需要重新構建)
非常大的鏡像大小
多個應用的日志難以處理(不能直接使用 stdout,否則多個應用的日志會混合到一起)
橫向擴展時非常浪費資源(不同的應用需要運行的容器數(shù)并不相同)
僵尸進程問題 - 你需要選擇合適的 init 進程
因此,我建議大家為每個應用構建多帶帶的 Docker 鏡像,然后使用 Docker Compose 運行多個 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 start3. 將多個 RUN 指令合并為一個
Docker 鏡像是分層的,下面這些知識點非常重要:
Dockerfile 中的每個指令都會創(chuàng)建一個新的鏡像層。
鏡像層將被緩存和復用
當 Dockerfile 的指令修改了,復制的文件變化了,或者構建鏡像時指定的變量不同了,對應的鏡像層緩存就會失效
某一層的鏡像緩存失效之后,它之后的鏡像層緩存都會失效
鏡像層是不可變的,如果我們再某一層中添加一個文件,然后在下一層中刪除它,則鏡像中依然會包含該文件(只是這個文件在 Docker 容器中不可見了)。
Docker 鏡像類似于洋蔥。它們都有很多層。為了修改內(nèi)層,則需要將外面的層都刪掉。記住這一點的話,其他內(nèi)容就很好理解了。
現(xiàn)在,我們將所有的RUN指令合并為一個。同時把apt-get upgrade刪除,因為它會使得鏡像構建非常不確定(我們只需要依賴基礎鏡像的更新就好了)
FROM ubuntu ADD . /app RUN apt-get update && apt-get install -y nodejs && cd /app && npm install CMD npm start
記住一點,我們只能將變化頻率一樣的指令合并在一起。將 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 start4. 基礎鏡像的標簽不要用 latest
當鏡像沒有指定標簽時,將默認使用latest 標簽。因此, FROM ubuntu 指令等同于FROM ubuntu:latest。當時,當鏡像更新時,latest 標簽會指向不同的鏡像,這時構建鏡像有可能失敗。如果你的確需要使用最新版的基礎鏡像,可以使用 latest 標簽,否則的話,最好指定確定的鏡像標簽。
示例 Dockerfile 應該使用16.04作為標簽。
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 start5. 每個 RUN 指令后刪除多余文件
假設我們更新了 apt-get 源,下載,解壓并安裝了一些軟件包,它們都保存在/var/lib/apt/lists/目錄中。但是,運行應用時 Docker 鏡像中并不需要這些文件。我們最好將它們刪除,因為它會使 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 start6. 選擇合適的基礎鏡像(alpine 版本最好)
在示例中,我們選擇了ubuntu作為基礎鏡像。但是我們只需要運行 node 程序,有必要使用一個通用的基礎鏡像嗎?node鏡像應該是更好的選擇。
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 是一個極小化的 Linux 發(fā)行版,只有 4MB,這讓它非常適合作為基礎鏡像。
FROM node:7-alpine ADD . /app RUN cd /app && npm install CMD npm start
apk是 Alpine 的包管理工具。它與apt-get有些不同,但是非常容易上手。另外,它還有一些非常有用的特性,比如no-cache和 --virtual選項,它們都可以幫助我們減少鏡像的大小。
7. 設置 WORKDIR 和 CMDWORKDIR指令可以設置默認目錄,也就是運行RUN / CMD / ENTRYPOINT指令的地方。
CMD指令可以設置容器創(chuàng)建是執(zhí)行的默認命令。另外,你應該講命令寫在一個數(shù)組中,數(shù)組中每個元素為命令的每個單詞(參考官方文檔)。
FROM node:7-alpine WORKDIR /app ADD . /app RUN npm install CMD ["npm", "start"]8. 使用 ENTRYPOINT (可選)
ENTRYPOINT指令并不是必須的,因為它會增加復雜度。ENTRYPOINT是一個腳本,它會默認執(zhí)行,并且將指定的命令錯誤其參數(shù)。它通常用于構建可執(zhí)行的 Docker 鏡像。entrypoint.sh 如下:
#!/usr/bin/env sh # $0 is a script name, # $1, $2, $3 etc are passed arguments # $1 is our command CMD=$1 case "$CMD" in "dev" ) npm install export NODE_ENV=development exec npm run dev ;; "start" ) # we can modify files here, using ENV variables passed in # "docker create" command. It can"t be done during build process. echo "db: $DATABASE_ADDRESS" >> /app/config.yml export NODE_ENV=production exec npm start ;; * ) # Run custom command. Thanks to this line we can still use # "docker run our_image /bin/bash" and it will work exec $CMD ${@:2} ;; esac
示例 Dockerfile:
FROM node:7-alpine WORKDIR /app ADD . /app RUN npm install ENTRYPOINT ["./entrypoint.sh"] CMD ["start"]
可以使用如下命令運行該鏡像:
# 運行開發(fā)版本 docker run our-app dev # 運行生產(chǎn)版本 docker run our-app start # 運行bash docker run -it our-app /bin/bash9. 在 entrypoint 腳本中使用 exec
在前文的 entrypoint 腳本中,我使用了exec命令運行 node 應用。不使用exec的話,我們則不能順利地關閉容器,因為 SIGTERM 信號會被 bash 腳本進程吞沒。exec命令啟動的進程可以取代腳本進程,因此所有的信號都會正常工作。
10. COPY 與 ADD 優(yōu)先使用前者COPY指令非常簡單,僅用于將文件拷貝到鏡像中。ADD相對來講復雜一些,可以用于下載遠程文件以及解壓壓縮包(參考官方文檔)。
FROM node:7-alpine WORKDIR /app COPY . /app RUN npm install ENTRYPOINT ["./entrypoint.sh"] CMD ["start"]11. 合理調(diào)整 COPY 與 RUN 的順序
我們應該把變化最少的部分放在 Dockerfile 的前面,這樣可以充分利用鏡像緩存。
示例中,源代碼會經(jīng)常變化,則每次構建鏡像時都需要重新安裝 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"]12. 設置默認的環(huán)境變量,映射端口和數(shù)據(jù)卷
運行 Docker 容器時很可能需要一些環(huán)境變量。在 Dockerfile 設置默認的環(huán)境變量是一種很好的方式。另外,我們應該在 Dockerfile 中設置映射端口和數(shù)據(jù)卷。示例如下:
FROM node:7-alpine 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 ENTRYPOINT ["./entrypoint.sh"] CMD ["start"]
ENV指令指定的環(huán)境變量在容器中可以使用。如果你只是需要指定構建鏡像時的變量,你可以使用ARG指令。
13. 使用 LABEL 設置鏡像元數(shù)據(jù)使用LABEL指令,可以為鏡像設置元數(shù)據(jù),例如鏡像創(chuàng)建者或者鏡像說明。舊版的 Dockerfile 語法使用MAINTAINER指令指定鏡像創(chuàng)建者,但是它已經(jīng)被棄用了。有時,一些外部程序需要用到鏡像的元數(shù)據(jù),例如nvidia-docker需要用到com.nvidia.volumes.needed。示例如下:
FROM node:7-alpine LABEL maintainer "jakub.skalecki@example.com" ...14. 添加 HEALTHCHECK
運行容器時,可以指定--restart always選項。這樣的話,容器崩潰時,Docker 守護進程(docker daemon)會重啟容器。對于需要長時間運行的容器,這個選項非常有用。但是,如果容器的確在運行,但是不可(陷入死循環(huán),配置錯誤)用怎么辦?使用HEALTHCHECK指令可以讓 Docker 周期性的檢查容器的健康狀況。我們只需要指定一個命令,如果一切正常的話返回 0,否則返回 1。對 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"]
當請求失敗時,curl --fail 命令返回非 0 狀態(tài)。
關于FundebugFundebug專注于JavaScript、微信小程序、微信小游戲、支付寶小程序、React Native、Node.js和Java線上應用實時BUG監(jiān)控。 自從2016年雙十一正式上線,F(xiàn)undebug累計處理了10億+錯誤事件,付費客戶有Google、360、金山軟件、百姓網(wǎng)等眾多品牌企業(yè)。歡迎大家免費試用!
版權聲明轉載時請注明作者Fundebug以及本文地址:
https://blog.fundebug.com/2017/05/15/write-excellent-dockerfile/
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://www.ezyhdfw.cn/yun/26932.html
摘要:最終疊加在一起成為新的鏡像。優(yōu)化的方法就是使用和換行符將多條語句合并成一條執(zhí)行。第一點,所有指令在中需要大寫,從而和具體的操作命令區(qū)分開來。第二點,對于過長的指令請合理使用換行符,從而增加的可閱讀性。 前言 有一定旅行經(jīng)驗的朋友都知道,即使在出發(fā)前制定了詳細的出行計劃,也會在路途中因為各式各樣的狀況而不得不重新修改計劃。這個狀況在我們編寫Dockerfile時一樣存在?;?0分鐘編寫...
摘要:現(xiàn)在有了之后,部署方式變了更加容易,不容易出現(xiàn)配置錯誤,環(huán)境不一致問題。實際應用中,經(jīng)常遇到多個容器相配合運行一套應用程序的情況。所以想要將應用運行在容器中,該容器必須將該應用所有的環(huán)境依賴安裝好。 showImg(https://segmentfault.com/img/remote/1460000017081815); 背景 ??以前部署應用,需要各種環(huán)境配置,各種shell操作...
摘要:現(xiàn)在有了之后,部署方式變了更加容易,不容易出現(xiàn)配置錯誤,環(huán)境不一致問題。實際應用中,經(jīng)常遇到多個容器相配合運行一套應用程序的情況。所以想要將應用運行在容器中,該容器必須將該應用所有的環(huán)境依賴安裝好。 showImg(https://segmentfault.com/img/remote/1460000017081815); 背景 ??以前部署應用,需要各種環(huán)境配置,各種shell操作...
摘要:的主要作用是自己根據(jù)基礎鏡像,重新定制鏡像,而不是直接從官方倉庫拿現(xiàn)成的使用。以接下來要構建的環(huán)境來說明下,下面我將要搭建一個的開發(fā)環(huán)境,需要進行配合。它的主要作用是持久化數(shù)據(jù),避免容器銷毀后內(nèi)部數(shù)據(jù)丟失暴露到宿主機的端口。 以前一直使用 Vagrant 作為自己的開發(fā)環(huán)境,并且在上家公司也推行大家采用 Vagrant 作為開發(fā)環(huán)境,保障公司使用的是同一套開發(fā)環(huán)境。隨著docker的流...
閱讀 2019·2021-09-22 10:02
閱讀 2037·2021-09-02 15:40
閱讀 2948·2019-08-30 15:55
閱讀 2419·2019-08-30 15:44
閱讀 3686·2019-08-30 13:18
閱讀 3318·2019-08-30 11:00
閱讀 2043·2019-08-29 16:57
閱讀 642·2019-08-29 16:41