Files
cs-note/hhs/MS/05-部署运维/03-CICD与GitOps/01-CICD基础与实践.md
T
2026-05-24 11:42:38 +08:00

300 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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]] — 参考手册与决策指南