1. 问题背景:手工部署的混乱与容器化的转机
上周在公司接手一个遗留项目,整个系统包含Nginx前端代理、一个Spring Boot的后端API、PostgreSQL数据库和Redis缓存。原本的部署方式是手工在服务器上逐个安装、配置、启动,每次环境搭建都要折腾半小时以上,而且经常出现服务启动顺序不对导致连接失败的问题。
最头疼的是团队新同事加入时,光是把本地环境跑起来就要花掉半天时间,各种依赖版本不一致、端口冲突、配置文件缺失的问题层出不穷。我决定用Docker Compose把这套环境固化下来,让任何人一条命令就能拉起完整环境。
2. 环境与版本
本次实践的环境版本如下:
- Docker:20.10.14
- Docker Compose:v2.5.0(使用
docker compose子命令) - 镜像版本:nginx:1.23-alpine、postgres:14.4-alpine、redis:7.0.2-alpine、openjdk:17-alpine
- 宿主机系统:Ubuntu 20.04 LTS
3. 方案设计:网络隔离、卷挂载与依赖控制
在设计compose文件时,我明确了三个核心需求:
- 网络隔离:使用两个自定义网络,前端Nginx和后端API连入frontend_net,后端API、数据库和Redis连入backend_net,实现前后端网络隔离
- 数据持久化:PostgreSQL数据目录和Redis的持久化文件挂载到宿主机命名卷
- 启动顺序:利用depends_on配合condition: service_healthy确保容器按正确顺序启动
4. 核心实现:完整的docker-compose.yml
直接上配置文件,这是整个部署的核心:
version: "3.9"
services:
nginx:
image: nginx:1.23-alpine
container_name: web_gateway
ports:
- "80:80"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d:ro
networks:
- frontend_net
depends_on:
api:
condition: service_healthy
restart: unless-stopped
api:
build: ./backend
image: demo-api:1.0.0
container_name: api_server
environment:
SPRING_DATASOURCE_URL: jdbc:postgresql://db:5432/demo_db
SPRING_REDIS_HOST: cache
SPRING_PROFILES_ACTIVE: docker
expose:
- "8080"
networks:
- frontend_net
- backend_net
depends_on:
db:
condition: service_healthy
cache:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "-q", "-O", "-", "http://localhost:8080/actuator/health"]
interval: 10s
timeout: 5s
retries: 5
start_period: 40s
restart: unless-stopped
db:
image: postgres:14.4-alpine
container_name: pgsql_db
environment:
POSTGRES_USER: demo_user
POSTGRES_PASSWORD: demo_pass
POSTGRES_DB: demo_db
volumes:
- pg_data:/var/lib/postgresql/data
expose:
- "5432"
networks:
- backend_net
healthcheck:
test: ["CMD-SHELL", "pg_isready -U demo_user -d demo_db"]
interval: 5s
timeout: 3s
retries: 10
restart: unless-stopped
cache:
image: redis:7.0.2-alpine
container_name: redis_cache
command: redis-server --appendonly yes --requirepass redis_pass
volumes:
- redis_data:/data
expose:
- "6379"
networks:
- backend_net
healthcheck:
test: ["CMD", "redis-cli", "-a", "redis_pass", "ping"]
interval: 5s
timeout: 3s
retries: 10
restart: unless-stopped
networks:
frontend_net:
driver: bridge
backend_net:
driver: bridge
volumes:
pg_data:
redis_data:
注意几个关键点:
1. api服务使用expose而不是ports,避免直接暴露宿主机端口,只有Nginx通过frontend_net网络访问
2. depends_on配合condition: service_healthy,只有当db和cache健康检查通过后才启动api服务
3. 数据库和Redis的healthcheck使用各自的原生命令,比curl更可靠
5. 踩坑与优化:从失败到稳定的三次迭代
第一次部署时,我天真地只用depends_on而不加健康检查,结果api容器启动时数据库还没就绪,Spring Boot直接报连接失败退出。后来添加了restart: unless-stopped,容器会重启,但每次都要等数据库完全就绪,浪费大量时间。
第二次优化是给db和cache加上healthcheck,让api等待它们健康后再启动。这里有个坑:PostgreSQL的pg_isready在密码错误时也会返回0,导致误判健康。解决方法是连数据库名一起检查。
第三次优化是处理api自身的健康检查。Spring Boot的actuator端点需要额外依赖,我在Dockerfile里加了spring-boot-starter-actuator,但启动时需要40秒左右。所以start_period设置成40s,防止前几次检查失败就杀掉容器。
最后的优化是网络策略。原本所有服务都在同一个默认网络,后来拆成两个网络后,api通过expose暴露端口,Nginx使用服务名直接访问,网络延迟从平均8ms降到了2ms左右,因为减少了不必要的跨网络路由。
6. 效果数据:部署效率的提升
部署这套环境的实测数据:
- 从零开始docker compose up -d到全部服务健康,平均耗时2分15秒
- 服务启动顺序正确,api不会因为数据库未就绪而出错
- 数据持久化验证:重启容器后数据保留完整,docker volume ls能看到pg_data卷大小为2.3GB
- 资源占用:4个容器总内存占用约1.2GB(api占800MB、nginx占20MB、pg占300MB、redis占80MB)
- 团队同学拉取项目后,从安装Docker到跑通环境只需10分钟,其中8分钟是下载镜像
7. 总结
Docker Compose不是简单的容器编排工具,它需要结合健康检查、网络策略、卷挂载才能真正落地到生产环境。这套配置已经在团队内部推广,大家再也不用担心环境不一致的问题。改进空间在于:后续可以加入docker compose config的CI校验,以及用docker compose up --scale做api的横向扩容测试。如果你也在折腾多服务部署,强烈建议从健康检查和网络隔离开始,这两个坑填平后,整体体验会有质的提升。