第14章:Docker 与 CI/CD

第14章:Docker 与 CI/CD

本章目标:掌握 Docker 在 CI/CD 流水线中的应用,实现代码提交到自动部署的全流程自动化。


14.1 CI/CD 概念

14.1.1 什么是 CI/CD

复制代码
┌─────────────────────────────────────────────────────────────┐
│                     CI/CD 流水线                              │
│                                                             │
│  ┌─────┐    ┌─────┐    ┌─────┐    ┌─────┐    ┌─────┐     │
│  │提交  │ →  │构建  │ →  │测试  │ →  │打包  │ →  │部署  │     │
│  │Code │    │Build│    │Test │    │Image│    │Deploy│    │
│  └─────┘    └─────┘    └─────┘    └─────┘    └─────┘     │
│                                                             │
│  CI: 持续集成 (Continuous Integration)                      │
│      代码提交后自动构建和测试                                 │
│                                                             │
│  CD: 持续交付/部署 (Continuous Delivery/Deployment)          │
│      通过测试后自动打包和部署                                 │
└─────────────────────────────────────────────────────────────┘

14.1.2 CI/CD 的价值

价值 说明
快速反馈 代码提交后立即知道是否破坏了功能
减少风险 小批量频繁发布,降低每次发布风险
自动化 减少人工操作,避免人为错误
可追溯 每次发布都有完整的构建和部署记录
一致性 开发、测试、生产环境完全一致

14.2 GitLab CI/CD 集成

14.2.1 GitLab CI/CD 架构

复制代码
┌─────────────────────────────────────────────────────────────┐
│                GitLab CI/CD 架构                              │
│                                                             │
│  ┌──────────────┐                                          │
│  │  GitLab      │                                          │
│  │  Server      │                                          │
│  │  (.gitlab-ci.yml)                                       │
│  └──────┬───────┘                                          │
│         │                                                   │
│         ▼                                                   │
│  ┌──────────────┐     ┌──────────────┐                    │
│  │  GitLab      │     │  GitLab      │                    │
│  │  Runner 1    │     │  Runner 2    │  (CI/CD 执行器)    │
│  │  (Docker)    │     │  (Docker)    │                    │
│  └──────────────┘     └──────────────┘                    │
│         │                                                   │
│         ▼                                                   │
│  ┌──────────────┐     ┌──────────────┐                    │
│  │  Docker      │     │  部署目标     │                    │
│  │  Registry    │     │  (服务器)     │                    │
│  └──────────────┘     └──────────────┘                    │
└─────────────────────────────────────────────────────────────┘

14.2.2 .gitlab-ci.yml 配置

yaml 复制代码
# .gitlab-ci.yml
stages:
  - build
  - test
  - security
  - package
  - deploy

variables:
  DOCKER_REGISTRY: registry.example.com
  IMAGE_NAME: ${DOCKER_REGISTRY}/myproject/app
  IMAGE_TAG: ${CI_COMMIT_SHORT_SHA}

# 构建阶段
build:
  stage: build
  image: docker:latest
  services:
    - docker:dind
  before_script:
    - docker login -u $DOCKER_USER -p $DOCKER_PASSWORD $DOCKER_REGISTRY
  script:
    - docker build -t ${IMAGE_NAME}:${IMAGE_TAG} .
    - docker push ${IMAGE_NAME}:${IMAGE_TAG}
  only:
    - main
    - develop

# 测试阶段
test:
  stage: test
  image: ${IMAGE_NAME}:${IMAGE_TAG}
  services:
    - mysql:8.0
    - redis:7
  variables:
    MYSQL_ROOT_PASSWORD: testpassword
    MYSQL_DATABASE: testdb
    DATABASE_URL: mysql2://root:testpassword@mysql:3306/testdb
    REDIS_URL: redis://redis:6379
  script:
    - npm install
    - npm run test
    - npm run test:integration
  only:
    - main
    - develop
    - merge_requests

# 安全扫描
security_scan:
  stage: security
  image:
    name: aquasec/trivy:latest
    entrypoint: [""]
  script:
    - trivy image --exit-code 1 --severity HIGH,CRITICAL ${IMAGE_NAME}:${IMAGE_TAG}
  allow_failure: true
  only:
    - main

# 打包镜像(添加更多标签)
package:
  stage: package
  image: docker:latest
  services:
    - docker:dind
  before_script:
    - docker login -u $DOCKER_USER -p $DOCKER_PASSWORD $DOCKER_REGISTRY
  script:
    - docker pull ${IMAGE_NAME}:${IMAGE_TAG}
    - docker tag ${IMAGE_NAME}:${IMAGE_TAG} ${IMAGE_NAME}:latest
    - docker push ${IMAGE_NAME}:latest
  only:
    - main

# 部署到测试环境
deploy_staging:
  stage: deploy
  image: alpine:latest
  before_script:
    - apk add --no-cache openssh-client
    - eval $(ssh-agent -s)
    - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
    - mkdir -p ~/.ssh
    - echo "$SSH_KNOWN_HOSTS" >> ~/.ssh/known_hosts
  script:
    - ssh $STAGING_USER@$STAGING_HOST "
        docker pull ${IMAGE_NAME}:${IMAGE_TAG} &&
        docker compose -f docker-compose.staging.yml up -d"
  environment:
    name: staging
    url: https://staging.example.com
  only:
    - develop

# 部署到生产环境
deploy_production:
  stage: deploy
  image: alpine:latest
  before_script:
    - apk add --no-cache openssh-client
    - eval $(ssh-agent -s)
    - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
    - mkdir -p ~/.ssh
    - echo "$SSH_KNOWN_HOSTS" >> ~/.ssh/known_hosts
  script:
    - ssh $PRODUCTION_USER@$PRODUCTION_HOST "
        docker pull ${IMAGE_NAME}:${IMAGE_TAG} &&
        docker compose -f docker-compose.prod.yml up -d"
  environment:
    name: production
    url: https://example.com
  when: manual  # 手动触发
  only:
    - main

14.3 GitHub Actions 集成

14.3.1 GitHub Actions 工作流

yaml 复制代码
# .github/workflows/ci-cd.yml
name: CI/CD Pipeline

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  # 构建和测试
  build-and-test:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=ref,event=branch
            type=ref,event=pr
            type=sha,prefix=

      - name: Build and push Docker image
        uses: docker/build-push-action@v5
        with:
          context: .
          push: ${{ github.event_name != 'pull_request' }}
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

      - name: Run tests
        run: |
          docker compose -f docker-compose.test.yml up --abort-on-container-exit
          docker compose -f docker-compose.test.yml down

  # 安全扫描
  security:
    needs: build-and-test
    runs-on: ubuntu-latest
    steps:
      - name: Run Trivy vulnerability scanner
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
          format: 'sarif'
          output: 'trivy-results.sarif'
          severity: 'CRITICAL,HIGH'

      - name: Upload Trivy scan results
        uses: github/codeql-action/upload-sarif@v2
        with:
          sarif_file: 'trivy-results.sarif'

  # 部署到测试环境
  deploy-staging:
    needs: security
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/develop'
    environment:
      name: staging
      url: https://staging.example.com
    steps:
      - name: Deploy to staging
        uses: appleboy/ssh-action@v1.0.0
        with:
          host: ${{ secrets.STAGING_HOST }}
          username: ${{ secrets.STAGING_USER }}
          key: ${{ secrets.STAGING_SSH_KEY }}
          script: |
            docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
            docker compose -f docker-compose.staging.yml up -d

  # 部署到生产环境
  deploy-production:
    needs: security
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    environment:
      name: production
      url: https://example.com
    steps:
      - name: Deploy to production
        uses: appleboy/ssh-action@v1.0.0
        with:
          host: ${{ secrets.PRODUCTION_HOST }}
          username: ${{ secrets.PRODUCTION_USER }}
          key: ${{ secrets.PRODUCTION_SSH_KEY }}
          script: |
            docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
            docker compose -f docker-compose.prod.yml up -d

14.4 Jenkins Pipeline 集成

14.4.1 Jenkins Docker Pipeline

groovy 复制代码
// Jenkinsfile
pipeline {
    agent any

    environment {
        DOCKER_REGISTRY = 'registry.example.com'
        IMAGE_NAME = "${DOCKER_REGISTRY}/myproject/app"
        IMAGE_TAG = "${env.BUILD_NUMBER}"
    }

    stages {
        stage('Build') {
            steps {
                script {
                    docker.build("${IMAGE_NAME}:${IMAGE_TAG}")
                }
            }
        }

        stage('Test') {
            steps {
                script {
                    docker.image("${IMAGE_NAME}:${IMAGE_TAG}").inside {
                        sh 'npm install'
                        sh 'npm test'
                    }
                }
            }
        }

        stage('Security Scan') {
            steps {
                script {
                    sh """
                        docker run --rm \
                          -v /var/run/docker.sock:/var/run/docker.sock \
                          aquasec/trivy:latest \
                          image --exit-code 1 --severity HIGH,CRITICAL \
                          ${IMAGE_NAME}:${IMAGE_TAG}
                    """
                }
            }
        }

        stage('Push') {
            steps {
                script {
                    docker.withRegistry("https://${DOCKER_REGISTRY}", 'docker-credentials') {
                        docker.image("${IMAGE_NAME}:${IMAGE_TAG}").push()
                        docker.image("${IMAGE_NAME}:${IMAGE_TAG}").push('latest')
                    }
                }
            }
        }

        stage('Deploy to Staging') {
            when {
                branch 'develop'
            }
            steps {
                script {
                    sshagent(['staging-ssh-key']) {
                        sh """
                            ssh ${STAGING_USER}@${STAGING_HOST} "
                              docker pull ${IMAGE_NAME}:${IMAGE_TAG} &&
                              docker compose -f docker-compose.staging.yml up -d
                            "
                        """
                    }
                }
            }
        }

        stage('Deploy to Production') {
            when {
                branch 'main'
            }
            input {
                message "Deploy to production?"
                ok "Yes, deploy it!"
            }
            steps {
                script {
                    sshagent(['production-ssh-key']) {
                        sh """
                            ssh ${PRODUCTION_USER}@${PRODUCTION_HOST} "
                              docker pull ${IMAGE_NAME}:${IMAGE_TAG} &&
                              docker compose -f docker-compose.prod.yml up -d
                            "
                        """
                    }
                }
            }
        }
    }

    post {
        always {
            sh "docker rmi ${IMAGE_NAME}:${IMAGE_TAG} || true"
        }
        success {
            slackSend(
                color: 'good',
                message: "Build ${env.BUILD_NUMBER} succeeded"
            )
        }
        failure {
            slackSend(
                color: 'danger',
                message: "Build ${env.BUILD_NUMBER} failed"
            )
        }
    }
}

14.5 多阶段构建优化 CI/CD

14.5.1 优化 Dockerfile

dockerfile 复制代码
# 优化的多阶段 Dockerfile
# Stage 1: 依赖安装
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

# Stage 2: 构建
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Stage 3: 测试
FROM builder AS tester
RUN npm run test

# Stage 4: 生产
FROM node:20-alpine AS production
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY package*.json ./

# 安全加固
RUN addgroup -g 1001 -S appgroup && \
    adduser -S appuser -u 1001 -G appgroup
USER appuser

EXPOSE 3000
CMD ["node", "dist/index.js"]

14.5.2 CI/CD 中的构建优化

yaml 复制代码
# .github/workflows/optimized.yml
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Build with cache
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: myapp:latest
          cache-from: type=gha
          cache-to: type=gha,mode=max
          # 利用 GitHub Actions 缓存加速构建

14.6 Docker Compose 在 CI/CD 中的应用

14.6.1 测试环境编排

yaml 复制代码
# docker-compose.test.yml
version: '3.8'

services:
  app:
    build:
      context: .
      target: tester
    environment:
      - DATABASE_URL=postgresql://testuser:testpass@db:5432/testdb
      - REDIS_URL=redis://redis:6379
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    command: npm run test

  db:
    image: postgres:15-alpine
    environment:
      POSTGRES_DB: testdb
      POSTGRES_USER: testuser
      POSTGRES_PASSWORD: testpass
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U testuser"]
      interval: 5s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7-alpine
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 5s
      retries: 5

14.6.2 集成测试命令

bash 复制代码
# 在 CI/CD 中运行集成测试
docker compose -f docker-compose.test.yml up --build --abort-on-container-exit --exit-code-from app

# 参数说明:
# --build              构建镜像
# --abort-on-container-exit  任一容器退出则停止所有容器
# --exit-code-from app 使用 app 容器的退出码作为最终退出码

14.7 镜像版本管理策略

14.7.1 语义化版本

bash 复制代码
# 语义化版本格式
MAJOR.MINOR.PATCH

# 示例
v1.0.0    初始版本
v1.1.0    新增功能
v1.1.1    Bug 修复
v2.0.0    重大变更

# 在 CI/CD 中自动打标签
# GitLab CI
deploy_production:
  script:
    - docker tag ${IMAGE_NAME}:${CI_COMMIT_SHA} ${IMAGE_NAME}:v${CI_COMMIT_TAG}
    - docker push ${IMAGE_NAME}:v${CI_COMMIT_TAG}
  only:
    - tags

14.7.2 镜像标签策略

bash 复制代码
# 推荐的标签策略
myapp:v1.2.3          语义化版本
myapp:commit-a1b2c3d  Git SHA
myapp:2024.06.15      日期标签
myapp:latest          最新版本(仅开发环境)
myapp:staging         环境标签
myapp:production      环境标签

14.8 动手实验

实验 14.1:GitLab CI/CD

bash 复制代码
# 1. 创建 GitLab 项目
# 2. 添加 .gitlab-ci.yml
# 3. 配置 CI/CD 变量
#    DOCKER_USER
#    DOCKER_PASSWORD
#    STAGING_HOST
#    STAGING_USER
#    SSH_PRIVATE_KEY
# 4. 推送代码触发流水线
git push origin main

# 5. 在 GitLab 中查看流水线状态

实验 14.2:GitHub Actions

bash 复制代码
# 1. 创建 GitHub 项目
# 2. 添加 .github/workflows/ci-cd.yml
# 3. 配置 Secrets
#    STAGING_HOST
#    STAGING_USER
#    STAGING_SSH_KEY
# 4. 推送代码触发工作流
git push origin main

# 5. 在 GitHub 中查看 Actions 状态

实验 14.3:Jenkins Pipeline

bash 复制代码
# 1. 安装 Jenkins
docker run -d --name jenkins \
  -p 8080:8080 \
  -p 50000:50000 \
  -v jenkins-data:/var/jenkins_home \
  jenkins/jenkins:lts

# 2. 访问 http://localhost:8080
# 3. 安装 Docker Pipeline 插件
# 4. 创建 Pipeline 项目
# 5. 配置 Jenkinsfile
# 6. 运行构建

14.9 本章小结

CI/CD 工具 特点 适用场景
GitLab CI/CD 与 GitLab 深度集成 GitLab 用户
GitHub Actions 与 GitHub 深度集成 GitHub 用户
Jenkins 功能强大、插件丰富 复杂流水线
Docker Compose 多容器编排 测试环境

14.10 课后练习

  1. 基础题:使用 GitHub Actions 或 GitLab CI/CD 构建和推送 Docker 镜像。
  2. 进阶题:配置完整的 CI/CD 流水线(构建→测试→安全扫描→部署)。
  3. 实践题:使用 Jenkins 构建企业级 CI/CD 流水线。

📖 下一章:故障排查与面试题 ------ 常见问题处理和面试准备

相关推荐
Promise微笑1 小时前
工业微量水分监测:精密露点仪选型逻辑与行业应用实证深度报告
大数据·运维
联盟分享专家3 小时前
垂直工具型 SaaS 的增长实战:如何把用户变成推广者?
运维
江畔柳前堤3 小时前
第16章:docker企业级实战综合项目
运维·git·安全·docker·容器·eureka
阿里嘎多学长4 小时前
2026-07-03 GitHub 热点项目精选
开发语言·程序员·github·代码托管
Leon-Ning Liu4 小时前
【真实经验分享】OGG抽取进程报错 ORA-07445 [kgherrordmp()+986] ORA-00600 [17114]分析步骤
运维·数据库
QWEDDRFTG4 小时前
运维长期经验总结:从故障倒推服务器电源线选购标准
运维·服务器
Mr.wangh4 小时前
聊天模型--流式传输
运维·服务器
fei_sun4 小时前
等价负载均衡(等价路由ECMP)
运维·负载均衡
zh73145 小时前
docker日志监控dozzle,高性能,性能消耗小
运维·docker·容器
weixin_471383035 小时前
Docker - 05 - Railway 部署
运维·docker·容器