CI/CD 流水线设计与 DevOps 实践

CI/CD 流水线设计与 DevOps 实践 本文分享完整的 CI/CD 流水线设计方案,涵盖 GitLab CI、Jenkins、GitHub Actions 等主流工具的实践配置,帮助团队实现自动化交付。 CI/CD 基础概念 什么是 CI/CD CI (Continuous Integrati

CI/CD 流水线设计与 DevOps 实践

本文分享完整的 CI/CD 流水线设计方案,涵盖 GitLab CI、Jenkins、GitHub Actions 等主流工具的实践配置,帮助团队实现自动化交付。

CI/CD 基础概念

什么是 CI/CD

  • CI (Continuous Integration):持续集成,代码频繁合并,自动构建测试
  • CD (Continuous Delivery/Deployment):持续交付/部署,自动发布到环境

典型流水线阶段

代码提交 → 编译构建 → 单元测试 → 安全扫描 → 镜像打包 → 部署测试 → 集成测试 → 部署生产

GitLab CI 实践

基础配置

.gitlab-ci.yml

stages:
  - build
  - test
  - security
  - package
  - deploy

variables:
  DOCKER_REGISTRY: "registry.example.com"
  PROJECT_NAME: "myapp"

# 构建阶段
build:
  stage: build
  image: node:18-alpine
  script:
    - npm ci
    - npm run build
  artifacts:
    paths:
      - dist/
    expire_in: 1 hour

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

# 安全扫描
security:
  stage: security
  image: returntocorp/semgrep
  script:
    - semgrep --config=auto --json --output=report.json .
  artifacts:
    reports:
      sast: report.json

# 镜像打包
docker-build:
  stage: package
  image: docker:24-dind
  services:
    - docker:24-dind
  script:
    - docker build -t $DOCKER_REGISTRY/$PROJECT_NAME:$CI_COMMIT_SHA .
    - docker push $DOCKER_REGISTRY/$PROJECT_NAME:$CI_COMMIT_SHA

# 部署到 K8s
deploy-staging:
  stage: deploy
  image: bitnami/kubectl:latest
  script:
    - kubectl config use-context staging
    - kubectl set image deployment/$PROJECT_NAME \
        app=$DOCKER_REGISTRY/$PROJECT_NAME:$CI_COMMIT_SHA
  environment:
    name: staging
    url: https://staging.example.com
  only:
    - develop

deploy-production:
  stage: deploy
  image: bitnami/kubectl:latest
  script:
    - kubectl config use-context production
    - kubectl set image deployment/$PROJECT_NAME \
        app=$DOCKER_REGISTRY/$PROJECT_NAME:$CI_COMMIT_SHA
  environment:
    name: production
    url: https://example.com
  only:
    - main
  when: manual  # 需要手动触发

高级特性

并行执行

test:unit:
  stage: test
  parallel: 4
  script:
    - npm run test:unit -- --shard=$CI_NODE_INDEX/$CI_NODE_TOTAL

缓存优化

cache:
  key: ${CI_COMMIT_REF_SLUG}
  paths:
    - node_modules/
    - .npm/

动态流水线

# 根据文件变更触发不同任务
workflow:
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == "main"

GitHub Actions 实践

基础工作流

.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:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '18'
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Run tests
        run: npm run test
      
      - name: Build
        run: npm run build
      
      - name: Upload artifacts
        uses: actions/upload-artifact@v4
        with:
          name: build-files
          path: dist/

  docker:
    needs: build
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@v4
      
      - name: Log in to 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 }}
      
      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}

  deploy:
    needs: docker
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - name: Deploy to production
        run: |
          echo "${{ secrets.KUBECONFIG }}" | base64 -d > kubeconfig
          kubectl --kubeconfig=kubeconfig set image deployment/app \
            app=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}

Jenkins 实践

Pipeline 脚本

Jenkinsfile

pipeline {
    agent any
    
    environment {
        DOCKER_REGISTRY = 'registry.example.com'
        PROJECT_NAME = 'myapp'
    }
    
    stages {
        stage('Checkout') {
            steps {
                checkout scm
            }
        }
        
        stage('Build') {
            steps {
                sh 'npm ci'
                sh 'npm run build'
            }
        }
        
        stage('Test') {
            parallel {
                stage('Unit Tests') {
                    steps {
                        sh 'npm run test:unit'
                    }
                }
                stage('Integration Tests') {
                    steps {
                        sh 'npm run test:integration'
                    }
                }
            }
        }
        
        stage('Security Scan') {
            steps {
                sh 'npm audit'
            }
        }
        
        stage('Docker Build') {
            steps {
                script {
                    def image = docker.build("${DOCKER_REGISTRY}/${PROJECT_NAME}:${BUILD_NUMBER}")
                    docker.withRegistry("https://${DOCKER_REGISTRY}", 'docker-credentials') {
                        image.push()
                        image.push('latest')
                    }
                }
            }
        }
        
        stage('Deploy') {
            when {
                branch 'main'
            }
            steps {
                sh """
                    kubectl set image deployment/${PROJECT_NAME} \
                        app=${DOCKER_REGISTRY}/${PROJECT_NAME}:${BUILD_NUMBER}
                """
            }
        }
    }
    
    post {
        always {
            cleanWs()
        }
        failure {
            slackSend color: 'danger', message: "Build failed: ${env.JOB_NAME} - ${env.BUILD_NUMBER}"
        }
        success {
            slackSend color: 'good', message: "Build succeeded: ${env.JOB_NAME} - ${env.BUILD_NUMBER}"
        }
    }
}

ArgoCD GitOps 实践

应用配置

application.yaml

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: myapp
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/example/myapp.git
    targetRevision: HEAD
    path: k8s/overlays/production
  destination:
    server: https://kubernetes.default.svc
    namespace: production
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true

最佳实践

1. 镜像标签策略

# 避免使用 latest
docker build -t myapp:${GIT_COMMIT_SHA} .
docker build -t myapp:${GIT_BRANCH}-${BUILD_NUMBER} .

# 语义化版本
docker build -t myapp:v1.2.3 .

2. 多阶段构建

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

# 运行阶段
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html

3. 健康检查

# K8s deployment
livenessProbe:
  httpGet:
    path: /health
    port: 8080
  initialDelaySeconds: 30
  periodSeconds: 10

readinessProbe:
  httpGet:
    path: /ready
    port: 8080
  initialDelaySeconds: 5
  periodSeconds: 5

4. 环境隔离

├── k8s/
│   ├── base/              # 基础资源
│   │   ├── deployment.yaml
│   │   ├── service.yaml
│   │   └── kustomization.yaml
│   └── overlays/
│       ├── develop/       # 开发环境
│       │   └── kustomization.yaml
│       ├── staging/       # 测试环境
│       │   └── kustomization.yaml
│       └── production/    # 生产环境
│           └── kustomization.yaml

小结

本文介绍了主流 CI/CD 工具的实践方案:

  1. GitLab CI:一体化 DevOps 平台
  2. GitHub Actions:与 GitHub 深度集成
  3. Jenkins:灵活的企业级方案
  4. ArgoCD:云原生 GitOps 工具

选择建议:

  • 代码托管在 GitLab → 使用 GitLab CI
  • 代码托管在 GitHub → 使用 GitHub Actions
  • 需要复杂流水线编排 → 使用 Jenkins
  • K8s 环境优先 → 使用 ArgoCD

参考资料

Comment