技术
20 min read

Docker 容器化部署实践:从开发到生产

2025-01-12by LZG
#Docker#DevOps#Deployment

引言

Docker 已经成为现代应用部署的标准工具。本文将介绍如何使用 Docker 进行应用容器化,从开发环境到生产环境的完整流程。

1. Docker 基础

1.1 核心概念

镜像 (Image):只读的模板,包含运行应用所需的一切

容器 (Container):镜像的运行实例

仓库 (Registry):存储和分发镜像的地方

# 基础命令
docker build -t myapp:1.0 .
docker run -d -p 3000:3000 --name myapp myapp:1.0
docker ps
docker logs myapp
docker stop myapp
docker rm myapp

1.2 Dockerfile 编写

# 多阶段构建
FROM node:18-alpine AS builder

WORKDIR /app

# 复制依赖文件
COPY package*.json ./

# 安装依赖
RUN npm ci --only=production

# 复制源代码
COPY . .

# 构建应用
RUN npm run build

# 生产阶段
FROM node:18-alpine AS production

WORKDIR /app

# 从构建阶段复制构建产物
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./

# 暴露端口
EXPOSE 3000

# 健康检查
HEALTHCHECK --interval=30s --timeout=3s \
  CMD node healthcheck.js || exit 1

# 启动应用
CMD ["node", "dist/index.js"]

2. Next.js 应用容器化

2.1 优化 Dockerfile

# 使用官方 Next.js 基础镜像
FROM node:18-alpine AS base

# 安装依赖
FROM base AS deps
WORKDIR /app

COPY package.json package-lock.json ./
RUN npm ci

# 构建阶段
FROM base AS builder
WORKDIR /app

COPY --from=deps /app/node_modules ./node_modules
COPY . .

# 设置环境变量
ENV NEXT_TELEMETRY_DISABLED 1

RUN npm run build

# 生产阶段
FROM base AS runner
WORKDIR /app

ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1

# 创建非 root 用户
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

# 复制必要文件
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs

EXPOSE 3000

ENV PORT 3000
ENV HOSTNAME "0.0.0.0"

CMD ["node", "server.js"]

2.2 Docker Compose 配置

version: '3.8'

services:
  # Next.js 应用
  app:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgresql://user:password@db:5432/mydb
    depends_on:
      - db
      - redis
    restart: unless-stopped
    networks:
      - app-network

  # PostgreSQL 数据库
  db:
    image: postgres:15-alpine
    environment:
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=password
      - POSTGRES_DB=mydb
    volumes:
      - postgres-data:/var/lib/postgresql/data
    ports:
      - "5432:5432"
    restart: unless-stopped
    networks:
      - app-network

  # Redis 缓存
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis-data:/data
    restart: unless-stopped
    networks:
      - app-network

  # Nginx 反向代理
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - ./ssl:/etc/nginx/ssl
    depends_on:
      - app
    restart: unless-stopped
    networks:
      - app-network

volumes:
  postgres-data:
  redis-data:

networks:
  app-network:
    driver: bridge

3. Docker Compose 高级配置

3.1 环境变量管理

# docker-compose.yml
version: '3.8'

services:
  app:
    build: .
    env_file:
      - .env.production
    environment:
      - NODE_ENV=${NODE_ENV}
      - API_URL=${API_URL}
# .env.production
NODE_ENV=production
API_URL=https://api.example.com
DATABASE_URL=postgresql://user:password@db:5432/mydb

3.2 健康检查

services:
  app:
    build: .
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

3.3 资源限制

services:
  app:
    build: .
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 512M
        reservations:
          cpus: '0.5'
          memory: 256M

4. CI/CD 集成

4.1 GitHub Actions 配置

name: Docker Build and Deploy

on:
  push:
    branches: [main]

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout code
        uses: actions/checkout@v3
      
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v2
      
      - name: Login to Docker Hub
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}
      
      - name: Build and push
        uses: docker/build-push-action@v4
        with:
          context: .
          push: true
          tags: |
            myorg/myapp:latest
            myorg/myapp:${{ github.sha }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
      
      - name: Deploy to server
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          script: |
            docker pull myorg/myapp:latest
            docker stop myapp
            docker rm myapp
            docker run -d --name myapp -p 3000:3000 myorg/myapp:latest

4.2 自动化部署脚本

#!/bin/bash

# deploy.sh

set -e

IMAGE_NAME="myorg/myapp"
CONTAINER_NAME="myapp"
PORT=3000

echo "拉取最新镜像..."
docker pull $IMAGE_NAME:latest

echo "停止旧容器..."
docker stop $CONTAINER_NAME 2>/dev/null || true
docker rm $CONTAINER_NAME 2>/dev/null || true

echo "启动新容器..."
docker run -d \
  --name $CONTAINER_NAME \
  -p $PORT:$PORT \
  --restart unless-stopped \
  --env-file .env.production \
  $IMAGE_NAME:latest

echo "清理旧镜像..."
docker image prune -f

echo "部署完成!"

5. 生产环境优化

5.1 Nginx 反向代理

# nginx.conf
events {
    worker_connections 1024;
}

http {
    upstream app {
        least_conn;
        server app:3000;
        server app:3001;
        server app:3002;
    }

    # 限流配置
    limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;

    server {
        listen 80;
        server_name example.com;

        # 重定向到 HTTPS
        return 301 https://$server_name$request_uri;
    }

    server {
        listen 443 ssl http2;
        server_name example.com;

        ssl_certificate /etc/nginx/ssl/cert.pem;
        ssl_certificate_key /etc/nginx/ssl/key.pem;
        ssl_protocols TLSv1.2 TLSv1.3;

        # Gzip 压缩
        gzip on;
        gzip_types text/plain text/css application/json application/javascript;

        # 静态文件缓存
        location /static/ {
            proxy_pass http://app;
            expires 1y;
            add_header Cache-Control "public, immutable";
        }

        # API 限流
        location /api/ {
            limit_req zone=api burst=20 nodelay;
            proxy_pass http://app;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
        }

        # 默认代理
        location / {
            proxy_pass http://app;
            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;
        }
    }
}

5.2 日志管理

# docker-compose.yml
services:
  app:
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
        labels: "app=nextjs"
# 日志收集脚本
#!/bin/bash

docker logs --tail 100 -f myapp | while read line; do
  echo "$line" | logger -t myapp
done

5.3 监控与告警

# 添加 Prometheus 监控
services:
  prometheus:
    image: prom/prometheus
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    ports:
      - "9090:9090"

  grafana:
    image: grafana/grafana
    ports:
      - "3001:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
# prometheus.yml
global:
  scrape_interval: 15s

scrape_configs:
  - job_name: 'docker'
    static_configs:
      - targets: ['cadvisor:8080']

6. 安全最佳实践

6.1 镜像安全扫描

# 使用 Trivy 扫描镜像
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
  aquasec/trivy image myorg/myapp:latest

# GitHub Actions 集成
- name: Run Trivy vulnerability scanner
  uses: aquasecurity/trivy-action@master
  with:
    image-ref: 'myorg/myapp:latest'
    format: 'sarif'
    output: 'trivy-results.sarif'

6.2 最小权限原则

# 使用非 root 用户
FROM node:18-alpine

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

USER nextjs

# 只复制必要文件
COPY --chown=nextjs:nodejs package*.json ./
RUN npm ci --only=production

COPY --chown=nextjs:nodejs . .

6.3 敏感信息管理

# 使用 Docker Secrets
echo "my-secret-password" | docker secret create db_password -

# docker-compose.yml
services:
  db:
    image: postgres:15
    secrets:
      - db_password

secrets:
  db_password:
    external: true

7. 故障排查

7.1 常见问题

容器无法启动

# 查看容器日志
docker logs myapp

# 检查容器状态
docker inspect myapp

# 进入容器调试
docker exec -it myapp sh

网络连接问题

# 检查网络
docker network ls
docker network inspect app-network

# 测试连接
docker exec myapp ping db

资源占用过高

# 查看资源使用
docker stats

# 限制资源
docker update --memory="512m" --cpus="1.0" myapp

7.2 性能优化

# 多阶段构建减小镜像大小
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 3000
CMD ["node", "dist/index.js"]
# 清理无用镜像
docker image prune -a

# 清理无用容器
docker container prune

# 清理无用卷
docker volume prune

结语

Docker 容器化技术为应用部署提供了标准化、可移植的解决方案。通过合理使用 Docker、Docker Compose 和 CI/CD 工具,我们可以构建出高效、可靠的生产环境。关键是要遵循最佳实践,持续优化和改进部署流程。