Docker Compose 多容器編排

在現代的微服務(Microservices)架構中,一個完整的應用系統通常由多個獨立的容器組成。例如:一個標準的電商網站可能包含前端、後端、資料庫 (PostgreSQL)、快取 (Redis) 以及訊息佇列 (RabbitMQ)。

如果手動使用 docker run 來啟動這些相互連結的服務,你需要記住每個容器的啟動順序、網路配置、環境變數與磁碟掛載,這不僅耗時且極難維護。Docker Compose 就是為了解決這個問題而生的「編排指揮官」。

什麼是 Docker Compose?

Docker Compose 是 Docker 官方提供的工具,它讓我們能透過一個 YAML 格式的設定檔 (docker-compose.yaml) 來宣告應用程式的所有服務、網路與磁碟卷。

其核心價值在於 基礎架構即代碼 (Infrastructure as Code, IaC)

  • 環境一致性:無論是開發、測試還是生產環境,都共用同一套配置,徹底解決「在我電腦上明明可以跑」的窘境。
  • 一鍵操作:只需一個簡單指令,就能啟動或拆除整個系統。
  • 配置自動化:自動處理容器間的網路通訊,讓你專注於開發而非網路設定。

docker-compose.yaml 配置深度詳解

Compose 檔案是整個工具的靈魂。以下是一個整合了進階選項(如資源限制與健康檢查)的完整配置範例:

services:
  web:
    build:
      context: .
      dockerfile: Dockerfile
    # 完整語法 (Long Syntax) 的埠號對應
    ports:
      - target: 80
        published: 8080
        protocol: tcp
        mode: host
    # 環境變數
    environment:
      - DATABASE_URL=postgres://user:pass@db:5432/mydb
    # 依賴關係(確保啟動順序與就緒狀態)
    depends_on:
      db:
        condition: service_healthy
    # 資源限制與預留
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 512M
        reservations:
          memory: 128M
    restart: unless-stopped
    # 解決殭屍進程問題
    init: true
    networks:
      - backend

  db:
    image: postgres:15-alpine
    env_file:
      - .env.db
    # 完整語法 (Long Syntax) 的磁碟卷掛載
    volumes:
      - type: volume
        source: pg_data
        target: /var/lib/postgresql/data
        read_only: false
    # 健康檢查:這決定了其他服務是否能開始連接
    healthcheck:
      test: ['CMD-SHELL', 'pg_isready -U postgres']
      interval: 5s
      timeout: 3s
      retries: 5
      start_period: 10s
    networks:
      - backend

networks:
  backend:
    driver: bridge

volumes:
  pg_data:

YAML 層級結構與語法邏輯

在編寫 Docker Compose 檔案時,正確理解層級(Indentation)關鍵字分類至關重要。YAML 檔案是透過縮排來決定「誰屬於誰」的母子關係。

第一層級:宣告資源類型 (Level 1)

這是 YAML 檔案的最頂層(無縮排),用來告訴 Docker Compose 你需要準備哪些類型的資源:

  • services:最核心區塊。在這裡定義應用程式的各個容器。
  • networks:定義專案使用的虛擬網路。
  • volumes:定義專案使用的持久化數據卷。

第二層級:定義資源名稱 (Level 2)

這是緊接在第一層級下方的縮排項目。由你自定義名稱,這些名稱具有重要的實務意義:

  1. 服務名稱 (Service Name)
    • 如範例中的 webdb
    • DNS 作用:這就是容器在內網中的「主機名稱」。在 web 容器內,你只要連線到 http://db:5432 就能找到資料庫,不需要記住變動的 IP。
  2. 網路/卷名稱
    • backendpg_data
    • 這讓多個服務可以「引用」同一個資源。例如 webdb 都宣告使用 backend 網路,它們才能互相通訊。

第三層級與更深:配置參數 (Level 3+)

在資源名稱下方,就是該資源的具體配置參數(如 image, ports, build 等),這些就是我們接下來要詳解的關鍵設定。

配置參數說明

深入了解這些參數,能讓你從單純的「容器啟動者」進化為「架構設計師」。以下是針對 YAML 範例中出現的各個重要區塊的深度技術詳解:

1. image:指定映像檔來源

  • 如果不是自行建置,則使用 image 指定官方或私有的映像檔。建議帶上具體的標籤(如 postgres:15-alpine)而非使用 latest,以確保環境的一致性與穩定性。

2. build:客製化建置參數

當你需要從本地原始碼打造映像檔時,build 區塊提供了詳細的控制:

  • contextdockerfile:分別定義建置的目錄路徑與 Dockerfile 的檔名。
  • args (建置時變數):傳遞變數給 Dockerfile 內的 ARG 指令,方便在建置時決定環境參數(如版本號)。
  • target:在多階段建置中,指定要建置到哪一個階段(如 devprod)。

3. ports:詳細的埠號映射

範例中使用長語法 (Long Syntax) 展示了更精確的設定:

  • target:容器內部的埠號。
  • published:暴露在宿主機上的埠號。
  • protocol:通訊協定,常見為 tcpudp
  • mode:常見為 host,在叢集模式 (Swarm) 下則有不同的路由選擇。

4. depends_on:定義啟動順序與就緒狀態

這是處理多服務依賴的關鍵:

  • condition: service_healthy:這讓 Web 服務不僅是在資料庫容器「啟動」後執行,而是要等到資料庫通過「健康檢查 (Healthcheck)」並回報為 Ready 狀態後才啟動。

5. deploy: resources:資源的硬限制與保留

  • limits (硬限制):容器佔用的天花板。例如 cpus: '0.5' 表示該容器最多只能消耗宿主機一半的核心性能;memory 則限制記憶體上限。
  • reservations (預留值):當宿主機資源緊張時,Docker 保證該容器一定能拿到的最低資源額度。

6. restart:自動重啟策略

定義容器因故障停止後的反應:

  • unless-stopped:除非被手動停止(如執行 docker compose stop),否則當容器崩潰或 Docker 服務重啟時,該容器會自動重跑。

7. volumes:磁碟卷與持續性數據

範例使用長語法來精確定義掛載:

  • type: volume:使用由 Docker 管理的磁碟卷(如範例中的 pg_data),這是生產環境最推薦的做法。
  • read_only: false:設定為可讀寫,若設為 true 則容器內無法修改掛載的內容。

8. healthcheck:健康狀況監控

範例中展示了確保服務可用的全套配置:

  • test:執行的指令。
  • interval:每隔多久執行一次檢查。
  • timeout:單次檢查的超時時間。
  • retries:連續失敗幾次後將容器標示為 unhealthy
  • start_period:啟動初期的寬限期。

9. 架構層級配置 (Networks & Volumes)

在 YAML 的最底層,我們需要定義全域資源:

  • networks (頂層):定義該專案使用的網路名稱與驅動程式 (driver: bridge 是單機最常用的模式)。
  • volumes (頂層):定義持久化數據的磁碟卷空間。

10. command 與 entrypoint:容器的啟動靈魂

這兩個參數定義了容器啟動時該跑什麼程式:

  • entrypoint:設定容器作為一個「可執行指令」的存在(例如一個 Python 直譯器)。
  • command:作為 entrypoint 的預設參數。
  • 交互關係:如果你設定了 entrypoint: ["python"]command: ["app.py"],容器最終會執行 python app.py。你可以隨時透過 docker runcompose run 的指令參數來覆蓋 command 的內容。

11. environment:環境變數的權重奧秘

配置的可管理性往往取決於你如何處理環境變數:

  • 優先權順序:在 Compose 中,environment 的設定優先權最高,會直接蓋掉來自宿主機或是 .env 檔案的同名變數。
  • env_file:當環境變數多達數十個時,將它們歸類到獨立檔案中管理(如 .env.db, .env.api)能讓你的 YAML 保持乾淨清爽。

12. logging:防止磁碟被撐爆的最後防線

許多生產環境的意外都是因為容器日誌把硬碟塞爆:

  • driver: json-file:預設的日誌驅動。
  • 滾動備份:強烈建議設定 max-size: "10m"max-file: "3"。這表示每個日誌檔滿 10MB 就自動切分,且最多只保留最近的 3 份檔案。

Docker Compose 常用指令與技巧

掌握了 YAML 配置後,你需要一套流暢的指令來操作你的服務集群。

在舊版中指令是 docker-compose。在最新的 Docker 版本中,建議使用整合後的 docker compose(中間是空格)。

1. 服務啟動與生命週期管理

這組指令控制著容器的「生老病死」:

  • docker compose up -d: 最核心指令。它會自動建置/拉取映像檔、建立網路與磁碟卷,並背景啟動容器。
  • docker compose down: 優雅地停止並移除容器與網路。
  • docker compose stop / docker compose start: 僅停止或啟動容器。與 down 不同,這不會刪除容器,容器內的變動(非 Volume 部分)會被保留。
  • docker compose restart: 重新啟動服務。常用於修改配置或程式碼後(若不需要重新建置映像檔時)。
  • docker compose pause / docker compose unpause: 這是維運時的「定格」神器。pause 會透過 Linux 的 cgroups 掛起容器內的所有進程,讓容器暫停運作但不釋放資源;unpause 則恢復運作。

2. 進階啟動參數

  • --build:強制重新建置映像檔。
  • --force-recreate:即便配置沒變,也強迫刪除並重新建立容器。
  • --remove-orphans:清理掉那些曾經在 YAML 中,但現在已被刪除的舊服務殘留容器。

3. 日誌、偵錯與狀態檢視

  • docker compose logs -f --tail="100" --timestamps: 即時追蹤日誌。--timestamps 能幫你分析服務啟動時各階段的耗時。
  • docker compose ps: 列出該分案下所有服務的運行狀態、埠號映射與健康狀況。
  • docker compose exec [service] [cmd]: 進去執行中的容器。例如 docker compose exec db psql -U user
  • docker compose run --rm [service] [cmd]: 啟動一個一次性的容器來執行特定任務(如資料庫遷移),執行完畢後自動移除 (--rm)。

4. 維運與性能監控

當你的服務變多時,你需要知道誰在「偷吃」資源:

  • docker compose stats: 即時顯示所有服務的 CPU、記憶體、網路 I/O 與磁碟 I/O 佔用率。
  • docker compose top: 列出特定服務容器內部正在運行的 Linux 進程列表。
  • docker compose images: 查看目前專案所使用的所有映像檔及其 ID、大小、標籤資訊。

5. 配置驗證與安全性

  • docker compose config: 這不只是檢查語法。它會將你分散在多個 Overrides 檔案與 .env 中的配置進行「最終合併」並顯示出來。在執行 up 之前,這是確保合併結果符合預期的保險動作。
  • docker compose version: 確認目前 Compose 套件的版本,這對確認是否支援某些 V2 新功能(如 Watch)至關重要。

高效率開發:Compose Watch

這是 Docker Compose V2 引入的強大功能,旨在取代傳統笨重的「掛載整份原始碼」方案。

配置範例:

services:
  web:
    build: .
    develop:
      watch:
        - action: sync
          path: ./src
          target: /app/src
          ignore:
            - node_modules/
        - action: rebuild
          path: package.json

執行指令:

docker compose watch

當你修改 src/ 下的代碼時,Compose 會即時「同步」檔案到容器內;若修改了 package.json,它則會自動「重新建置」映像檔。這能維持開發環境的高效與純淨。

進階應用:多環境配置與 Overrides

在真實的開發流程中,我們通常需要針對不同階段(開發、測試、生產)使用不同的配置。例如:開發環境需要自動加載代碼變化,而生產環境需要資源限制與日誌監控。

Docker Compose 提供了強大的 Overrides 機制,讓你不需要為了每個環境都重新寫一份完整的 YAML 檔案。

合併機制的運作細節

當你指定多個 Compose 檔案時,Docker Compose 會按照順序將它們合併:

  • 單一數值(Overwritten):對於只有一個值的設定(如 image, restart),後面的檔案會直接覆蓋前面的檔案。
  • 列表與字典(Merged):對於列表狀的設定(如 ports, environment, volumes),後續檔案的內容會「附加」到前面檔案之後。

實戰範例:開發 vs. 生產配置

1. 基礎配置 (docker-compose.yaml):存放所有環境通用的設定。

services:
  web:
    image: my-app:latest
    environment:
      - DB_HOST=db

2. 生產環境覆蓋 (docker-compose.prod.yaml):增加限制與安全。

services:
  web:
    restart: always # 覆蓋
    deploy: # 增加資源限制
      resources:
        limits:
          memory: 1G

3. 本地開發覆蓋 (docker-compose.override.yaml):方便調試。

services:
  web:
    command: npm run dev # 使用開發模式
    volumes: # 增加代碼掛載
      - .:/app
    ports: # 增加對外埠號
      - '3000:3000'

指令執行與優先權

  • 自動載入:如果你只執行 docker compose up,Compose 會自動尋找並合併 docker-compose.yamldocker-compose.override.yaml
  • 特定環境:如果要部署到正式環境,你應該明確排除開發用的 override 檔案:
    # 基礎設定 + 生產設定
    docker compose -f docker-compose.yaml -f docker-compose.prod.yaml up -d
    
-f 的順序非常重要:後面出現的檔案擁有更高的優先權,能覆蓋前面檔案中的相同欄位。

常見問題與排查 (Troubleshooting)

問題一:啟動順序不代表服務可以用了

現象:Web 容器啟動後報錯「無法連接資料庫」,但資料庫容器明明正在 Running。 解決:這是因為資料庫容器「啟動」不代表資料庫「初始化完成」。你必須在 YAML 中使用 healthcheck,並讓 Web 的 depends_on 監聽 service_healthy 狀態。

問題二:埠號冲突

現象:報錯 Bind for 0.0.0.0:80 failed: port is already allocated解決:代表你宿主機上已經有其他程式(或另一個 Compose 專案)占用了該埠號。請修改 YAML 左側的埠號(Published port)。

問題三:修改 Dockerfile 後 up 沒反應

現象:修改了程式碼或 Dockerfile,但執行 up 後發現跑的還是舊版本。 解決:Compose 預設會優先使用現存映像檔。請加上 --build 參數確保重新建置:docker compose up -d --build

總結

Docker Compose 不僅僅是縮短指令的腳本,更是定義應用生命週期的重要文件。透過 Healthcheck 確保服務強健、使用 Resources 保護宿主機性能、善用 Watch 加速開發、搭配 Overrides 管理不同環境,你就能輕鬆駕馭複雜的微服務系統。