--- tags: [cicd, github-actions, docker, kubernetes, pipeline] create time: 2026-05-17 10:00 --- # CI/CD 基础与实践 ## 概述 本文档带你从零搭建微服务的自动化发布流水线。内容覆盖:从代码提交到镜像构建、安全扫描、Staging 验证、灰度上线的完整流程,以 GitHub Actions 为例进行实战讲解。更多决策分析(如工具选型)参见 [[../03-CICD与GitOps]]。 ## 为什么需要 CI/CD? > [!question] 想象一下 > > 你有 50 个微服务,每次发布都需要手动 SSH 到服务器、停旧启新、检查日志——如果出错了还得回滚。**人工操作**在这个规模下就是最大的风险源。 > > CI/CD 的核心目标:**消除发布日的手工操作**,让任何一次 commit 都可以被安全地部署。 ```mermaid 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 用的是**同一个镜像**。 ```mermaid 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 → 人工审批 → 灰度上线**。 ```yaml # .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 模式对比 ```mermaid 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 上一个版本 | ### 回滚命令速查 ```bash # 查看当前 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 ` | | **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` | ```bash # 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 不会被自动中止 ## 关联笔记 - [[02-GitOps与ArgoCD]] — GitOps 模式下的部署工作流 - [[03-Helm模板管理]] — Helm Chart 编写与管理 - [[04-安全与发布策略]] — 安全管理和发布策略决策 - [[../03-CICD与GitOps]] — 参考手册与决策指南