前言

應該有不少人正為 Docker 環境太過沉重而頭痛吧。
我自己也曾多次接觸過非常卡頓的 Docker 環境,並且有一步一步把原因排除掉的經驗。

這次要介紹的是,針對本機的 Docker 環境(Windows)進行輕量化的方法,並搭配我實際執行過的步驟一起說明。

不過,其中包含像是刪除資源、移轉 named volume 等,如果省略確認與備份,可能會影響本機環境的操作。請先確認各步驟的注意事項,並先用工作副本或不重要的資料來測試。

請依照上方順序,從你自己的環境中看起來最可能有效的地方開始嘗試。

注意:.dockerignore、多階段建置、基底映像輕量化、善用建置快取,主要是改善建置、拉取、容器重建與啟動時間。它們不是直接加快運行中頁面回應速度的對策。

1. 清理不必要的資源

Docker 不會自動刪除建置快取、舊映像檔、已停止的容器。這些東西累積起來會壓迫磁碟空間,甚至讓整個主機的運作變慢。

步驟

  1. 確認目前的磁碟使用狀況
docker system df
  1. 檢查 RECLAIMABLE 欄位的數值。如果累積到數十 GB,就要注意了
  2. 確認要刪除的對象
docker ps -a
docker images
docker volume ls
  1. 一次刪除未使用的映像檔、容器、網路
docker system prune

如果你有保留停止中的容器作為復原用,執行前請務必先確認目標。刪除後若要重新建立由 Compose 管理的服務,請執行 docker compose up -d

2. 用 docker stats 找出瓶頸

如果只是「感覺很慢」就直接挑對策,很容易做白工。先確認到底是哪裡出問題。

步驟

  1. 確認所有執行中的容器資源狀況
docker stats --no-stream
  1. CPU %MEM USAGE 欄位,檢查是否只有特定容器特別突出
  2. 如果有找到,確認那個容器的應用程式日誌

如果只有某一個容器異常高負載,問題大多不在 Docker 本身,而是應用程式程式碼的可能性較高。

頁面回應時間也要在修改前後進行測量。由於 PowerShell 裡的 curl 可能是 Invoke-WebRequest 的別名,因此這裡明確使用 curl.exe

1..30 | ForEach-Object {
    curl.exe -s -o NUL -w "TTFB=%{time_starttransfer}s TOTAL=%{time_total}s`n" http://localhost:8080/
}

TTFB 是第一個資料回傳前的時間,TOTAL 是整體回應完成的時間。

curl 能測到的是 HTTP 請求的回應時間。瀏覽器上的完整顯示時間還包含圖片、JavaScript、CSS 的載入、瀏覽器快取、外部服務處理時間等。最後確認時,請搭配瀏覽器開發者工具一起看。

3. 防止日誌檔過度膨脹

Docker 預設的日誌驅動程式 json-file 若放著不管,日誌檔會無限制地膨脹。對於流量高的網站來說,這常常就是磁碟空間被吃光的原因。

步驟

  1. 打開 docker-compose.yml,在變得很重的服務加上以下設定
services:
  web:
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "10"
  1. 套用設定
docker compose up -d --force-recreate web

由於會重新建立既有容器,服務會暫時停止。web 請替換成實際的服務名稱。

max-file 是指定要保留的檔案數。因為這會讓你無法再往前查更早以前的日誌,所以使用這個設定時也要留意這個缺點。

4. 重新檢視 Docker Desktop 的資源配置

很容易被忽略的一點,是分配給 Docker Desktop 本身的 CPU、記憶體設定。

步驟

  1. 開啟 Docker Desktop 的「Settings」→「Resources」
  2. 確認 CPU 與 Memory 的分配量(若是 WSL2 後端,則在 C:\Users\使用者名稱\.wslconfig 中設定)
  3. 如果主機端還有餘裕,就逐步增加後重新啟動
  4. docker stats 確認是否有改善

即使是往增加配置的方向調整,如果壓迫到主機端的記憶體或 CPU,也可能讓包含 IDE 和瀏覽器在內的整體環境變慢。請小幅度調整,並用相同的測量方法比較效果。

我目前的環境是在 .wslconfig 裡這樣寫的。

image.png

5. 重新檢視 WSL2 的檔案共享方式

如果把 Windows 主機上的專案直接做 bind mount,PHP、Node.js、Python 等在大量讀寫小檔案時,檔案存取可能會成為瓶頸。

步驟

  1. 確認專案是放在 Windows 端(C:\...)還是 WSL2 的 Linux 端(\\wsl$\...
  2. 在 Docker Desktop 的「Settings」→「General」確認是否啟用了 WSL2 後端
  3. 將現有工作資料夾備份,或把本機的工作副本搬到 WSL2 的 Linux 端
  4. 比較搬移前後同一頁面的回應時間與檔案變更偵測

從 Linux 端檔案系統做 bind mount,通常比從 Windows 端檔案系統做 bind mount 更容易得到較好的檔案存取效能。

尤其是大量使用 bind mount 的 Node.js 或 PHP 專案,通常更能感受到差異。

6. 用 .dockerignore 縮小建置內容

如果每次建置都變慢,很常見的情況是把 node_modules.git 之類不必要的檔案也一起送進 Docker 了。

步驟

  1. 在專案根目錄建立 .dockerignore 檔案
node_modules
.git
*.log
  1. 執行建置,確認傳送的建置內容大小有沒有變小
docker build .

如果建置時顯示的 Sending build context to Docker daemon 數值變小了,就代表有生效。

7. 用多階段建置讓映像檔變輕量

如果建置所需工具被原封不動地留在執行用映像檔裡,映像檔就會白白變大,容器啟動與重建也會變慢。

依照所使用的框架與語言,Dockerfile 的寫法會差很多。請先確認執行時需要的共用函式庫、設定檔、憑證、檔案權限後再套用。

步驟

  1. 將 Dockerfile 分成建置階段與執行階段
FROM node:20 AS builder
WORKDIR /app
COPY . .
RUN npm ci && npm run build

FROM node:20-slim
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/index.js"]

產物類型會依語言與框架而不同。以 PHP 來說,PHP 擴充、Composer 的 vendor、Apache 或 PHP-FPM 設定等,執行時需要的檔案都必須包含到最終階段。

  1. 重新建置並比較映像檔大小
docker images

不過,如果忘了複製產物或共用函式庫,啟動後就會出現錯誤,所以請確認啟動、資料庫連線、檔案上傳、圖片處理與主要頁面都正常。

8. 選擇輕量版基底映像檔

如果 FROM 指定的基底映像檔太大,映像檔的 pull 與容器啟動就會花更多時間。

・Node.js:node:20node:20-slim
・PHP:php:8.2-apachephp:8.2-apache-slim,如果不需要 Apache,則可用 CLI 版 php:8.2-cli
・Python:python:3.12python:3.12-slim

步驟

  1. 查看 Dockerfile 第一行的基底映像檔
  2. 確認是否有輕量版標籤(-slim-alpine
  3. 將標籤改成輕量版後重新建置
docker compose build

alpine 系列不是基於 glibc,而是 musl,因此有些相依套件可能無法運作。特別是用 apt 前提來建置 PHP 擴充功能時,要注意套件名稱在 alpine(apk)底下會改變。修改後請確認啟動、資料庫連線、登入、檔案上傳、圖片處理、主要頁面、批次處理都正常。

9. 讓建置快取發揮作用

舊版 Docker builder 有時候只要原始碼稍微改一下,快取就不會命中,導致每次都得完整建置。

重點

・先只 COPY 相依性定義檔(例如 package.json、composer.json 等),先執行安裝,再 COPY 其餘原始碼;這樣在原始碼變更時,相依性層仍可保留快取並重複使用

COPY composer.json composer.lock ./
RUN composer install
COPY . .

在 BuildKit 中,也可以使用 cache mount,讓套件管理器的下載快取能在不同次建置之間重複利用。

# syntax=docker/dockerfile:1
RUN --mount=type=cache,target=/root/.npm npm ci

步驟

  1. 確認 BuildKit 與使用中的 builder
docker buildx ls
  1. 在 PowerShell 中啟用 BuildKit 後進行建置
$env:DOCKER_BUILDKIT = "1"
docker compose build

在 Docker Desktop 裡有時本來就會預設使用 BuildKit,因此就算設定了環境變數,也不一定會有額外效果。

10. 將經常讀寫的目錄切換成 named volume

直接掛載主機目錄的 bind mount,尤其在 Windows 上,每次檔案存取都會牽涉跨越 OS 邊界的處理,因此效能很容易下降。像 DB 資料目錄這種頻繁讀寫的地方,改用 named volume 後,體感通常會有明顯差異。

步驟

  1. 切換前務必先備份既有資料
docker compose exec -T db mysqldump -u root -p mydb > backup.sql
  1. 修改 docker-compose.yml 的 volume 定義
services:
  db:
    volumes:
      - db-data:/var/lib/mysql

volumes:
  db-data:
  1. 重新建立容器,並從備份還原資料
docker compose down
docker compose up -d --force-recreate db
docker compose exec -T db mysql -u root -p mydb < backup.sql
  1. 確認應用程式是否能正常讀寫資料

dbmydb/var/lib/mysql 都只是範例,請替換成實際設定。若執行 docker compose down -v,named volume 也會被刪除,因此若要保留 DB 資料,請不要加 -v

不只要確認備份有成功,也要確認能還原到另一個本機 volume 或工作副本。

總結

・先做資源清理、找出瓶頸、設定日誌上限,絕對不吃虧。不過,docker system prune 請先確認刪除對象,並注意不要刪到 DB volume
・Docker Desktop 的資源配置與 WSL2 檔案共享方式,在資源不足或 bind mount 成為瓶頸時特別有效
.dockerignore、多階段建置、基底映像輕量化、善用建置快取,主要是改善建置、拉取、容器重建與啟動時間
・切換成 named volume 適合 DB、快取等 I/O 成為瓶頸的情況。請務必先備份既有資料,並確認能成功還原
・請在修改前後測量 docker system dfdocker stats 與頁面回應時間,並比較中位數與第 95 百分位

這次我介紹了自己實際做過的 Docker 環境輕量化、加速方法。
我自己的環境在這樣調整後,運作狀況明顯改善,建置速度與本機環境的頁面顯示也快了很多。

實際能不能感受到效果會依環境而異,因此如果照著這些方法做了還是沒改善,建議可以把 Docker 相關檔案交給 AI 讀取,問問看瓶頸到底在哪裡。

通常它會列出一長串改善建議,但如果不小心全部套用,環境也可能被破壞,所以哪些可以執行、哪些不行,還是得由你自己判斷。

先從安全的方法開始,一個一個試吧!

參考
・Docker 日誌設定:https://docs.docker.com/engine/logging/configure/
・Docker 資源限制:https://docs.docker.com/engine/containers/resource_constraints/
・Docker WSL2 最佳實務:https://docs.docker.com/desktop/features/wsl/best-practices/
・Docker Volume:https://docs.docker.com/engine/storage/volumes/


原文出處:https://qiita.com/nolanlover0527/items/24ee07122dd4d0523069


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝25  
370
🥈
我愛JS
3
🥉
NewsData
2
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
📢 贊助商廣告 · 我要刊登