譯者按: Dockerfile 的語法很是簡單,然而如何加快鏡像構建速度,如何減小 Docker 鏡像的大小卻不是那麼直觀,須要積累實踐經驗。這篇博客能夠幫助你快速掌握編寫 Dockerfile 的技巧。前端
本文采用意譯,版權歸原做者全部node
我已經使用 Docker 有一段時間了,其中編寫 Dockerfile 是很是重要的一部分工做。在這篇博客中,我打算分享一些建議,幫助你們編寫更好的 Dockerfile。mysql
示例 Dockerfile 犯了幾乎全部的錯(固然我是故意的)。接下來,我會一步步優化它。假設咱們須要使用 Docker 運行一個 Node.js 應用,下面就是它的 Dockerfile(CMD 指令太複雜了,因此我簡化了,它是錯誤的,僅供參考)。linux
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 複製代碼
構建鏡像:git
docker build -t wtf .
複製代碼
構建鏡像時,Docker 須要先準備context
,將全部須要的文件收集到進程中。默認的context
包含 Dockerfile 目錄中的全部文件,可是實際上,咱們並不須要.git 目錄,node_modules 目錄等內容。 .dockerignore
的做用和語法相似於 .gitignore
,能夠忽略一些不須要的文件,這樣能夠有效加快鏡像構建時間,同時減小 Docker 鏡像的大小。示例以下:github
.git/
node_modules/
複製代碼
從技術角度講,你能夠在 Docker 容器中運行多個進程。你能夠將數據庫,前端,後端,ssh,supervisor 都運行在同一個 Docker 容器中。可是,這會讓你很是痛苦:sql
所以,我建議你們爲每一個應用構建單獨的 Docker 鏡像,而後使用 Docker Compose 運行多個 Docker 容器。docker
如今,我從 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 複製代碼
Docker 鏡像是分層的,下面這些知識點很是重要:npm
Docker 鏡像相似於洋蔥。它們都有不少層。爲了修改內層,則須要將外面的層都刪掉。記住這一點的話,其餘內容就很好理解了。
如今,咱們將全部的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 start 複製代碼
當鏡像沒有指定標籤時,將默認使用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 start 複製代碼
假設咱們更新了 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 start 複製代碼
在示例中,咱們選擇了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 發行版,只有 4MB,這讓它很是適合做爲基礎鏡像。
FROM node:7-alpine
ADD . /app RUN cd /app && npm install
CMD npm start 複製代碼
apk是 Alpine 的包管理工具。它與apt-get
有些不一樣,可是很是容易上手。另外,它還有一些很是有用的特性,好比no-cache
和 --virtual
選項,它們均可以幫助咱們減小鏡像的大小。
WORKDIR指令能夠設置默認目錄,也就是運行RUN
/ CMD
/ ENTRYPOINT
指令的地方。
CMD指令能夠設置容器建立是執行的默認命令。另外,你應該講命令寫在一個數組中,數組中每一個元素爲命令的每一個單詞(參考官方文檔)。
FROM node:7-alpine
WORKDIR /app ADD . /app RUN npm install
CMD ["npm", "start"] 複製代碼
ENTRYPOINT指令並非必須的,由於它會增長複雜度。ENTRYPOINT
是一個腳本,它會默認執行,而且將指定的命令錯誤其參數。它一般用於構建可執行的 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"] 複製代碼
可使用以下命令運行該鏡像:
# 運行開發版本
docker run our-app dev
# 運行生產版本
docker run our-app start
# 運行bash
docker run -it our-app /bin/bash
複製代碼
在前文的 entrypoint 腳本中,我使用了exec
命令運行 node 應用。不使用exec
的話,咱們則不能順利地關閉容器,由於 SIGTERM 信號會被 bash 腳本進程吞沒。exec
命令啓動的進程能夠取代腳本進程,所以全部的信號都會正常工做。
COPY指令很是簡單,僅用於將文件拷貝到鏡像中。ADD相對來說複雜一些,能夠用於下載遠程文件以及解壓壓縮包(參考官方文檔)。
FROM node:7-alpine
WORKDIR /app
COPY . /app RUN npm install
ENTRYPOINT ["./entrypoint.sh"] CMD ["start"] 複製代碼
咱們應該把變化最少的部分放在 Dockerfile 的前面,這樣能夠充分利用鏡像緩存。
示例中,源代碼會常常變化,則每次構建鏡像時都須要從新安裝 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"] 複製代碼
運行 Docker 容器時極可能須要一些環境變量。在 Dockerfile 設置默認的環境變量是一種很好的方式。另外,咱們應該在 Dockerfile 中設置映射端口和數據卷。示例以下:
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指令指定的環境變量在容器中可使用。若是你只是須要指定構建鏡像時的變量,你可使用ARG指令。
使用LABEL指令,能夠爲鏡像設置元數據,例如鏡像建立者或者鏡像說明。舊版的 Dockerfile 語法使用MAINTAINER指令指定鏡像建立者,可是它已經被棄用了。有時,一些外部程序須要用到鏡像的元數據,例如nvidia-docker須要用到com.nvidia.volumes.needed
。示例以下:
FROM node:7-alpine
LABEL maintainer "jakub.skalecki@example.com" ...
複製代碼
運行容器時,能夠指定--restart always
選項。這樣的話,容器崩潰時,Docker 守護進程(docker daemon)會重啓容器。對於須要長時間運行的容器,這個選項很是有用。可是,若是容器的確在運行,可是不可(陷入死循環,配置錯誤)用怎麼辦?使用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 狀態。
Fundebug專一於JavaScript、微信小程序、微信小遊戲、支付寶小程序、React Native、Node.js和Java線上應用實時BUG監控。 自從2016年雙十一正式上線,Fundebug累計處理了10億+錯誤事件,付費客戶有Google、360、金山軟件、百姓網等衆多品牌企業。歡迎你們免費試用!
轉載時請註明做者Fundebug以及本文地址: blog.fundebug.com/2017/05/15/…