知远漫谈头像
Docker - Docker Compose初识,一键管理多容器封面图

Docker - Docker Compose初识,一键管理多容器

在这里插入图片描述

👋 大家好,欢迎来到我的技术博客!
📚 在这里,我会分享学习笔记、实战经验与技术思考,力求用简单的方式讲清楚复杂的问题。
🎯 本文将围绕Docker这个话题展开,希望能为你带来一些启发或实用的参考。
🌱 无论你是刚入门的新手,还是正在进阶的开发者,希望你都能有所收获!


文章目录

Docker - Docker Compose初识,一键管理多容器 🐳✨

在现代云原生应用开发中,单体架构正加速向微服务化演进。一个典型的企业级 Java 应用,往往不再由一个 JAR 包承载全部功能,而是被拆解为:用户服务(User Service)、订单服务(Order Service)、商品服务(Product Service)、认证中心(Auth Service)、API 网关(Gateway)、Redis 缓存、PostgreSQL 数据库、Elasticsearch 搜索引擎、RabbitMQ 消息队列……甚至前端 Vue/React 应用也独立部署。当这些组件各自运行在不同进程、不同端口、不同配置下时,本地开发与测试的复杂度陡然飙升——你是否曾为启动 7 个终端窗口、逐条执行 java -jar、docker run、redis-server、psql 而手忙脚乱?又是否因环境变量漏配、网络未互通、依赖启动顺序错乱导致“在我机器上是好的”(It works on my machine 🧪)成为团队高频梗?

此时,Docker Compose 就像一位沉稳可靠的运维指挥官,只需一份声明式 YAML 文件,即可完成「一键拉起、统一编排、网络互通、依赖感知、日志聚合」的全生命周期管理。它不是 Docker 的替代品,而是 Docker CLI 的强力增强;它不解决容器镜像构建问题,却完美填补了「多容器协同」这一关键空白。

本文将带你从零深入 Docker Compose 的核心理念与实战脉络:理解 docker-compose.yml 的语法骨架与语义逻辑,亲手搭建一个基于 Spring Boot 的微服务演示系统(含 API 网关 + 用户服务 + PostgreSQL + Redis),掌握服务发现、健康检查、环境隔离、配置外置等生产级能力,并通过 Java 代码直连多容器服务验证端到端连通性。全程无黑盒,每一步皆可复现,每一个命令皆有回响 💫。


一、为什么是 Docker Compose?——告别“终端风暴” 🌪️

设想这样一个场景:你正在开发一个电商后端,本地需同时运行:

  • gateway: Spring Cloud Gateway,监听 8080
  • user-service: Spring Boot 微服务,暴露 8081,依赖数据库与缓存
  • postgres: 关系型数据库,端口 5432
  • redis: 缓存中间件,端口 6379

若全部手动启动,你需要依次执行:

# 终端 1:启动 PostgreSQL
docker run -d --name my-postgres \
  -e POSTGRES_PASSWORD=pass123 \
  -p 5432:5432 \
  -v pgdata:/var/lib/postgresql/data \
  postgres:15

# 终端 2:启动 Redis
docker run -d --name my-redis \
  -p 6379:6379 \
  redis:7-alpine

# 终端 3:构建并启动 user-service(假设已打包为 jar)
java -jar user-service.jar \
  --spring.datasource.url=jdbc:postgresql://localhost:5432/ecommerce \
  --spring.redis.host=localhost \
  --server.port=8081

# 终端 4:启动 gateway(需确保 user-service 已就绪)
java -jar gateway.jar \
  --spring.cloud.gateway.routes[0].id=user-route \
  --spring.cloud.gateway.routes[0].uri=http://localhost:8081 \
  --server.port=8080

⚠️ 问题立现:

  • localhost 在容器内 ≠ 宿主机 localhost → user-service 连不上 postgres
  • gateway 启动时 user-service 可能尚未 ready → 503 错误频发
  • 每次重启需手动 docker stop / kill -9 → 容易残留僵尸进程
  • 配置散落各处(命令行、properties、环境变量)→ 难以版本化与协作
  • 无法定义服务启动顺序与健康依赖 → 系统脆弱如纸

而 Docker Compose 的哲学是:用代码定义基础设施(Infrastructure as Code)。你只需编写一个 docker-compose.yml,所有服务即成为一个逻辑单元(project),共享默认网络,自动 DNS 解析,支持健康检查驱动的启动顺序,且一条命令即可启停整套环境:

docker compose up -d  # 后台启动全部服务 ✅
docker compose logs -f gateway  # 实时查看网关日志 ✅
docker compose down    # 彻底清理(含网络、卷)✅

💡 关键认知:Docker Compose 是 Docker Engine 的客户端工具,它本身不运行容器,而是调用 Docker API 创建容器、网络、卷等资源。它的价值在于抽象与编排,而非虚拟化。


二、Docker Compose 核心概念速览 🧩

在深入 YAML 之前,先建立对 Compose 体系的关键术语共识:

术语含义类比
Project由 docker-compose.yml 定义的一组关联服务的集合,以目录名或 -p 参数命名一个“软件系统”(如 ecommerce-system)
Serviceservices: 下定义的一个可缩放、可独立部署的容器化组件(如 gateway, user-service)一个“微服务进程”或“中间件实例”
ContainerService 运行时的具体实例(1 个 Service 可有 N 个 Container,通过 scale 控制)一个“Java 进程”或“Redis 进程”的具体运行体
NetworkCompose 自动创建的默认桥接网络(<project>_default),所有 Service 容器自动加入,支持 service-name 作为 DNS 名解析一个“局域网”,所有设备可通过主机名通信
Volume持久化数据的存储机制,独立于容器生命周期“U 盘”——拔掉容器,数据仍在

✅ 默认网络行为:
在 ecommerce 项目中,若定义了 postgres 和 user-service 两个 service,则:

  • user-service 容器内可直接 ping postgres 或 jdbc:postgresql://postgres:5432/...
  • postgres 容器内可 telnet user-service 8081(若开放端口)
  • 无需 --link,无需 host.docker.internal,无需硬编码 IP —— 这是 Compose 最优雅的魔法之一 🪄

三、动手实践:搭建 Spring Boot 微服务演示系统 🛠️

我们构建一个极简但具备完整链路的电商子系统:

  • gateway: Spring Cloud Gateway(路由至用户服务)
  • user-service: Spring Boot WebFlux 应用(提供 /api/users REST 接口)
  • postgres: 存储用户数据(users 表)
  • redis: 缓存用户详情(user:{id})

所有服务均通过 Docker Compose 统一编排,Java 代码使用标准 Spring Data JPA + Spring Cache,零侵入 Compose。

3.1 初始化项目结构

mkdir ecommerce-demo && cd ecommerce-demo
mkdir gateway user-service

项目结构概览:

ecommerce-demo/
├── docker-compose.yml          # 👈 Compose 主配置(核心!)
├── gateway/
│   ├── pom.xml
│   └── src/main/java/...       # Gateway 启动类、路由配置
├── user-service/
│   ├── pom.xml
│   ├── src/main/resources/application.yml
│   └── src/main/java/...       # User 实体、Repository、Controller、Cache 配置
└── init-db.sql                 # PostgreSQL 初始化脚本(可选)

3.2 编写 docker-compose.yml(核心配置文件)📄

# docker-compose.yml
version: '3.8'

# 全局设置:项目名称(覆盖目录名)
name: ecommerce-system

# 定义服务
services:

  # 👇 PostgreSQL 数据库服务
  postgres:
    image: postgres:15
    container_name: ecommerce-postgres
    restart: unless-stopped
    environment:
      POSTGRES_DB: ecommerce
      POSTGRES_USER: appuser
      POSTGRES_PASSWORD: app123
    volumes:
      - pgdata:/var/lib/postgresql/data
      - ./init-db.sql:/docker-entrypoint-initdb.d/init.sql
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U appuser -d ecommerce"]
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 40s
    ports:
      - "5432:5432"

  # 👇 Redis 缓存服务
  redis:
    image: redis:7-alpine
    container_name: ecommerce-redis
    restart: unless-stopped
    command: redis-server --appendonly yes
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 20s
      timeout: 10s
      retries: 5
    ports:
      - "6379:6379"

  # 👇 用户微服务(Spring Boot)
  user-service:
    build:
      context: ./user-service
      dockerfile: Dockerfile
    container_name: ecommerce-user-service
    restart: unless-stopped
    environment:
      SPRING_PROFILES_ACTIVE: docker
      SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/ecommerce
      SPRING_DATASOURCE_USERNAME: appuser
      SPRING_DATASOURCE_PASSWORD: app123
      SPRING_REDIS_HOST: redis
      SPRING_REDIS_PORT: 6379
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    ports:
      - "8081:8081"
    # 显式声明网络(虽默认存在,但显式更清晰)
    networks:
      - ecommerce-net

  # 👇 API 网关(Spring Cloud Gateway)
  gateway:
    build:
      context: ./gateway
      dockerfile: Dockerfile
    container_name: ecommerce-gateway
    restart: unless-stopped
    ports:
      - "8080:8080"
    environment:
      SPRING_PROFILES_ACTIVE: docker
    depends_on:
      user-service:
        condition: service_started  # 网关仅需 user-service 启动即可路由(不强依赖其健康)
    networks:
      - ecommerce-net

# 自定义网络(显式定义,便于理解)
networks:
  ecommerce-net:
    driver: bridge

# 持久化卷(独立于容器生命周期)
volumes:
  pgdata:

🔍 关键点解析:

  • version: '3.8': 使用最新稳定版 Compose 文件规范(兼容 Docker Engine 20.10+)
  • healthcheck: 为 postgres 和 redis 定义健康探针,是 depends_on 实现智能等待的基础 🔍
  • depends_on + condition:
    • service_healthy → 等待目标服务通过健康检查(推荐用于数据库/缓存)
    • service_started → 仅等待容器创建成功(适用于无健康检查的普通服务,如 gateway)
  • environment: 所有环境变量均以 SPRING_* 开头,将被 Spring Boot 自动绑定,无需修改 Java 代码。
  • build.context: 指向 ./user-service 目录,Compose 将在此路径下执行 docker build。
  • networks & volumes: 显式声明,提升可读性与可维护性。

🌐 网络原理图(Mermaid 渲染):

渲染错误: Mermaid 渲染失败: Parse error on line 5: ...graph Docker Network
ecommerce-ne -----------------------^ Expecting 'SEMI', 'NEWLINE', 'SPACE', 'EOF', 'GRAPH', 'DIR', 'subgraph', 'SQS', 'end', 'AMP', 'COLON', 'START_LINK', 'STYLE', 'LINKSTYLE', 'CLASSDEF', 'CLASS', 'CLICK', 'DOWN', 'UP', 'NUM', 'NODE_STRING', 'BRKT', 'MINUS', 'MULT', 'UNICODE_TEXT', got 'TAGSTART'

该图清晰展示了四个容器如何在同一个自定义桥接网络 ecommerce-net 中通过服务名(postgres, redis, user-service)实现无缝通信 —— 这是 Docker Compose 解决“网络发现”问题的核心机制。


3.3 编写 user-service 的 Java 代码(Spring Boot)💻

user-service/pom.xml(精简版)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.5</version>
        <relativePath/>
    </parent>
    <groupId>com.ecommerce</groupId>
    <artifactId>user-service</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>user-service</name>

    <properties>
        <java.version>17</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
user-service/src/main/resources/application-docker.yml
# application-docker.yml —— Compose 中指定的 profile
spring:
  datasource:
    url: ${SPRING_DATASOURCE_URL:jdbc:postgresql://localhost:5432/ecommerce}
    username: ${SPRING_DATASOURCE_USERNAME:appuser}
    password: ${SPRING_DATASOURCE_PASSWORD:app123}
    driver-class-name: org.postgresql.Driver
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
    properties:
      hibernate:
        format_sql: true
  redis:
    host: ${SPRING_REDIS_HOST:localhost}
    port: ${SPRING_REDIS_PORT:6379}
  cache:
    type: redis

# 自定义缓存配置
spring.cache.redis.time-to-live=3600000 # 1h

# Actuator 端点(用于健康检查)
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
  endpoint:
    health:
      show-details: always

⚠️ 注意:application-docker.yml 中的 ${SPRING_DATASOURCE_URL} 等占位符,会自动被 Compose 的 environment 值覆盖。这是 Spring Boot 与 Docker Compose 天然契合的体现 ✅。

user-service/src/main/java/com/ecommerce/user/entity/User.java
package com.ecommerce.user.entity;

import jakarta.persistence.*;
import lombok.*;

@Entity
@Table(name = "users")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true)
    private String username;

    @Column(nullable = false)
    private String email;
}
user-service/src/main/java/com/ecommerce/user/repository/UserRepository.java
package com.ecommerce.user.repository;

import com.ecommerce.user.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    User findByUsername(String username);
}
user-service/src/main/java/com/ecommerce/user/service/UserService.java
package com.ecommerce.user.service;

import com.ecommerce.user.entity.User;
import com.ecommerce.user.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    private final UserRepository userRepository;

    @Autowired
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    // ✅ 启用 Redis 缓存:方法返回值将被序列化存入 Redis,Key 为 "user::#{id}"
    @Cacheable(value = "users", key = "#id")
    public User findById(Long id) {
        return userRepository.findById(id)
                .orElseThrow(() -> new RuntimeException("User not found: " + id));
    }

    public User save(User user) {
        return userRepository.save(user);
    }
}
user-service/src/main/java/com/ecommerce/user/controller/UserController.java
package com.ecommerce.user.controller;

import com.ecommerce.user.entity.User;
import com.ecommerce.user.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/users")
public class UserController {

    private final UserService userService;

    @Autowired
    public UserController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping("/{id}")
    public ResponseEntity<User> getUser(@PathVariable Long id) {
        User user = userService.findById(id); // ✅ 触发 @Cacheable
        return ResponseEntity.ok(user);
    }

    @PostMapping
    public ResponseEntity<User> createUser(@RequestBody User user) {
        User saved = userService.save(user);
        return ResponseEntity.ok(saved);
    }
}
user-service/src/main/java/com/ecommerce/user/UserServiceApplication.java
package com.ecommerce.user;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;

@SpringBootApplication
@EnableCaching // ✅ 启用 Spring Cache 抽象
public class UserServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserServiceApplication.class, args);
    }
}
user-service/Dockerfile
# user-service/Dockerfile
FROM eclipse/jetty:11-jre17-slim

# 复制 JAR 到容器
COPY target/user-service-0.0.1-SNAPSHOT.jar /app.jar

# 暴露端口
EXPOSE 8081

# 启动命令
ENTRYPOINT ["java","-Dspring.profiles.active=docker","-jar","/app.jar"]

✅ 构建说明:
在 user-service/ 目录下执行 mvn clean package -DskipTests,生成 target/user-service-0.0.1-SNAPSHOT.jar。Compose 的 build.context 会自动找到此 JAR 并 COPY 进镜像。


3.4 编写 gateway 的 Java 代码(Spring Cloud Gateway)🌐

gateway/pom.xml(关键依赖)
<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
        <version>4.1.1</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
</dependencies>
gateway/src/main/resources/application-docker.yml
spring:
  cloud:
    gateway:
      routes:
        - id: user-service-route
          uri: http://user-service:8081  # ✅ 直接使用 service name!
          predicates:
            - Path=/api/users/**
          filters:
            - RewritePath=/api/users/(?<segment>.*), /$\{segment}

# 允许跨域(开发友好)
spring.web.cors.allowed-origins: http://localhost:3000
spring.web.cors.allowed-methods: GET,POST,PUT,DELETE
gateway/src/main/java/com/ecommerce/gateway/GatewayApplication.java
package com.ecommerce.gateway;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class GatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }
}
gateway/Dockerfile
FROM openjdk:17-jre-slim
COPY target/gateway-0.0.1-SNAPSHOT.jar /app.jar
EXPOSE 8080
ENTRYPOINT ["java","-Dspring.profiles.active=docker","-jar","/app.jar"]

四、启动与验证:见证一键编排的力量 ⚡

一切就绪,进入项目根目录 ecommerce-demo/,执行:

# 第一次启动(会构建镜像、拉取基础镜像、初始化卷)
docker compose up -d

# 查看所有服务状态
docker compose ps

预期输出(部分):

NAME                      COMMAND                  SERVICE             STATUS              PORTS
ecommerce-gateway         "java -Dspring.profi…"   gateway             running (healthy)   0.0.0.0:8080->8080/tcp
ecommerce-postgres        "docker-entrypoint.s…"   postgres            running (healthy)   0.0.0.0:5432->5432/tcp
ecommerce-redis           "docker-entrypoint.s…"   redis               running (healthy)   0.0.0.0:6379->6379/tcp
ecommerce-user-service    "java -Dspring.profi…"   user-service        running (healthy)   0.0.0.0:8081->8081/tcp

✅ 所有 STATUS 列显示 running (healthy),表明健康检查通过,服务已就绪。

4.1 验证数据库连接(从 user-service 容器内部)

# 进入 user-service 容器
docker exec -it ecommerce-user-service sh

# 在容器内尝试连接 postgres(使用 service name)
/ # apk add postgresql-client  # 安装 psql 客户端(alpine)
/ # psql -h postgres -U appuser -d ecommerce
Password for user appuser: app123
ecommerce=# \dt  -- 查看表,应看到 users 表
         List of relations
 Schema | Name  | Type  | Owner
--------+-------+-------+--------
 public | users | table | appuser
(1 row)

4.2 验证 Redis 连接

/ # apk add redis
/ # redis-cli -h redis ping
PONG

4.3 通过网关调用用户服务(宿主机 curl)

# 创建用户
curl -X POST http://localhost:8080/api/users \
  -H "Content-Type: application/json" \
  -d '{"username":"alice","email":"[email protected]"}'

# 响应:{"id":1,"username":"alice","email":"[email protected]"}

# 查询用户(首次:查 DB + 写缓存)
curl http://localhost:8080/api/users/1

# 再次查询(命中 Redis 缓存,无 DB 查询日志)
curl http://localhost:8080/api/users/1

🔍 验证缓存生效:观察 user-service 日志(docker compose logs -f user-service),第二次请求不应出现 Hibernate: select ... from users SQL 日志,证明 @Cacheable 已生效。

4.4 查看实时日志流

# 查看整个系统的实时日志(按服务分色)
docker compose logs -f

# 或只看网关日志(绿色高亮)
docker compose logs -f gateway

# 或查看 PostgreSQL 的初始化日志
docker compose logs postgres | head -20

4.5 健康检查端点(Production Ready)

所有 Spring Boot 服务均暴露 /actuator/health:

  • http://localhost:8080/actuator/health → Gateway 健康
  • http://localhost:8081/actuator/health → User Service 健康(含 db, redis, diskSpace)

响应示例:

{
  "status": "UP",
  "components": {
    "db": { "status": "UP" },
    "redis": { "status": "UP" },
    "diskSpace": { "status": "UP" }
  }
}

这正是 Compose healthcheck 与 Spring Boot Actuator 协同工作的结果 🤝。


五、进阶能力:让 Compose 更强大 🚀

5.1 环境隔离:.env 文件与多环境配置

在项目根目录创建 .env:

# .env
COMPOSE_PROJECT_NAME=ecommerce-dev
DB_PASSWORD=dev123
REDIS_PORT=6379

修改 docker-compose.yml 中的环境变量引用:

environment:
  POSTGRES_PASSWORD: ${DB_PASSWORD}
  SPRING_REDIS_PORT: ${REDIS_PORT}

再创建 docker-compose.prod.yml(生产环境覆盖):

# docker-compose.prod.yml
services:
  user-service:
    deploy:
      replicas: 3
      resources:
        limits:
          memory: 1G
          cpus: '0.5'
  postgres:
    environment:
      POSTGRES_PASSWORD: ${PROD_DB_PASSWORD}
    volumes:
      - /mnt/pgdata:/var/lib/postgresql/data

启动生产环境:

PROD_DB_PASSWORD=prodPass123 docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

🌐 更多环境管理技巧可参考官方文档:Docker Compose Environment Variables

5.2 配置外置:config 驱动与远程配置中心

对于大型系统,可将 application.yml 抽离为独立配置服务。Spring Cloud Config Server 是业界标准方案,其官方文档详尽清晰:Spring Cloud Config Reference Guide

Compose 可轻松集成:

config-server:
  image: springio/configserver
  environment:
    SPRING_CLOUD_CONFIG_SERVER_GIT_URI: https://github.com/myorg/config-repo

5.3 依赖服务等待:wait-for-it.sh 作为兜底方案

尽管 healthcheck + depends_on 已很强大,但某些旧版应用(如未暴露健康端点的 Legacy Java App)仍需手动等待。此时可引入轻量脚本 wait-for-it.sh:

# 在服务 Dockerfile 中
COPY wait-for-it.sh /wait-for-it.sh
RUN chmod +x /wait-for-it.sh
ENTRYPOINT ["/wait-for-it.sh", "postgres:5432", "--", "java", "-jar", "/app.jar"]

5.4 卷(Volume)深度实践:数据持久化与迁移

当前 pgdata 卷是匿名卷,数据随 docker compose down 保留,但不易备份。升级为命名卷 + 备份脚本:

# 创建命名卷(显式控制)
docker volume create ecommerce-pgdata

# 修改 docker-compose.yml
volumes:
  pgdata:
    name: ecommerce-pgdata

备份脚本 backup-pg.sh:

#!/bin/bash
docker exec ecommerce-postgres pg_dump -U appuser ecommerce > backup_$(date +%F).sql

💾 数据安全是生产基石,Docker 卷管理是必修课。深入学习请访问:Docker Volumes Documentation


六、常见陷阱与避坑指南 🚫

陷阱表现正确解法
localhost 在容器内失效user-service 连 postgres 报 Connection refused✅ 使用服务名 postgres,而非 localhost;localhost 在容器内指代自身
depends_on 不等待健康user-service 启动时报 Connection refused to postgres✅ 必须配合 healthcheck + condition: service_healthy;depends_on 默认只等容器启动
端口冲突docker compose up 报 Bind for 0.0.0.0:5432 failed✅ 检查宿主机是否已有 PostgreSQL 进程:lsof -i :5432 或 netstat -tulpn | grep :5432
Spring Profile 未生效application-docker.yml 未加载✅ 确认 Dockerfile 中 ENTRYPOINT 包含 -Dspring.profiles.active=docker;或在 environment 中设 SPRING_PROFILES_ACTIVE=docker
Redis 连接超时user-service 启动慢、报 Cannot connect to redis✅ 检查 redis 服务的 healthcheck 是否配置正确;增加 start_period 给 Redis 充足启动时间
卷权限问题(Linux)PostgreSQL 启动失败,报 Permission denied✅ 在 postgres service 中添加 user: "1001:1001"(匹配宿主机 UID/GID),或使用 :z 标签(SELinux)

🧭 遇到疑难问题?Docker 官方故障排除指南是权威参考:Docker Compose Troubleshooting


七、Docker Compose 与 Kubernetes:如何选择?🤔

开发者常困惑:该用 Compose 还是 Kubernetes?

简单说:

  • Docker Compose = 本地开发 & CI/CD 测试环境 & 小型生产部署
    ✅ 语法简洁,学习曲线平缓,YAML 直观,适合单机或多节点(通过 Swarm)编排。
    ✅ 是理解容器编排的绝佳起点。

  • Kubernetes = 大规模生产环境(高可用、自动扩缩、滚动更新、服务网格)
    ✅ 提供 Pod、Deployment、Service、Ingress、HPA 等丰富原语。
    ✅ 生态庞大(Istio, Prometheus, Argo CD)。

💡 关键洞察:Compose 的 docker-compose.yml 可通过 Kompose 工具一键转换为 Kubernetes YAML(deployment.yaml, service.yaml),实现平滑演进。这意味着:今天用 Compose 写的编排,明天就能上 K8s。

🌐 Kompose 官网提供详细转换示例与最佳实践:Kompose Documentation


八、总结:拥抱声明式基础设施 🌟

Docker Compose 不仅仅是一个“多容器启动脚本”,它是 现代软件交付流水线的基石。通过本文的完整实践,你应该已经深刻体会到:

  • ✅ 一份 YAML,定义整个系统拓扑:从数据库、缓存到业务服务,逻辑清晰,一目了然;
  • ✅ 服务名即 DNS:postgres, redis, user-service 成为可靠通信标识,彻底告别 IP 绑定;
  • ✅ 健康驱动启动:depends_on + healthcheck 让依赖关系真正“活”起来;
  • ✅ 环境一致性:开发、测试、预发环境使用同一份 Compose 文件,消除“环境差异”魔咒;
  • ✅ Java 生态无缝集成:Spring Boot 的外部化配置(application-{profile}.yml)与 Compose environment 天然契合;
  • ✅ 可扩展性强:从单机开发到 Swarm 集群,再到 Kubernetes 迁移,路径清晰。

🐳 最后,以 Docker 官方对 Compose 的定义作结:
“Docker Compose is a tool for defining and running multi-container Docker applications.”
—— 简洁,有力,永恒。

当你下次面对一个包含 5 个服务的 Spring Boot 项目时,请不要打开 5 个终端。请打开你的 docker-compose.yml,敲下 docker compose up -d,然后泡一杯咖啡,静待整个系统如交响乐般和谐奏响 🎻。

世界因此而简单,开发因此而快乐。这就是 Docker Compose 的魅力所在。


✨ 延伸思考:
如果你想进一步探索容器化监控,Prometheus + Grafana 是黄金组合,其官方入门指南极为友好:Prometheus Getting Started

若想深入理解容器网络底层,Docker 官方网络教程是必读经典:Docker Networking Overview

而对于 Java 开发者,Spring Boot 官方文档中关于容器部署的最佳实践,值得反复研读:Spring Boot Docker Reference


🙌 感谢你读到这里!
🔍 技术之路没有捷径,但每一次阅读、思考和实践,都在悄悄拉近你与目标的距离。
💡 如果本文对你有帮助,不妨 👍 点赞、📌 收藏、📤 分享 给更多需要的朋友!
💬 欢迎在评论区留下你的想法、疑问或建议,我会一一回复,我们一起交流、共同成长 🌿
🔔 关注我,不错过下一篇干货!我们下期再见!✨

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/qq_41187124/article/details/157611526

文章来源转载

评论

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--