最佳化 Dockerfile 還有很多方向,那筆者以精簡 image 做為結尾,有興趣可以參考文末的參考資料連結。
精簡 image 筆者分成兩個部分說明,一個是容量,另一個是 commit 數。到昨天為止,Dockerfile 內容有點少,筆者先根據 Laravel 官網的 Server Requirements 與 Redis 把 bcmath
和 redis
PHP Extension 安裝也加入 Dockerfile:
FROM php:7.3
# 全域設定
WORKDIR /source
# 安裝環境、安裝工具
RUN curl -sS https://getcomposer.org/installer | php && mv composer.phar /usr/local/bin/composer
RUN apt update && apt install unzip
# 安裝 bcmath 與 redis
RUN docker-php-ext-install bcmath
RUN pecl install redis
RUN docker-php-ext-enable redis
# 加速套件下載的套件
RUN composer global require hirak/prestissimo
# 安裝程式依賴套件
COPY composer.* ./
RUN composer install --no-scripts
# 複製程式碼
COPY . .
RUN composer run post-autoload-dump
CMD ["php", "artisan", "serve", "--host", "0.0.0.0"]
接著就看筆者如何精簡這份 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
RUN rm download.zip
這個寫法會產生一個有 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/*
# 或使用 apt-get
apt-get clean && rm -rf /var/lib/apt/lists/*
筆者實測下面兩種寫法:
RUN apt update && apt install unzip
RUN apt update && apt install unzip && apt clean && rm -rf /var/lib/apt/lists/*
結果如下:
$ docker images laravel
REPOSITORY TAG IMAGE ID CREATED SIZE
laravel latest ac4543dab760 37 minutes ago 502MB
laravel optimized c68838f961f0 43 seconds ago 485MB
可以看得出明顯的差異。
composer global require
調整寫法實測Composer 清快取的指令為 composer clear-cache
。實測下面兩種寫法:
RUN composer global require hirak/prestissimo
RUN composer global require hirak/prestissimo && composer clear-cache
RUN composer install --no-scripts
RUN composer install --no-scripts && composer clear-cache
結果如下:
$ docker images laravel
REPOSITORY TAG IMAGE ID CREATED SIZE
laravel latest ac4543dab760 41 minutes ago 502MB
laravel optimized 5672f76be599 6 seconds ago 461MB
可以看出這個差異也非常多,兩個總合起來就能省掉 50MB 的空間,不無小省。
最終的 Dockerfile 如下:
FROM php:7.3
# 全域設定
WORKDIR /source
# 安裝環境、安裝工具
RUN curl -sS https://getcomposer.org/installer | php && mv composer.phar /usr/local/bin/composer
RUN apt update && apt install unzip && apt clean && rm -rf /var/lib/apt/lists/*
RUN docker-php-ext-install bcmath
RUN pecl install redis
RUN docker-php-ext-enable redis
# 加速套件下載的套件
RUN composer global require hirak/prestissimo && composer clear-cache
# 安裝程式依賴套件
COPY composer.* ./
RUN composer install --no-scripts && composer clear-cache
# 複製程式碼
COPY . .
RUN composer run post-autoload-dump
CMD ["php", "artisan", "serve", "--host", "0.0.0.0"]
雙管齊下的結果如下:
$ docker images laravel
REPOSITORY TAG IMAGE ID CREATED SIZE
laravel latest 097e4cc0805f 3 seconds ago 502MB
laravel optimized 55d3a5fcfd3d About a minute ago 443MB
以最初的目標來看,是要啟動一個 Laravel 的 server 並看到歡迎頁,那其實有些套件就不是必要的,如單元測試套件。Composer 加上 --no-dev
參數即可排除開發階段安裝的套件。
RUN composer install --no-dev --no-scripts && composer clear-cache
結果如下:
$ docker images laravel
REPOSITORY TAG IMAGE ID CREATED SIZE
laravel latest 097e4cc0805f 38 minutes ago 502MB
laravel optimized fd655c5532e0 3 seconds ago 426MB
其他常見非必要的工具如 vim、sshd、git 等,在 container 執行的時候,通常都用不到,這些都是可以移除的目標。
本範例沒有非必要的工具。
剛開始使用 Docker 的時候,通常會找自己比較熟悉的 Linux 發行版,像筆者是 Ubuntu 派的。不同的發行版,其實容量也有點差異,以下是今天下載 Ubuntu、Debian、CentOS、Alpine 的容量比較:
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu latest 9140108b62dc 3 days ago 72.9MB
debian stable-slim da838f7eb4f8 2 weeks ago 69.2MB
debian latest f6dcff9b59af 2 weeks ago 114MB
centos latest 0d120b6ccaa8 7 weeks ago 215MB
alpine latest a24bb4013296 4 months ago 5.57MB
這裡可以看到 Alpine 小到很不可思議,而 Debian 則是有普通版跟 slim 版,這些都是可以嘗試的選擇。以 PHP 官方 image 來說,主要版本是 Debian,但也有 Alpine 的版本,因此如果有需要追求極小 image 的話,也是可以多找看看有沒有已經做好的 Alpine 版本可以 FROM
來用,網路也比較多文章在討論使用 Alpine 的好處。
以下為改寫成 Alpine 的 Dockerfile,主要差異在 apk
安裝指令:
FROM php:7.3-alpine
# 全域設定
WORKDIR /source
# 安裝環境、安裝工具
RUN curl -sS https://getcomposer.org/installer | php && mv composer.phar /usr/local/bin/composer
RUN apk add --no-cache unzip
RUN docker-php-ext-install bcmath
RUN apk add --no-cache --virtual .build-deps autoconf g++ make && pecl install redis && apk del .build-deps
RUN docker-php-ext-enable redis
# 加速套件下載的套件
RUN composer global require hirak/prestissimo && composer clear-cache
# 安裝程式依賴套件
COPY composer.* ./
RUN composer install --no-dev --no-scripts && composer clear-cache
# 複製程式碼
COPY . .
RUN composer run post-autoload-dump
CMD ["php", "artisan", "serve", "--host", "0.0.0.0"]
改完之後的結果如下,差了 300MB:
$ docker images laravel
REPOSITORY TAG IMAGE ID CREATED SIZE
laravel optimized ce9f7438e818 14 seconds ago 102MB
laravel latest 097e4cc0805f 53 minutes ago 502MB
雖然看起來很美好,但事實上改用 Alpine 並不是簡單的事,以下聊聊筆者從 Ubuntu 轉 Alpine 的一點心得。
因筆者對寫腳本不熟,因此有發生 bash 指令在 sh 上不能使用的問題,當時處理了很久才解決。
Ubuntu 為 apt
,Alpine 為 apk
。用途都是下載套件,使用概念大多都差不多,而實際使用上最大的困擾在於套件名稱不同。比方說,Memcached 的相關套件,apt 叫 libmemcached-dev
,apk 則是 libmemcached-libs
,這類問題都得上網查詢資料,以及做嘗試才能找到真正要的套件為何。
Alpine 採用 musl libc 而不是 glibc 系列,因此某些套件可能會無法使用。
更詳細的說明可參考 PHP image 或其他官方 image 對於 Alpine image 的解釋。
官方說法是大多數套件都沒有問題,但聽朋友說過確實踩過這個雷,只是筆者從來沒遇過,所以沒有範例可以說明。
Docker 的 commit 數量是有限制的,為 127 個,這是精簡的理由之一。另一個更重大的理由是:如果檔案系統使用 AUFS 的前提下,只要 commit 數越多,檔案系統的操作就會越慢,因此更需要想辦法來減少 commit 數。
再次提醒,一個 Docker 指令就是一個 commit。通常能合併的會是 RUN
指令,如:
# 合併前
RUN apk add --no-cache git
RUN apk add --no-cache unzip
# 合併後
RUN apk add --no-cache git unzip
可是如果指令過長的話,就會失去維護性。通常筆者會改寫成像下面這樣:
RUN apk add --no-cache \
git \
unzip
這樣比較能一眼看出目前裝了什麼套件,但相對在 build 的過程,資訊相較就會比較雜亂。
筆者參考官方 Dockerfile,發現 set -xe
可以對查 log 有幫助:
FROM php:7.3-alpine
RUN set -xe && \
apk add --no-cache \
git \
unzip
實際作用筆者沒有特別研究,但對於 build 過程的影響,最主要的差異在:不管串接幾個指令,它都會有一個像下面的 log:
+ apk add --no-cache git unzip
它還會同時把空白全部去除,這樣在看 build log 會非常清楚。
追求極致,把所有 RUN
合併在一個 commit,那這樣就會喪失分層式可以共用 image 的優點,因此如何拿捏適點的 commit 大小,是很有藝術的。
以 PHP 為例,筆者通常會分成下面幾種類型:
unzip
bcmath
或 redis
這邊就不說明怎麼處理的,直接給結果吧:
FROM php:7.3-alpine
# 全域設定
WORKDIR /source
# 安裝環境
RUN apk add --no-cache unzip
# 安裝 extension
RUN set -xe && \
apk add --no-cache --virtual .build-deps \
autoconf \
g++ \
make \
&& \
docker-php-ext-install \
bcmath \
&& \
pecl install \
redis \
&& \
docker-php-ext-enable \
redis \
&& \
apk del .build-deps \
&& \
php -m
RUN set -xe && \
curl -sS https://getcomposer.org/installer | php && \
mv composer.phar /usr/local/bin/composer
# 加速套件下載的套件
RUN composer global require hirak/prestissimo && composer clear-cache
# 安裝程式依賴套件
COPY composer.* ./
RUN composer install --no-dev --no-scripts && composer clear-cache
# 複製程式碼
COPY . .
RUN composer run post-autoload-dump
CMD ["php", "artisan", "serve", "--host", "0.0.0.0"]
會影響 container 啟動快與否,有一部分會取決於 image 下載時間,這也是今天能解掉的問題;另外則是 process 的調整,未來有機會再討論。
請問為什麼會這樣? 我在ubuntu 22 執行"make build"會出現以下錯誤。thank you
如果我不執行"RUN composer global require hirak/prestissimo" 這一句,會不會影響之後學習docker?
這不是 Docker 的問題,是 PHP 與 Composer 的問題。
因為新版 PHP 不支援 Composer v1 的那個套件。改用 Composer v2 即可取代那個套件。
這不是 Docker 的問題,是 PHP 與 Composer 的問題。
因為新版 PHP 不支援 Composer v1 的那個套件。改用 Composer v2 即可取代那個套件。
我嘗試過將php7.0 和將composer 更新去2.4.2,但是都是有錯誤,然後我將"RUN composer global require hirak/prestissimo"移除,我上網找過資料,它是能夠令下載包加速。應該沒有影響學習docker。
可能是我沒說明清楚
「改用 Composer v2 即可取代那個套件」指的就是用 v2 之後,就不需要那個套件了。