Docker Compose多容器编排实战:端口映射、数据卷与网络隔离配置

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/

(0)
小编小编
上一篇 10小时前
下一篇 10小时前

相关推荐