vault backup: 2026-05-18 00:17:59
This commit is contained in:
@@ -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]] — 参考手册与决策指南
|
||||
@@ -0,0 +1,449 @@
|
||||
---
|
||||
tags: [gitops, argocd, flux, kubernetes, deployment, cicd, app-of-apps]
|
||||
create time: 2026-05-17 10:00
|
||||
update time: 2026-05-17 10:30
|
||||
---
|
||||
|
||||
# GitOps 与 ArgoCD
|
||||
|
||||
## 概述
|
||||
|
||||
本文档深入讲解 GitOps 工作流和 ArgoCD 的使用。内容覆盖:从"传统 CI/CD 的痛点"到 "Git 即真相源",ArgoCD 架构解析、Application CRD 编写、App of Apps 模式、Helm/Kustomize 混合使用、同步钩子与安全实践。工具选型对比参见 [[../03-CICD与GitOps]]。
|
||||
|
||||
## 核心思想:让集群自己"拉取"状态
|
||||
|
||||
> [!question] 谁来"推动"变更到集群?
|
||||
>
|
||||
> 传统模式下,Jenkins 拿着 kubeconfig SSH 到 K8s 执行 `kubectl apply`。但问题来了:如果有人在集群里直接改了配置(比如手动 `kubectl edit deployment`),Jenkins 并不知道——这就是**配置漂移**。
|
||||
>
|
||||
> GitOps 的答案:**让 K8s 集群自己"拉取"自己的状态。** Git 仓库是唯一真相源,任何变更都通过 PR 进入 Git,工具自动同步到集群。
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Dev["开发者 PR"] -->|"修改 K8s manifest"| Git[(Git Repo)]
|
||||
|
||||
subgraph Cluster["K8s Cluster"]
|
||||
Argo["ArgoCD / Flux"] -->|同步| K8sState["Pod/Service/ConfigMap"]
|
||||
end
|
||||
|
||||
Git -.->|Webhook Polling| Argo
|
||||
|
||||
Argo -->|"检测到差异"| Diff{"状态一致?"}
|
||||
Diff -- 否 --> Sync["自动同步到 K8s ✅"]
|
||||
Diff -- 是 --> OK["已一致 ⏸️"]
|
||||
|
||||
style Git fill:#e3f2fd
|
||||
style Argo fill:#fff3e0
|
||||
style K8sState fill:#e8f5e9
|
||||
```
|
||||
|
||||
## Push vs Pull:本质区别
|
||||
|
||||
| | 传统 CI/CD (Push) | GitOps (Pull) |
|
||||
|---|---|---|
|
||||
| **部署驱动** | CI 服务器主动推送 | Git 仓库变动触发拉取 |
|
||||
| **安全边界** | CI 需要直连 K8s(暴露 kubeconfig) | ArgoCD 在集群内运行,无需外部访问 |
|
||||
| **漂移检测** | 通常无 | 持续比对,自动修复 |
|
||||
| **回滚方式** | 回到上一次 pipeline | `git revert` + 自动同步 |
|
||||
|
||||
> [!tip] Push vs Pull 的安全含义
|
||||
>
|
||||
> - **Push**:CI 服务器持有集群凭据,主动向 K8s 发请求。凭据泄露 = 集群沦陷。
|
||||
> - **Pull**:ArgoCD 在集群内部以 Pod 运行,只需读取 Git 的只读权限。即使 Git 凭据泄露,攻击者也无法写入集群——他们无法改变 Git 中的 YAML。
|
||||
|
||||
## ArgoCD 架构深度解析
|
||||
|
||||
ArgoCD 是 CNCF 级别的 GitOps 持续交付工具。
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
Git[(Git Repository)]
|
||||
|
||||
subgraph ArgoCDServer["ArgoCD Server (集群外)"]
|
||||
UI["Web UI"]
|
||||
API["API"]
|
||||
end
|
||||
|
||||
subgraph ArgoCDController["ArgoCD Controller (集群内)"]
|
||||
SyncLoop["同步循环\n(每 3 分钟)"]
|
||||
Reconcile["Reconcile: Git Manifest vs K8s 实际状态"]
|
||||
end
|
||||
|
||||
Git -->|只读 access_token| SyncLoop
|
||||
SyncLoop --> Reconcile
|
||||
Reconcile -->|"发现不一致"| Apply["kubectl apply"]
|
||||
Apply --> K8s["K8s Cluster"]
|
||||
K8s -->|"读取实际状态"| SyncLoop
|
||||
|
||||
style Git fill:#e3f2fd
|
||||
style ArgoCDServer fill:#fff3e0
|
||||
style ArgoCDController fill:#e8f5e9
|
||||
```
|
||||
|
||||
### 关键概念
|
||||
|
||||
| 术语 | 说明 |
|
||||
|------|------|
|
||||
| **Application** | ArgoCD 管理的核心资源,定义"从哪个 Git 路径同步到哪个命名空间" |
|
||||
| **Sync Policy** | 决定是自动同步还是手动触发;以及同步策略(Prune resources, Self-heal) |
|
||||
| **Health Status** | ArgoCD 对资源的健康检查(Deployment 是否有可用副本、Pod 是否 Running 等) |
|
||||
| **Drift Detection** | 对比 Git 中声明的配置与实际 K8s 状态的差异 |
|
||||
| **App of Apps** | 用 Application 来管理 Application,实现分层编排 |
|
||||
|
||||
## Application 配置实战
|
||||
|
||||
### 典型 Application
|
||||
|
||||
```yaml
|
||||
# apps/order-service.yaml
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: order-service
|
||||
namespace: argocd
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: https://github.com/team/k8s-manifests.git
|
||||
targetRevision: main
|
||||
path: overlays/production/order-service
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: production
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true # 删除 Git 中不存在的资源
|
||||
selfHeal: true # 自动修复被手动改动的资源
|
||||
syncOptions:
|
||||
- CreateNamespace=true # 目标命名空间不存在时自动创建
|
||||
```
|
||||
|
||||
**字段速查**:
|
||||
|
||||
- `prune: true` — Git 里删了的东西,集群上也删掉
|
||||
- `selfHeal: true` — 有人手动改了 Deployment?下次同步循环自动改回来
|
||||
- `CreateNamespace=true` — 不需要提前手动创建命名空间
|
||||
|
||||
### App of Apps:多层级应用编排
|
||||
|
||||
当有几十上百个服务时,用一个 Application 管理一个服务太繁琐了。ArgoCD 支持 **"应用的 Application"** 模式:
|
||||
|
||||
```yaml
|
||||
# root-application.yaml —— 根级别 Application
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: cluster-services
|
||||
spec:
|
||||
sources:
|
||||
- repoURL: https://github.com/team/k8s-manifests.git
|
||||
targetRevision: main
|
||||
path: apps/order-service
|
||||
- repoURL: https://github.com/team/k8s-manifests.git
|
||||
targetRevision: main
|
||||
path: apps/payment-service
|
||||
- repoURL: https://github.com/team/k8s-manifests.git
|
||||
targetRevision: main
|
||||
path: apps/user-service
|
||||
```
|
||||
|
||||
这种模式下,开发者只需要往 `apps/` 目录下新增文件夹并提交 PR,根 Application 就会自动把新服务拉到集群。
|
||||
|
||||
> [!tip] App of Apps 的最佳实践
|
||||
>
|
||||
> - 按团队或环境划分层级:`root-app → team-a-services / team-b-services`
|
||||
> - 每个子 Application 可以有自己的 `syncPolicy` 和 `namespace`
|
||||
> - 不要超过 3 层嵌套,调试会变得困难
|
||||
|
||||
## 进阶:中间件集成
|
||||
|
||||
### Helm + Kustomize 混合使用
|
||||
|
||||
ArgoCD **同时支持** Helm 和 Kustomize 作为 source plugin。实际工程中,最常见的组合是:
|
||||
|
||||
> CI pipeline 用 Helm build chart → ArgoCD 通过 Helm source 读取并部署到集群。
|
||||
|
||||
```yaml
|
||||
# apps/order-service.yaml —— 使用 Helm Source
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: order-service
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: https://github.com/team/k8s-manifests.git
|
||||
targetRevision: main
|
||||
path: charts/order-service # Helm Chart 目录
|
||||
helm:
|
||||
valueFiles:
|
||||
- values.yaml # 默认值文件
|
||||
- values-production.yaml # 生产环境覆盖
|
||||
parameters: # 命令行参数覆盖(优先级最高)
|
||||
- name: replicas
|
||||
value: "3"
|
||||
- name: image.tag
|
||||
value: "{{ .Values.image.sha }}" # 引用 CI 写入的 commit SHA
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: production
|
||||
```
|
||||
|
||||
> [!question] 为什么不只用纯 YAML?
|
||||
>
|
||||
> 想象一下:每个环境的 Deployment 只有 `replicas`、`resources`、`imageTag` 不同,其余完全一样。如果全部写为裸 YAML,维护 5 个环境 = 维护 5 份几乎相同的文件。Helm/Kustomize 让你用 **模板化 + 差异覆盖** 解决这个 DRY 问题。
|
||||
|
||||
### ArgoCD 原生 Kustomize 示例
|
||||
|
||||
```yaml
|
||||
# 直接用 kustomize 作为 source
|
||||
spec:
|
||||
source:
|
||||
repoURL: https://github.com/team/k8s-manifests.git
|
||||
targetRevision: main
|
||||
path: overlays/production/order-service # kustomize overlay 路径
|
||||
kustomize:
|
||||
images:
|
||||
- name: myregistry/order-service
|
||||
newTag: v1.2.3 # CI 注入的镜像版本
|
||||
```
|
||||
|
||||
> [!note] 选择建议
|
||||
>
|
||||
> | 场景 | 推荐 |
|
||||
> |------|------|
|
||||
> | 需要复杂的条件渲染和复用 | Helm |
|
||||
> | 简单的环境覆盖(改几个字段) | Kustomize |
|
||||
> | ArgoCD 原生支持两者,可以混用(多源模式已在 App of Apps 中展示) | — |
|
||||
|
||||
## 同步工作流:Hook 与健康检查
|
||||
|
||||
### 同步生命周期
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["Git 提交"] --> B{"ArgoCD\n检测到差异"}
|
||||
B -->|"执行 PreSync Hook"| C["PreSync"]
|
||||
C -->|"迁移 DB Schema"| D[健康检查]
|
||||
D -->|"前向兼容? "| E["Sync"]
|
||||
E -->|"部署新版本"| F["PostSync Hook"]
|
||||
F -->|"灰度验证 / 通知"| G["Health Status"]
|
||||
|
||||
style C fill:#fff3e0
|
||||
style E fill:#e3f2fd
|
||||
style F fill:#e8f5e9
|
||||
style G fill:#f3e5f5
|
||||
```
|
||||
|
||||
### PreSync Hook:数据库迁移示例
|
||||
|
||||
```yaml
|
||||
# hooks/db-migration.yaml
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: db-migrate
|
||||
annotations:
|
||||
argocd.argoproj.io/hook: PreSync # Sync 之前执行
|
||||
argocd.argoproj.io/hook-delete-policy: HookSucceeded # 成功后自动清理
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: migrate
|
||||
image: myregistry/order-service:v1.2.3
|
||||
command: ["./migrate", "--up"]
|
||||
restartPolicy: Never
|
||||
```
|
||||
|
||||
**常用 Hook 阶段**:
|
||||
|
||||
| 阶段 | 时机 | 典型用途 |
|
||||
|------|------|---------|
|
||||
| `PreSync` | Sync 之前 | DB 迁移、缓存预热 |
|
||||
| `Sync` | 替代默认同步行为 | 复杂的多步骤部署 |
|
||||
| `PostSync` | Sync 之后 | 灰度验证、发 Slack 通知 |
|
||||
| `SyncFail` | Sync 失败时 | 回滚、告警 |
|
||||
|
||||
> [!important] Hook 注意事项
|
||||
>
|
||||
> - `hook-delete-policy: HookSucceeded` — 避免残留大量已完成的历史 Job
|
||||
> - PreSync Hook 本身也有健康检查机制,Job 必须 Running → Succeeded 才算通过
|
||||
> - Hook Job 和目标 Application 必须在同一个 Kubernetes 集群
|
||||
|
||||
### 自定义健康检查
|
||||
|
||||
ArgoCD 内置了对 Deployment、StatefulSet、Service 等资源的健康检查。对于自定义 CRD(比如 CassandraCluster),可以通过 Script Health Check 实现:
|
||||
|
||||
```yaml
|
||||
# ConfigMap 挂载到 ArgoCD Server
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: resource-customizations
|
||||
namespace: argocd
|
||||
data:
|
||||
myapp.example.com_CassandraCluster.health.lua: |
|
||||
local status = {}
|
||||
if obj.status ~= nil then
|
||||
if obj.status.readyNodes ~= nil then
|
||||
if obj.status.readyNodes >= 3 then
|
||||
status.status = "Healthy"
|
||||
else
|
||||
status.status = "Progressing"
|
||||
status.message = "Only " .. tostring(obj.status.readyNodes) .. " ready nodes"
|
||||
end
|
||||
end
|
||||
end
|
||||
return status
|
||||
```
|
||||
|
||||
> [!tip] 常见内置资源健康状态速查
|
||||
>
|
||||
> | 资源类型 | Healthy 条件 | Progressing 条件 |
|
||||
> |----------|-------------|-----------------|
|
||||
> | Deployment | 所有 replicas Ready 且当前 | replicaReadyCount < desired |
|
||||
> | StatefulSet | 所有 Pod Running | Pending 或 replica 不足 |
|
||||
> | Service | 存在即 Healthy | —(通常不 Progressing) |
|
||||
> | Ingress | 有 backend 即 Healthy | — |
|
||||
> | ConfigMap/Secret | 存在即 Healthy | — |
|
||||
|
||||
## 日志调试与故障排查
|
||||
|
||||
### ArgoCD CLI 常用命令
|
||||
|
||||
```bash
|
||||
# 实时日志
|
||||
argocd app logs order-service --follow
|
||||
|
||||
# 指定Pod日志(查看特定容器)
|
||||
argocd app logs order-service -p order-service-pod-abc123 -c sidecar
|
||||
|
||||
# 查看应用事件(谁触发的同步、为什么失败)
|
||||
argocd app events order-service
|
||||
|
||||
# 强制刷新(跳过缓存)
|
||||
argocd app get order-service --refresh
|
||||
```
|
||||
|
||||
### 常见故障排查流程
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["Application 显示 OutOfSync"] --> B{手动 sync 是否成功?}
|
||||
B -- 是 --> C["✅ 可能是临时网络抖动, Watch 恢复即可"]
|
||||
B -- 否 --> D["查看 Events\nargocd app events"]
|
||||
|
||||
D --> E{"错误原因?"}
|
||||
E -- "ImagePullBackOff" --> F["镜像仓库不可达/凭据过期\n→ 检查 ImagePullSecrets"]
|
||||
E -- "CrashLoopBackOff" --> G["应用启动失败\n→ 查看 Pod 日志\nargocd app logs"]
|
||||
E -- "ResourceQuota exceeded" --> H["命名空间配额不足\n→ 调整 Quota 或精简资源"]
|
||||
E -- "RBAC denied" --> I["ArgoCD SA 权限不足\n→ 检查 ClusterRole Binding"]
|
||||
E -- "Custom health check failing" --> J["自定义健康脚本语法错误\n→ 检查 resource-customizations ConfigMap"]
|
||||
|
||||
style F fill:#ffebee
|
||||
style G fill:#e3f2fd
|
||||
style H fill:#fff3e0
|
||||
style I fill:#f3e5f5
|
||||
style J fill:#e8f5e9
|
||||
```
|
||||
|
||||
### 调试 Checklist
|
||||
|
||||
- [ ] `kubectl get application -n argocd` — 确认 Application CR 状态
|
||||
- [ ] `kubectl describe application xxx -n argocd` — 查看 Conditions 和最后同步信息
|
||||
- [ ] `argocd app logs xxx --tail 100` — Controller 日志,搜索 Error 关键字
|
||||
- [ ] 确认 Git Repo URL 和 token 可用:手动 curl 测试
|
||||
- [ ] 如果是 self-heal 反复触发:检查是否有外部控制器在修改资源
|
||||
|
||||
## 安全要点
|
||||
|
||||
### RBAC 配置
|
||||
|
||||
```yaml
|
||||
# argocd-rbac-cm.yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: argocd-rbac-cm
|
||||
namespace: argocd
|
||||
data:
|
||||
policy.default: role:readonly # 默认只读
|
||||
policy.csv: |
|
||||
# g, github-org:team-name, role:admin # 团队级管理
|
||||
p, role:order-service-deployer, applications, sync, */order-service, allow
|
||||
g, github:alice, role:order-service-deployer # alice 可同步 order-service
|
||||
scopes: '[groups]' # 从 OIDC token 获取 groups
|
||||
```
|
||||
|
||||
### Secrets 管理建议
|
||||
|
||||
| 方式 | 适用场景 | 备注 |
|
||||
|------|---------|------|
|
||||
| **ArgoCD Secret** | Git 仓库凭据、K8s cluster 注册 | 存储为 SealedSecret/ExternalSecret |
|
||||
| **External Secrets Operator** | RDS 密码、API Key | 从 AWS Secrets Manager / Vault 拉取 |
|
||||
| **SealedSecret** | 跨集群复用加密 secrets | 公钥加密,仅 controller 可解密 |
|
||||
|
||||
> [!warning] 常见误区
|
||||
>
|
||||
> - **不要把 Git SSH Private Key 存入集群的普通 Secret** — 应该用 `argocd reponame` 配置项统一管理
|
||||
> - **Application 的 namespace 要单独隔离** — `argocd` 命名空间不应和生产共享
|
||||
> - **启用 OIDC + RBAC scopes** — 让团队只能看到自己管理的 Application
|
||||
|
||||
---
|
||||
|
||||
现在回到日常操作:
|
||||
|
||||
## 常见运维场景
|
||||
|
||||
### 手动触发同步
|
||||
|
||||
当 Git 已更新但 ArgoCD 未自动同步时:
|
||||
|
||||
```bash
|
||||
argocd app sync order-service --force
|
||||
```
|
||||
|
||||
### 查看同步状态和历史
|
||||
|
||||
```bash
|
||||
argocd app get order-service # 当前状态
|
||||
argocd app history order-service # 同步历史
|
||||
argocd app diff order-service # Git vs 实际的差异
|
||||
```
|
||||
|
||||
### 回滚到上一个版本
|
||||
|
||||
```bash
|
||||
argocd app rollback order-service
|
||||
# 或直接 git revert 对应的 commit,ArgoCD 自动同步
|
||||
```
|
||||
|
||||
### 解除锁定(Stuck App 恢复)
|
||||
|
||||
```bash
|
||||
argocd app unlock order-service
|
||||
```
|
||||
|
||||
## 工具选型:ArgoCD vs Flux
|
||||
|
||||
| 特性 | ArgoCD | Flux v2 |
|
||||
|------|--------|---------|
|
||||
| **UI** | 丰富的 Web UI,可视化对比 | CLI + Kubernetes-native,轻量 UI |
|
||||
| **通知** | Slack/Discord/GitHub native | Event controller + Webhook |
|
||||
| **Helm** | 内置 Helm Source 插件 | 一等公民(HelmRelease) |
|
||||
| **多集群** | 多集群统一管理 | 需配合 Image Automation |
|
||||
| **适合场景** | 可视化管理需求强的团队 | 纯 GitOps 极客团队 |
|
||||
|
||||
> [!note] 选型建议
|
||||
>
|
||||
> - 如果你已经重度使用 Helm:Flux 的 HelmRelease CRD 更自然
|
||||
> - 如果你希望可视化查看每次部署的差异:ArgoCD 的 diff view 是杀手功能
|
||||
> - 两者可以并存——比如 ArgoCD 管应用层,Flux 管基础设施层(CNI、CSI 等)
|
||||
|
||||
## 关联笔记
|
||||
|
||||
- [[01-CICD基础与实践]] — CI/CD Pipeline 的搭建与实践
|
||||
- [[03-Helm模板管理]] — Helm Chart 编写与管理
|
||||
- [[04-安全与发布策略]] — 安全管理与发布决策
|
||||
- [[../03-CICD与GitOps]] — 参考手册与决策指南
|
||||
@@ -0,0 +1,454 @@
|
||||
---
|
||||
tags: [helm, kubernetes, templating, kustomize, devops]
|
||||
create time: 2026-05-17 10:00
|
||||
---
|
||||
|
||||
# Helm 模板管理
|
||||
|
||||
## 概述
|
||||
|
||||
本文档系统讲解 Helm Chart 的编写与管理实践。内容覆盖:Chart 骨架、values.yaml 参数化、Go 模板语法、多环境值覆盖、Chart 依赖管理、Hooks 机制,以及与 ArgoCD 的集成方式。架构决策参见 [[../03-CICD与GitOps]]。
|
||||
|
||||
## 为什么需要 Helm?
|
||||
|
||||
> [!question] 每个服务几十个 YAML 文件,怎么管理?
|
||||
>
|
||||
> 一个典型的 `order-service` 部署包含:Deployment、Service、Ingress、ConfigMap、HPA、NetworkPolicy、PDB……当你有 50 个服务,每个都要维护这么一堆模板——**这是重复劳动的噩梦**。
|
||||
|
||||
Helm 提供了两个核心抽象:
|
||||
|
||||
| 概念 | 类比 | 说明 |
|
||||
|------|------|------|
|
||||
| **Chart** | NPM 包 / Maven jar | 打包格式,定义"我要部署什么" |
|
||||
| **Release** | npm install 的实例 | 运行实例,同一个 Chart 可以有多个 Release(staging/prod) |
|
||||
|
||||
```bash
|
||||
# 创建 Chart 骨架
|
||||
helm create order-service
|
||||
|
||||
# 目录结构一览
|
||||
charts/order-service/
|
||||
├── Chart.yaml # 元信息 (name, version, description)
|
||||
├── values.yaml # 默认值 —— 你唯一需要经常改的文件
|
||||
├── templates/ # Go 模板文件
|
||||
│ ├── deployment.yaml
|
||||
│ ├── service.yaml
|
||||
│ └── ingress.yaml
|
||||
└── .helmignore # 类似 .gitignore
|
||||
```
|
||||
|
||||
### Helm Release 生命周期
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["install"] --> B["running"]
|
||||
C["upgrade"] --> B
|
||||
D["rollback"] --> B
|
||||
E["uninstall"] --> B
|
||||
style A fill:#e1f5fe
|
||||
style C fill:#fff3e0
|
||||
style D fill:#fce4ec
|
||||
style E fill:#f3e5f5
|
||||
```
|
||||
|
||||
## 第一步:让配置全部参数化
|
||||
|
||||
**values.yaml** 中存放所有可变参数,遵循"一处定义、多处引用"原则:
|
||||
|
||||
```yaml
|
||||
# values.yaml
|
||||
replicaCount: 2
|
||||
|
||||
image:
|
||||
repository: registry.example.com/order-service
|
||||
tag: "v1.2.3"
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 8080
|
||||
|
||||
resources:
|
||||
requests:
|
||||
cpu: 200m
|
||||
memory: 256Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
|
||||
autoscaling:
|
||||
enabled: true
|
||||
minReplicas: 3
|
||||
maxReplicas: 20
|
||||
targetCPUUtilizationPercentage: 75
|
||||
```
|
||||
|
||||
**templates/deployment.yaml** 中使用 `{{ }}` 引用:
|
||||
|
||||
```yaml
|
||||
# templates/deployment.yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ .Release.Name }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
spec:
|
||||
replicas: {{ .Values.replicaCount }}
|
||||
selector:
|
||||
matchLabels:
|
||||
app: {{ .Release.Name }}
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: {{ .Chart.Name }}
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
resources: {{ toYaml .Values.resources | nindent 12 }}
|
||||
```
|
||||
|
||||
> [!tip] 两个关键技巧
|
||||
>
|
||||
> 1. **`toYaml .X | nindent 12`**:将嵌套的对象序列化为 YAML 并缩进。如果直接写 `{{ .Values.resources }}`,输出会压缩成一行,Kubernetes 无法解析。
|
||||
> 2. **`-` 修剪空白**:`{{- if ...` 和 `{{- end }}` 中的 `-` 会消除模板前后多余的换行符,避免生成空行导致 YAML 格式错误。
|
||||
|
||||
### Helm 内置对象速查
|
||||
|
||||
| Helm 对象 | 访问方式 | 用途 |
|
||||
|-----------|----------|------|
|
||||
| `.Release` | `.Release.Name`, `.Release.Namespace` | 当前发布实例的信息 |
|
||||
| `.Chart` | `.Chart.Name`, `.Chart.Version` | Chart 本身的元信息 |
|
||||
| `.Values` | `.Values.replicaCount` 等 | 用户传入的值(合并了 values.yaml + --set) |
|
||||
| `.Capabilities` | `.Capabilities.KubeVersion` | 集群 API 版本信息 |
|
||||
| `.Files` | `.Files.Get "config.ini"` | 读取同目录下的非模板文件 |
|
||||
| `.Notes` | 渲染后显示给用户的提示文本 | Post-install 使用说明 |
|
||||
|
||||
## 第二步:多环境值覆盖
|
||||
|
||||
通过 **values 文件叠加**实现不同环境的差异化:
|
||||
|
||||
```bash
|
||||
# Staging 使用命令行临时覆盖
|
||||
helm upgrade --install order-service ./charts/order-service \
|
||||
--namespace=staging \
|
||||
--set image.tag=v1.2.4-dev
|
||||
|
||||
# Production 叠加额外值文件
|
||||
helm upgrade --install order-service ./charts/order-service \
|
||||
--namespace=production \
|
||||
--values charts/order-service/values.prod.yaml \
|
||||
--set image.tag=v1.2.3
|
||||
```
|
||||
|
||||
典型 `values.prod.yaml`(只写与默认值不同的部分):
|
||||
|
||||
```yaml
|
||||
replicaCount: 5
|
||||
|
||||
resources:
|
||||
requests:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
limits:
|
||||
cpu: "2"
|
||||
memory: 2Gi
|
||||
|
||||
autoscaling:
|
||||
minReplicas: 5
|
||||
maxReplicas: 50
|
||||
```
|
||||
|
||||
> [!tip] Values 文件合并优先级(低 → 高)
|
||||
>
|
||||
> 1. `values.yaml` — 默认值
|
||||
> 2. `values.prod.yaml` — 环境变量覆盖文件
|
||||
> 3. `--set image.tag=xxx` — 命令行参数(最高优先级)
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["values.yaml"] --> D["合并结果"]
|
||||
B["values.prod.yaml"] --> D
|
||||
C["--set image.tag=v1.2.3"] --> D
|
||||
D --> E[".Values"]
|
||||
```
|
||||
|
||||
> [!warning] 常见陷阱:JSON Patch
|
||||
>
|
||||
> 当使用 `--set` 修改嵌套字段时,Helm 使用 JSON Patch,意味着它会**替换整个对象**而非合并。例如 `--set resources.limits.cpu="1"` 会将 `memory` 字段丢弃。对于复杂嵌套,优先使用独立的 values 文件。
|
||||
|
||||
## 第三步:条件渲染与循环
|
||||
|
||||
### 条件渲染
|
||||
|
||||
用 Helm 的条件语法处理可选资源:
|
||||
|
||||
```yaml
|
||||
# templates/ingress.yaml
|
||||
{{- if .Values.ingress.enabled }}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ .Release.Name }}
|
||||
spec:
|
||||
rules:
|
||||
- host: {{ .Values.ingress.host | quote }}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: {{ .Release.Name }}
|
||||
port:
|
||||
number: {{ .Values.service.port }}
|
||||
{{- end }}
|
||||
```
|
||||
|
||||
> [!note] `quote` 管道函数
|
||||
>
|
||||
> 当值的类型不确定时(可能是字符串也可能是数字),加 `| quote` 确保输出带引号,避免 `true/false` 被 YAML 解析为布尔值。
|
||||
|
||||
### Range 循环生成多个资源
|
||||
|
||||
当需要批量创建多个 ServiceMonitor、ConfigMap 等时,`range` 非常有用:
|
||||
|
||||
```yaml
|
||||
# templates/servicemonitor.yaml
|
||||
{{- range $key, $value := .Values.extraServices }}
|
||||
---
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: ServiceMonitor
|
||||
metadata:
|
||||
name: {{ $.Release.Name }}-{{ $key }}
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: {{ $.Release.Name }}
|
||||
endpoints:
|
||||
- port: {{ $value.port }}
|
||||
interval: {{ $value.interval | default "30s" }}
|
||||
{{- end }}
|
||||
```
|
||||
|
||||
对应的 values:
|
||||
|
||||
```yaml
|
||||
extraServices:
|
||||
grpc-exporter:
|
||||
port: grpc
|
||||
interval: 15s
|
||||
prometheus-path:
|
||||
port: metrics
|
||||
interval: 10s
|
||||
```
|
||||
|
||||
> [!example] 生成结果
|
||||
>
|
||||
> 上面这段模板会为每个 `extraServices` 条目生成一个独立的 ServiceMonitor。注意 `$key` 绑定到键名(`grpc-exporter`),而 `$value` 绑定到对应对象。使用 `$.Release.Name`(带美元前缀)是因为在 range 内部 `.` 已被重绑定。
|
||||
|
||||
## 第四步:复用模板片段
|
||||
|
||||
随着 Chart 膨胀,`deployment.yaml` 和 `service.yaml` 之间会产生大量重复逻辑。Helm 提供 `_helpers.tpl` 来统一管理可复用的模板片段。
|
||||
|
||||
### _helpers.tpl 标准写法
|
||||
|
||||
```yaml
|
||||
{{/*
|
||||
Common labels — 所有资源统一标注 */}}
|
||||
{{- define "order.labels" -}}
|
||||
app.kubernetes.io/name: {{ .Chart.Name }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Selector labels — Deployment selector 必须使用固定值 */}}
|
||||
{{- define "order.selectorLabels" -}}
|
||||
app.kubernetes.io/name: {{ .Chart.Name }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- end -}}
|
||||
```
|
||||
|
||||
### 在模板中引用
|
||||
|
||||
```yaml
|
||||
# templates/deployment.yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ .Release.Name }}
|
||||
labels:
|
||||
{{- include "order.labels" . | nindent 4 }}
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "order.selectorLabels" . | nindent 6 }}
|
||||
```
|
||||
|
||||
> [!tip] include vs template
|
||||
>
|
||||
> - `include "name" .`:**返回值**,可通过 `| nindent` 继续管道处理 —— 适合注入 label、annotation
|
||||
> - `template "name" .`:**直接输出**,不返回结果 —— 很少用,通常只在特殊情况下需要
|
||||
|
||||
## Chart 依赖与仓库管理
|
||||
|
||||
大型项目往往不会从零编写所有模板,而是复用社区已有的 Chart。
|
||||
|
||||
### 声明子依赖
|
||||
|
||||
```yaml
|
||||
# Chart.yaml
|
||||
apiVersion: v2
|
||||
name: order-service
|
||||
type: application
|
||||
version: 1.2.0
|
||||
appVersion: "1.2.3"
|
||||
|
||||
dependencies:
|
||||
- name: postgresql
|
||||
version: "15.0.0"
|
||||
repository: https://charts.bitnami.com/bitnami
|
||||
condition: postgresql.enabled
|
||||
- name: redis
|
||||
version: "18.0.0"
|
||||
repository: https://charts.bitnami.com/bitnami
|
||||
condition: redis.enabled
|
||||
```
|
||||
|
||||
- `condition` 告诉 Helm:当 values 中对应路径为 `false` 时,跳过安装该子 Chart。
|
||||
- 子 Chart 的 values 通过 `postgresql.xxx` / `redis.xxx` 命名空间传入。
|
||||
|
||||
### 常用命令
|
||||
|
||||
```bash
|
||||
helm dependency update ./charts/order-service # 拉取 / 更新子依赖
|
||||
helm dependency build ./charts/order-service # 仅从 Chart.lock 构建(无网络变更时使用)
|
||||
helm repo add bitnami https://charts.bitnami.com/bitnami
|
||||
helm search repo prometheus # 搜索公开 Chart
|
||||
```
|
||||
|
||||
> [!note] Chart.yaml 的 apiVersion
|
||||
>
|
||||
> - `apiVersion: v1`(Helm 2):依赖写在 `dependencies:` 数组里,语义不同
|
||||
> - `apiVersion: v2`(Helm 3):引入 `requirements.yaml` 的概念整合到 Chart.yaml 自身,推荐使用 v2
|
||||
|
||||
## 与 ArgoCD 集成
|
||||
|
||||
ArgoCD 原生支持将 Helm Chart 作为 Application source,自动解析 `values.yaml`:
|
||||
|
||||
```yaml
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: order-service
|
||||
spec:
|
||||
source:
|
||||
repoURL: https://github.com/team/k8s-manifests.git
|
||||
targetRevision: main
|
||||
path: charts/order-service
|
||||
helm:
|
||||
parameters:
|
||||
- name: image.tag
|
||||
value: v1.2.3
|
||||
- name: replicaCount
|
||||
value: "5"
|
||||
```
|
||||
|
||||
> [!tip] ArgoCD + Helm 进阶用法
|
||||
>
|
||||
> - **`helm.valueFrom`**:从 ConfigMap 或 Secret 取值,避免把敏感值暴露在 Git 中
|
||||
> - **`helm.fileParameters`**:从文件加载大段配置值,适合 CI/CD 流水线场景
|
||||
> - **`helm.ignoreMissingValueFiles: true`**:允许某些 values 文件按环境选择性存在
|
||||
|
||||
## Hooks:在关键时机执行自定义逻辑
|
||||
|
||||
Helm Hooks 允许你在 Release 的生命周期事件中挂载自定义 Job 或 Pod:
|
||||
|
||||
```yaml
|
||||
# templates/migrate-db.yaml
|
||||
{{- if .Values.dbMigration.enabled }}
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: {{ .Release.Name }}-db-migrate
|
||||
annotations:
|
||||
"hooks.helm.sh/hook": pre-upgrade,pre-install
|
||||
"hooks.helm.sh/hook-delete-policy": hook-succeeded
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: migrate
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
|
||||
command: ["./migrate", "up"]
|
||||
restartPolicy: Never
|
||||
{{- end }}
|
||||
```
|
||||
|
||||
| Hook 事件 | 触发时机 |
|
||||
|-----------|----------|
|
||||
| `pre-install` | 资源创建之前(如数据库迁移) |
|
||||
| `post-install` | 资源创建之后(如初始化数据) |
|
||||
| `pre-upgrade` | 升级操作之前 |
|
||||
| `post-upgrade` | 升级操作之后 |
|
||||
| `pre-delete` | 删除操作之前(如优雅停机通知) |
|
||||
| `post-delete` | 删除操作之后 |
|
||||
|
||||
## 调试与测试
|
||||
|
||||
### 可视化渲染结果
|
||||
|
||||
```bash
|
||||
# 查看渲染后的纯 YAML(不做实际部署)
|
||||
helm template my-release ./charts/order-service
|
||||
|
||||
# 指定命名空间和 values 文件
|
||||
helm template my-release ./charts/order-service \
|
||||
--namespace=staging \
|
||||
-f values.staging.yaml
|
||||
```
|
||||
|
||||
> [!tip] 调试三件套
|
||||
>
|
||||
> 1. **`helm lint ./charts/order-service`**:静态检查,快速定位语法问题
|
||||
> 2. **`helm template`**:看渲染后的全量 YAML,逐段排查问题
|
||||
> 3. **`helm diff upgrade --install ...`**:配合 [helm-diff 插件](https://github.com/helm/diff),对比升级前后的差异
|
||||
|
||||
### Dry Run
|
||||
|
||||
```bash
|
||||
# 模拟部署(服务端校验,但不会真正改变集群状态)
|
||||
helm upgrade --install my-release ./charts/order-service \
|
||||
--dry-run --debug
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
| 原则 | 具体做法 |
|
||||
|------|---------|
|
||||
| **单一真相源** | 所有可配参数集中在 values.yaml,模板层只做引用不做硬编码 |
|
||||
| **Helper 集中管理** | 公共 labels / selectors / annotations 一律放在 `_helpers.tpl` |
|
||||
| **条件最小化** | 能用 defaults 解决的就不加 `if`;减少分支能大幅降低测试复杂度 |
|
||||
| **版本号分离** | `Chart.yaml` 的 `version` 管 Chart 本身迭代,`appVersion` 管应用版本 |
|
||||
| **先 lint 再 push** | CI 中加入 `helm lint` + `helm template --strict` 门禁 |
|
||||
| **值文件只写差异** | `values.prod.yaml` 只覆盖与默认值不同的字段,保持可读性 |
|
||||
|
||||
## 何时用裸 YAML vs Helm vs Kustomize?
|
||||
|
||||
| 场景 | 推荐方案 | 理由 |
|
||||
|------|---------|------|
|
||||
| < 10 个服务 | Kustomize / 裸 YAML | 复杂度高于收益 |
|
||||
| 10~50 个服务 | Helm | 模板复用价值明显 |
|
||||
| > 50 个服务 | Helm + Kustomize overlays | Helm 管模板,Kustomize 管环境差异 |
|
||||
|
||||
> [!note] Kustomize 的定位
|
||||
>
|
||||
> Kustomize 不依赖 Go templating,更轻量。适合"基于同一套模板,按环境覆盖差异配置"的场景。
|
||||
>
|
||||
> **常见组合**:Helm 生成基础模板,Kustomize overlay 处理 staging/prod 差异。
|
||||
|
||||
## 关联笔记
|
||||
|
||||
- [[01-CICD基础与实践]] — CI/CD Pipeline 的搭建与实践
|
||||
- [[02-GitOps与ArgoCD]] — GitOps 工作流与 ArgoCD
|
||||
- [[04-安全与发布策略]] — 安全管理与发布决策
|
||||
- [[../03-CICD与GitOps]] — 参考手册与决策指南
|
||||
@@ -0,0 +1,470 @@
|
||||
---
|
||||
tags: [security, secrets-management, canary, rollback, deployment-strategy, feature-flag, database-migration]
|
||||
create time: 2026-05-18 14:30
|
||||
---
|
||||
|
||||
# 安全与发布策略
|
||||
|
||||
## 概述
|
||||
|
||||
本文档聚焦生产级部署中的两大决策点:**敏感信息管理**和**发布策略选择**。提供方案对比、实操检查清单以及零停机发布的数据库迁移策略。架构概览参见 [[../03-CICD与GitOps]]。
|
||||
|
||||
## Secret 管理:分层决策树
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
START["开始选型"] --> SIZE["团队规模?"]
|
||||
SIZE -- "< 20人" --> K8SSECRET["K8s 原生 Secret\n✅ 开箱即用"]
|
||||
SIZE -- "≥ 20人" --> CLOUD["是否深度绑定云厂商?"]
|
||||
|
||||
CLOUD -- "是 (AWS/GCP/Azure)" --> SECRETMGR["云厂商 Secret Manager\n✅ 集成度高,审计完善"]
|
||||
CLOUD -- "否 / 多云" --> ESOOROPS["需要 GitOps?"]
|
||||
|
||||
ESOOROPS -- "是 (ArgoCD)" --> SOPS["SOPS + sealed-secrets\n✅ 加密文件可提交到 Git"]
|
||||
ESOOROPS -- "否 / 已有 Vault" --> ESO["External Secrets Operator\n✅ Git 中不存任何密文"]
|
||||
|
||||
style K8SSECRET fill:#e8f5e9
|
||||
style SECRETMGR fill:#e3f2fd
|
||||
style SOPS fill:#fff3e0
|
||||
style ESO fill:#f3e5f5
|
||||
```
|
||||
|
||||
### 方案速查表
|
||||
|
||||
| 方案 | 适用场景 | 优点 | 缺点 |
|
||||
|------|---------|------|------|
|
||||
| **K8s 原生 Secret** | 小规模、内部团队 | 零额外成本,开箱即用 | 明文存储在 etcd,RBAC 控制有限 |
|
||||
| **External Secrets Operator (ESO)** | 已有 Vault / AWS SSM | Git 中不存任何密文 | 需维护额外组件 |
|
||||
| **SOPS + sealed-secrets** | ArgoCD 用户 | 加密文件可提交到 Git | 需管理公钥基础设施 |
|
||||
| **AWS Secrets Manager / GCP Secret Manager** | 云厂商深度绑定 | 集成度高,审计完善 | 锁定特定云平台 |
|
||||
|
||||
### ❌ 绝对不要做的事
|
||||
|
||||
```yaml
|
||||
# 错误示范:明文写密码
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: app-config
|
||||
data:
|
||||
DB_PASSWORD: "my-secret-password" # 绝对不要这样做!
|
||||
```
|
||||
|
||||
### ✅ 正确做法
|
||||
|
||||
```yaml
|
||||
# 使用 Secret(stringData 接收明文,API Server 自动 Base64)
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: app-secrets
|
||||
namespace: production
|
||||
type: Opaque
|
||||
stringData:
|
||||
DB_PASSWORD: "${DB_PASSWORD}" # 通过 CI secrets 注入
|
||||
API_KEY: "${API_KEY}"
|
||||
---
|
||||
# 在 Deployment 中引用
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: order-service
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: app-secrets
|
||||
```
|
||||
|
||||
> [!warning] 为什么不能把 Secret 明文放进 Git?
|
||||
>
|
||||
> 即使使用 GitOps(ArgoCD),也不建议将明文密码提交到仓库。一旦权限管控疏漏——比如某个离职员工的账号未被撤销——整个公司的数据库密码就暴露了。使用 ESO 或 sealed-secrets,**Git commit 历史中永远不会有明文密钥**。
|
||||
|
||||
### Secret 进阶实践
|
||||
|
||||
#### 1. RBAC:最小权限原则
|
||||
|
||||
```yaml
|
||||
# 限制只有特定 ServiceAccount 可以读取特定 Secret
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: order-service-read-secret
|
||||
namespace: production
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: secret-reader # 仅限 Read,禁止 Create/Update/Delete
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: order-service-sa # 仅该 SA 有权读取
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: secret-reader
|
||||
namespace: production
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["secrets"]
|
||||
resourceNames: ["app-secrets"] # 按名称限定,不可通读所有 Secret
|
||||
verbs: ["get", "list"]
|
||||
```
|
||||
|
||||
> [!tip] `resourceNames` 是什么?
|
||||
> K8s RBAC 默认是资源级别的(你能读这个 namespace 下**所有** Secret)。加上 `resourceNames` 后变成**细粒度**控制——只能读指定的几个 Secret 名。这是防止越权访问的关键手段。
|
||||
|
||||
#### 2. 开启 etcd 加密
|
||||
|
||||
K8s 的 Secret 在 etcd 中以 Base64 编码存储,而非加密。建议在集群层面启用 EncryptionConfiguration:
|
||||
|
||||
```yaml
|
||||
# encryption-config.yaml
|
||||
apiVersion: apiserver.config.k8s.io/v1
|
||||
kind: EncryptionConfiguration
|
||||
resources:
|
||||
- resources:
|
||||
- secrets
|
||||
providers:
|
||||
- aescbc: # 首选 AES-CBC 加密
|
||||
keys:
|
||||
- secret: $(ENCRYPTION_KEY) # 从环境变量注入,不要硬编码
|
||||
- identity: # fallback:旧数据用明文
|
||||
```
|
||||
|
||||
> [!note] 为什么要开启 etcd 加密?
|
||||
> 如果不加密, anyone 能访问 etcd(比如 K8s admin 角色的人、云厂商控制台的操作员)就能直接读取所有 Secret 明文。启用后,etcd 中实际存储的是密文。⚠️ **加密必须一次性完成**——一旦写入数据,后续切换加密 provider 会导致旧数据丢失。
|
||||
|
||||
#### 3. 审计日志
|
||||
|
||||
确保 kube-apiserver 启用了审计策略,记录所有 Secret 的读写操作:
|
||||
|
||||
```yaml
|
||||
# audit-policy.yaml
|
||||
apiVersion: audit.k8s.io/v1
|
||||
kind: Policy
|
||||
rules:
|
||||
- level: Metadata # Secret 只记录元数据,不记录内容
|
||||
resources:
|
||||
- group: "" # core API group
|
||||
resources: ["secrets"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
- level: None
|
||||
resources: ["secrets"]
|
||||
verbs: ["update", "patch", "delete"] # 修改类操作也需记录
|
||||
```
|
||||
|
||||
## 数据库迁移策略
|
||||
|
||||
> [!question] 发布新版本时,代码先上还是数据库先改?
|
||||
> 答案:**两边要同时准备好**——但「先扩后缩」是核心原则。
|
||||
|
||||
零停机发布的最大陷阱是**数据库 schema 变更**。如果代码 A 版本写了字段 X,而代码 B 版本还没发布,此时如果删掉字段 X,代码 A 就会崩溃。遵循以下原则:
|
||||
|
||||
### 向前兼容原则
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph Phase1["Phase 1: 加字段(向后兼容)"]
|
||||
P1A[添加新字段(可为 NULL)] --> P1B[部署新代码(双写新旧字段)]
|
||||
end
|
||||
|
||||
subgraph Phase2["Phase 2: 填存量数据"]
|
||||
P2A[后台任务回填历史数据] --> P2B[验证数据一致性]
|
||||
end
|
||||
|
||||
subgraph Phase3["Phase 3: 切换到新字段"]
|
||||
P3A[部署代码(仅读新字段)] --> P3B[删除旧字段]
|
||||
end
|
||||
|
||||
P1B --> P2A
|
||||
P2B --> P3A
|
||||
|
||||
style Phase1 fill:#e8f5e9
|
||||
style Phase2 fill:#fff3e0
|
||||
style Phase3 fill:#fce4ec
|
||||
```
|
||||
|
||||
**三阶段详解:**
|
||||
|
||||
| 阶段 | 动作 | 兼容性保证 |
|
||||
|------|------|-----------|
|
||||
| **Phase 1** | `ALTER TABLE` 新增列(NOT NULL 不行!设为可 NULL)+ 新代码同时写入新旧字段 | 旧代码继续读旧字段,正常运行 |
|
||||
| **Phase 2** | 用后台 Job 批量填充新字段的存量数据,直到全量一致 | 旧代码仍然正常,新代码已具备降级能力 |
|
||||
| **Phase 3** | 部署仅读新字段的代码 → 确认无误后 `ALTER TABLE DROP` 旧字段 | 完全切换到新模式 |
|
||||
|
||||
> [!example] 具体例子:给订单表增加 `status_v2` 枚举字段
|
||||
>
|
||||
> **Phase 1 — 加列 + 双写:**
|
||||
> ```sql
|
||||
> ALTER TABLE orders ADD COLUMN status_v2 VARCHAR(20) NULL;
|
||||
> ```
|
||||
> ```go
|
||||
> // 新代码:写入两个字段
|
||||
> db.Exec("UPDATE orders SET status=?, status_v2=? WHERE id=?", oldStatus, newV2, orderId)
|
||||
> ```
|
||||
>
|
||||
> **Phase 2 — 回填存量:**
|
||||
> ```go
|
||||
> func migrateLegacyStatus() {
|
||||
> batch := 1000
|
||||
> offset := 0
|
||||
> for {
|
||||
> rows, _ := db.Query("SELECT id, status FROM orders LIMIT $1 OFFSET $2", batch, offset)
|
||||
> // ... 转换 status -> status_v2 并 UPDATE ...
|
||||
> if rows.Count() < batch { break }
|
||||
> offset += batch
|
||||
> }
|
||||
> }
|
||||
> ```
|
||||
>
|
||||
> **Phase 3 — 切流 + 清理:**
|
||||
> ```go
|
||||
> // 新代码:只读写 status_v2
|
||||
> db.Query("SELECT * FROM orders WHERE status_v2='paid'")
|
||||
> // 确认全量稳定运行一周后执行:
|
||||
> ALTER TABLE orders DROP COLUMN status;
|
||||
> ```
|
||||
|
||||
### 关键注意事项
|
||||
|
||||
- **永远不要在同一个 deploy 中「删字段 + 上线读该字段的代码」** ——这必出事故
|
||||
- `ALTER TABLE` 对大表是重操作:使用 `pt-online-schema-change`(MySQL)或 `gh-ost` 避免锁表
|
||||
- DML(增删改行)可以用分批 + delay 方式;DDL(改结构)一定要离线窗口或使用在线工具
|
||||
- 每次迁移前备份:`mysqldump` 或快照,回滚有退路
|
||||
|
||||
## 回滚策略与机制
|
||||
|
||||
> [!question] 发布后发现 Bug,怎么办?
|
||||
> 最快的回滚不是「修代码再发布」,而是「切回上一个版本」。
|
||||
|
||||
### 回滚时机判断
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
ALERT["告警触发 / 监控异常"] --> THRESHOLD{"指标超过阈值?"}
|
||||
|
||||
THRESHOLD -- "Error Rate > 目标值" --> AUTO_ROLLBACK["⚡ 自动回滚 Canary"]
|
||||
THRESHOLD -- "Latency p99 升高" --> MANUAL["人工评估是否需要回滚"]
|
||||
THRESHOLD -- "业务指标下降但不紧急" --> MONITOR["持续观察,暂不回滚"]
|
||||
|
||||
AUTO_ROLLBACK --> LOG["通知 Team + 记录事件"]
|
||||
MANUAL -- "决定回滚" --> LOG
|
||||
MANUAL -- "决定观察" --> MONITOR
|
||||
|
||||
LOG --> POSTMORTEM["事后复盘 Post-mortem"]
|
||||
|
||||
style AUTO_ROLLBACK fill:#ffebee
|
||||
style MANUAL fill:#fff3e0
|
||||
style MONITOR fill:#e8f5e9
|
||||
style POSTMORTEM fill:#e3f2fd
|
||||
```
|
||||
|
||||
### Rolling Update 的回滚
|
||||
|
||||
```bash
|
||||
# kubectl rollout 是最基础的回滚手段
|
||||
kubectl rollout status deployment/order-service # 查看当前状态
|
||||
kubectl rollout undo deployment/order-service # 回滚到上一版本
|
||||
kubectl rollout history deployment/order-service # 查看所有 revision
|
||||
kubectl rollout undo deployment/order-service --to-revision=3 # 回滚到指定版本
|
||||
|
||||
# 也可以直接用之前验证过的镜像重新 apply
|
||||
kubectl set image deployment/order-service order-service=myregistry/app:v1.2.3@sha256:...
|
||||
```
|
||||
|
||||
### Blue-Green 的回滚
|
||||
|
||||
Blue-Green 天然支持秒级回滚——只需把 Service 的 selector 切回蓝环境即可:
|
||||
|
||||
```yaml
|
||||
# 当前绿环境在服务,切回蓝环境:
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: order-service
|
||||
spec:
|
||||
selector:
|
||||
app: order-service
|
||||
version: blue # ← 从 green 改成 blue
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 8080
|
||||
```
|
||||
|
||||
> [!tip] 如何做到无缝切换?
|
||||
> 配合 **外部 DNS**(Route53 / CloudFlare)可以跨 Kubernetes Service 实现全局流量切换。Service Level Switching 毫秒级,DNS TTL 通常分钟级。关键是把流量入口层和服务层都做好双写。
|
||||
|
||||
### ArgoCD 一键回滚
|
||||
|
||||
```yaml
|
||||
# ArgoCD Application 定义
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: order-service
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: https://github.com/team/k8s-manifests.git
|
||||
targetRevision: v1.2.3 # ← 改到这个 tag 即回滚
|
||||
path: overlays/production
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true # 自动删除不在 Git 中的资源
|
||||
selfHeal: true # 检测到漂移自动修复
|
||||
```
|
||||
|
||||
ArgoCD 的回滚就是「改 Git 里的目标版本 + push」——这就是 GitOps 的魅力:**回滚也是提交**。
|
||||
|
||||
## 特性开关(Feature Flag)
|
||||
|
||||
> [!question] 有没有一种方法可以让新功能「已经部署到生产」但「对用户还不可见」?
|
||||
> 答案是 Feature Flag —— **代码提交 ≠ 功能上线**。
|
||||
|
||||
### 典型使用模式
|
||||
|
||||
```go
|
||||
// feature_flags.go
|
||||
package flags
|
||||
|
||||
import "github.com/thomaspoison/go-feature-flag/provider/fileprovider"
|
||||
|
||||
func init() {
|
||||
ffclient.Init(ffconfig.Config{
|
||||
FilePath: "features.yaml", // 配置文件路径
|
||||
PollInterval: 30, // 每 30 秒刷新
|
||||
})
|
||||
}
|
||||
|
||||
// 调用处
|
||||
if flags.Value("enable-new-checkout", false) {
|
||||
// 新功能逻辑
|
||||
return NewCheckoutFlow(ctx)
|
||||
}
|
||||
return LegacyCheckoutFlow(ctx) // 兜底:传统流程
|
||||
```
|
||||
|
||||
对应配置文件 `features.yaml`:
|
||||
|
||||
```yaml
|
||||
enable-new-checkout:
|
||||
percentage: 0 # 初始为 0%,对所有人关闭
|
||||
rollout: gradual # 渐进式放量
|
||||
rules:
|
||||
- attribute: env
|
||||
operator: equal
|
||||
value: production
|
||||
invert: false
|
||||
values: [50] # 灰度到 50% 的用户
|
||||
```
|
||||
|
||||
### 何时使用 Feature Flag
|
||||
|
||||
| 场景 | 推荐度 | 理由 |
|
||||
|------|--------|------|
|
||||
| 复杂业务逻辑开关 | ⭐⭐⭐⭐⭐ | 无需重新部署即可上线下线功能 |
|
||||
| A/B 测试 | ⭐⭐⭐⭐⭐ | 按用户维度控制实验组/对照组 |
|
||||
| 简单配置项 | ⭐⭐ | 用 ConfigMap 就够了,Flag 太重 |
|
||||
| 临时 Debug | ⭐⭐⭐ | 用环境变量更直接 |
|
||||
|
||||
### Feature Flag vs Release Branch
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
trunk["主分支 main/master\n🟢 始终可构建"] --> CI["CI 自动化构建"]
|
||||
|
||||
subgraph flag["Feature Flag 方式"]
|
||||
F1["新功能开发完成"] --> F2["合并到 main\nFlag = OFF"] --> F3["发布到生产"] --> F4["调试后 Flag = ON"]
|
||||
end
|
||||
|
||||
subgraph branch["Release Branch 方式"]
|
||||
B1["创建 release/v2.0 分支"] --> B2["隔离新功能"] --> B3["发布时合入 main"]
|
||||
end
|
||||
|
||||
CI --> F1
|
||||
CI --> B2
|
||||
|
||||
style trunk fill:#e8f5e9
|
||||
style flag fill:#f3e5f5
|
||||
style branch fill:#fff3e9
|
||||
```
|
||||
|
||||
核心差异:**Feature Flag 保持单一 main 分支,Release Branch 产生长期隔离分支**。现代 GitOps 实践更推荐 Flag,因为可以避免 merge conflict 堆积和重复测试。
|
||||
|
||||
## 发布前检查清单
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
PRE["发布前检查"] --> T1{"镜像 Tag 是否精确?"}
|
||||
T1 -- ✅ SHA-based --> T2{"Staging 是否已通过?"}
|
||||
T1 -- ❌ latest / head --> FAIL1["❌ 拒绝部署"]
|
||||
T2 -- ✅ Passed --> T3{"Prod Approval 是否完成?"}
|
||||
T2 -- ❌ Failed/Pending --> FAIL2["❌ 暂停等待"]
|
||||
T3 -- ✅ Approved --> T4{"Canary 指标是否正常?"}
|
||||
T3 -- ❌ Rejected --> FAIL2
|
||||
T4 -- ✅ All Green --> SUCCESS["✅ 全量发布"]
|
||||
T4 -- ❌ Error Rate 升高 --> ROLLBACK["⚠️ 自动回滚"]
|
||||
|
||||
style FAIL1 fill:#ffebee
|
||||
style FAIL2 fill:#fff3e0
|
||||
style SUCCESS fill:#e8f5e9
|
||||
style ROLLBACK fill:#fce4ec
|
||||
```
|
||||
|
||||
| # | 检查项 | 工具/方法 |
|
||||
|---|--------|-----------|
|
||||
| 1 | 镜像 Tag 使用 Commit SHA | GitHub Actions `${{ github.sha }}` |
|
||||
| 2 | 单元测试覆盖率达标 | `make test` + coverage threshold |
|
||||
| 3 | 安全扫描无 HIGH/CRITICAL | Trivy / Snyk |
|
||||
| 4 | Staging 环境冒烟测试通过 | 自动化 health check |
|
||||
| 5 | Prod 审批人工确认 | GitHub Environment Protection Rule |
|
||||
| 6 | Canary 阶段 error rate < 0.1% | Prometheus + Alertmanager |
|
||||
| 7 | Git manifest 已更新并 merged | ArgoCD Application status = Synced |
|
||||
|
||||
## 渐进式采纳路径
|
||||
|
||||
如果你的团队还在手工部署,不必一步到位。建议分三步走:
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
STEP1[("1. 自动化构建+Push")] --> STEP2[("2. Staging + 审批关卡")] --> STEP3[("3. GitOps, Git 即真相源")]
|
||||
|
||||
style STEP1 fill:#e8f5e9
|
||||
style STEP2 fill:#fff3e0
|
||||
style STEP3 fill:#e3f2fd
|
||||
```
|
||||
|
||||
1. **第一步:用 GitHub Actions/GitLab CI 实现自动化构建 + push** — 推荐立即做
|
||||
2. **第二步:引入 Staging 环境和审批关卡** — 降低上线风险
|
||||
3. **第三步:迁移到 GitOps(ArgoCD)** — 让 Git 成为唯一真相源
|
||||
|
||||
实际落地时,很多团队的最终架构是 **CI/CD + GitOps 结合**:
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Dev["开发者 push 代码"] --> CI["GitHub Actions\n(CI: 测试→构建→Push镜像)"]
|
||||
CI -->|"推送新镜像"| IMGREG["Container Registry"]
|
||||
|
||||
Dev2["开发者 PR 修改 K8s manifest"] --> GIT[(Git Manifest Repo)]
|
||||
|
||||
subgraph Cluster["K8s Cluster"]
|
||||
Argo["ArgoCD"] -->|"自动同步"| K8s["Pod/Service/ConfigMap"]
|
||||
end
|
||||
|
||||
GIT -.->|Watch 变动| Argo
|
||||
|
||||
style CI fill:#e8f5e9
|
||||
style Argo fill:#fff3e0
|
||||
style K8s fill:#e3f2fd
|
||||
```
|
||||
|
||||
核心分工:**CI/CD 管"怎么建",GitOps 管"怎么维持"**。两者合在一起,才构成了完整的现代软件交付链。
|
||||
|
||||
## 关联笔记
|
||||
|
||||
- [[01-CICD基础与实践]] — CI/CD Pipeline 的搭建与实践
|
||||
- [[02-GitOps与ArgoCD]] — GitOps 工作流与 ArgoCD
|
||||
- [[03-Helm模板管理]] — Helm Chart 编写与管理
|
||||
- [[../03-CICD与GitOps]] — 参考手册与决策指南总入口
|
||||
Reference in New Issue
Block a user