最佳化 Dockerfile - 精簡 image
最佳化 Dockerfile 還有很多方向,以精簡 image 做為結尾,有興趣可以參考文末的參考資料連結。
精簡 image 分成兩個部分說明,一個是容量,另一個是 commit 數。到昨天為止,Dockerfile 內容有點少,先根據 Laravel 官網的 Server Requirements 與 Redis 把 bcmath
和 redis
PHP Extension 安裝也加入 Dockerfile:
FROM php:7.3 |
接著就看如何精簡這份 image
精簡容量
Docker 讓啟動 container 的流程變簡單,理論上啟動 container 應該是飛快的,但 image 如果又肥又大,那這件事就只能活在理論裡了。
移除暫存檔案
有的指令會在執行過程產生暫存檔案,如上例的 apt
和 composer global require
都會產生下載檔案的 cache 並 commit 進 image。這些檔案通常沒有必要保留,因此可以移除節省空間。
這裡要重新提醒一個 Dockerfile 產生 Docker image 的觀念:一個指令就是一個 commit,有多少 commit 就會佔用多少容量。比方說下面的寫法:
RUN curl -LO https://example.com/download.zip && dosomething |
這個寫法會產生一個有 download.zip
檔案的 commit 與一個移除 download.zip
檔案的 commit,這樣是無法確實地把 download.zip
容量釋放出來的,需要改用下面的寫法:
RUN curl -LO https://example.com/download.zip && dosomething && rm download.zip |
這個寫法,RUN
指令最後的結果會是 download.zip
檔案已移除,因此 commit 就不會有 download.zip
的內容。
總之,當看到移除檔案的指令是獨立一個 RUN
的話,那個指令通常是有像上面範例一樣的改善空間。
apt
調整寫法實測
apt
清除快取的寫法為:
apt clean && rm -rf /var/lib/apt/lists/* |
實測下面兩種寫法:
RUN apt update && apt install unzip |
結果如下:
$ docker images laravel |
可以看得出明顯的差異。
composer global require
調整寫法實測
Composer 清快取的指令為 composer clear-cache
。實測下面兩種寫法:
RUN composer global require hirak/prestissimo |
結果如下:
$ docker images laravel |
可以看出這個差異也非常多,兩個總合起來就能省掉 50MB 的空間,不無小省。
最終的 Dockerfile 如下:
FROM php:7.3 |
雙管齊下的結果如下:
$ docker images laravel |
移除非必要的東西
以最初的目標來看,是要啟動一個 Laravel 的 server 並看到歡迎頁,那其實有些套件就不是必要的,如單元測試套件。Composer 加上 --no-dev
參數即可排除開發階段安裝的套件。
RUN composer install --no-dev --no-scripts && composer clear-cache |
結果如下:
$ docker images laravel |
其他常見非必要的工具如 vim、sshd、git 等,在 container 執行的時候,通常都用不到,這些都是可以移除的目標。
本範例沒有非必要的工具。
改使用容量較小的 image
剛開始使用 Docker 的時候,通常會找自己比較熟悉的 Linux 發行版,如 Ubuntu。不同的發行版,其實容量也有點差異,以下是今天下載 Ubuntu、Debian、CentOS、Alpine 的容量比較:
REPOSITORY TAG IMAGE ID CREATED SIZE |
這裡可以看到 Alpine 小到很不可思議,而 Debian 則是有普通版跟 slim 版,這些都是可以嘗試的選擇。以 PHP 官方 image 來說,主要版本是 Debian,但也有 Alpine 的版本,因此如果有需要追求極小 image 的話,也是可以多找看看有沒有已經做好的 Alpine 版本可以 FROM
來用,網路也比較多文章在討論使用 Alpine 的好處。
以下為改寫成 Alpine 的 Dockerfile,主要差異在 apk
安裝指令:
FROM php:7.3-alpine |
改完之後的結果如下,差了 300MB:
$ docker images laravel |
雖然看起來很美好,但事實上改用 Alpine 並不是簡單的事,以下聊聊從 Ubuntu 轉 Alpine 的一點心得。
shell 不是 bash
因對寫腳本不熟,因此有發生 bash 指令在 sh 上不能使用的問題,當時處理了很久才解決。
不同的套件管理工具
Ubuntu 為 apt
,Alpine 為 apk
。用途都是下載套件,使用概念大多都差不多,而實際使用上最大的困擾在於套件名稱不同。比方說,Memcached 的相關套件,apt 叫 libmemcached-dev
,apk 則是 libmemcached-libs
,這類問題都得上網查詢資料,以及做嘗試才能找到真正要的套件為何。
系統使用的 libc 不同
Alpine 採用 musl libc 而不是 glibc 系列,因此某些套件可能會無法使用。
更詳細的說明可參考 PHP image 或其他官方 image 對於 Alpine image 的解釋。
官方說法是大多數套件都沒有問題,但聽朋友說過確實踩過這個雷,只是從來沒遇過,所以沒有範例可以說明。
精簡 commit
Docker 的 commit 數量是有限制的,為 127 個,這是精簡的理由之一。另一個更重大的理由是:如果檔案系統使用 AUFS 的前提下,只要 commit 數越多,檔案系統的操作就會越慢,因此更需要想辦法來減少 commit 數。
合併 commit
再次提醒,一個 Docker 指令就是一個 commit。通常能合併的會是 RUN
指令,如:
# 合併前 |
可是如果指令過長的話,就會失去維護性。通常會改寫成像下面這樣:
RUN apk add --no-cache \ |
這樣比較能一眼看出目前裝了什麼套件,但相對在 build 的過程,資訊相較就會比較雜亂。
參考官方 Dockerfile,發現 set -xe
可以對查 log 有幫助:
FROM php:7.3-alpine |
實際作用沒有特別研究,但對於 build 過程的影響,最主要的差異在:不管串接幾個指令,它都會有一個像下面的 log:
+ apk add --no-cache git unzip |
它還會同時把空白全部去除,這樣在看 build log 會非常清楚。
要如何決定合併的做法
追求極致,把所有 RUN
合併在一個 commit,那這樣就會喪失分層式可以共用 image 的優點,因此如何拿捏適點的 commit 大小,是很有藝術的。
以 PHP 為例,通常會分成下面幾種類型:
- 系統層的準備,如
unzip
- PHP extension 的準備,如
bcmath
或redis
- 依賴下載,包括 Composer 執行檔
- 執行程式初始化順序
這邊就不說明怎麼處理的,直接給結果吧:
FROM php:7.3-alpine |
今日自我回顧
會影響 container 啟動快與否,有一部分會取決於 image 下載時間,這也是今天能解掉的問題;另外則是 process 的調整,未來有機會再討論。
- 練習減少 image 的空間
- 練習整理 RUN 指令