This commit is contained in:
2026-05-24 11:42:38 +08:00
commit 30d312ac35
521 changed files with 146481 additions and 0 deletions
@@ -0,0 +1,299 @@
---
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 <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]] — 参考手册与决策指南