11 KiB
tags, create time
| tags | create time | |||||
|---|---|---|---|---|---|---|
|
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_STAGINGK8s 集群配置 Environment: staging KUBECONFIG_PRODUCTIONK8s 集群配置 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 等流量治理工具,回滚步骤变为:
- 先把流量切回稳定版本(改 weight = 0)— 秒级止血
- 再删除问题部署 — 释放资源
- 最后排查和修复
流量切换比 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] 这些坑踩过一次就记住了
- 不要在 pipeline 里用
latesttag — 不同环境编译行为不一致 - 不要让 Staging 和 Production 各自
docker build— 这是最常见的一致性 bug 来源 - 不要跳过 Staging 直连 Production — 哪怕你的测试写得再好,也需要一个集成验证环节
- 不要硬编码 Secret 到 YAML 或代码里 — 参考 04-安全与发布策略
- 不要忽略
kubectl rollout status— 不检查 rollout 状态等于盲发 - 不要把日志级别开到 DEBUG 上生产 — 不仅浪费存储,还会暴露敏感数据
- 不要用 cron 做主触发器替代 push — 定时构建无法响应具体 commit,丢失审计链
- 不要忘记配置
ProgressDeadlineSeconds— 一个卡在 Pending 状态的 rollout 不会被自动中止
关联笔记
- 02-GitOps与ArgoCD — GitOps 模式下的部署工作流
- 03-Helm模板管理 — Helm Chart 编写与管理
- 04-安全与发布策略 — 安全管理和发布策略决策
- ../03-CICD与GitOps — 参考手册与决策指南