最佳化 Dockerfile - 調整 build context

寫 Dockerfile 並不困難,但好用的 Dockerfile 就需要利用許多技巧,加上不斷嘗試,才有辦法寫出來。

接下來會以昨天的 Dockerfile 為例,說明要如何寫出好的 Dockerfile。

FROM php:7.3

WORKDIR /source
COPY . .

CMD ["php", "artisan", "serve", "--host", "0.0.0.0"]

什麼是 build context?

一開始執行 docker build 指令時,Docker 會將建置目錄裡的檔案複製一份到 Docker daemon 裡,接著才會開始執行 build image。

Sending build context to Docker daemon  42.95MB

Build context 指的是複製到 Docker daemon 的檔案。

昨天完成的 Dockerfile 存在一個很明確的問題:建置過程從 build context 傳入了太多非必要的檔案(指 COPY . . 無腦複製法),這會有下面的問題:

  1. 讓建置時間變久
  2. 增加不穩定因素
  3. 增加不必要的檔案

今天針對 build context 來看看該如何調整 Dockerfile

減少不必要的 build context

在 build image 的過程,如果遇到了 COPY 指令時,會從 build context 複製進 container。(其他需要用到 host 檔案的指令也是相同概念)

build context 的某些檔案可能會跟 build image 的流程或結果毫無關係,如 .git 目錄或 .vagrant 目錄。若不做任何處理,一來啟動 docker build 會花時間在複製檔案到 Docker daemon;二來在使用懶人複製法 COPY . . 也會把這些用不到的檔案複製進 container 佔用空間,可說是百害無一利。

Docker 定義了 .dockerignore 檔案,可以直接在裡面定義不進 build context 的檔案,範例如下:

.git
.vagrant

減少與 host 環境的關聯

.git.vagrant 跟 build 出 Laravel image 的過程或結果無關,所以全部排除是沒有問題的。有些檔案則是結果需要,但過程會不希望從 host 複製進去的,比方說 vendor 目錄。

舉個例子,當 host 是 PHP 7.3,image 是 PHP 7.2 的時候,就很有可能會出問題。如 host 的 PHP 7.3 可能會安裝 PHPUnit 9,但在 image PHP 7.2 是不能使用的。

這時也可以利用 .dockerignore 來排除 vendor 目錄,達到加速 build image 的目的:

.git
.vagrant
vendor

同樣的概念可以應用在 node_modules 或類似的目錄上。

vendor 裡有程式運作必要的檔案,而剛剛只提到了要排除 vendor,並沒有提到如何把 vendor 生出來,因此才會出現找不到檔案的錯誤。

安裝需要的工具與依賴

vendor 在 host 可能會因為開發者環境不同,而安裝到不一樣的套件。但依昨天 Dockerfile 第一手的測試結果可以知道,container 的環境是 PHP 7.3,因此我們在 container 裡執行,就能確保安裝的環境是 PHP 7.3。

Dockerfile 執行指令使用 RUN 指令,而 Composer 安裝套件指令為 composer install,再改一次 Dockerfile 檔如下:

FROM php:7.3

WORKDIR /source
COPY . .
RUN composer install

CMD ["php", "artisan", "serve", "--host", "0.0.0.0"]

這裡發現還是有問題,關鍵訊息為 /bin/sh: 1: composer: not found,意思是 composer 這個指令在 container 裡面找不到。

即然找不到指令,先把指令安裝好再執行 composer install 總沒問題了吧?

FROM php:7.3

WORKDIR /source
COPY . .
# 安裝 Composer 指令
RUN curl -sS https://getcomposer.org/installer | php && mv composer.phar /usr/local/bin/composer
RUN composer install

CMD ["php", "artisan", "serve", "--host", "0.0.0.0"]

composer install 看起來可以執行,但執行過程有問題,關鍵訊息有兩個:

  1. The zip extension and unzip command are both missing, skipping.
  2. sh: 1: git: not found

從訊息上看起來,第一個訊息是因為 zip ext 和 unzip 指令找不到,所以 Composer 採用了替代方案,使用 git 指令,但也沒有安裝,所以出現第二個訊息。

接下來有三個選擇:

  1. 安裝 zip ext
  2. 安裝 unzip 指令
  3. 安裝 git 指令

因為第一個選擇會需要提到更多 PHP 的細節,因此採取第二個選擇。安裝指令視平台,會用到 aptyumapk 等指令,PHP 7.3 是 Debian 系統,所以會用 apt 指令。

範例是進 shell 先嘗試安裝指令。一開始無法安裝,但只要 update 套件資訊後就可以安裝了。最終的 Dockerfile 如下:

FROM php:7.3

WORKDIR /source
COPY . .
RUN curl -sS https://getcomposer.org/installer | php && mv composer.phar /usr/local/bin/composer
# 安裝 unzip 指令
RUN apt update && apt install unzip
RUN composer install

CMD ["php", "artisan", "serve", "--host", "0.0.0.0"]

Dockerfile 嘗試可以建置完成,並使用 make run 開瀏覽器可以看到網頁。

今日自我回顧

今天有多加了幾個 Dockerfile 指令,目的是為了減少 build context,這讓 build image 的效率加快非常多。

另外更重要的,為了讓 composer install 指令能在 container 裡正常執行,有花了非常多功夫在說明如何發現、以及安裝工具。當要 build 客製化的 image 時,常常會遇到這類問題,建議讀者可以多了解並嘗試這些過程,這對未來解決問題會很有幫助。

  • 練習使用 .dockerignore 將不必要的 build context 排除
  • 練習使用 Dockerfile 三循環的流程,來發現缺少的工具,以及安裝需要的工具