This repository has been archived on 2026-05-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
2026-05-18 00:17:59 +08:00

11 KiB
Raw Permalink Blame History

tags, create time
tags create time
cicd
github-actions
docker
kubernetes
pipeline
2026-05-17 10:00

CI/CD 基础与实践

概述

本文档带你从零搭建微服务的自动化发布流水线。内容覆盖:从代码提交到镜像构建、安全扫描、Staging 验证、灰度上线的完整流程,以 GitHub Actions 为例进行实战讲解。更多决策分析(如工具选型)参见 ../03-CICD与GitOps。

为什么需要 CI/CD?

[!question] 想象一下

你有 50 个微服务,每次发布都需要手动 SSH 到服务器、停旧启新、检查日志——如果出错了还得回滚。人工操作在这个规模下就是最大的风险源。

CI/CD 的核心目标:消除发布日的手工操作,让任何一次 commit 都可以被安全地部署。

flowchart LR
    CODE["代码提交"] --> TEST["测试 & 静态分析"]
    TEST --> SCAN["安全扫描"]
    SCAN --> BUILD["构建镜像"]
    BUILD --> PUSH["推送仓库"]
    PUSH --> STAGING["Staging 验证"]
    STAGING -->|"人工审批"| PROD["Production 部署"]

    style CODE fill:#e3f2fd
    style TEST fill:#fff3e0
    style SCAN fill:#fce4ec
    style BUILD fill:#e8f5e9
    style PUSH fill:#f3e5f5
    style STAGING fill:#e0f7fa
    style PROD fill:#c8e6c9

核心设计原则

一次构建,多处部署

镜像不随环境重新编译,只改 K8s ConfigMap 或环境变量。Staging 和 Production 用的是同一个镜像。

flowchart LR
    DEV["开发机"] --> BUILD["构建一次"]
    BUILD --> REGISTRY["Docker Registry"]
    REGISTRY --> STAGING["Staging 环境"]
    REGISTRY --> PROD["Production 环境"]

    STAGING -. "同一镜像" .- PROD

    style DEV fill:#e3f2fd
    style BUILD fill:#e8f5e9
    style REGISTRY fill:#f3e5f5
    style STAGING fill:#fff3e0
    style PROD fill:#c8e6c9

语义化版本 + Commit SHA 双标签

Tag 类型 示例 用途
Commit SHA sha256:a1b2c3d 机器精确引用,保证可回滚
运行序号 v123 人类快速参考,方便手动回滚

[!tip] 永远不要用 latest 做生产环境的镜像标签。latest 可被覆盖,回滚时你不知道回滚到哪一刻的镜像。

路径过滤

只对相关服务的代码变更触发构建。order-service 改了代码,不应该触发 payment-service 的 pipeline。

流水线即代码

Pipeline 配置写在 Git 中(.github/workflows/),和源码一起 Review。没人知道它长什么样,就是最好的理由。

Pipeline 实战:单服务完整流水线

以下是一个微服务(以 order-service 为例)的 CI/CD 流水线。核心思路:代码推送到 main 分支 → 测试 → 构建镜像 → 推到 Staging → 人工审批 → 灰度上线。

# .github/workflows/deploy.yml
name: Deploy order-service

on:
  push:
    branches: [main]
    paths:
      - "services/order/**"   # 只在 order 服务有变更时触发

env:
  REGISTRY: registry.example.com
  IMAGE: order-service

jobs:
  # ========== Stage 1: Build & Test ==========
  build-and-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Run tests
        run: make test

      - name: Security scan (filesystem)
        uses: aquasecurity/trivy-action@master
        with:
          scan-type: 'fs'
          severity: 'CRITICAL,HIGH'

      - name: Build Docker image
        run: |
          docker build \
            -t ${{ env.REGISTRY }}/${{ env.IMAGE }}:${{ github.sha }} \
            -t ${{ env.REGISTRY }}/${{ env.IMAGE }}:v${{ github.run_number }} \
            -f services/order/Dockerfile \
            services/order

      - name: Push to registry
        run: |
          echo "${{ secrets.REGISTRY_PASSWORD }}" | \
            docker login -u ${{ secrets.REGISTRY_USER }} --password-stdin
          docker push ${{ env.REGISTRY }}/${{ env.IMAGE }}:${{ github.sha }}
          docker push ${{ env.REGISTRY }}/${{ env.IMAGE }}:v${{ github.run_number }}

  # ========== Stage 2: Deploy to Staging ==========
  deploy-staging:
    needs: build-and-test
    runs-on: ubuntu-latest
    environment: staging
    steps:
      - name: Deploy to staging
        run: |
          kubectl set image deployment/order-service \
            order=${{ env.REGISTRY }}/${{ env.IMAGE }}:${{ github.sha }} \
            --namespace=staging
          kubectl rollout status deployment/order-service \
            --namespace=staging --timeout=120s

      - name: Smoke test
        run: |
          # 等待 Pod Ready 后发送探测请求
          sleep 10
          ENDPOINT=$(kubectl get ingress order-service \
            -o jsonpath='{.spec.rules[0].host}' -n staging)
          curl -f http://$ENDPOINT/health || exit 1

  # ========== Stage 3: Deploy to Production ==========
  deploy-production:
    needs: deploy-staging
    runs-on: ubuntu-latest
    environment: production  # 需人工 Approval Gate
    steps:
      - name: Canary release (10% → 50% → 100%)
        run: |
          kubectl patch canary order-service --type merge \
            -p '{"spec":{"weight":10}}'
          # 等待 10 分钟,检查 error rate / latency
          echo "Monitor metrics before proceeding..."

      - name: Promote to 50%
        if: success()
        run: |
          kubectl patch canary order-service --type merge \
            -p '{"spec":{"weight":50}}'

      - name: Full promotion
        if: success()
        run: |
          kubectl patch canary order-service --type merge \
            -p '{"spec":{"weight":100}}'

[!info] Secret 管理原则

所有敏感信息通过 GitHub Actions Secrets 存储,按环境分离:

Secret 名称 用途 存放位置
REGISTRY_USER / REGISTRY_PASSWORD 镜像仓库认证 Repository Settings → Secrets
KUBECONFIG_STAGING K8s 集群配置 Environment: staging
KUBECONFIG_PRODUCTION K8s 集群配置 Environment: production

[!tip] 不要在 YAML 中明文写密码,哪怕加了 ${{ secrets.XXX }} 的 workflow 在 fork 的 PR 中仍然有泄露风险。对 fork PR 使用 environment 保护可以阻止 secrets 暴露。

Pipeline 逐阶段解读

阶段 做什么 为什么重要
路径过滤 paths 指定只对特定目录变更触发 避免每次 PR 都重建所有服务的镜像
Secrets 管理 GitHub Secrets 按环境分离存储 防止密钥泄露到 fork PR 或日志中
安全扫描 Trivy 在构建前后扫描漏洞 把安全问题挡在镜像层
双 Tag 策略 同时打 Commit SHA + run number SHA 用于精确操作,run number 用于快速参考
Environment 保护 environment: production 配置审批人 GitHub 级别的 gates
冒烟测试 部署后发送健康探测请求 确认 Pod 真正 Ready,而非只等 rollout 完成
Canary 发布 10% → 50% → 100% 逐步放量 有 Bug 只影响 10% 用户

回滚与 Rollout

[!question] 上线后发现 Bug,怎么最快恢复?

答案不是「快速修好再发一次」——而是先回滚,后修复。MTTR(平均恢复时间)比根因分析优先级更高。

K8s Rollout 模式对比

flowchart LR
    RU["RollingUpdate\n滚动更新"] --> P1["逐步替换旧 Pod"]
    RU --> P2["期间服务不中断"]
    RU --> P3["支持 ProgressDeadline"]

    RC["Recreate\n全部重建"] --> R1["先杀所有旧 Pod"]
    RC --> R2["再启新 Pod"]
    RC --> R3["有短暂不可用窗口"]

    style RU fill:#e8f5e9
    style RC fill:#fff3e0
特性 RollingUpdate Recreate
可用性 零停机 短暂中断
资源峰值 Old + New 并存 只有一个版本运行
适用场景 绝大多数微服务 独占存储卷、单写数据库
回滚速度 kubectl rollout undo 秒级恢复 重新 deploy 上一个版本

回滚命令速查

# 查看当前 rollout 状态
kubectl rollout status deployment/order-service -n production

# 回滚到上一个版本
kubectl rollout undo deployment/order-service -n production

# 查看历史版本列表
kubectl rollout history deployment/order-service -n production

# 回滚到指定 revision
kubectl rollout undo deployment/order-service -n production --to-revision=3

# 暂停/恢复 rollout(适合批量变更)
kubectl rollout pause deployment/order-service -n production
# ... 应用多个变更 ...
kubectl rollout resume deployment/order-service -n production

[!tip] 如果使用了 Canary / Istio 等流量治理工具,回滚步骤变为:

  1. 先把流量切回稳定版本(改 weight = 0)— 秒级止血
  2. 再删除问题部署 — 释放资源
  3. 最后排查和修复

流量切换比 Pod 重建更快,这是灰度发布最大的安全优势。

生产级增强

上面的示例做了精简,真实环境中通常还会加入:

增强项 说明 工具 / 做法
Dependency Lock go mod tidy 后提交 go.sum,确保每次依赖一致 go.sum / package-lock.json
Trivy Image Scan 镜像推送后再扫描一次镜像内漏洞 trivy image <image>
OpenTelemetry Trace ID 注入 trace ID,追踪本次构建产生的请求 环境变量注入 OTEL_SERVICE_VERSION
Slack 通知 每阶段完成后通过 Webhook 通知团队 GitHub Actions slack/notification
代码覆盖率门控 低于阈值直接失败(coverage < 80% = fail) gocover / codecov
SBOM 生成 用 syft 生成软件物料清单,满足合规要求 syft dir:. -o cyclonedx-json > sbom.json
Docker BuildKit 利用缓存层加速重建 DOCKER_BUILDKIT=1 docker build
并发测试 单元测试和 lint 并行执行缩短流水线时间 GitHub Actions strategy.matrix
# Docker BuildKit 加速构建
DOCKER_BUILDKIT=1 docker build \
  --cache-from ${{ env.REGISTRY }}/${{ env.IMAGE }}:${{ github.sha }} \
  -t ${{ env.REGISTRY }}/${{ env.IMAGE }}:${{ github.sha }} \
  services/order

# SBOM 生成
syft dir:. -o cyclonedx-json > sbom.json

常见陷阱

[!warning] 这些坑踩过一次就记住了

  1. 不要在 pipeline 里用 latest tag — 不同环境编译行为不一致
  2. 不要让 Staging 和 Production 各自 docker build — 这是最常见的一致性 bug 来源
  3. 不要跳过 Staging 直连 Production — 哪怕你的测试写得再好,也需要一个集成验证环节
  4. 不要硬编码 Secret 到 YAML 或代码里 — 参考 04-安全与发布策略
  5. 不要忽略 kubectl rollout status — 不检查 rollout 状态等于盲发
  6. 不要把日志级别开到 DEBUG 上生产 — 不仅浪费存储,还会暴露敏感数据
  7. 不要用 cron 做主触发器替代 push — 定时构建无法响应具体 commit,丢失审计链
  8. 不要忘记配置 ProgressDeadlineSeconds — 一个卡在 Pending 状态的 rollout 不会被自动中止

关联笔记