Docker Compose是DevOps实践中编排多容器应用的标准工具。单容器部署无法覆盖真实业务场景——Web应用通常需要前端、后端API、数据库、缓存、消息队列等多个服务协同运行。本文以一套完整的Web应用栈为例,演示Docker Compose从编排配置到生产部署的完整流程。
Docker Compose编排架构与服务拆分
以一个典型的内容管理系统为例,服务拆分为:Nginx(反向代理)、Web应用(Node.js/Python)、PostgreSQL(主数据库)、Redis(缓存)、Elasticsearch(全文搜索)。各服务独立容器运行,通过Docker自定义网络通信,数据通过命名卷持久化。
docker-compose.yml完整配置
# docker-compose.yml
version: "3.9"
services:
nginx:
image: nginx:1.25-alpine
container_name: cms-nginx
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- ./nginx/ssl:/etc/nginx/ssl:ro
- nginx_logs:/var/log/nginx
- static_files:/usr/share/nginx/html:ro
depends_on:
- web-app
networks:
- frontend
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost/health"]
interval: 30s
timeout: 5s
retries: 3
web-app:
build:
context: ./app
dockerfile: Dockerfile
container_name: cms-app
environment:
- NODE_ENV=production
- DB_HOST=postgres
- DB_PORT=5432
- DB_NAME=cms
- DB_USER=${DB_USER}
- DB_PASSWORD=${DB_PASSWORD}
- REDIS_URL=redis://redis:6379/0
- ES_URL=http://elasticsearch:9200
expose:
- "3000"
volumes:
- app_uploads:/app/uploads
- app_logs:/app/logs
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_started
networks:
- frontend
- backend
restart: unless-stopped
deploy:
resources:
limits:
cpus: "2.0"
memory: 1G
reservations:
cpus: "0.5"
memory: 256M
postgres:
image: postgres:16-alpine
container_name: cms-db
environment:
- POSTGRES_DB=cms
- POSTGRES_USER=${DB_USER}
- POSTGRES_PASSWORD=${DB_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
- ./db/init:/docker-entrypoint-initdb.d:ro
- ./db/backups:/backups
expose:
- "5432"
networks:
- backend
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d cms"]
interval: 10s
timeout: 5s
retries: 5
command: >
postgres
-c shared_buffers=256MB
-c effective_cache_size=1GB
-c max_connections=100
-c log_min_duration_statement=500
redis:
image: redis:7-alpine
container_name: cms-cache
command: redis-server
--maxmemory 256mb
--maxmemory-policy allkeys-lru
--appendonly yes
volumes:
- redis_data:/data
expose:
- "6379"
networks:
- backend
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 3
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.13.0
container_name: cms-search
environment:
- discovery.type=single-node
- xpack.security.enabled=false
- ES_JAVA_OPTS=-Xms512m -Xmx512m
volumes:
- es_data:/usr/share/elasticsearch/data
expose:
- "9200"
networks:
- backend
restart: unless-stopped
deploy:
resources:
limits:
memory: 1G
networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true
volumes:
nginx_logs:
static_files:
app_uploads:
app_logs:
postgres_data:
redis_data:
es_data:
Nginx反向代理与静态资源服务配置
./nginx/conf.d/default.conf:
upstream app_backend {
server web-app:3000;
keepalive 32;
}
server {
listen 80;
server_name cms.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name cms.example.com;
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
# 静态资源
location /static/ {
alias /usr/share/nginx/html/static/;
expires 30d;
add_header Cache-Control "public, immutable";
}
# 上传文件
location /uploads/ {
alias /usr/share/nginx/html/uploads/;
expires 7d;
}
# API代理
location /api/ {
proxy_pass http://app_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 5s;
proxy_read_timeout 60s;
}
# WebSocket
location /ws/ {
proxy_pass http://app_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 86400s;
}
# SPA前端
location / {
proxy_pass http://app_backend;
proxy_set_header Host $host;
}
# 健康检查
location /health {
access_log off;
return 200 "ok";
add_header Content-Type text/plain;
}
}
static_files卷由web-app容器写入构建产物,Nginx容器以只读方式挂载读取。这种单向数据流避免了Nginx直接访问应用容器内部文件系统。
Dockerfile构建优化与多阶段构建
./app/Dockerfile:
# 构建阶段
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --production=false
COPY . .
RUN npm run build
# 运行阶段(仅包含构建产物和运行时依赖)
FROM node:20-alpine AS runner
WORKDIR /app
# 安装dumb-init处理信号
RUN apk add --no-cache dumb-init
# 创建非root用户
RUN addgroup -g 1001 -S nodejs && \
adduser -S nextjs -u 1001
USER nextjs
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json ./
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD wget --spider -q http://localhost:3000/health || exit 1
ENTRYPOINT ["dumb-init", "--"]
CMD ["node", "dist/server.js"]
多阶段构建将builder阶段的全量node_modules排除在最终镜像外,镜像体积从1.2GB降至180MB。dumb-init解决PID 1的僵尸进程问题,确保容器收到SIGTERM时正确优雅退出。非root用户运行防止容器逃逸后的权限提升。
网络隔离与安全设计
docker-compose.yml中定义了两个网络:frontend和backend。backend网络设置internal: true,该网络上的容器无法访问外部网络,仅限内部服务间通信。
网络隔离效果:
– Nginx在frontend网络,接收外部流量并代理到web-app
– web-app同时连接frontend和backend,作为跨网关
– PostgreSQL、Redis、Elasticsearch仅在backend网络,外部完全不可达
即使Nginx被入侵,攻击者也无法直接访问数据库容器。数据库服务使用expose而非ports,不映射到宿主机端口,仅通过Docker内部DNS解析访问。
数据持久化与备份策略
命名卷由Docker管理,存储在/var/lib/docker/volumes/下。PostgreSQL数据卷是最关键的数据资产,配置定时备份:
# /etc/cron.d/pg-backup
# 每日凌晨3点备份数据库
0 3 * * * root docker exec cms-db pg_dump -U $DB_USER cms | gzip > /opt/backups/postgres/cms_$(date +\%Y\%m\%d).sql.gz
# 保留最近30天备份
30 3 * * * root find /opt/backups/postgres -name "cms_*.sql.gz" -mtime +30 -delete
Redis配置了appendonly yes开启AOF持久化,容器重启后自动恢复数据。Elasticsearch数据量大,采用快照备份到共享存储。
部署运维命令与故障排查
# 构建并启动所有服务
docker compose up -d --build
# 查看服务状态
docker compose ps
docker compose ps --format "table {{.Name}} {{.Status}} {{.Ports}}"
# 查看日志(实时跟踪)
docker compose logs -f web-app
docker compose logs --since 10m nginx
# 进入容器调试
docker compose exec web-app sh
docker compose exec postgres psql -U $DB_USER -d cms
# 单独重启某个服务(不影响其他)
docker compose restart web-app
# 更新单个服务镜像
docker compose pull web-app
docker compose up -d web-app
# 查看资源使用
docker stats cms-nginx cms-app cms-db cms-cache
# 优雅停止(等待30秒后强制)
docker compose stop -t 30
# 完全清理(停止并删除容器、网络,保留卷)
docker compose down
# 清理并删除卷(危险!仅在需要重置数据时使用)
docker compose down -v
故障排查时先看容器状态,Exited状态的容器使用docker logs查看退出原因。健康检查失败时进入容器手动执行healthcheck命令定位问题。资源限制导致的OOM通过docker inspect查看OOMKilled标志。
# 检查是否被OOM Kill
docker inspect cms-app --format '{{.State.OOMKilled}}'
# 查看容器资源限制
docker inspect cms-app --format '{{.HostConfig.Memory}} {{.HostConfig.NanoCpus}}'
生产环境通过Docker Compose编排多容器应用,配合CI/CD流水线实现自动构建和部署。GitLab CI或GitHub Actions在代码合并后触发docker compose build,镜像推送到Harbor仓库,部署节点docker compose pull && docker compose up -d完成滚动更新。日志通过Filebeat采集到ELK集中分析,监控通过cAdvisor+Prometheus采集容器指标。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/dockercompose-duo-rong-qi-bian-pai-shi-zhan-duan-kou-ying/