CI/CD流水线搭建实战

· 阅读约需23分钟

一、CI/CD概述

CI/CD是现代软件开发的核心实践,通过自动化流程实现持续集成、持续交付和持续部署,大幅提升开发效率和软件质量。

核心概念:

  • CI(Continuous Integration)持续集成:频繁将代码合并到主干,自动构建和测试
  • CD(Continuous Delivery)持续交付:自动化部署到预发布环境,手动发布到生产
  • CD(Continuous Deployment)持续部署:全自动部署到生产环境

CI/CD流水线价值:

  1. 快速反馈:代码提交立即验证,问题早发现早解决
  2. 降低风险:频繁小批量发布,减少变更风险
  3. 提高效率:自动化重复工作,释放人力
  4. 质量保障:自动化测试,确保代码质量
  5. 标准化:统一构建部署流程,消除环境差异

二、主流CI/CD工具对比

工具类型优势适用场景
Jenkins自托管高度可定制,插件丰富复杂企业级流水线
GitLab CI集成式GitLab原生集成,配置简单GitLab代码托管用户
GitHub ActionsSaaSGitHub原生,市场丰富GitHub开源项目
Drone云原生Docker原生,轻量高效Kubernetes环境
CircleCISaaS速度快,配置简单中小团队

工具选型建议:

  • 中小团队:GitHub Actions / GitLab CI
  • 企业级:Jenkins / GitLab CI
  • 云原生:Drone / Tekton
  • 开源项目:GitHub Actions

三、GitLab CI完整实战

1. 基础配置(.gitlab-ci.yml)

# 定义执行阶段
stages:
  - build    # 构建
  - test     # 测试
  - deploy   # 部署

# 全局变量
variables:
  DOCKER_IMAGE: $CI_REGISTRY/myapp:latest
  NODE_ENV: production

# 缓存配置
cache:
  paths:
    - node_modules/
    - .npm/

2. 构建阶段

build_job:
  stage: build
  image: node:18-alpine
  script:
    - npm config set registry https://registry.npmmirror.com
    - npm ci
    - npm run build
  artifacts:
    paths:
      - dist/
    expire_in: 1 week
  only:
    - main
    - develop
  tags:
    - docker

3. 测试阶段

lint_job:
  stage: test
  image: node:18-alpine
  script:
    - npm ci
    - npm run lint

unit_test_job:
  stage: test
  image: node:18-alpine
  script:
    - npm ci
    - npm run test:unit
  coverage: '/All files[^|]*\|[^|]*\s+([\d\.]+)/'

e2e_test_job:
  stage: test
  image: cypress/browsers:node18-chrome109
  script:
    - npm ci
    - npm run test:e2e
  when: manual  # 手动触发

4. Docker镜像构建

docker_build_job:
  stage: build
  image: docker:latest
  services:
    - docker:dind
  script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
    - docker build -t $DOCKER_IMAGE .
    - docker push $DOCKER_IMAGE
  only:
    - main

5. 部署阶段

deploy_staging:
  stage: deploy
  image: alpine:latest
  script:
    - apk add --no-cache openssh-client
    - eval $(ssh-agent -s)
    - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
    - ssh -o StrictHostKeyChecking=no user@staging-server "docker pull $DOCKER_IMAGE && docker-compose up -d"
  environment:
    name: staging
    url: https://staging.myapp.com
  only:
    - develop

deploy_production:
  stage: deploy
  script:
    - echo "部署到生产环境"
  environment:
    name: production
    url: https://myapp.com
  when: manual  # 生产环境手动确认
  only:
    - main

四、GitHub Actions实战

1. 基础工作流配置

# .github/workflows/ci.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:
      - uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Lint
        run: npm run lint

      - name: Test
        run: npm run test

      - name: Build
        run: npm run build

2. Docker构建与推送

  build-and-push-image:
    runs-on: ubuntu-latest
    needs: build-and-test
    permissions:
      contents: read
      packages: write

    steps:
      - name: Checkout repository
        uses: actions/checkout@v3

      - name: Log in to Container registry
        uses: docker/login-action@v2
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v4
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}

      - name: Build and push Docker image
        uses: docker/build-push-action@v4
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}

3. 自动部署到服务器

  deploy:
    runs-on: ubuntu-latest
    needs: build-and-push-image
    if: github.ref == 'refs/heads/main'

    steps:
      - name: Deploy to server
        uses: appleboy/ssh-action@v0.1.10
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          script: |
            cd /opt/myapp
            docker-compose pull
            docker-compose up -d
            docker image prune -f

五、Jenkins Pipeline实战

1. Jenkinsfile(声明式流水线)

pipeline {
    agent any

    environment {
        DOCKER_CREDENTIALS = credentials('docker-registry')
        SSH_CREDENTIALS = credentials('ssh-deploy')
    }

    options {
        buildDiscarder(logRotator(numToKeepStr: '10'))
        timeout(time: 30, unit: 'MINUTES')
    }

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

        stage('Build') {
            agent {
                docker {
                    image 'node:18-alpine'
                    reuseNode true
                }
            }
            steps {
                sh 'npm ci'
                sh 'npm run build'
            }
        }

        stage('Test') {
            parallel {
                stage('Unit Test') {
                    steps {
                        sh 'npm run test:unit'
                    }
                }
                stage('Lint') {
                    steps {
                        sh 'npm run lint'
                    }
                }
            }
        }

        stage('Docker Build') {
            steps {
                script {
                    docker.withRegistry('https://registry.example.com', 'docker-registry') {
                        def image = docker.build("myapp:${BUILD_NUMBER}")
                        image.push()
                        image.push('latest')
                    }
                }
            }
        }

        stage('Deploy Staging') {
            when {
                branch 'develop'
            }
            steps {
                sshagent(['ssh-deploy']) {
                    sh '''
                        ssh -o StrictHostKeyChecking=no user@staging "
                            cd /opt/myapp &&
                            docker-compose pull &&
                            docker-compose up -d
                        "
                    '''
                }
            }
        }

        stage('Deploy Production') {
            when {
                branch 'main'
            }
            input {
                message 'Deploy to production?'
                ok 'Yes, deploy'
            }
            steps {
                echo 'Deploying to production...'
            }
        }
    }

    post {
        success {
            echo 'Pipeline succeeded!'
        }
        failure {
            echo 'Pipeline failed!'
            // 发送钉钉/企业微信通知
        }
        always {
            cleanWs()
        }
    }
}

六、流水线最佳实践

1. 流水线设计原则

代码提交 → 构建 → 测试 → 镜像 → 预发布 → 生产
    ↓         ↓      ↓      ↓       ↓       ↓
  快速反馈   一致   质量    一次     验证    人工
            环境   门禁    构建     功能    确认

2. 关键优化点

加速构建:

# 1. 依赖缓存
cache:
  key: ${CI_COMMIT_REF_SLUG}
  paths:
    - node_modules/

# 2. 并行执行
parallel:
  matrix:
    - NODE_VERSION: ['16', '18', '20']

# 3. 按需执行
rules:
  - changes:
      - src/**/*
      - package.json

质量门禁:

# 代码质量扫描
sonar-scanner \
  -Dsonar.projectKey=myapp \
  -Dsonar.qualitygate.wait=true

# 安全漏洞扫描
trivy image $DOCKER_IMAGE

# 依赖漏洞检查
npm audit --audit-level high

3. 通知与告警

钉钉通知示例:

notify:
  stage: .post
  script:
    - |
      curl "$DINGTALK_WEBHOOK" \
        -H 'Content-Type: application/json' \
        -d "{
          \"msgtype\": \"text\",
          \"text\": {
            \"content\": \"构建${CI_JOB_STATUS}: ${CI_PROJECT_NAME}\n分支: ${CI_COMMIT_BRANCH}\n日志: ${CI_JOB_URL}\"
          }
        }"
  when: always

七、常见问题与解决方案

1. 流水线太慢?

  • ✅ 启用依赖缓存
  • ✅ 并行执行任务
  • ✅ 优化Docker镜像(多阶段构建)
  • ✅ 使用更快的构建机器

2. 测试不稳定?

  • ✅ 测试隔离,避免互相影响
  • ✅ 增加重试机制
  • ✅ 分离单元测试和E2E测试
  • ✅ 优化测试用例

3. 部署失败?

  • ✅ 蓝绿部署/滚动发布
  • ✅ 自动化回滚机制
  • ✅ 健康检查验证
  • ✅ 灰度发布策略

4. 安全问题?

  • ✅ 密钥使用CI变量,不硬编码
  • ✅ 镜像安全扫描
  • ✅ 最小权限原则
  • ✅ 定期轮换凭证

总结

CI/CD实施路径:

  1. 基础搭建:选择工具,配置基础流水线
  2. 自动化测试:单元测试、集成测试、E2E测试
  3. 容器化:Docker标准化构建产物
  4. 环境分离:开发、测试、预发、生产
  5. 高级特性:蓝绿部署、灰度发布、自动回滚
  6. 监控告警:流水线可视化、失败告警

记住:CI/CD不是目的,提升交付效率和质量才是核心!