一、為什麼必須用 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 端口,或之前的容器未清理;
- 解決方案:
- 查看佔用進程:netstat -tulpn | grep 8081(Linux),結束進程;
- 或修改 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 健康檢查配置)。
八、下一篇預告:部署優化(監控 + 日誌 + 灰度發佈)
本篇實現了 “一鍵啓動整套分佈式環境”,但生產環境還需解決「運維監控」「日誌收集」「平滑發佈」三大問題 —— 下一篇將聚焦:
- 容器監控:用 Prometheus+Grafana 監控服務 CPU / 內存、接口 QPS / 響應時間;
- 日誌收集:用 ELK 棧(Elasticsearch+Logstash+Kibana)統一收集容器日誌;
- 灰度發佈:基於 Docker 實現 “金絲雀發佈”,避免全量發佈風險。
現在你已擁有 “開發 / 測試環境一鍵部署” 能力,跟着下一篇實戰,即可將分佈式應用推向生產級運維水平!