Docker Swarm 叢集編排

當你的容器應用程式需要跨越多台伺服器部署,並要求具備自動修復 (Self-healing)、水平擴展 (Scaling)、負載均衡 (Load Balancing) 與滾動更新 (Rolling Updates) 等能力時,你就需要叢集編排 (Orchestration) 工具。

雖然 Kubernetes (K8s) 是目前的主流,但對於中小型專案或追求簡單運維的團隊來說,Docker 內建的 Swarm Mode 是一個極佳的選擇,它簡單易用且與 Docker 指令高度整合。

為什麼選擇 Docker Swarm?

Docker Swarm 是 Docker 官方提供的叢集管理工具。它的最大優勢在於:開箱即用。只要你安裝了 Docker,就已經具備了 Swarm,無需額外安裝複雜的組件。

核心優勢:

  • 極簡安裝:一條指令即可初始化叢集。
  • 宣告式狀態 (Declarative Service Model):你只需定義理想狀態(例如:維持 5 個副本),Swarm 會自動確保實際運行狀態與之相符。
  • 內建負載均衡:自動處理內部與外部網路流量派送。
  • 安全性:預設啟用 TLS 相互認證,節點間通訊皆經過加密。

Swarm 核心架構

Swarm 叢集由多個 Nodes (節點) 組成,每個節點都是一個 Docker 主機。

節點角色

  • Manager Nodes
    • 管理叢集狀態、調度任務並維護集群一致性。
    • 使用 Raft 共識演算法 來同步狀態。為了維持高可用性,建議部署奇數個 (1, 3, 5) Manager 節點。
  • Worker Nodes
    • 單純接收 Manager 分派的任務並執行容器。
    • Manager 節點預設也具備執行任務的能力(可設定為僅限管理)。

服務與任務 (Service & Task)

  • Service (服務):這是你在 Swarm 中操作的最小單位。你定義「我要一個 Nginx 服務,副本數為 3」。
  • Task (任務):正在節點上執行的單個容器實例。Service 是 Blueprint,Task 是具體的實例。

實戰:建立與管理叢集

初始化叢集 (Manager)

在預計作為管理者的機器執行:

docker swarm init --advertise-addr <MANAGER_IP>

執行後會得到兩行關鍵指令:一行用於加入新的 Manager,另一行用於加入 Worker。

加入工作節點 (Worker)

在其他伺服器上貼上剛產生的 docker swarm join 指令:

docker swarm join --token <TOKEN_STR> <MANAGER_IP>:2377

查看與管理節點

Manager 上執行:

# 查看所有節點
docker node ls

# 查看特定節點詳細資訊
docker node inspect <NODE_ID>

節點維護與可用性

你可以手動控制節點的調度狀態:

  • active:正常狀態,可以接受新任務。
  • pause:暫停狀態,現有任務繼續執行,但不接受新任務。
  • drain:排空狀態,現有任務會被遷移到其他節點,並不接受新任務(常用於系統維護)。
# 將節點改為維護模式
docker node update --availability drain <NODE_ID>

# 恢復節點
docker node update --availability active <NODE_ID>

離開叢集 (Leave)

當節點不再需要參與叢集時:

# 在 Worker 節點執行
docker swarm leave

# 在 Manager 節點執行 (若要強制解散或離開)
docker swarm leave --force

部署與管理 Service

在 Swarm 中,我們使用 docker service 系列指令來管理個別服務。

建立服務

# 建立一個名為 web-service 的服務,啟動 3 個副本,對外開放 80 port
docker service create --name web-service --replicas 3 -p 80:80 nginx:latest

服務模式:Replicated vs Global

  • Replicated (預設):根據指定的副本數在各節點分佈。
  • Global:在叢集中的「每一個」合格節點上都運行一個容器(常用於監控或日誌收集工具)。
# 建立一個 global 模式的服務
docker service create --name monitor --mode global prometheus-exporter

服務維護指令

當服務上線後,你經常需要進行動態調整:

# 水平擴展 (Scaling)
docker service scale web-service=5

# 更新服務 (例如更換映像檔版本)
docker service update --image nginx:1.25 web-service

# 查看服務日誌
docker service logs -f web-service

# 刪除服務
docker service rm web-service

常用 Service 管理與除錯指令

除了上述的基本指令,運維時你經常需要檢查服務健康狀況或排查錯誤:

# 查看所有服務列表 (含副本數狀態)
docker service ls

# 查看特定服務的詳細任務 (Task) 狀態
# 這是最常用的除錯指令,可以看出哪個 Container 失敗及其錯誤訊息與所在的節點
docker service ps web-service

# 查看包含詳細錯誤訊息 (不截斷)
docker service ps --no-trunc web-service

# 查看服務詳細設定 (Pretty print)
docker service inspect --pretty web-service

# 強制重啟服務 (Force Update)
# 當服務卡住或需要強制重跑時使用
docker service update --force web-service

# 服務回滾 (Rollback)
# 當新部署的版本爛掉時,快速回到上一個版本
docker service rollback web-service

如何進入 Service/Stack 容器 (Exec)

這是初學者最常遇到的問題:「我怎麼 exec 進去 Swarm 的服務?」

首先要釐清觀念:docker exec 是針對 Container (容器) 指令,而不是 Service。Service 只是抽象層。所以你需要先找到具體的 Container ID 及其所在的機器。

步驟一:找出 Container 在哪一台機器

使用 docker service ps 查詢任務分佈:

$ docker service ps web-service

ID             NAME                IMAGE          NODE      DESIRED STATE   CURRENT STATE
x9d8...        web-service.1       nginx:latest   node-1    Running         Running 5 minutes ago

從上面的輸出來看,web-service.1 這個任務正在 node-1 這個節點上運作。

步驟二:進入該節點的操作環境

  • 如果 Container 剛好在本機 (Manager):直接跳步驟三。
  • 如果 Container 在遠端 Worker 節點:你需要先 SSH 連線到該伺服器 (ssh user@node-1)。

步驟三:執行 Exec

在該節點上,先用 docker ps 找到對應的容器 ID,再進入:

# 1. 搜尋容器 ID (過濾名字通常比較快)
docker ps | grep web-service

# 2. 進入容器
docker exec -it <CONTAINER_ID> bash
# 若映像檔沒有 bash,改用 sh
docker exec -it <CONTAINER_ID> sh

Swarm 網路與路由 (Routing Mesh)

Swarm 使用 Overlay Network 來實現跨主機的容器通訊。

Overlay 網路

建立一個可以跨越多台伺服器的網路:

docker network create --driver overlay my-net

將服務加入此網路後,容器間可以透過 Service Name 互相訪問,Swarm 會自動處理 VIP (Virtual IP) 的解析與負載均衡。

Ingress Routing Mesh (路由網格)

這是 Swarm 最神奇也最強大的功能。這解決了一個核心問題:「在叢集中,Container 可能隨時會在任何節點上漂移,那外部流量到底要連去哪裡?」

在傳統架構中,如果 Nginx 跑在 Node A,你就必須連去 Node A 的 IP。但在 Swarm 中,你可以訪問叢集中 「任何一個節點」 的 IP,Swarm 都會自動把流量轉送到正確的 Container 身上,即使該 Container 根本不在你訪問的那個節點上。

運作原理 (The Flow)

當你對外開放一個 Port (例如 -p 80:80) 時,Swarm 會在每一個節點上都監聽這個 80 Port。

  1. Client Request:使用者訪問叢集中的 任意節點 (Node A) 的 80 Port。
  2. Ingress Network:如果該節點上剛好有跑這個服務的容器,直接處理。如果沒有,Swarm 的 Ingress Overlay Network 會介入。
  3. IPVS Load Balancing:Swarm 內部的 Load Balancer (IPVS) 會將請求轉發到真正的 Service VIP (虛擬 IP)
  4. Forwarding:流量透過 Overlay Network 被隧道傳輸到真正運行該容器的 節點 (Node B)
  5. Container:最終由 Node B 上的容器接收並處理請求。
這意味著,你可以在前端掛載一個外部 Load Balancer (如 AWS ELB),將流量隨機分派給 Swarm 中的任何節點,而不需要擔心後端容器到底在哪裡。

限制與注意事項

雖然方便,但 Routing Mesh 也有一個知名的限制:Source IP NAT

當流量經過 Routing Mesh 轉發後,容器看到的 來源 IP (Source IP) 會變成 Ingress Network 的內部 IP,而不是使用者的真實 IP。如果你需要紀錄使用者真實 IP (例如 Access Log),你需要:

  • 使用 host mode (mode: host) 發布端口(這會繞過 Routing Mesh,但你必須確保該節點真的有跑該容器)。
  • 或者在最外層的 Load Balancer (例如 Nginx Proxy) 使用 X-Forwarded-For標頭傳遞真實 IP。

安全管理:Secrets 與 Configs

在生產環境中,你不應該把密碼或配置寫死在映像檔或環境變數中。

Docker Secrets

用於存取機密資訊(如資料庫密碼、SSL 證書)。Secret 會加密儲存在 Manager 節點中,並僅在任務啟動時掛載給特定的容器。

如何使用 Secrets?

  1. 掛載路徑:Secrets 預設會被掛載到容器內的 /run/secrets/<secret_name> 檔案中(這是一個內存文件系統,不會寫入硬碟)。
  2. 存取內容:應用程式可以直接讀取該檔案來取得密碼。
  3. 環境變數模式:許多官方映像檔(如 MySQL、Postgres)支援 _FILE 結尾的環境變數,讓你指定 Secret 檔案的路徑。
# 建立一個名為 db_password 的 secret
echo "my_secure_password" | docker secret create db_password -

# 在服務中使用該 secret,並告知 MySQL 從該路徑讀取密碼
docker service create \
  --name db \
  --secret db_password \
  -e MYSQL_ROOT_PASSWORD_FILE=/run/secrets/db_password \
  mysql

Docker Configs

類似於 Secrets,但用於非加密的設定檔(如 nginx.confprometheus.yml),讓你可以動態更新配置而無需重新建置映像檔。

如何使用 Configs?

  1. 掛載路徑:預設也是掛載到容器根目錄下的某處,但通常我們會自訂掛載位置。
  2. 動態更新:當你更新 Config 時,Swarm 會重新啟動關聯的 Service 以套用新設定。
# 1. 建立一個 config (從本地 nginx.conf 檔案)
docker config create my_nginx_config ./nginx.conf

# 2. 建立服務,並將 config 掛載到 Nginx 預設讀取的地方
docker service create \
  --name web \
  --config source=my_nginx_config,target=/etc/nginx/nginx.conf \
  -p 80:80 \
  nginx

大規模部署:Docker Stack (堆疊)

到目前為止,我們都是用 docker service create 一個一個建立服務。但在真實的生產環境中,一個應用程式通常包含多個關聯服務(例如:Frontend + Backend + Redis + DB)。

這時候,我們需要 Docker Stack

Stack vs Service:有什麼不同?

  • Service:是 Swarm 的最小單位,就像是「手動單點」一道菜。適合測試或簡單操作。
  • Stack:是 Service 的集合,就像是「宣告式套餐」。你寫好一張 docker-compose.yaml 菜單,Swarm 就會自動幫你把整桌菜(所有服務、網路、Volume、Config)一次準備好。
簡單來說:在 Swarm 生產環境中,我們幾乎總是使用 docker stack deploy,而不是手動敲 docker service create

核心觀念:Zero Downtime (零停機) 更新

Swarm 最強大的賣點就是「更新服務時,使用者完全無感」。這背後依賴兩個關鍵機制:Rolling Update (滾動更新)Health Check (健康檢查)

想像你要把服務從 v1 更新到 v2:

  1. Rolling Update:Swarm 不會一次殺掉所有舊容器。它會分批(例如一次 2 個)處理。
  2. Health Check:這是紅綠燈。Swarm 啟動新容器後,會等待它通過「健康檢查」。只有新容器亮綠燈了,Swarm 才會真正把流量導過去,並開始關閉舊容器。

如果沒有設定 Health Check 會怎樣? Swarm 會以為「容器啟動」就等於「服務好了」。但實際上你的 Java/Node.js 可能還在初始化。結果流量進來全部報錯 (502 Bad Gateway)。所以 Health Check 是零停機的絕對關鍵!

實戰:生產級 docker-compose.yaml 範例

這是一個包含所有進階觀念(資源限制、滾動更新策略、節點限制、Secrets)的完整範例:

version: '3.8'

services:
  web-app:
    image: my-app:v2.0

    # 資源限制 (Resource Limits)
    # 這是生產環境必備!避免某個容器吃光整台機器的 CPU/RAM 導致當機。
    deploy:
      replicas: 5 # 副本數:維持 5 個容器在跑

      # 資源預留與限制
      resources:
        limits:
          cpus: '0.50' # 最多只能用 50% CPU
          memory: 512M # 最多只能用 512MB RAM
        reservations:
          cpus: '0.10' # 保證最少有 10% CPU 可用
          memory: 128M # 保證最少有 128MB RAM 可用

      # 滾動更新策略 (Update Config)
      update_config:
        parallelism: 2 # 每次更新 2 個容器
        delay: 10s # 每一批更新完,休息 10 秒再做下一批
        order: start-first # [關鍵] 先啟動新的,確認成功後再殺舊的 (達成零停機)
        failure_action: rollback # 如果更新失敗,自動回滾到上個版本

      # 錯誤重啟策略 (Restart Policy)
      restart_policy:
        condition: on-failure # 只有非正常退出時才重啟
        delay: 5s # 重啟前等待 5 秒
        max_attempts: 3 # 最多重試 3 次

      # 部署位置限制 (Placement Constraints)
      # 例如:只把此服務部署在標籤為 "region=asia" 的 Worker 節點上
      placement:
        constraints:
          - 'node.role == worker'
          # - "node.labels.region == asia"

    # 健康檢查 (Health Check) - 零停機的核心!
    healthcheck:
      test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
      interval: 30s # 每 30 秒檢查一次
      timeout: 10s # 超過 10 秒沒回應算失敗
      retries: 3 # 連續失敗 3 次才判定為不健康 (Unhealthy)
      start_period: 40s # 給予 40 秒的寬限期讓程式初始化 (這段期間失敗不算數)

    # 網路設定
    networks:
      - app-net

    # 綁定 Secrets (敏感資料) 與 Configs (設定檔)
    secrets:
      - db_password
    configs:
      - source: my_nginx_conf
        target: /etc/nginx/conf.d/default.conf

    ports:
      - target: 80 # 容器內部的埠口
        published: 3000 # 對外公開(宿主機)的埠口
        protocol: tcp # 協議 (tcp 或 udp)
        mode: ingress # 模式 (ingress 或 host)

# 定義網路
networks:
  app-net:
    driver: overlay # Swarm 必須使用 overlay 網路才能跨主機通訊

# 定義 Secrets (需先在 Manager 建立:echo "12345" | docker secret create db_password -)
secrets:
  db_password:
    # 這 secret 已經事先建立好了,請直接引用,不要試圖建立它
    external: true

# 定義 Configs
configs:
  my_nginx_conf:
    file: ./nginx.conf

Stack 管理常用指令

一旦寫好了 yaml 檔,管理就變得非常簡單:

# 1. 部署或更新 Stack (宣告式)
# 如果 Stack 不存在則建立;如果已存在,Swarm 會自動比對差異並進行更新 (Rolling Update)
docker stack deploy -c docker-compose.yaml my-stack-name

# 2. 查看所有 Stacks
docker stack ls

# 3. 查看 Stack 裡面有哪些服務
docker stack services my-stack-name

# 4. 查看 Stack 的具體任務 (在哪個節點跑、狀態如何)
docker stack ps my-stack-name

# 5. 移除整個 Stack (危險操作:會移除所有服務與網路)
docker stack rm my-stack-name

如何更新 Stack? (Day 2 Operations)

許多人會問:「我有新的 Docker Image 或是改了設定,怎麼更新?」

答案很簡單:只要再次執行 docker stack deploy 指令即可。

  1. 修改你的 docker-compose.yaml (例如將 image tag 改為 v2.1)。
  2. 再次執行 docker stack deploy -c docker-compose.yaml my-stack-name
  3. Swarm 會自動偵測差異,並依照你設定的 update_config (滾動更新策略) 來優雅地更新服務。
這就是 宣告式 (Declarative) 的好處:你只需要描述「目標狀態」,不需要手動告訴電腦「怎麼做」。

Swarm vs. Kubernetes (K8s)

特性Docker SwarmKubernetes (K8s)
安裝與學習極低,與 Docker 完全一致高,有大量的專有名詞與組件
功能性基本編排功能紮實,適合 90% 場景極其強大,適合極大規模微服務
生態系統較小巨大 (CNCF 生態圈)
跨雲支援需手動管理各雲端節點各大雲端皆有 Managed Service (EKS, GKE, AKS)

總結

Docker Swarm 是進入容器編排世界的最佳起點。它對於中小型企業或需要快速部署的場景來說,提供了極高的投資報酬率。如果你已經熟悉 Docker Compose,轉移到 Docker Swarm 只需幾分鐘的時間,卻能讓你的系統具備真正的運維彈性。