<返回目录     Powered by claude/xia兄

第14课: CI/CD集成

什么是CI/CD?

GitHub Actions + Docker

# .github/workflows/docker.yml
name: Docker CI/CD

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

jobs:
  build:
    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: |
          myuser/myapp:latest
          myuser/myapp:${{ github.sha }}
        cache-from: type=registry,ref=myuser/myapp:buildcache
        cache-to: type=registry,ref=myuser/myapp:buildcache,mode=max

    - name: Run tests
      run: |
        docker run --rm myuser/myapp:${{ github.sha }} npm test

    - name: Deploy to production
      if: github.ref == 'refs/heads/main'
      run: |
        ssh user@server "docker pull myuser/myapp:latest && docker-compose up -d"

GitLab CI + Docker

# .gitlab-ci.yml
stages:
  - build
  - test
  - deploy

variables:
  DOCKER_DRIVER: overlay2
  IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA

build:
  stage: build
  image: docker:latest
  services:
    - docker:dind
  before_script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
  script:
    - docker build -t $IMAGE_TAG .
    - docker push $IMAGE_TAG
    - docker tag $IMAGE_TAG $CI_REGISTRY_IMAGE:latest
    - docker push $CI_REGISTRY_IMAGE:latest

test:
  stage: test
  image: $IMAGE_TAG
  script:
    - npm test
    - npm run lint

deploy_staging:
  stage: deploy
  only:
    - develop
  script:
    - ssh user@staging-server "docker pull $IMAGE_TAG && docker-compose up -d"

deploy_production:
  stage: deploy
  only:
    - main
  when: manual
  script:
    - ssh user@prod-server "docker pull $IMAGE_TAG && docker-compose up -d"

Jenkins Pipeline

// Jenkinsfile
pipeline {
    agent any

    environment {
        DOCKER_IMAGE = "myapp"
        DOCKER_TAG = "${env.BUILD_NUMBER}"
        REGISTRY = "registry.example.com"
    }

    stages {
        stage('Checkout') {
            steps {
                checkout scm
            }
        }

        stage('Build') {
            steps {
                script {
                    docker.build("${REGISTRY}/${DOCKER_IMAGE}:${DOCKER_TAG}")
                }
            }
        }

        stage('Test') {
            steps {
                script {
                    docker.image("${REGISTRY}/${DOCKER_IMAGE}:${DOCKER_TAG}").inside {
                        sh 'npm test'
                        sh 'npm run lint'
                    }
                }
            }
        }

        stage('Push') {
            steps {
                script {
                    docker.withRegistry("https://${REGISTRY}", 'docker-credentials') {
                        docker.image("${REGISTRY}/${DOCKER_IMAGE}:${DOCKER_TAG}").push()
                        docker.image("${REGISTRY}/${DOCKER_IMAGE}:${DOCKER_TAG}").push('latest')
                    }
                }
            }
        }

        stage('Deploy') {
            when {
                branch 'main'
            }
            steps {
                sh '''
                    ssh user@server "
                        docker pull ${REGISTRY}/${DOCKER_IMAGE}:${DOCKER_TAG} &&
                        docker-compose up -d
                    "
                '''
            }
        }
    }

    post {
        always {
            cleanWs()
        }
    }
}

Docker in Docker (DinD)

# 在CI环境中使用Docker
# GitLab CI示例
build:
  image: docker:latest
  services:
    - docker:dind
  variables:
    DOCKER_HOST: tcp://docker:2375
    DOCKER_TLS_CERTDIR: ""
  script:
    - docker build -t myapp .
    - docker run --rm myapp npm test
DinD注意事项:

使用Kaniko构建镜像

# 无需Docker守护进程的镜像构建
# .gitlab-ci.yml
build:
  stage: build
  image:
    name: gcr.io/kaniko-project/executor:debug
    entrypoint: [""]
  script:
    - mkdir -p /kaniko/.docker
    - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
    - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG

多环境部署策略

# docker-compose.yml (基础配置)
version: '3.8'
services:
  app:
    image: myapp:${TAG:-latest}
    environment:
      - NODE_ENV=${ENV:-production}

# docker-compose.staging.yml (覆盖配置)
version: '3.8'
services:
  app:
    environment:
      - NODE_ENV=staging
      - DEBUG=true

# docker-compose.prod.yml (生产配置)
version: '3.8'
services:
  app:
    deploy:
      replicas: 3
      resources:
        limits:
          cpus: '2'
          memory: 2G

# 部署命令
# 测试环境
docker-compose -f docker-compose.yml -f docker-compose.staging.yml up -d

# 生产环境
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d

蓝绿部署

# 蓝绿部署脚本
#!/bin/bash

# 当前运行的是蓝色还是绿色
CURRENT=$(docker ps --filter "name=app-blue" --format "{{.Names}}" | grep -q "app-blue" && echo "blue" || echo "green")
NEW=$([ "$CURRENT" = "blue" ] && echo "green" || echo "blue")

echo "Current: $CURRENT, Deploying: $NEW"

# 启动新版本
docker-compose -f docker-compose.$NEW.yml up -d

# 健康检查
for i in {1..30}; do
    if curl -f http://localhost:8080/health; then
        echo "New version is healthy"
        break
    fi
    sleep 2
done

# 切换流量(更新Nginx配置)
sed -i "s/app-$CURRENT/app-$NEW/g" /etc/nginx/conf.d/default.conf
nginx -s reload

# 停止旧版本
docker-compose -f docker-compose.$CURRENT.yml down

滚动更新

# Docker Swarm滚动更新
docker service update \
  --image myapp:v2.0 \
  --update-parallelism 2 \
  --update-delay 10s \
  myapp

# Kubernetes滚动更新
kubectl set image deployment/myapp myapp=myapp:v2.0
kubectl rollout status deployment/myapp

# 回滚
kubectl rollout undo deployment/myapp

自动化测试集成

# docker-compose.test.yml
version: '3.8'

services:
  app:
    build: .
    environment:
      - NODE_ENV=test
    depends_on:
      - db
      - redis

  db:
    image: postgres:14
    environment:
      POSTGRES_PASSWORD: test

  redis:
    image: redis:alpine

  test:
    build: .
    command: npm test
    depends_on:
      - app
      - db
      - redis

# 运行测试
docker-compose -f docker-compose.test.yml up --abort-on-container-exit --exit-code-from test

镜像版本管理

# 语义化版本标签
docker build -t myapp:1.2.3 .
docker tag myapp:1.2.3 myapp:1.2
docker tag myapp:1.2.3 myapp:1
docker tag myapp:1.2.3 myapp:latest

# 使用Git提交哈希
docker build -t myapp:$(git rev-parse --short HEAD) .

# 使用构建时间
docker build -t myapp:$(date +%Y%m%d-%H%M%S) .

# 组合标签
docker build -t myapp:v1.2.3-$(git rev-parse --short HEAD) .

监控和通知

# GitHub Actions通知
- name: Notify Slack
  if: always()
  uses: 8398a7/action-slack@v3
  with:
    status: ${{ job.status }}
    text: 'Deployment ${{ job.status }}'
    webhook_url: ${{ secrets.SLACK_WEBHOOK }}

# 部署后健康检查
- name: Health check
  run: |
    for i in {1..30}; do
      if curl -f https://myapp.com/health; then
        echo "Deployment successful"
        exit 0
      fi
      sleep 10
    done
    echo "Deployment failed"
    exit 1

完整CI/CD流程

# .github/workflows/complete-pipeline.yml
name: Complete CI/CD Pipeline

on:
  push:
    branches: [ main, develop ]
    tags: [ 'v*' ]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Run tests
        run: |
          docker-compose -f docker-compose.test.yml up --abort-on-container-exit

  build:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Build image
        run: docker build -t myapp:${{ github.sha }} .
      - name: Scan image
        run: |
          docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
            aquasec/trivy image myapp:${{ github.sha }}
      - name: Push image
        run: |
          echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
          docker push myapp:${{ github.sha }}

  deploy-staging:
    needs: build
    if: github.ref == 'refs/heads/develop'
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to staging
        run: |
          ssh user@staging "docker pull myapp:${{ github.sha }} && docker-compose up -d"

  deploy-production:
    needs: build
    if: startsWith(github.ref, 'refs/tags/v')
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to production
        run: |
          ssh user@prod "docker pull myapp:${{ github.sha }} && docker-compose up -d"

最佳实践

实践练习

  1. 配置GitHub Actions自动构建Docker镜像
  2. 集成自动化测试到CI流程
  3. 实现多环境部署(开发、测试、生产)
  4. 配置镜像安全扫描
  5. 实现蓝绿部署或滚动更新
  6. 添加部署后的健康检查和通知