一、為什麼必須用 Docker Compose?手動啓動多容器的 “四宗罪”

1. 手動操作的痛點(開發 / 測試環境高頻踩坑)

  • 命令繁瑣:啓動 5 個服務需執行docker run5 次,每次需配置端口映射、環境變量、數據卷,複製粘貼易出錯;
  • 依賴混亂:若先啓用户服務再啓 Nacos,用户服務會因連接 Nacos 失敗反覆重啓;
  • 數據丟失:TiDB/Nacos 容器刪除後,未掛載數據卷導致配置和業務數據全部丟失;
  • 運維低效:需逐個查看容器日誌(docker logs)排查問題,無法統一管理。

2. Docker Compose 的核心價值(針對性解決)

  • 一鍵啓停:docker-compose up -d啓動所有服務,docker-compose down一鍵清理;
  • 依賴控制:通過depends_on定義服務啓動順序(如 Nacos→TiDB→業務服務→Gateway);
  • 配置集中:所有服務的端口、環境變量、數據卷統一寫在docker-compose.yml,避免分散管理;
  • 環境一致:開發 / 測試人員用同一 Compose 文件,確保環境完全一致(告別 “我這能跑”)。

二、實戰準備:確認鏡像與依賴關係

1. 必備鏡像清單(需提前構建 / 拉取)

服務名稱

鏡像來源

鏡像標籤規範

驗證方式

user-service

本地構建(上一篇實戰)

user-service:v1.0.0

`docker images

order-service

本地構建(參考上一篇 Dockerfile)

order-service:v1.0.0

`docker images

api-gateway

本地構建(需補充 Dockerfile)

api-gateway:v1.0.0

`docker images

nacos-server

官方鏡像

nacos/nacos-server:v2.2.3

docker pull nacos/nacos-server:v2.2.3

tidb/tikv/pd

官方鏡像

pingcap/tidb:v7.0.0 等

無需手動拉取,Compose 自動拉取

2. 服務依賴關係(關鍵!決定啓動順序)

  • 核心規則:註冊中心(Nacos)和數據庫(TiDB)先啓動,業務服務次之,網關最後啓動

三、第一步:補充 Gateway 服務 Dockerfile(上一篇未覆蓋)

Gateway 服務為 Spring Cloud Gateway 項目(無 Spring MVC 依賴),Dockerfile 與用户服務類似,需注意 “無 Web 依賴” 的健康檢查配置:

# Gateway服務Dockerfile(放在Gateway項目根目錄)# ==================== 構建階段(builder)====================FROM maven:3.8.5-openjdk-11 AS builderWORKDIR /appCOPY pom.xml .# 下載依賴(Gateway依賴少,速度更快)RUN mvn dependency:go-offline -BCOPY src ./src# 打包(Gateway無測試類,可跳過測試)RUN mvn package -DskipTests=true# ==================== 運行階段(runner)====================FROM openjdk:11-jre-slimWORKDIR /app# 創建非root用户RUN groupadd -r appuser && useradd -r -g appuser appuser# 複製jar包(pom.xml中finalName設為api-gateway)COPY --from=builder /app/target/api-gateway.jar ./api-gateway.jarRUN chown -R appuser:appuser /appUSER appuser# 暴露網關端口(8080,與application.yml一致)EXPOSE 8080# 健康檢查(Gateway用WebFlux,健康端點同樣是/actuator/health)HEALTHCHECK --interval=30s --timeout=3s --retries=3 \  CMD curl -f http://localhost:8080/actuator/health || exit 1# 啓動命令(Gateway無特殊JVM參數,基礎配置即可)ENTRYPOINT ["java", "-jar", "api-gateway.jar"]

構建 Gateway 鏡像:

# 進入Gateway項目根目錄cd ~/projects/api-gateway# 構建鏡像docker build -t api-gateway:v1.0.0 .# 驗證:docker images | grep api-gateway 顯示鏡像即成功

四、第二步:編寫 docker-compose.yml(核心配置)

在~/distributed-project目錄下創建docker-compose.yml,統一管理所有服務。配置遵循 “分層原則”:先基礎服務(Nacos/TiDB),再業務服務(user/order),最後網關(api-gateway)。

version: '3.8' # 兼容Docker 24.x版本,支持健康檢查和依賴控制# 1. 定義環境變量(所有服務共享,避免重複配置)x-common-env: &common-env  NACOS_ADDR: nacos-server:8848 # 服務名作為域名(Compose自動解析)  TIDB_HOST: tidb  TIDB_PORT: 4000  TIDB_DB: test_db  TIDB_USER: root  TIDB_PASSWORD: ""# 2. 數據卷定義(持久化數據,容器刪除不丟失)volumes:  nacos-data: # Nacos配置和數據存儲  tidb-pd-data: # TiDB PD節點數據  tidb-tikv-data: # TiDB TiKV節點數據services:  # ------------------- 基礎服務1:Nacos註冊中心 -------------------  nacos-server:    image: nacos/nacos-server:v2.2.3    container_name: nacos-server    ports:      - "8848:8848" # Nacos控制枱端口      - "9848:9848" # Nacos集羣通信端口(單機可保留)    environment:      - MODE=standalone # 單機模式(開發/測試用)      - NACOS_AUTH_ENABLE=false # 關閉認證(簡化開發,生產需開啓)    volumes:      - nacos-data:/home/nacos/data # 數據持久化    healthcheck: # 健康檢查:確保Nacos啓動成功才啓動後續服務      test: ["CMD", "curl", "-f", "http://localhost:8848/nacos/v1/console/health/readiness"]      interval: 10s      timeout: 5s      retries: 5  # ------------------- 基礎服務2:TiDB國產數據庫 -------------------  pd: # TiDB PD節點(元數據管理)    image: pingcap/pd:v7.0.0    container_name: tidb-pd    ports:      - "2379:2379"    volumes:      - tidb-pd-data:/data    command: --name=pd1 --client-urls=http://0.0.0.0:2379 --peer-urls=http://0.0.0.0:2380 --initial-cluster=pd1=http://pd:2380    healthcheck:      test: ["CMD", "curl", "-f", "http://localhost:2379/pd/api/v1/health"]      interval: 10s      timeout: 5s      retries: 5  tikv: # TiDB TiKV節點(存儲引擎)    image: pingcap/tikv:v7.0.0    container_name: tidb-tikv    ports:      - "20160:20160"    volumes:      - tidb-tikv-data:/data    command: --pd=http://pd:2379 --addr=0.0.0.0:20160 --data-dir=/data    depends_on:      pd:        condition: service_healthy # 依賴PD健康後啓動    healthcheck:      test: ["CMD", "tikv-server", "--health-check", "--addr", "0.0.0.0:20160"]      interval: 10s      timeout: 5s      retries: 5  tidb: # TiDB SQL層(對外提供MySQL接口)    image: pingcap/tidb:v7.0.0    container_name: tidb-server    ports:      - "4000:4000" # MySQL客户端連接端口      - "10080:10080"    command: --store=tikv --path=pd:2379    depends_on:      tikv:        condition: service_healthy # 依賴TiKV健康後啓動    healthcheck:      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-P", "4000", "-u", "root"]      interval: 10s      timeout: 5s      retries: 5  # ------------------- 業務服務1:用户服務 -------------------  user-service:    image: user-service:v1.0.0 # 本地構建的鏡像    container_name: user-service    ports:      - "8081:8081"    environment:      <<: *common-env # 引用共享環境變量      # 覆蓋服務特有配置(如端口、日誌級別)      - SERVER_PORT=8081      - LOGGING_LEVEL_ROOT=INFO      # Nacos和TiDB配置(用服務名作為域名,無需IP)      - SPRING_CLOUD_NACOS_DISCOVERY_SERVER_ADDR=${NACOS_ADDR}      - SPRING_DATASOURCE_URL=jdbc:mysql://${TIDB_HOST}:${TIDB_PORT}/${TIDB_DB}?useSSL=false&serverTimezone=Asia/Shanghai      - SPRING_DATASOURCE_USERNAME=${TIDB_USER}      - SPRING_DATASOURCE_PASSWORD=${TIDB_PASSWORD}    depends_on:      nacos-server:        condition: service_healthy # 依賴Nacos健康      tidb:        condition: service_healthy # 依賴TiDB健康    healthcheck:      test: ["CMD", "curl", "-f", "http://localhost:8081/actuator/health"]      interval: 10s      timeout: 3s      retries: 3  # ------------------- 業務服務2:訂單服務 -------------------  order-service:    image: order-service:v1.0.0 # 本地構建的鏡像    container_name: order-service    ports:      - "8082:8082"    environment:      <<: *common-env      - SERVER_PORT=8082      - LOGGING_LEVEL_ROOT=INFO      # 配置Nacos、TiDB和Feign調用(user-service用服務名)      - SPRING_CLOUD_NACOS_DISCOVERY_SERVER_ADDR=${NACOS_ADDR}      - SPRING_DATASOURCE_URL=jdbc:mysql://${TIDB_HOST}:${TIDB_PORT}/${TIDB_DB}?useSSL=false&serverTimezone=Asia/Shanghai      - SPRING_DATASOURCE_USERNAME=${TIDB_USER}      - SPRING_DATASOURCE_PASSWORD=${TIDB_PASSWORD}      - FEIGN_CLIENT_USER_SERVICE_URL=http://user-service:8081 # Feign調用地址    depends_on:      user-service:        condition: service_healthy # 依賴用户服務健康(Feign調用需要)      nacos-server:        condition: service_healthy      tidb:        condition: service_healthy    healthcheck:      test: ["CMD", "curl", "-f", "http://localhost:8082/actuator/health"]      interval: 10s      timeout: 3s      retries: 3  # ------------------- 網關服務:API Gateway -------------------  api-gateway:    image: api-gateway:v1.0.0 # 本地構建的鏡像    container_name: api-gateway    ports:      - "8080:8080" # 網關統一入口端口    environment:      <<: *common-env      - SERVER_PORT=8080      - LOGGING_LEVEL_ROOT=INFO      # 網關路由配置(用户/訂單服務用服務名)      - SPRING_CLOUD_NACOS_DISCOVERY_SERVER_ADDR=${NACOS_ADDR}      - SPRING_CLOUD_GATEWAY_ROUTES[0].URI=lb://user-service      - SPRING_CLOUD_GATEWAY_ROUTES[0].PREDICATES[0]=Path=/api/user/**      - SPRING_CLOUD_GATEWAY_ROUTES[0].FILTERS[0]=RewritePath=/api/user/(?<segment>.*),/user/$\{segment}      - SPRING_CLOUD_GATEWAY_ROUTES[1].URI=lb://order-service      - SPRING_CLOUD_GATEWAY_ROUTES[1].PREDICATES[0]=Path=/api/order/**      - SPRING_CLOUD_GATEWAY_ROUTES[1].FILTERS[0]=RewritePath=/api/order/(?<segment>.*),/order/$\{segment}    depends_on:      user-service:        condition: service_healthy # 依賴業務服務健康      order-service:        condition: service_healthy      nacos-server:        condition: service_healthy    healthcheck:      test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]      interval: 10s      timeout: 3s      retries: 3

五、第三步:一鍵啓動與全鏈路驗證

1. 啓動所有服務(僅需一條命令)

# 1. 進入Compose文件所在目錄cd ~/distributed-project# 2. 後台啓動所有服務(-d=detached模式,後台運行)docker-compose up -d# 3. 查看啓動狀態(確認所有服務狀態為Up)docker-compose ps# 輸出示例:#       Name                     Command               State                          Ports                       # ----------------------------------------------------------------------------------------------------------------# api-gateway           java -jar api-gateway.jar       Up (healthy)   0.0.0.0:8080->8080/tcp              # nacos-server          bin/docker-startup.sh           Up (healthy)   0.0.0.0:8848->8848/tcp, 0.0.0.0:9848->9848/tcp# order-service         java -jar order-service.jar     Up (healthy)   0.0.0.0:8082->8082/tcp              # tidb-pd               /pd-server --name=pd1 --cl ...   Up (healthy)   0.0.0.0:2379->2379/tcp              # tidb-server           /tidb-server --store=tikv  ...   Up (healthy)   0.0.0.0:10080->10080/tcp, 0.0.0.0:4000->4000/tcp# tidb-tikv             /tikv-server --pd=http://p ...   Up (healthy)   0.0.0.0:20160->20160/tcp            # user-service          java -jar user-service.jar      Up (healthy)   0.0.0.0:8081->8081/tcp

2. 全鏈路驗證(確保每個環節正常)

(1)驗證 Nacos 服務註冊
  • 訪問 Nacos 控制枱:http://localhost:8848/nacos(賬號 / 密碼:nacos/nacos);
  • 進入「服務列表」,確認user-service、order-service、api-gateway均為「健康」狀態。
(2)驗證 TiDB 數據持久化
  • 用 MySQL 客户端連接 TiDB:mysql -h 127.0.0.1 -P 4000 -u root -D test_db;
  • 執行SELECT * FROM user;和SELECT * FROM order;,若之前有數據則能正常查詢(數據卷掛載生效)。
(3)驗證業務服務接口
  • 調用用户服務:http://localhost:8081/user/get/1(返回用户數據);
  • 調用訂單服務:http://localhost:8082/order/create?userId=1&amount=99.99(創建訂單成功)。
(4)驗證 Gateway 網關路由
  • 通過網關調用用户服務:http://localhost:8080/api/user/get/1(與直接調用 8081 結果一致);
  • 通過網關調用訂單服務:http://localhost:8080/api/order/create?userId=1&amount=199.99(創建訂單成功,路由生效)。
(5)查看服務日誌(排查問題用)
# 查看訂單服務日誌(實時輸出)docker-compose logs -f order-service# 查看網關日誌(僅顯示錯誤日誌)docker-compose logs --tail=100 api-gateway | grep ERROR

六、第四步:Compose 環境優化(生產級配置補充)

1. 用.env 文件統一管理環境變量(避免硬編碼)

在docker-compose.yml同級目錄創建.env文件,存儲所有環境變量,Compose 會自動加載:

# .env文件(無需引號,註釋用#)# Nacos配置NACOS_ADDR=nacos-server:8848NACOS_AUTH_ENABLE=false# TiDB配置TIDB_HOST=tidbTIDB_PORT=4000TIDB_DB=test_dbTIDB_USER=rootTIDB_PASSWORD=# 服務端口配置USER_SERVICE_PORT=8081ORDER_SERVICE_PORT=8082GATEWAY_PORT=8080# 日誌級別LOGGING_LEVEL=INFO

修改docker-compose.yml中的環境變量引用,如:

environment:  - SERVER_PORT=${USER_SERVICE_PORT}  - LOGGING_LEVEL_ROOT=${LOGGING_LEVEL}  - SPRING_CLOUD_NACOS_DISCOVERY_SERVER_ADDR=${NACOS_ADDR}

2. 限制服務資源(避免單服務佔用過多 CPU / 內存)

在每個服務下添加deploy配置,限制資源使用(開發環境可選,生產環境必配):

services:  user-service:    # 其他配置...    deploy:      resources:        limits: # 最大資源限制          cpus: '0.5' # 最多佔用0.5核CPU          memory: 512M # 最多佔用512MB內存        reservations: # 最小資源預留          cpus: '0.2'          memory: 256M

3. 配置日誌輪轉(避免日誌佔滿磁盤)

在docker-compose.yml中添加日誌配置,限制單個日誌文件大小和保留數量:

services:  user-service:    # 其他配置...    logging:      driver: "json-file"      options:        max-size: "10m" # 單個日誌文件最大10MB        max-file: "3" # 最多保留3個日誌文件(超過自動刪除)

七、避坑指南:Compose 編排的 6 個高頻問題

1. 坑 1:服務啓動順序錯誤(依賴配置無效)

  • 現象:user-service 先於 Nacos 啓動,報 “Nacos connection refused”;
  • 原因:僅用depends_on: [nacos-server](只保證啓動順序,不保證 Nacos 就緒);
  • 解決方案:用condition: service_healthy(依賴服務健康檢查通過),如:
depends_on:  nacos-server:    condition: service_healthy

2. 坑 2:TiDB 數據丟失(容器刪除後數據為空)

  • 現象:docker-compose down後再啓動,TiDB 中數據消失;
  • 原因:未配置 TiDB 數據卷掛載,或掛載路徑錯誤;
  • 解決方案:確保pd和tikv服務配置了volumes,且卷名在volumes節點定義:
volumes:  tidb-pd-data: # 必須在頂層volumes定義services:  pd:    volumes:      - tidb-pd-data:/data # 掛載到容器/data目錄

3. 坑 3:服務間無法通信(用localhost連接失敗)

  • 現象:order-service 用http://localhost:8081調用 user-service,報 “connection refused”;
  • 原因:容器內localhost指容器自身,非宿主機,服務間需用 “服務名” 通信;
  • 解決方案:Feign 調用地址改為服務名,如FEIGN_CLIENT_USER_SERVICE_URL=http://user-service:8081。

4. 坑 4:端口衝突(啓動時報 “port is already allocated”)

  • 現象:啓動時提示 8081 端口已被佔用;
  • 原因:宿主機已有其他進程佔用 8081 端口,或之前的容器未清理;
  • 解決方案
  1. 查看佔用進程:netstat -tulpn | grep 8081(Linux),結束進程;
  2. 或修改 Compose 中的端口映射(如"80810:8081",外部用 80810 訪問)。

5. 坑 5:鏡像拉取失敗(報 “pull access denied”)

  • 現象:啓動時拉取user-service:v1.0.0報 “pull access denied”;
  • 原因:Compose 默認從 Docker Hub 拉取鏡像,本地構建的鏡像未推送到 Hub;
  • 解決方案:確保鏡像在本地存在(docker images確認),或在image前加local/(避免拉取遠程):
image: local/user-service:v1.0.0 # 明確使用本地鏡像

6. 坑 6:健康檢查失敗(服務反覆重啓)

  • 現象:api-gateway 健康檢查失敗,狀態為Restarting (1);
  • 原因 1:Gateway 未集成spring-boot-starter-actuator,/actuator/health端點不存在;
  • 原因 2:健康檢查命令錯誤(如 Gateway 用 WebFlux,卻用wget檢測,需用curl);
  • 解決方案:集成 Actuator,確保健康檢查命令正確(參考 Gateway 的 Dockerfile 健康檢查配置)。

八、下一篇預告:部署優化(監控 + 日誌 + 灰度發佈)

本篇實現了 “一鍵啓動整套分佈式環境”,但生產環境還需解決「運維監控」「日誌收集」「平滑發佈」三大問題 —— 下一篇將聚焦:

  1. 容器監控:用 Prometheus+Grafana 監控服務 CPU / 內存、接口 QPS / 響應時間;
  2. 日誌收集:用 ELK 棧(Elasticsearch+Logstash+Kibana)統一收集容器日誌;
  3. 灰度發佈:基於 Docker 實現 “金絲雀發佈”,避免全量發佈風險。

現在你已擁有 “開發 / 測試環境一鍵部署” 能力,跟着下一篇實戰,即可將分佈式應用推向生產級運維水平!