高效編寫Dockerfile的幾條準則

CodeSheep發表於2018-07-11

概述

  • Dockerfile 是專門用來進行自動化構建映象的編排檔案(就像Jenkins 2.0時代的Jenkinsfile是對Jenkins的Job和Stage的編排一樣),我們可以通過 docker build 命令來自動化地從 Dockerfile 所描述的步驟來構建自定義的 Docker映象,這比我們去命令列一條條指令執行的方式構建高效得多。

  • 另一方面,由於 Dockerfile 提供了統一的配置語法,因此通過這樣一份配置檔案,我們可以在各種不同的平臺上進行分發,需要時通過 Dockerfile 構建一下就能得到所需的映象。

  • 最後一個必須提的優點便是:Dockerfile 通過與映象配合使用,使得 Docker映象構建之時可以充分利用 “映象的快取功能”,因此也提效不少!

然而寫 Dockerfile 也像寫程式碼一樣,一份精心設計、Clean Code 的 Dockerfile 能在提高可讀性的同時也大大提升Docker的使用效率

因此下面就結合實踐來講幾條 Dockerfile 的實踐心得!

注: 本文首發於 My 公眾號 CodeSheep ,可 長按掃描 下面的 小心心 來訂閱 ↓ ↓ ↓

CodeSheep · 程式羊



基礎映象的選擇有講究

在我的文章 《利用K8S技術棧打造個人私有云(連載之:基礎映象製作與實驗)》 中,我們是基於某個Linux基礎映象作為底包,然後打包進我需要的功能從而形成自己的映象。

這裡選擇基礎映象時是有講究的:

  • 一是 應當儘量選擇官方映象庫裡的基礎映象;
  • 二是 應當選擇輕量級的映象做底包

就典型的Linux基礎映象來說,大小關係如下:

Ubuntu > CentOS > Debian
複製程式碼

因此相比 Ubuntu,其實更推薦使用最輕量級的 Debian映象,而且它也是一個完整的Release版,可以放心使用



多使用標籤Tag 有好處

  • 構建映象時,給其打上一個易讀的映象標籤有助於幫助瞭解映象的功能,比如:
docker build -t=“centos:wordpress" .
複製程式碼

例如上面的這個centos映象是用來做wordpress用的,所以已經整合了wordpress功能,這一看就很清晰明瞭

  • 再者,我們也應該在 Dockerfile 的 FROM 指令中明確指明標籤 Tag,不要再讓 Docker daemon 去猜,如
FROM debian:codesheep
複製程式碼


充分利用映象快取

什麼是映象快取?

由 Dockerfile 最終構建出來的映象是在基礎映象之上一層層疊加而得,因此在過程中會產生一個個新的 映象層。Docker daemon 在構建映象的過程中會快取一系列中間映象。

docker build映象時,會順序執行Dockerfile中的指令,並同時比較當前指令和其基礎映象的所有子映象,若發現有一個子映象也是由相同的指令生成,則 命中快取,同時可以直接使用該子映象而避免再去重新生成了。

為了有效地使用快取,需要保證 Dockerfile 中指令的 連續一致,儘量將相同指令的部分放在前面,而將有差異性的指令放在後面

**舉例:**假如我想用 Dockerfile方式 基於最基本的 CentOS 映象來構建兩個不同的映象時,兩個Dockerfile的開頭可以相同:

FROM centos:latest

# 下面安裝兩個常用的工具
RUN yum install -y net-tools.x86_64

RUN yum install lrzsz

######## 上面為兩個Dockerfile檔案中相同的部分######

######## 下面為兩個Dockerfile檔案中不同的部分######

......

複製程式碼


ADD 與 COPY 指令的正確使用

雖然兩者都可以新增檔案到映象中,但在一般用法中,還是推薦以COPY指令為首選,原因在於ADD指令並沒有COPY指令來的純粹,ADD會新增一些額外功能,典型的如下 ADD 一個壓縮包時,其不僅會複製,還會自動解壓,而有時我們並不需要這種額外的功能。

ADD codesheep.tar.gz /path
複製程式碼

除此之外,在需要新增多個檔案到映象中的時候,不要一次性集中新增,而是選擇 按需 在必要時 逐個 新增即可,因為這樣有利於利用映象快取



##儘量使用docker volume

雖然上面一條原則說推薦通過 COPY 命令來向映象中新增多個檔案,然而實際情況中,若檔案 大而多 的時候還是應該優先用 docker -v 命令來掛載檔案,而不是依賴於 ADD 或者 COPY

最後必須說一下,這裡的“儘量”是有個度的,適度把握才行。



CMD 和 ENTRYPOINT指令 的理解使用

Dockerfile 製作映象時,會組合 CMD 和 ENTRYPOINT 指令來作為容器執行時的預設命令:即 CMD + ENTRYPOINT。此時的預設命令組成中:

  • ENTRYPOINT 指令部分“一般”固定不變,容器執行時不修改
  • 而 CMD 部分的指令也可以改變,表現在執行容器時,docker run 命令中提供的引數會覆蓋CMD的指令內容。

舉個例子:

FROM debian:latest

MAINTAINER codesheep@163.com

ENTRYPOINT [ "ls", "-l"]
CMD ["-a"]
複製程式碼

若以預設命令執行容器,可以發現,執行的是 ls -a -l 命令:

ls -l -a

docker run 中增加引數 -t

docker run -it --rm --name test debian:codesheep -t
複製程式碼

也可以發現執行的是 ls -l -t,即 Dockerfile 中的 CMD 原引數被覆蓋了:

ls -l -t

因此推薦的使用方式是:

  • 使用exec格式的 ENTRYPOINT指令 設定固定的預設命令和引數

  • 使用 CMD指令 設定可變的引數



不推薦在 Dockerfile中 做埠對映

Dockerfile 可以通過 EXPOSE指令 將容器埠對映到主機埠上,但這樣會導致映象在一臺主機上僅能啟動一個容器!

所以應該在 docker run 命令中來用 -p 引數來指定埠對映,而不要將該工作置於 Dockerfile 之中:

#儘量避免這種方式
EXPOSE 8080:8899

#僅僅暴露埠
EXPOSE 8080
複製程式碼


使用 Dockerfile 來共享映象

推薦通過共享 Dockerfile 的方式來共享映象,優點多多:

  • 通過 Dockerfile 構建的映象使用者可以清楚地看到構建的過程

  • 就像 Jenkinsfile 可以加入版本控制從而追蹤CI系統的變遷和步驟的回滾一樣,Dockerfile 作為一個編排檔案同樣可以入庫做版本控制,這樣也可以回溯

  • 使用 Dockerfile 構建的映象具有確定性,沒有玄學的成分



後記

如果有興趣,也可以抽點時間看看作者一些關於容器化、微服務化方面的文章:

作者相關的SpringBt實踐文章在此:



CodeSheep · 程式羊


相關文章