From f8bcbb9d374beae98e482e7f9c6e99629556f9b7 Mon Sep 17 00:00:00 2001
From: hhs <386998068@qq.com>
Date: Mon, 18 May 2026 00:17:59 +0800
Subject: [PATCH] vault backup: 2026-05-18 00:17:59
---
hhs/MS/02-服务治理/07-配置管理.md | 2 +-
hhs/MS/03-数据一致性/01-数据库拆分.md | 613 ++++++++++++++----
hhs/MS/03-数据一致性/02-分布式事务.md | 341 +++++++++-
hhs/MS/03-数据一致性/03-ID生成.md | 2 +-
hhs/MS/05-部署运维/01-容器化.md | 163 ++---
.../01-容器化/01-Dockerfile最佳实践.md | 150 +++++
hhs/MS/05-部署运维/01-容器化/02-多架构构建.md | 91 +++
hhs/MS/05-部署运维/01-容器化/03-镜像安全.md | 150 +++++
hhs/MS/05-部署运维/02-Kubernetes.md | 293 +++------
.../02-Kubernetes/01-核心概念与Deployment.md | 202 ++++++
.../05-部署运维/02-Kubernetes/02-配置管理.md | 188 ++++++
.../02-Kubernetes/03-网络与服务发现.md | 144 ++++
.../02-Kubernetes/04-扩缩容与有状态应用.md | 217 +++++++
.../05-部署运维/02-Kubernetes/05-调度控制.md | 179 +++++
.../02-Kubernetes/06-资源与安全管控.md | 208 ++++++
.../05-部署运维/02-Kubernetes/07-运维排查.md | 143 ++++
hhs/MS/05-部署运维/03-CICD与GitOps.md | 323 ++++-----
.../03-CICD与GitOps/01-CICD基础与实践.md | 299 +++++++++
.../03-CICD与GitOps/02-GitOps与ArgoCD.md | 449 +++++++++++++
.../03-CICD与GitOps/03-Helm模板管理.md | 454 +++++++++++++
.../03-CICD与GitOps/04-安全与发布策略.md | 470 ++++++++++++++
hhs/MS/05-部署运维/04-SRE实践.md | 136 +---
.../04-SRE实践/01-SLI-SLO-SLA详解.md | 77 +++
.../04-SRE实践/02-错误预算深度解析.md | 113 ++++
.../04-SRE实践/03-SRE心法与事故复盘.md | 114 ++++
hhs/MS/05-部署运维/README.md | 19 +-
26 files changed, 4828 insertions(+), 712 deletions(-)
create mode 100644 hhs/MS/05-部署运维/01-容器化/01-Dockerfile最佳实践.md
create mode 100644 hhs/MS/05-部署运维/01-容器化/02-多架构构建.md
create mode 100644 hhs/MS/05-部署运维/01-容器化/03-镜像安全.md
create mode 100644 hhs/MS/05-部署运维/02-Kubernetes/01-核心概念与Deployment.md
create mode 100644 hhs/MS/05-部署运维/02-Kubernetes/02-配置管理.md
create mode 100644 hhs/MS/05-部署运维/02-Kubernetes/03-网络与服务发现.md
create mode 100644 hhs/MS/05-部署运维/02-Kubernetes/04-扩缩容与有状态应用.md
create mode 100644 hhs/MS/05-部署运维/02-Kubernetes/05-调度控制.md
create mode 100644 hhs/MS/05-部署运维/02-Kubernetes/06-资源与安全管控.md
create mode 100644 hhs/MS/05-部署运维/02-Kubernetes/07-运维排查.md
create mode 100644 hhs/MS/05-部署运维/03-CICD与GitOps/01-CICD基础与实践.md
create mode 100644 hhs/MS/05-部署运维/03-CICD与GitOps/02-GitOps与ArgoCD.md
create mode 100644 hhs/MS/05-部署运维/03-CICD与GitOps/03-Helm模板管理.md
create mode 100644 hhs/MS/05-部署运维/03-CICD与GitOps/04-安全与发布策略.md
create mode 100644 hhs/MS/05-部署运维/04-SRE实践/01-SLI-SLO-SLA详解.md
create mode 100644 hhs/MS/05-部署运维/04-SRE实践/02-错误预算深度解析.md
create mode 100644 hhs/MS/05-部署运维/04-SRE实践/03-SRE心法与事故复盘.md
diff --git a/hhs/MS/02-服务治理/07-配置管理.md b/hhs/MS/02-服务治理/07-配置管理.md
index 98c70e8..d0e007d 100644
--- a/hhs/MS/02-服务治理/07-配置管理.md
+++ b/hhs/MS/02-服务治理/07-配置管理.md
@@ -195,7 +195,7 @@ public class OrderController {
if (newOrderFlowEnabled) {
// 新版流程
}
- return orderService.list();
+ return orderService.list()
}
}
```
diff --git a/hhs/MS/03-数据一致性/01-数据库拆分.md b/hhs/MS/03-数据一致性/01-数据库拆分.md
index 089e18a..15ffa5c 100644
--- a/hhs/MS/03-数据一致性/01-数据库拆分.md
+++ b/hhs/MS/03-数据一致性/01-数据库拆分.md
@@ -1,186 +1,574 @@
---
tags: [microservice, database, sharding, replication]
-create time: 2026-05-05
+create time: 2026-05-17 14:30
---
# 数据库拆分
## 概述
-微服务的核心设计原则是 **"每个服务拥有独立数据库"**,这意味着每个服务的表结构、数据存储、甚至数据库类型都可以不同。但当单表数据量持续增长时,就面临拆分的需求。
+想象一下:你的电商系统刚上线时只有一张 `orders` 表,数据量不大,一条 SQL 就搞定。一年后,日订单量涨到 100 万,同样的查询慢到了 5 秒——数据库成了瓶颈,你不得不开始考虑:**怎么拆?**
+
+微服务的核心设计原则是 **"每个服务拥有独立数据库"**。这意味着每个服务的表结构、数据存储、甚至数据库类型都可以不同。但当单表数据量持续增长时,你就面临另一个维度的拆分需求。
```mermaid
graph LR
- S1[订单服务 DB]
- S2[库存服务 DB]
-
- subgraph BAD["反模式:共享数据库"]
- S1 --- SHARED[(共享 DB)]
- S2 --- SHARED
+ subgraph "理想状态:微服务 = 独立数据库"
+ S1["📦 订单服务
MySQL"]
+ S2["👤 用户服务
PostgreSQL"]
+ S3["📦 库存服务
Redis + MySQL"]
end
-
+
+ style S1 fill:#e8f5e9,stroke:#4caf50
+ style S2 fill:#e8f5e9,stroke:#4caf50
+ style S3 fill:#e8f5e9,stroke:#4caf50
+```
+
+> [!failure] 反面教材:共享数据库
+>
+> 如果两个微服务连接同一个数据库的同一张表,它们就不再是独立的微服务——你得到的是 **分布式单体**。服务可以随意互相查对方的数据,失去了边界和自治性。这在早期开发中很常见(方便联调),但一定要在正式微服务化之前拆掉。
+
+```mermaid
+graph LR
+ ORDER["📦 订单服务"] --- SHARED[(❌ 共享 DB)]
+ USER["👤 用户服务"] --- SHARED
+
style SHARED fill:#ffebee,stroke:#ef5350
+ style ORDER fill:#fff3e0,stroke:#ff9800
+ style USER fill:#fff3e0,stroke:#ff9800
```
-> [!failure] 反模式警告
-> 如果两个服务连接同一个数据库的同一张表,它们就不再是独立的微服务——你得到的是**分布式单体**。服务可以随意互相查询彼此的数据,失去了边界和自治性。
+## 两种拆分思路:先"纵向切",再"横向切"
-## 垂直拆分 vs 水平拆分
+拆分不是选择题,而是**两步走**:第一步按业务域垂直拆分(微服务化的标配),第二步当单个表太大时再水平拆分(分库分表)。
-### 垂直拆分(按业务域)
+### 第一步:垂直拆分 — 按业务域独立建库
-按 **微服务边界** 拆库——这是微服务的标配。
+> [!question] 思考一下
+>
+> 如果一个"用户下单"操作需要同时访问订单表和商品信息表——这说明这两个表应该属于同一个数据库吗?
+>
+> **答案是否定的。** 商品不会因为你改了订单就跟着变。真正的判断标准是:**哪些表经常一起被修改?** 如果一组表的变更总是由同一个业务逻辑触发,它们就属于同一个限界上下文,应该放在同一个库里。
+
+核心做法:以 **微服务边界** 为基准,将相关表打包到一个数据库中。
```mermaid
-graph TB
- subgraph "DB-Order"
- orders[orders]
- order_items[order_items]
- order_status[order_status]
+flowchart TB
+ subgraph DBOrder["DB-Order:订单域"]
+ direction LR
+ T1[orders]
+ T2[order_items]
+ T3[order_status_log]
end
-
- subgraph "DB-User"
- users[users]
- user_profiles[user_profiles]
- user_addresses[user_addresses]
+
+ subgraph DBUser["DB-User:用户域"]
+ direction LR
+ U1[users]
+ U2[user_profiles]
+ U3[user_addresses]
end
-
- subgraph "DB-Product"
- products[products]
- categories[categories]
- product_images[product_images]
+
+ subgraph DBProduct["DB-Product:商品域"]
+ direction LR
+ P1[products]
+ P2[categories]
+ P3[product_images]
end
+
+ style DBOrder fill:#e3f2fd,stroke:#1976d2,rx:8
+ style DBUser fill:#e3f2fd,stroke:#1976d2,rx:8
+ style DBProduct fill:#e3f2fd,stroke:#1976d2,rx:8
```
-| 特点 | 说明 |
+| 优势 | 说明 |
|------|------|
-| 每个服务独占一个数据库 | 物理隔离,互不干扰 |
-| 可异构选型 | 订单用 MySQL,用户用 PostgreSQL,搜索用 Elasticsearch |
-| 天然解耦 | 服务间不能直接查对方库 |
+| **物理隔离,互不干扰** | 订单库崩了不影响用户登录 |
+| **异构选型** | 订单用 MySQL(事务强),搜索用 Elasticsearch,缓存用 Redis |
+| **天然解耦** | 服务之间不能直连对方数据库,只能通过 API 通信 |
-### 水平拆分(分库分表)
+> [!tip] 拆分的粒度
+>
+> 不要把一张表里的字段都拆到不同库里——那叫"过度拆分"。**以表为单位**进行垂直拆分是最常见的做法。一个微服务对应一个库,一个库包含多个相关表。
-当单个表的记录量达到千万级以上,需要进一步拆分。
+### 垂直拆分的判断标准(实操 checklist)
+
+当你在犹豫某张表应该留在这个库还是挪到另一个库时,用下面几个维度来判断:
+
+```mermaid
+flowchart TD
+ START["开始判断"] --> A["这张表和当前库里的表
是否经常被同一个事务修改?"]
+ A -->|是| SAME_DB["留在同一库 ✅"]
+ A -->|否| B["它们是否属于同一个
业务限界上下文?"]
+ B -->|是| SAME_CTX["考虑放在同一库
(降低跨库调用成本)"]
+ B -->|否| DIFF_CTX["必须独立建库 ✅"]
+ SAME_CTX --> C["变更频率差异大吗?"]
+ C -->|高频 vs 低频| SPLIT["建议拆分
(避免互相影响)✅"]
+ C -->|同频| SAME_DB
+ DIFF_CTX --> END["完成"]
+ SAME_DB --> END
+ SPLIT --> END
+```
+
+| 判断维度 | 放同一库的信号 | 拆开的信号 |
+|---------|--------------|----------|
+| **事务耦合度** | 经常在一个 `BEGIN...COMMIT` 里一起改 | 各自有独立的写入路径 |
+| **读取热点** | 总是被一起查询、一起展示 | 访问模式完全不同 |
+| **团队归属** | 同一小组维护 | 不同团队负责(Conway 定律) |
+| **数据增长率** | 增长速度接近 | 一个涨得快、一个基本不变 |
+
+> [!note] 经验法则
+>
+> 如果一组表之间的跨库 JOIN 操作占了日常 SQL 的 **80% 以上**,把它们放在一起通常更合理。反过来,如果大部分关联查询都只需要一次 LEFT JOIN,说明拆分时机已经成熟——因为 JOIN 已经在两个数据库之间产生网络开销了。
+
+## 第二步:水平拆分 — 一张表太大了怎么办?
+
+垂直拆分解决的是"谁来负责什么"的问题。但当 **单个服务内的单表** 达到千万级甚至亿级记录时,就需要水平拆分(也叫分片/Sharding)。
+
+> [!note] 为什么要分?
+>
+> - 单表超过 2000 万行后,索引效率急剧下降
+> - 单机 MySQL 的写入 QPS 通常在 5000~20000,超出后成为瓶颈
+> - InnoDB 缓冲池装不下全部索引数据,大量磁盘 IO
+
+水平拆分的核心思想:**把一张大表按规则切成多张小表,分散到不同的数据库实例中。**
```mermaid
graph TB
- subgraph "分库策略"
- DB1[(DB-01)]
- DB2[(DB-02)]
- DB3[(DB-03)]
+ subgraph "拆分前:一张表扛所有"
+ BIG_TABLE[(order 表 1 亿行)]
end
-
- subgraph "order_0001 表"
- Row1[userId=1 → order_0001]
- Row2[userId=4 → order_0001]
+
+ subgraph "拆分后:按 user_id 散列"
+ subgraph "DB-01"
+ T1[(order_0001 2500 万行)]
+ end
+ subgraph "DB-02"
+ T2[(order_0002 2500 万行)]
+ end
+ subgraph "DB-03"
+ T3[(order_0003 2500 万行)]
+ end
+ subgraph "DB-04"
+ T4[(order_0004 2500 万行)]
+ end
end
-
- subgraph "order_0002 表"
- Row3[userId=2 → order_0002]
- Row4[userId=5 → order_0002]
- end
-
- userId_mod["ORDER BY user_id % 2"] --> DB1
- userId_mod --> DB2
+
+ ROUTE["user_id % 4"] -->|"uid=1001"| T1
+ ROUTE -->|"uid=2002"| T2
+ ROUTE -->|"uid=3003"| T3
+ ROUTE -->|"uid=4004"| T4
+
+ style BIG_TABLE fill:#ffebee,stroke:#ef5350
+ style T1 fill:#e8f5e9,stroke:#4caf50
+ style T2 fill:#e8f5e9,stroke:#4caf50
+ style T3 fill:#e8f5e9,stroke:#4caf50
+ style T4 fill:#e8f5e9,stroke:#4caf50
```
-### 常见分片策略
+#### 常见分片策略对比
-| 策略 | 哈希公式 | 优点 | 缺点 |
-|------|---------|------|------|
-| **Hash Mod** | `user_id % N` | 简单高效,路由确定 | 扩缩容困难,数据迁移成本高 |
-| **Range** | `user_id BETWEEN x AND y` | 范围查询友好 | 热点用户集中到单分片 |
-| **Time-based** | `year_month` | 按生命周期管理 | 新分片写入压力大 |
-| **Geo-based** | 按地域分片 | 本地化访问,延迟低 | 跨区域操作复杂 |
+| 策略 | 怎么分 | 适合场景 | 代价 |
+|------|--------|---------|------|
+| **哈希取模** `hash % N` | `user_id % 4`,余数决定去哪个库 | 均匀分布,写入均衡 | 扩库时需要迁移大部分数据 |
+| **范围划分** `BETWEEN x AND y` | userId 1~10000 → DB-A,10001~20000 → DB-B | 范围查询友好 | 热点账号集中在一个分片 |
+| **时间分区** `year_month` | 2024_01 → DB-Jan, 2024_02 → DB-Feb | 按生命周期管理(冷数据归档) | 最新月份写入压力大 |
+| **地理位置** | 华东用户 → 杭州 DB,华南 → 广州 DB | 降低跨地域延迟 | 跨区域操作复杂 |
-### ShardingSphere / MyCat
+> [!important] 扩容陷阱
+>
+> Hash Mod 最容易踩坑:当你从 4 个分片扩展到 8 个分片时,`% 4` 变成 `% 8`,**几乎所有数据的新归属都变了**,需要大规模数据迁移。这是一个需要提前规划的重大决策。
+>
+> 如果扩容是高频需求,建议从一开始就用一致性哈希或预留足够多的槽位。
-> [!tip] 推荐中间件
->
-> **Apache ShardingSphere** 是国内使用最广泛的分库分表方案:
-> - 支持 JDBC / Proxy / Sidecar 三种部署模式
-> - 内置分片算法:Mod、Range、Hash、Tag
-> - 分布式主键生成器(Snowflake)原生集成
-> - 读写分离、强制路由、广播表等高级特性
+### 分片扩容方案:不停机迁移
-#### ShardingSphere 配置示例
+生产环境的扩容不能停服停机——你需要一个 **双写 + 历史数据回迁** 的渐进式流程:
+
+```mermaid
+flowchart TD
+ A["当前状态: N 个分片
hash % N"] --> B["第一步: 新增 M 个分片
总容量变为 N+M"]
+ B --> C["第二步: 双写阶段
新写入同时写到旧分片和新分片"]
+ C --> D["第三步: 历史数据回迁
按分片逐个搬移存量数据"]
+ D --> E{"全部搬完?"}
+ E -->|否| D
+ E -->|是| F["第四步: 校验数据一致性
checksum 对比"]
+ F --> G["第五步: 切读流量
新请求读新分片"]
+ G --> H["第六步: 关闭双写
恢复到单写单读"]
+
+ style B fill:#fff3e0,stroke:#ff9800
+ style C fill:#fff3e0,stroke:#ff9800
+ style D fill:#e3f2fd,stroke:#1976d2
+ style F fill:#e8f5e9,stroke:#4caf50
+ style G fill:#e8f5e9,stroke:#4caf50
+ style H fill:#f3e5f5,stroke:#7b1fa2
+```
+
+| 阶段 | 核心操作 | 关键风险 | 规避方法 |
+|------|---------|---------|---------|
+| **双写** | 新数据同时写入新旧两套分片 | 数据不一致、写入性能下降 | 用消息队列保证异步双写;设置标记位可快速回滚 |
+| **回迁** | 按 user_id 范围分批迁移历史数据 | 迁移期间持续写入导致数据漂移 | 迁移后对已迁移范围做一次增量同步 |
+| **切读** | 将读取路由切换到新分片 | 漏读、脏数据 | 先灰度 1% 流量验证,逐步放量 |
+| **关双写** | 停止向旧分片写入 | 遗漏最后一段增量数据 | 双写关之前做一次全量 checksum 校验 |
+
+> [!warning] 平滑扩容的时间成本
+>
+> 假设你有 1 亿条订单数据,网络带宽 1Gbps,压缩后约 50GB。**理论传输时间不到 1 分钟**——但实际中还要考虑锁竞争、慢查询、监控告警等因素。建议给每个分片预留 **2~4 小时** 的迁移窗口期,夜间低峰期执行。
+
+## 业界方案:ShardingSphere
+
+> [!tip] 为什么选 ShardingSphere?
+>
+> Apache ShardingSphere 是国内使用最广泛的分库分表中间件。它提供三种部署模式:
+> - **JDBC**:嵌入应用,零运维(最常用)
+> - **Proxy**:独立代理服务,语言无关
+> - **Sidecar**:Kubernetes 侧车模式
+
+#### 配置示例
+
+以下是 ShardingSphere-JDBC 的核心配置片段(YAML 格式):
```yaml
-# sharding-jdbc 配置
-sharding jdbc:
- data-sources:
- ds0: { type: HikariCP, ... }
- ds1: { type: HikariCP, ... }
-
+sharding-jdbc:
+ data-sources: # 定义数据源
+ ds0: { type: com.zaxxer.hikari.HikariDataSource, ... }
+ ds1: { type: com.zaxxer.hikari.HikariDataSource, ... }
+
sharding:
tables:
- orders:
+ orders: # 逻辑表名
actual-data-nodes: ds$->{0..1}.orders$->{0..1}
+ # 上面的表达式展开后是:ds0.orders0, ds0.orders1, ds1.orders0, ds1.orders1
table-strategy:
standard:
- sharding-column: user_id
+ sharding-column: user_id # 分片键
sharding-algorithm-name: user-id-mod
key-generate-strategy:
- column: order_id
- key-generator-name: snowflake
-
+ column: order_id # 主键生成
+ key-generator-name: snowflake # Snowflake 雪花算法
+
sharding-algorithms:
user-id-mod:
type: MOD
props:
- sharding-count: 2
+ sharding-count: 2 # 分成 2 个分片
```
-## 跨库查询方案
+关键点:
+- **逻辑表名 vs 实际数据节点**:应用层看到的永远是 `orders`,底层路由到 `ds0.orders0` 等是由中间件透明的完成的
+- **分片键选择**:一定要选写入频率高且用于查询条件的字段(如 `user_id`),否则每次查询都要扫全部分片
+
+### 分布式 ID 生成:为什么不能用自增主键?
+
+分库后每个库的 `AUTO_INCREMENT` 是独立的——两个库可能都生成了 `id = 100`。你必须用一种方式保证 **全局唯一**。
+
+#### Snowflake 雪花算法
+
+Twitter 开源的雪花算法是目前最主流的分布式 ID 方案:
+
+```mermaid
+graph LR
+ subgraph "64-bit 长整型 ID"
+ S["符号位
1 bit"]
+ T["时间戳
41 bits"]
+ D["机器 ID
10 bits"]
+ SQ["序列号
12 bits"]
+ end
+
+ style S fill:#f5f5f5,stroke:#9e9e9e
+ style T fill:#e3f2fd,stroke:#1976d2
+ style D fill:#e8f5e9,stroke:#4caf50
+ style SQ fill:#fff3e0,stroke:#ff9800
+```
+
+| 字段 | 长度 | 作用 | 范围 |
+|------|------|------|------|
+| **符号位** | 1 bit | 恒为 0(保证 ID 为正数) | - |
+| **时间戳** | 41 bits | 毫秒级时间戳 | 可支撑约 69 年 |
+| **机器 ID** | 10 bits | 区分部署实例 | 1024 个节点 |
+| **序列号** | 12 bits | 同一毫秒内的递增序号 | 每毫秒 4096 个 ID |
+
+> [!note] 核心特性
+>
+> - **单调递增**:基于时间戳保证整体趋势递增,适合 InnoDB 聚簇索引的 append-only 写入模式
+> - **高吞吐**:单实例每秒可生成 4096 × 1000 = 数百万个 ID
+> - **无中心节点**:不依赖 ZooKeeper 或数据库,挂掉一个机器不影响其他节点
+>
+> 如果同一毫秒内生成的 ID 超过 4096 个,算法会 **等待下一毫秒** 再重试——这在实际场景中极少发生(通常 QPS < 10 万)。
+
+#### 备选方案对比
+
+| 方案 | 唯一性保证 | 性能 | 复杂度 | 适用场景 |
+|------|----------|------|--------|---------|
+| **Snowflake** | 时间戳+机器ID+序列号组合 | 极高(本地生成) | 中 | 通用首选 |
+| **数据库号段模式** | 每次从 DB 批量拉取一段 ID | 高 | 中高 | 有现成 DB 基础设施 |
+| **UUID** | 随机 128 位 | 高 | 极低 | 对有序性无要求的场景 |
+| **Redis INCR** | Redis 原子递增 | 高 | 低 | 已有 Redis 集群 |
+
+> [!warning] UUID 的坑
+>
+> UUID 虽然简单,但在 MySQL InnoDB 中是 **灾难性的**:因为 UUID 无序,插入位置随机分布,导致大量的页分裂和碎片化。如果必须用 UUID,建议存为 `BINARY(16)` 并用 `UUID_TO_BIN(uuid, 1)` 转换,让它在索引中保持局部有序。
+
+## 跨库查询:拆分之后最难解决的问题之一
> [!question] 经典难题
-> 订单服务需要展示用户的姓名和手机号来做收货地址。但用户信息在用户库,订单数据在订单库——怎么办?
+>
+> 用户打开"我的订单"页面。订单服务拿到 `user_id` 查出订单列表,但现在要展示用户的头像和昵称——这些信息在用户库里。数据库已经拆开了,怎么做关联查询?
-### 方案对比
+这是拆分后必然遇到的挑战。没有银弹,只有 **权衡后的取舍**。
-| 方案 | 描述 | 性能 | 复杂度 | 适用场景 |
-|------|------|------|--------|---------|
-| **冗余字段** | 订单表存用户名字段 | ⭐⭐⭐⭐⭐ | 低 | 只读字段,变更频率低 |
-| **接口组装** | 先查订单,再调用户服务补全 | ⭐⭐⭐ | 中 | 偶尔需要关联的场景 |
-| **CQRS / 宽表** | 异步同步一份宽表用于查询 | ⭐⭐⭐⭐ | 高 | 高频关联查询 |
-| **搜索引擎** | ES/Kibana 做关联查询 | ⭐⭐⭐⭐ | 中高 | 复杂搜索 + 聚合 |
+### 四种方案对比
-### 冗余字段实践
+```mermaid
+mindmap
+ root((跨库查询方案))
+ 冗余字段
+ 最简单最直接
+ 少量字段
+ 快照语义
+ 一致性问题
+ 接口组装
+ 按需查询
+ 链路长时性能差
+ N+1 查询陷阱
+ 适合低频关联
+ CQRS / 宽表
+ 异步最终一致
+ 写入有额外开销
+ 适合高频关联
+ 搜索引擎
+ ES 做多维聚合
+ 架构重
+ 适合复杂搜索
+```
+
+| 方案 | 一句话描述 | 性能 | 复杂度 |
+|------|-----------|------|--------|
+| **冗余字段** | 在订单表里直接存用户名字段 | ⭐⭐⭐⭐⭐ | 低 |
+| **接口组装** | 先查订单,循环调用户服务补全信息 | ⭐⭐⭐ | 中 |
+| **CQRS / 宽表** | 异步同步一份含用户信息的宽表 | ⭐⭐⭐⭐ | 高 |
+| **搜索引擎** | 把数据推到 ES,用 ES 做关联查询 | ⭐⭐⭐⭐ | 中高 |
+
+### 实战一:冗余字段(最推荐的首选方案)
```sql
--- 订单表冗余关键字段
+-- 订单表中冗余关键字段(快照模式)
CREATE TABLE orders (
id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL,
- username VARCHAR(64), -- 冗余用户名(快照,不参与编辑)
- phone VARCHAR(20), -- 冗余手机号(脱敏存储)
+ username VARCHAR(64), -- 下单时的用户名快照
+ phone VARCHAR(20), -- 下单时的手机号(已脱敏)
created_at TIMESTAMP DEFAULT NOW(),
INDEX idx_user (user_id)
);
```
-> [!note] 冗余数据的维护
->
-> 用户改名字了怎么办?**不改订单表**。订单上的 username 是该时刻的"快照"——它反映的是下单时的状态,不是当前状态。这符合业务语义。
->
-> 如果需要批量更新冗余字段(如用户头像),通过消息队列异步通知订单服务更新。
+> [!note] 关键理解:快照语义
+>
+> 用户后来改了名字、换了手机号,**不改订单表**。订单上的 `username` 反映的是下单那一刻的状态,而不是"当前"状态。这不仅是合理的,而且是正确的——用户查看历史订单时,看到的是当时的信息。
+>
+> 如果需要主动更新冗余字段(如用户更换了头像),通过消息队列通知订单服务批量更新。
-## 数据库迁移工具
+Go 代码实现示例:
-```mermaid
-graph LR
- Dev["开发环境"] -->|"flyway migrate"| Stage["Staging"]
- Stage -->|"人工审批"| Prod["Production"]
- Prod -->|"flyway validate"| Check["校验版本一致性"]
+```go
+// 下单时将用户信息快照写入订单表
+func (s *orderSvc) CreateOrder(ctx context.Context, req *CreateOrderReq) (*Order, error) {
+ // 1. 查用户基本信息
+ user, err := s.userClient.GetByID(ctx, req.UserID)
+ if err != nil {
+ return nil, err
+ }
+
+ // 2. 构造订单,携带快照字段
+ order := &Order{
+ UserID: req.UserID,
+ Username: user.Username, // ✅ 快照:锁定下单时的值
+ Phone: maskPhone(user.Phone),
+ Items: req.Items,
+ }
+ return s.orderRepo.Save(ctx, order)
+}
```
-| 工具 | 语言 | 特点 |
-|------|------|------|
-| **Flyway** | Java | 基于文件命名,SQL 脚本方式,简单直观 |
-| **Liquibase** | Java | XML/YAML/JSON 格式,支持回滚生成 |
-| **golang-migrate** | Go | CLI 工具,轻量,适合 Go 项目 |
+### 实战二:接口组装(轻量场景够用)
-### Flyway 迁移流程
+适用于关联查询不频繁的场景,比如后台管理系统的偶尔查看详情。
+
+```go
+// 查订单 + 补齐用户信息
+func (s *orderSvc) GetOrderWithUser(ctx context.Context, orderID int64) (*OrderDetail, error) {
+ // 1. 先查订单
+ order, err := s.orderRepoFindByID(ctx, orderID)
+ if err != nil {
+ return nil, err
+ }
+
+ // 2. 再调用户服务补齐
+ user, err := s.userClient.GetByID(ctx, order.UserID)
+ if err != nil {
+ return nil, err
+ }
+
+ return &OrderDetail{
+ Order: order,
+ UserName: user.Username,
+ UserHead: user.AvatarURL,
+ }, nil
+}
+```
+
+> [!warning] N+1 陷阱
+>
+> 如果是列表查询(一次性返回 20 条订单),逐条调用户服务会导致 20 次 RPC 调用。正确做法是:先收集所有 `user_id`,**批量查询**用户信息,再拼回去。
+
+```go
+// ❌ 错误:N 次 RPC
+for _, o := range orders {
+ u, _ := userClient.GetByID(ctx, o.UserID)
+}
+
+// ✅ 正确:1 次批量 RPC
+ids := collectIDs(orders) // [1, 3, 7, 12, ...]
+users, _ := userClient.GetByIds(ctx, ids) // 一次拿回所有
+byID := indexBy(users, func(u *User) int64 { return u.ID })
+for _, o := range orders {
+ o.UserInfo = byID[o.UserID]
+}
+```
+
+### 实战三:CQRS / 宽表(重度关联查询必备)
+
+当跨库关联是高频操作(如运营后台的多维度筛选),冗余字段就不够了——你需要一套完整的 **事件驱动宽表同步机制**:
+
+```mermaid
+sequenceDiagram
+ participant U as 用户服务
+ participant MQ as 消息队列
+ participant O as 订单服务
+ participant W as 宽表 (Read DB)
+
+ U->>MQ: UserUpdated 事件 (userId, newAvatar)
+ MQ->>O: 消费事件
+ O->>W: 更新宽表中对应用户的头像
+
+ Note over W: 宽表包含了订单 + 用户 + 商品的冗余字段
只读,专为查询优化
+```
+
+Go 代码实现——事件消费者(订单服务侧):
+
+```go
+// Order宽表同步消费者:监听来自各服务的业务事件,维护一张可跨维度查询的宽表
+type WideTableSyncer struct {
+ wideDB *sql.DB // 独立的读库连接
+}
+
+// OnUserUpdated 消费用户变更事件,更新宽表中对应用户的信息
+func (s *WideTableSyncer) OnUserUpdated(ctx context.Context, evt UserUpdatedEvent) error {
+ tx, err := s.wideDB.BeginTx(ctx, nil)
+ if err != nil {
+ return err
+ }
+ defer tx.Rollback()
+
+ // 批量更新宽表中的用户快照字段
+ _, err = tx.ExecContext(ctx,
+ `UPDATE order_wide_table
+ SET username = ?, phone_masked = ?, avatar_url = ?
+ WHERE user_id = ?`,
+ evt.Username, evt.PhoneMasked, evt.AvatarURL, evt.UserID,
+ )
+ return tx.Commit()
+}
+
+// OnOrderCreated 订单创建时写入宽表(包含完整的关联信息)
+func (s *WideTableSyncer) OnOrderCreated(ctx context.Context, evt OrderCreatedEvent) error {
+ _, err := s.wideDB.ExecContext(ctx,
+ `INSERT INTO order_wide_table
+ (order_id, user_id, username, product_name, amount, status, created_at)
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
+ evt.OrderID, evt.UserID, evt.Username,
+ evt.ProductName, evt.Amount, evt.Status, evt.CreatedAt,
+ )
+ return err
+}
+```
+
+> [!tip] 宽表设计的三个原则
+>
+> 1. **反范式化**:宽表故意违反第一范式——同一个用户的名字可能出现在几百条订单记录中。这正是它的价值所在。
+> 2. **最终一致性延迟可控**:通过 MQ 保证,通常在 1~3 秒内同步完成。前端加一个 loading 状态即可掩盖这短暂的延迟。
+> 3. **写多读少时才考虑**:如果宽表的写入放大超过原始数据的 3 倍,说明你的场景用冗余字段就够了,不需要上宽表。
+
+## 何时该拆?——不要过早拆分
+
+拆分是有 **代价** 的:复杂度上升、运维成本增加、跨服务调用变慢。在决定拆之前,先看这些硬指标是否已经触达:
+
+```mermaid
+flowchart TD
+ START["你的数据量到了什么级别?"]
+
+ START --> SMALL["< 100 万行
单库单机足够"]
+ START --> MEDIUM["100 万 ~ 2000 万行
考虑读写分离"]
+ START --> LARGE["> 2000 万行
考虑分片"]
+
+ SMALL --> S1["✅ 先优化索引"]
+ S1 --> S2["✅ 加缓存层 Redis"]
+ S2 --> S3["✅ 读写分离
一主多从"]
+
+ MEDIUM --> M1["✅ 先做垂直拆分"]
+ M1 --> M2["按业务域独立建库"]
+
+ LARGE --> L1["✅ 再做水平拆分"]
+ L1 --> L2["分库分表 + 宽表同步"]
+
+ style SMALL fill:#e8f5e9,stroke:#4caf50
+ style MEDIUM fill:#fff3e0,stroke:#ff9800
+ style LARGE fill:#ffebee,stroke:#ef5350
+ style S3 fill:#e8f5e9,stroke:#4caf50
+ style M2 fill:#e8f5e9,stroke:#4caf50
+ style L2 fill:#ffebee,stroke:#ef5350
+```
+
+> [!quote] 一条经验法则
+>
+> **"在数据量还没到 2000 万行之前,不要做任何形式的数据水平拆分。"**
+>
+> 绝大多数系统通过索引优化 + 缓存 + 读写分离就能撑到日活百万级。见过太多团队在项目刚上线就搞分库分表——六个月后回头看,完全是提前踩雷。
+
+### 拆与不拆的判断矩阵
+
+| 场景 | 推荐方案 | 预期寿命 |
+|------|---------|---------|
+| DAU < 1 万,QPS < 500 | 单库单机,专心做好索引 | 半年~1年 |
+| DAU 1~50 万,QPS 500~5000 | 加 Redis 缓存 + 读写分离 | 1~2 年 |
+| DAU 50~200 万,单表 > 2000 万行 | 垂直拆分(按微服务) | 2~3 年 |
+| DAU > 200 万,单表 > 5000 万行 | 水平拆分 + 宽表同步 | 长期 |
+
+## 数据库迁移工具:告别手动执行 SQL
+
+随着服务拆分增多,SQL 脚本的管理变得复杂。手工执行、口头传达"我跑过 V3 了"的方式不再可行——你需要**版本化的、可重复执行的数据库迁移工具**。
+
+```mermaid
+flowchart LR
+ Dev["💻 开发环境
git commit SQL 文件"] -->|"CI/CD 自动执行"| Stage["🧪 Staging
flyway migrate"]
+ Stage -->|"人工审批"| Prod["🚀 Production
flyway migrate"]
+ Prod -->|"校验"| Check["🔒 flyway validate
确认无漂移"]
+
+ style Dev fill:#e3f2fd,stroke:#1976d2
+ style Stage fill:#fff3e0,stroke:#ff9800
+ style Prod fill:#e8f5e9,stroke:#4caf50
+ style Check fill:#f3e5f5,stroke:#7b1fa2
+```
+
+### 主流工具对比
+
+| 工具 | 生态 | 特点 |
+|------|------|------|
+| **Flyway** | Java / Go (via CLI) | 基于文件名命名,纯 SQL 脚本,简单直接 |
+| **Liquibase** | Java | 支持 XML/YAML/JSON,自带回滚生成能力 |
+| **golang-migrate** | Go | 轻量 CLI,Go 项目首选 |
+
+### Flyway 工作流
```
db/migration/
@@ -190,7 +578,12 @@ db/migration/
└── V4__add_order_status_enum.sql
```
-执行顺序:V1 → V2 → V3 → V4。Flyway 内部维护 `_schema_version` 表追踪已执行的迁移。
+执行顺序严格遵循版本号:V1 → V2 → V3 → V4。Flyway 会在数据库中维护一张 `_schema_version` 表,记录每个迁移的版本和执行状态。
+
+核心要点:
+- **文件名即版本**:`V` 开头 + 序号 + `__` + 描述
+- **不可修改已执行的文件**:改了的话 Flyway 会报验证失败(`validate` 阶段)
+- **永远不要写"反向"SQL**:迁移脚本只做"升级",回滚通过发版解决
## 关联笔记
diff --git a/hhs/MS/03-数据一致性/02-分布式事务.md b/hhs/MS/03-数据一致性/02-分布式事务.md
index 02297b8..f88a9a7 100644
--- a/hhs/MS/03-数据一致性/02-分布式事务.md
+++ b/hhs/MS/03-数据一致性/02-分布式事务.md
@@ -1,6 +1,6 @@
---
-tags: [microservice, distributed-transactions, saga, tcc, outbox]
-create time: 2026-05-05
+tags: [microservice, distributed-transactions, saga, tcc, outbox, seata, idempotency, dlq]
+create time: 2026-05-17 15:00
---
# 分布式事务
@@ -101,25 +101,6 @@ func outboxWorker(ctx context.Context, ticker *time.Ticker) {
}
```
-#### 事务消息(RocketMQ 原生支持)
-
-如果使用的是 RocketMQ,可以绕过 Outbox 模式直接用事务消息:
-
-```go
-// 发送事务消息
-txMsg := rocketmq.NewTransactionMessage("order-created", payload)
-localTx := &MyLocalTxChecker{}
-
-// half 消息发送 → 本地事务执行 → 提交/回查
-res, _ := producer.SendMessageInTransaction(txMsg, localTx)
-```
-
-RocketMQ 的事务消息流程:
-1. 生产者发送 "half 消息" 到 MQ(消费者不可见)
-2. 执行本地事务
-3. 根据结果 Commit(消费者可见)或 Rollback(丢弃)
-4. 如果步骤 2 超时,MQ 回查本地事务状态
-
### 消费幂等性
> [!warning] 关键保障
@@ -148,6 +129,57 @@ if !isUniqueViolation(err) {
// 执行业务逻辑——到这里说明消息是新到达的
```
+#### 事务消息 vs Outbox
+
+> [!question] 思考一下
+> Outbox 模式有一个天然缺点:定时轮询有延迟(通常秒级)。如果你需要**毫秒级的延迟敏感型通知**(如支付结果实时推送到用户端),该怎么办?
+
+如果使用的是 RocketMQ,可以绕过 Outbox 模式直接用事务消息:
+
+```go
+// 发送事务消息
+txMsg := rocketmq.NewTransactionMessage("order-created", payload)
+localTx := &MyLocalTxChecker{}
+
+// half 消息发送 → 本地事务执行 → 提交/回查
+res, _ := producer.SendMessageInTransaction(txMsg, localTx)
+```
+
+RocketMQ 事务消息的完整交互流程:
+
+```mermaid
+sequenceDiagram
+ participant Producer as 生产者
+ participant Broker as MQ Broker
+ participant DB as 业务数据库
+ participant Consumer as 消费者
+
+ Producer->>Broker: ① 发送 Half Message (消费者不可见)
+ Broker-->>Producer: ACK
+
+ Producer->>DB: ② 执行本地事务
+ alt 本地事务成功
+ Producer->>Broker: ③a Commit (消息对消费者可见)
+ else 本地事务失败
+ Producer->>Broker: ③b Rollback (消息丢弃)
+ end
+
+ Note over Producer,Broker: ④ 如果步骤 2 超时未回复
+ Broker->>Producer: ④ 回查请求 (CheckLocalTx)
+ Producer->>DB: 查询本地事务状态
+ DB-->>Producer: 返回 COMMIT / ROLLBACK
+ Producer->>Broker: 回查结果
+```
+
+> [!important] 回查机制的意义
+> 当生产者执行本地事务过程中发生崩溃或网络中断,Broker 收不到 Commit/Rollback 指令,就会通过回查主动询问生产者。这确保了 **half 消息最终一定会收敛到可见或被丢弃**,不会出现悬空状态。
+
+**可靠性要点清单:**
+- ✅ 业务数据库和 Outbox 必须在**同一个本地事务**中写入
+- ✅ 消费者必须有幂等保护(否则至少一次投递 = 无限次重复)
+- ✅ 定时扫描任务需要设置最大重试次数,超过后进入人工处理
+- ✅ 生产环境建议配置 **DLQ(死信队列)**,将连续消费失败的消息归档
+
## 方案二:Saga 模式
Saga 适用于**跨多个服务的长流程操作**,将大事务拆成一系列本地小事务,每个步骤都有对应的补偿操作。
@@ -198,6 +230,81 @@ graph TB
> [!warning] 补偿操作的幂等性
> 补偿操作也必须幂等——CancelOrder 可能被触发多次。用订单状态的流转来保证(如只有 PENDING 才能转到 CANCELLED)。
+### 编排式实现示例 (Go)
+
+编排式的核心是一个 **Saga Coordinator**,它维护着当前步骤的执行状态和已完成的正向/反向操作列表:
+
+```go
+// Step 定义一个 Saga 步骤
+type Step struct {
+ Name string
+ Execute func(ctx context.Context) error // 正向操作
+ Compensation func(ctx context.Context) error // 补偿操作
+}
+
+// Orchestrator 编排器
+type Orchestrator struct {
+ steps []Step
+ executed []string // 已完成步骤名,用于补偿时逆向遍历
+}
+
+func (o *Orchestrator) AddStep(name string, exec, compens func(context.Context) error) {
+ o.steps = append(o.steps, Step{Name: name, Execute: exec, Compensation: compens})
+}
+
+// Execute 按顺序执行所有步骤,任一步骤失败则逆向补偿
+func (o *Orchestrator) Execute(ctx context.Context) error {
+ for i, step := range o.steps {
+ if err := step.Execute(ctx); err != nil {
+ log.Error("step failed", z.String("step", step.Name), z.Err(err))
+ // 逆向补偿:从后往前执行已完步骤的补偿操作
+ o.rollback(ctx, i-1)
+ return fmt.Errorf("saga failed at %s: %w", step.Name, err)
+ }
+ o.executed = append(o.executed, step.Name)
+ }
+ return nil
+}
+
+func (o *Orchestrator) rollback(ctx context.Context, upTo int) {
+ for i := len(o.executed) - 1; i >= 0 && i <= upTo; i-- {
+ // 找到对应步骤的补偿函数并执行
+ step := o.steps[i]
+ if err := step.Compensation(ctx); err != nil {
+ log.Warn("compensation also failed — need manual intervention", z.String("step", step.Name))
+ // 补偿失败必须告警!人工介入处理
+ }
+ }
+}
+```
+
+使用方式——构建一个下单 Saga:
+
+```go
+saga := &Orchestrator{}
+
+saga.AddStep("create_order",
+ func(ctx context.Context) error { return orderSvc.Create(ctx, req) }, // 正向
+ func(ctx context.Context) error { return orderSvc.Cancel(ctx, orderID) }, // 反向
+)
+saga.AddStep("reserve_stock",
+ func(ctx context.Context) error { return stockSvc.Reserve(ctx, productIDs) },
+ func(ctx context.Context) error { return stockSvc.Release(ctx, productIDs) },
+)
+saga.AddStep("charge_payment",
+ func(ctx context.Context) error { return paySvc.Charge(ctx, amount) },
+ func(ctx context.Context) error { return paySvc.Refund(ctx, orderID) },
+)
+
+err := saga.Execute(ctx)
+```
+
+> [!note] 关键理解:补偿的局限性
+>
+> - **补偿不是撤销**:取消订单不会把商品退回到原始状态,而是创建一条反向业务记录
+> - **补偿可能失败**:如果退款接口也挂了,你需要重试 + 告警 + 人工介入机制
+> - **时间窗口问题**:如果正向前向到了第 5 步、补偿到了第 2 步时第 2 步也失败了,第 3~4 步的正向操作已经发生且无法撤销
+
## 方案三:TCC (Try-Confirm-Cancel)
TCC 在每个事务步骤中实现三个接口:
@@ -242,6 +349,198 @@ sequenceDiagram
| 性能 | 中(需要两阶段提交) | 中高(单阶段本地事务) |
| 适用场景 | 资金、库存等高敏感业务 | 订单流程、审批流等业务链 |
+## 方案四:AT 模式 (Seata)
+
+> [!question] 什么时候该用 Seata?
+>
+> 如果你的团队**不愿或没有能力为每个业务方法编写 TCC 接口**,但又有跨库事务需求——Seata 的 AT 模式就是为此设计的。它是"零侵入"的最强候选方案。
+
+Seata AT 模式的核心原理是 **全局锁 + 二阶段提交**,业务代码不需要任何改动:
+
+```
+第一阶段(Global Lock):
+ 1. 拦截 SQL → 解析前后镜像
+ 2. 申请全局锁(锁住被修改的行)
+ 3. 本地事务执行(含 undo_log 写入)
+ 4. 提交本地事务(全局锁暂不释放)
+
+第二阶段(Global Commit/Rollback):
+ Commit: 异步删除 undo_log,释放全局锁
+ Rollback: 利用 undo_log 生成反向 SQL,回滚并释放全局锁
+```
+
+### AT 模式时序图
+
+```mermaid
+sequenceDiagram
+ participant TC as Transaction Coordinator
(Seata Server)
+ participant TM as Transaction Manager
(应用 A)
+ participant RS1 as Resource 1
(订单 DB)
+ participant RS2 as Resource 2
(库存 DB)
+
+ TM->>TC: 开启全局事务 (xid)
+ TM->>RS1: BEGIN; UPDATE orders SET ...
+ Note over RS1: 自动捕获 BEFORE/AFTER 镜像
写入 undo_log
+
+ TM->>RS2: BEGIN; UPDATE stock SET qty = qty - 1
+ Note over RS2: 同样写 undo_log
+
+ TM->>RS1: COMMIT (本地事务完成)
+ TM->>RS2: COMMIT (本地事务完成)
+
+ TM->>TC: 报告二阶段提交完成
+ TC-->>RS1: 异步清理 undo_log
+ TC-->>RS2: 异步清理 undo_log
+```
+
+### AT 与 XA 的区别
+
+很多人混淆 AT 和 XA——它们都是两阶段提交,但实现方式完全不同:
+
+| 维度 | XA 模式 | AT 模式 (Seata) |
+|------|---------|----------------|
+| **锁粒度** | 数据库级(长事务锁) | 行级(短期全局锁) |
+| **隔离级别** | 需要 SERIALIZABLE | 基于脏读 + 全局锁 |
+| **性能** | 较差(锁持有时间长) | 较好(本地事务快速提交后异步清理) |
+| **侵入性** | 需配置 DataSource Proxy | 零侵入,自动代理 JDBC |
+
+### 使用注意事项
+
+```go
+// Seata Go SDK — 几乎零侵入
+import "github.com/seata/seata-sdk-go-client/tx"
+
+func CreateOrder(ctx context.Context, req *Request) error {
+ // 只需添加这一个注解
+ tx.GlobalTransaction()
+
+ // 下面的代码完全不变——Seata 自动代理数据源
+ orderRepo.Create(ctx, &order)
+ stockRepo.Decrease(ctx, productID, qty)
+
+ return nil
+}
+```
+
+> [!warning] 生产环境避坑清单
+>
+> 1. **undo_log 表必须建**:`seata_undo_log` 是 Seata 自动创建的,用于存储前后镜像。务必保证这个表可用(通常和业务库在同一实例)
+> 2. **不要混用本地事务和全局事务**:同一个方法里同时出现 `@Transactional` 和 `@GlobalTransactional` 会导致行为不可预测
+> 3. **异常处理**:全局事务中抛出的异常会被 Seata 捕获并触发回滚,确保异常能正确向上传递
+> 4. **性能考量**:全局锁虽然比 XA 短,但在高并发场景下仍然是瓶颈。**优先用 MQ 最终一致,实在做不到再上 AT**
+> 5. **只支持部分数据库**:MySQL、PostgreSQL、Oracle 支持良好;TiDB 等 NewSQL 数据库兼容性有限
+
+> [!tip] 选型建议
+>
+> AT 模式最适用的场景:**遗留系统微服务化改造**。当原有单体拆分成多个服务时,原有的 `@Transactional` 跨库调用突然失效,引入 AT 模式可以快速过渡——等业务稳定后再逐步迁移到 MQ 事件驱动架构。
+
+## 补充保障:重试与死信队列
+
+分布式系统中,**没有哪个组件永远可靠**。消息消费失败、网络闪断、下游超时——这些都不可避免。你需要一套完整的错误处理链路:
+
+```mermaid
+flowchart LR
+ MQ["消息队列"] --> C["消费者"]
+ C --> FAIL{消费成功}
+ FAIL -- 是 --> OK["正常结束"]
+ FAIL -- 否 --> RETRY{重试次数未满}
+ RETRY -- 是 --> DELAY["延迟重试
指数退避"]
+ DELAY --> C
+ RETRY -- 否 --> DLQ["进入死信队列
人工介入"]
+
+ DLQ --> ALERT["告警通知"]
+ DLQ --> REPLAY["手动重放或修复后补发"]
+```
+
+### 三种重试策略对比
+
+| 策略 | 适用场景 | 示例 |
+|------|---------|------|
+| **立即重试** | 瞬时故障(连接池未就绪) | 等 100ms 再试 3 次 |
+| **延迟重试** | 需要时间窗口等待恢复 | 1s → 2s → 4s → 8s |
+| **定时任务补偿** | 批量漏掉的消息 | 每 5 分钟扫描 pending 表 |
+
+### 死信队列 (DLQ) 设计
+
+```go
+// 消费者主循环:带重试和 DLQ 保护
+func (c *consumer) Process(ctx context.Context, msg Message) error {
+ for attempt := 0; attempt < c.maxRetries; attempt++ {
+ err := c.handle(ctx, msg)
+ if err == nil {
+ return nil // 成功
+ }
+
+ if isRetryable(err) {
+ time.Sleep(time.Duration(attempt+1) * time.Second)
+ continue // 重试
+ }
+
+ // 非可重试错误,直接进 DLQ
+ return c.sendToDLQ(msg, err)
+ }
+
+ // 超过最大重试次数,进 DLQ
+ return c.sendToDLQ(msg, fmt.Errorf("exhausted %d retries", c.maxRetries))
+}
+```
+
+> [!quote] 运维心态
+>
+> "一个没有死信队列的消息消费者,就像一辆没有备胎的车——任何一次不可预期的故障都会让你抛锚在路上。"
+
+## 如何选择分布式事务方案?
+
+> [!question]- 决策树
+>
+> 面对跨服务的数据一致性问题,你不需要每次都重新选型。按照下面的思路逐层判断即可:
+
+```mermaid
+flowchart TD
+ START["跨服务写操作"] --> Q1{核心资金或资产}
+
+ Q1 -- 是且要求强一致 --> TCC["TCC"]
+ Q1 -- 否 --> Q2{流程步数超过5}
+
+ Q2 -- 是长流程 --> SAGA["Saga"]
+ Q2 -- 否简单链路 --> Q3{容忍秒级延迟}
+
+ Q3 -- 可以 --> MQ["本地事务+MQ(Outbox)"]
+ Q3 -- 不可以毫秒级 --> TXMSG["RocketMQ事务消息"]
+
+ Q3 -- 无法接受最终一致 --> SEATA["Seata AT模式"]
+
+ TCC --> END["完成选择"]
+ SAGA --> END
+ MQ --> END
+ TXMSG --> END
+ SEATA --> WARNING["过渡方案,后续应迁移至事件驱动架构"]
+ WARNING --> END
+
+ style TCC fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
+ style SAGA fill:#fff3e0,stroke:#ff9800,stroke-width:2px
+ style MQ fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
+ style TXMSG fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
+ style SEATA fill:#fce4ec,stroke:#e91e63,stroke-width:2px
+```
+
+### 决策速查矩阵
+
+| 你的业务特征 | 推荐方案 | 理由 |
+|-------------|---------|------|
+| 日订单量百万级,通知类场景 | Outbox + MQ | 性能最好,开发成本最低 |
+| 支付/清算/账务核心链路 | TCC | 资源占用期间不允许其他事务修改 |
+| 审批流、物流状态流转(多步) | Saga 编排式 | 流程清晰,补偿机制完备 |
+| 已有 RocketMQ,对延迟敏感 | 事务消息 | 绕过 Outbox 轮询延迟 |
+| 遗留系统快速拆分 | Seata AT | 零侵入,快速过渡 |
+| 只需要"最终一致" | 本地事务 + MQ | 80% 的场景这就是答案 |
+
+> [!tip] 黄金法则
+>
+> **"能用异步事件解决的,不要用两阶段提交;能本地事务 + MQ 解决的,不要上框架。"**
+>
+> 复杂度越高,出问题的概率越大。每一层抽象都引入了新的故障点——协调器挂了怎么办?补偿逻辑写错了怎么发现?回滚又失败了谁来做?保持简单是最好的工程纪律。
+
## 关联笔记
- [[03-数据一致性/01-数据库拆分]] — 数据库拆分是分布式事务的前提
diff --git a/hhs/MS/03-数据一致性/03-ID生成.md b/hhs/MS/03-数据一致性/03-ID生成.md
index 0f476e5..439c86e 100644
--- a/hhs/MS/03-数据一致性/03-ID生成.md
+++ b/hhs/MS/03-数据一致性/03-ID生成.md
@@ -1,6 +1,6 @@
---
tags: [microservice, id-generation, snowflake, uuid, distributed-id]
-create time: 2026-05-05
+create time: 2026-05-05 12:00
---
# 分布式 ID 生成
diff --git a/hhs/MS/05-部署运维/01-容器化.md b/hhs/MS/05-部署运维/01-容器化.md
index e8014d0..c658d8c 100644
--- a/hhs/MS/05-部署运维/01-容器化.md
+++ b/hhs/MS/05-部署运维/01-容器化.md
@@ -1,76 +1,37 @@
---
tags: [microservice, docker, container, image-optimization]
-create time: 2026-05-05
+create time: 2026-05-05 15:30
---
-# 容器化
+# 容器化 — 参考手册
## 概述
Docker 容器是微服务交付的标准单元。它解决了"在我机器上是好的"这个经典问题——**开发、测试、生产使用完全一致的运行时环境**。
+本文档是总入口:核心概念、决策矩阵、速查表见本页;详细教程和实操指南在子文档中。
+
```mermaid
graph TB
CODE["源代码"] --> BUILD["CI 构建镜像"]
- BUILD --> REGISTRY["镜像仓库
Harbor / ECR / ACR"]
+ BUILD --> SCAN["安全扫描
Trivy / Snyk"]
+ SCAN --> REGISTRY["镜像仓库
Harbor / ECR / ACR"]
REGISTRY --> K8s["Kubernetes 部署"]
-
+
style BUILD fill:#e3f2fd
style REGISTRY fill:#fff3e0
style K8s fill:#e8f5e9
```
-## Dockerfile 最佳实践
+## 快速导航
-### Go 多阶段构建(推荐)
+| 主题 | 定位 | 文档 |
+|------|------|------|
+| Dockerfile 最佳实践 | **教程**:多阶段构建、BuildKit、缓存技巧 | [[01-容器化/01-Dockerfile最佳实践]] |
+| 多架构构建 | **进阶**:Buildx、跨平台编译 | [[01-容器化/02-多架构构建]] |
+| 镜像安全 | **安全**:漏洞扫描、distroless、签名 | [[01-容器化/03-镜像安全]] |
-```dockerfile
-# ========== 阶段 1: 构建 ==========
-FROM golang:1.22-alpine AS builder
-
-RUN apk --no-cache add git ca-certificates
-
-WORKDIR /app
-COPY go.mod go.sum ./
-RUN go mod download
-
-COPY . .
-ARG LDFLAGS="-s -w -extldflags '-static'"
-RUN CGO_ENABLED=0 GOOS=linux go build -ldflags "$LDFLAGS" -o server .
-
-# ========== 阶段 2: 运行时 ==========
-FROM alpine:latest
-
-RUN apk --no-cache add ca-certificates tzdata && \
- cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
- echo "Asia/Shanghai" > /etc/timezone
-
-RUN addgroup -S appgroup && adduser -S appuser -G appgroup
-USER appuser
-
-WORKDIR /app
-COPY --from=builder /app/server .
-
-EXPOSE 8080
-
-HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
- CMD wget -qO- http://localhost:8080/healthz || exit 1
-
-CMD ["./server"]
-```
-
-### 关键优化点
-
-| 优化项 | 方法 | 效果 |
-|--------|------|------|
-| **多阶段构建** | 编译和运行分离 | 镜像从 800MB → 15MB |
-| **alpine 基础镜像** | 替代 debian/ubuntu | 减小体积 |
-| **非 root 运行** | `USER appuser` | 安全合规 |
-| **静态链接** | `CGO_ENABLED=0` | 不依赖系统库 |
-| **layer cache** | `go.mod` 先 COPY | CI 加速构建 |
-| **健康检查** | HEALTHCHECK 指令 | K8s 原生支持 |
-
-### `.dockerignore`
+## .dockerignore 模板
```
.git
@@ -86,62 +47,35 @@ tests/
> [!tip] 为什么 .dockerignore 很重要?
>
-> 如果不排除 `.git` 目录,整个版本历史都会被打包进镜像(增加数百 MB)。如果排除不当,可能遗漏必要的配置文件。
+> 如果不排除 `.git` 目录,整个版本历史都会被打包进镜像(增加数百 MB)。如果排除不当,可能遗漏必要的配置文件。建议每个项目根目录都包含此文件。
-## 镜像安全
+## 层缓存核心原则
-```mermaid
-flowchart LR
- Scan["镜像扫描 (Trivy/Snyk)"] --> Clean{"漏洞等级?"}
-
- Clean -- "CRITICAL/HIGH" --> Block["❌ 阻止推送"]
- Clean -- "MEDIUM" --> Review["⚠️ 人工审核"]
- Clean -- "LOW/INFO" --> Allow["✅ 允许推送"]
-
- style Block fill:#ffebee
- style Review fill:#fff3e0
- style Allow fill:#e8f5e9
-```
+> **经常变动的内容靠近 COPY,少变的放前面**。
+>
+> ```dockerfile
+> # ✅ 好:只有 go.mod/go.sum 变化时才重新下载依赖
+> COPY go.mod go.sum ./
+> RUN go mod download
+> COPY . .
+> RUN go build
+> ```
-### 安全检查清单
+## 基础镜像选型
-- [ ] 不使用 `latest` tag(永远用具体版本号)
-- [ ] 不安装不必要的软件包(`apk del --purge .build-deps`)
-- [ ] 定期更新基础镜像(修复 CVE)
-- [ ] 使用非 root 用户运行
-- [ ] 镜像不包含密钥、密码、私钥
-- [ ] 使用最小基础镜像(scratch / distroless)
+| 基础镜像 | 典型 Go 服务大小 | 适用场景 |
+|---------|-----------------|---------|
+| `ubuntu:22.04` | ~35 MB | 兼容性要求高 |
+| `debian:bookworm-slim` | ~20 MB | 需要 glibc 的动态链接程序 |
+| `alpine:3.19` | ~7 MB | 小体积优先,注意 musl libc 兼容 |
+| `distroless/static` | ~5 MB | 生产推荐,无 shell |
+| `scratch` | <取决于二进制 | 仅静态链接二进制可运行 |
-### Distroless 镜像
+> [!warning] Alpine 与 musl libc
+>
+> Alpine 使用 musl libc 而非 glibc。某些 C 扩展(如 `node-gyp`、部分 Python wheel)可能无法编译或运行时行为不一致。Go 服务通过 `CGO_ENABLED=0` 规避了此问题,但 Node.js/Python 应用需谨慎评估。
-Google 推出的**无 shell、无包管理器**的极简运行时镜像:
-
-```dockerfile
-# 最极致的精简
-FROM gcr.io/distroless/static-debian12
-COPY --from=builder /app/server .
-USER nonroot
-CMD ["./server"]
-```
-
-> ⚠️ 注意:distroless 镜像没有 shell (`/bin/sh`),调试时需要额外工具(如 debug 镜像或 `kubectl exec` 到 sidecar)。
-
-## 镜像仓库管理
-
-```mermaid
-flowchart LR
- Dev["开发者本地"] -->|"docker push"| DEV_REPO["dev 仓库
v1.2.3-dev"]
-
- Staging["Staging 验证"] -->|"通过"| PROD_REPO["prod 仓库
v1.2.3"]
-
- K8s["K8s Cluster"] -->|"pull"| PROD_REPO
-
- PR["PR Merge"] --> Tag["打标签 v1.2.3"]
- Tag --> ProdRepoMove["移动到 prod 仓库"]
-
- style DEV_REPO fill:#fff3e0
- style PROD_REPO fill:#e8f5e9
-```
+## 镜像仓库方案对比
| 仓库方案 | 特点 | 适用场景 |
|---------|------|---------|
@@ -149,7 +83,26 @@ flowchart LR
| **ECR / ACR / GCR** | 云厂商托管 | 全云环境 |
| **Docker Hub** | 公共免费 | 开源项目 |
+## BuildKit 特性速查
+
+```bash
+export DOCKER_BUILDKIT=1
+docker build --cache-from=harbor.example.com/app/cache:latest .
+docker build --ssh default .
+docker build --secret id=token,env=GITHUB_TOKEN .
+```
+
+## 常见问题排查
+
+| 症状 | 原因 | 解决 |
+|------|------|------|
+| `exec: "app": not found` | 多阶段 COPY 路径错误 | 检查绝对路径和文件名拼写 |
+| `standard_init_linux.go: exec user process caused: permission denied` | 没有执行权限或缺少换行符 | `chmod +x`, 确保 Linux 换行 |
+| `service unavailable` (K8s) | HEALTHCHECK 未就绪 | 增大 `--start-period` |
+| 镜像体积异常大 | `.git` 未排除或多层 RUN 未清理 | 检查 `.dockerignore`,合并 RUN 并 `rm -rf` |
+| `cannot execute: executable file not found` | 架构不匹配(arm64 → amd64) | 确认 `TARGETARCH` 或手动指定 `--platform` |
+
## 关联笔记
-- [[05-部署运维/02-Kubernetes]] — K8s 以 Pod 为部署单元,镜像来自 Docker
-- [[05-部署运维/03-CICD与GitOps]] — CI/CD 流水线中的镜像构建环节
+- [[hhs/MS/05-部署运维/02-Kubernetes]] — K8s 以 Pod 为部署单元,镜像来自 Docker
+- [[hhs/MS/05-部署运维/03-CICD与GitOps]] — CI/CD 流水线中的镜像构建环节
diff --git a/hhs/MS/05-部署运维/01-容器化/01-Dockerfile最佳实践.md b/hhs/MS/05-部署运维/01-容器化/01-Dockerfile最佳实践.md
new file mode 100644
index 0000000..b88c345
--- /dev/null
+++ b/hhs/MS/05-部署运维/01-容器化/01-Dockerfile最佳实践.md
@@ -0,0 +1,150 @@
+---
+tags: [docker, container, dockerfile, buildkit, image-optimization]
+create time: 2026-05-18 00:45
+---
+
+# Dockerfile 最佳实践 — 教程
+
+## 概述
+
+Dockerfile 是微服务交付的标准起点。本文档从多阶段构建出发,详解层缓存技巧、BuildKit 高级特性和常见问题排查。更多话题:多架构构建见 [[../01-容器化/02-多架构构建]],镜像安全见 [[../01-容器化/03-镜像安全]]。
+
+## Go 多阶段构建(推荐)
+
+```dockerfile
+# ========== 阶段 1: 构建 ==========
+FROM golang:1.22-alpine AS builder
+
+RUN apk --no-cache add git ca-certificates
+
+WORKDIR /app
+COPY go.mod go.sum ./
+RUN go mod download
+
+COPY . .
+ARG LDFLAGS="-s -w -extldflags '-static'"
+RUN CGO_ENABLED=0 GOOS=linux go build -ldflags "$LDFLAGS" -o server .
+
+# ========== 阶段 2: 运行时 ==========
+FROM alpine:latest
+
+RUN apk --no-cache add ca-certificates tzdata && \
+ cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
+ echo "Asia/Shanghai" > /etc/timezone
+
+RUN addgroup -S appgroup && adduser -S appuser -G appgroup
+USER appuser
+
+WORKDIR /app
+COPY --from=builder /app/server .
+
+EXPOSE 8080
+
+HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
+ CMD wget -qO- http://localhost:8080/healthz || exit 1
+
+CMD ["./server"]
+```
+
+> [!question] 为什么要用多阶段构建?
+>
+> 单阶段构建中,编译工具和源码都在最终镜像里——一个 Go 项目的镜像轻松超过 800MB。**多阶段构建**把构建和运行拆成两个独立的镜像层,第二阶段只 COPY 二进制文件,最终镜像缩小到十几 MB。
+
+## 关键优化点速查
+
+| 优化项 | 方法 | 效果 |
+|--------|------|------|
+| **多阶段构建** | 编译和运行分离 | 镜像从 800MB → 15MB |
+| **alpine 基础镜像** | 替代 debian/ubuntu | 减小体积 |
+| **非 root 运行** | `USER appuser` | 安全合规 |
+| **静态链接** | `CGO_ENABLED=0` | 不依赖系统库 |
+| **layer cache** | `go.mod` 先 COPY | CI 加速构建 |
+| **健康检查** | HEALTHCHECK 指令 | K8s 原生支持 |
+
+## `.dockerignore` — 别忽略的文件
+
+```
+.git
+.gitignore
+*.md
+vendor/
+tests/
+*.log
+.DS_Store
+.idea/
+.vscode/
+```
+
+> [!tip] 为什么 .dockerignore 很重要?
+>
+> 如果不排除 `.git` 目录,整个版本历史都会被打包进镜像(增加数百 MB)。如果排除不当,可能遗漏必要的配置文件。建议每个项目根目录都包含此文件。
+
+## 层缓存技巧
+
+### ❌ 差的缓存策略
+
+```dockerfile
+# 差:任何文件改动都会让后续所有 layer 失效
+COPY . .
+RUN go build
+```
+
+### ✅ 好的缓存策略
+
+```dockerfile
+# 好:只有 go.mod/go.sum 变化时才重新下载依赖
+COPY go.mod go.sum ./
+RUN go mod download
+COPY . .
+RUN go build
+```
+
+核心原则:**经常变动的内容靠近 COPY,少变的放前面**。这样当代码频繁变更时,依赖下载和构建步骤仍能命中缓存。
+
+## BuildKit 特性
+
+BuildKit 是新一代构建引擎,默认在 Docker 18.09+ 和所有 Docker Desktop 中启用:
+
+```bash
+# 启用 BuildKit 加速
+export DOCKER_BUILDKIT=1
+
+# 利用远程缓存 (需配合 registry)
+docker build --cache-from=harbor.example.com/app/cache:latest .
+
+# SSH agent forwarding(拉取私有依赖用)
+docker build --ssh default .
+
+# 秘密变量注入(不进 Docker history)
+docker build --secret id=token,env=GITHUB_TOKEN .
+```
+
+```dockerfile
+# syntax=docker/dockerfile:1
+# ^^^ 必须声明语法版本以启用 BuildKit
+
+FROM golang:1.22-alpine AS builder
+RUN --mount=type=cache,target=/go/pkg/mod \
+ --mount=type=cache,target=/root/.cache/go-build \
+ go mod download && go build -o server .
+```
+
+> [!info] `--mount=type=cache` vs RUN 缓存
+>
+> Docker 默认的层缓存会在源文件变化时跳过整层;而 `type=cache` 是增量更新本地缓存目录,不受构建上下文变化影响,适合依赖下载和编译缓存。
+
+## 常见问题排查
+
+| 症状 | 原因 | 解决 |
+|------|------|------|
+| `exec: "app": not found` | 多阶段 COPY 路径错误 | 检查绝对路径和文件名拼写 |
+| `standard_init_linux.go: exec user process caused: permission denied` | 没有执行权限或缺少换行符 | `chmod +x`, 确保 Linux 换行 |
+| `service unavailable` (K8s) | HEALTHCHECK 未就绪 | 增大 `--start-period` |
+| 镜像体积异常大 | `.git` 未排除或多层 RUN 未清理 | 检查 `.dockerignore`,合并 RUN 并 `rm -rf` |
+| `cannot execute: executable file not found` | 架构不匹配(arm64 → amd64) | 确认 `TARGETARCH` 或手动指定 `--platform` |
+
+## 关联笔记
+
+- [[../01-容器化/02-多架构构建]] — 一次构建全平台镜像
+- [[../01-容器化/03-镜像安全]] — Trivy 扫描与 distroless 镜像
+- [[../hhs/MS/05-部署运维/02-Kubernetes]] — K8s 以 Pod 为部署单元,镜像来自 Docker
diff --git a/hhs/MS/05-部署运维/01-容器化/02-多架构构建.md b/hhs/MS/05-部署运维/01-容器化/02-多架构构建.md
new file mode 100644
index 0000000..b4a7fac
--- /dev/null
+++ b/hhs/MS/05-部署运维/01-容器化/02-多架构构建.md
@@ -0,0 +1,91 @@
+---
+tags: [docker, multi-arch, buildx, cross-platform]
+create time: 2026-05-18 00:45
+---
+
+# 多架构构建 — Buildx 实战
+
+## 概述
+
+随着 Apple Silicon 和 ARM 服务器普及,一个平台只能构建一种架构的镜像已无法满足需求。Docker Buildx 让我们**一次构建、全平台运行**。本文档讲解多架构构建的核心操作和原理。
+
+## Buildx 快速上手
+
+```bash
+# 启用 buildx 并创建 multi-platform builder
+docker buildx create --name mybuilder --use
+docker buildx inspect --bootstrap
+```
+
+## 跨平台构建推送
+
+```bash
+# 为 amd64 + arm64 同时构建并推送到仓库
+docker buildx build \
+ --platform linux/amd64,linux/arm64 \
+ -t harbor.example.com/app/server:v1.2.3 \
+ --push \
+ .
+```
+
+> [!note] manifest list 原理
+>
+> 多架构镜像本质上是一个 **manifest list**——它不直接包含二进制文件,而是记录每个架构对应的镜像 digest。拉取时,客户端根据自身平台自动选择正确的变体。可以用 `docker buildx imagetools inspect ` 查看完整列表。
+
+## 不同场景的构建策略
+
+| 场景 | 策略 | 命令 |
+|------|------|------|
+| 开发阶段 | 只构建本机架构 (`linux/arm64`),速度最快 | `docker buildx build --platform linux/arm64 .` |
+| CI 流水线 | 构建全平台 (`linux/amd64,linux/arm64`),统一推送 | `--platform linux/amd64,linux/arm64 --push` |
+| 临时调试 | `--load` 仅构建本地可用 | `docker buildx build --load .` |
+
+## Go 跨平台编译提示
+
+在 Dockerfile 中使用 `ARG TARGETARCH`(Buildx 自动传入):
+
+```dockerfile
+FROM golang:1.22-alpine AS builder
+
+ARG TARGETARCH
+ARG TARGETPLATFORM
+
+RUN echo "Building for: $TARGETARCH ($TARGETPLATFORM)"
+
+WORKDIR /app
+COPY go.mod go.sum ./
+RUN go mod download
+
+COPY . .
+RUN CGO_ENABLED=0 GOOS=linux GOARCH=$TARGETARCH \
+ go build -a -ldflags "-s -w" -o server-$TARGETARCH .
+```
+
+> [!info] 环境变量对照
+>
+> | 变量 | 示例值 | 说明 |
+> |------|--------|------|
+> | `TARGETARCH` | `amd64`, `arm64` | CPU 架构 |
+> | `TARGETOS` | `linux`, `darwin` | 操作系统 |
+> | `TARGETPLATFORM` | `linux/amd64`, `linux/arm64` | 完整平台标识 |
+
+## 补充:QEMU 模拟(CI 中的备用方案)
+
+当你的 CI 环境不支持 Buildx QEMU 插件时,可以手动安装:
+
+```bash
+# 注册 QEMU 模拟器
+docker run --privileged --rm tonistiigi/binfmt --install all
+
+# 之后即可通过 qemu 模拟不同平台构建(速度较慢)
+docker buildx build --platform linux/arm64 -t my-app:arm64 .
+```
+
+> [!warning] 模拟构建 vs 原生交叉编译
+>
+> QEMU 模拟可以运行在其他架构上,但构建速度极慢(通常比原生慢 5~10 倍)。生产环境推荐使用 **Go 原生交叉编译**(`GOOS=linux GOARCH=arm64`),零依赖且秒级完成。只在需要 C 扩展的场景才考虑 QEMU 模拟。
+
+## 关联笔记
+
+- [[../01-容器化/01-Dockerfile最佳实践]] — 多阶段构建 + BuildKit 特性
+- [[../hhs/MS/05-部署运维/02-Kubernetes]] — K8s 节点混合架构时的调度考量
diff --git a/hhs/MS/05-部署运维/01-容器化/03-镜像安全.md b/hhs/MS/05-部署运维/01-容器化/03-镜像安全.md
new file mode 100644
index 0000000..7979729
--- /dev/null
+++ b/hhs/MS/05-部署运维/01-容器化/03-镜像安全.md
@@ -0,0 +1,150 @@
+---
+tags: [docker, image-security, trivy, distroless, registry]
+create time: 2026-05-18 00:45
+---
+
+# 镜像安全 — 扫描、基线 & 仓库管理
+
+## 概述
+
+镜像安全不仅仅是"不要有 CVE"——它是一个完整的供应链安全体系,涵盖基础镜像选型、漏洞扫描、仓库分级管理和推送门禁。更多内容参见 [[../01-容器化/01-Dockerfile最佳实践]]。
+
+## 镜像扫描流程
+
+```mermaid
+flowchart LR
+ Scan["镜像扫描 (Trivy/Snyk)"] --> Clean{"漏洞等级?"}
+
+ Clean -- "CRITICAL/HIGH" --> Block["❌ 阻止推送"]
+ Clean -- "MEDIUM" --> Review["⚠️ 人工审核"]
+ Clean -- "LOW/INFO" --> Allow["✅ 允许推送"]
+
+ style Block fill:#ffebee
+ style Review fill:#fff3e0
+ style Allow fill:#e8f5e9
+```
+
+### Trivy 常用命令
+
+```bash
+# 扫描镜像
+trivy image harbor.example.com/app/server:v1.2.3
+
+# 只报告 HIGH 及以上级别
+trivy image --severity HIGH,CRITICAL harbor.example.com/app/server:v1.2.3
+
+# 输出 JSON 供 CI 集成
+trivy image --format json --output report.json harbor.example.com/app/server:v1.2.3
+
+# 扫描 Dockerfile 中的潜在问题
+trivy config Dockerfile
+```
+
+## 安全检查清单
+
+- [ ] 不使用 `latest` tag(永远用具体版本号)→ 精确回滚
+- [ ] 不安装不必要的软件包(`apk del --purge .build-deps`)→ 最小攻击面
+- [ ] 定期更新基础镜像(修复 CVE)→ 跟上安全补丁节奏
+- [ ] 使用非 root 用户运行 → `USER appuser`
+- [ ] 镜像不包含密钥、密码、私钥 → CI/CD Secret 挂载代替 ENV
+- [ ] 使用最小基础镜像(scratch / distroless)→ 减少暴露面
+
+## Distroless 镜像
+
+Google 推出的**无 shell、无包管理器**的极简运行时镜像:
+
+```dockerfile
+# 最极致的精简
+FROM gcr.io/distroless/static-debian12
+COPY --from=builder /app/server .
+USER nonroot
+CMD ["./server"]
+```
+
+> ⚠️ 注意:distroless 镜像没有 shell (`/bin/sh`),调试时需要额外工具(如 debug 镜像或 `kubectl exec` 到 sidecar)。
+
+### 常用 Distroless 变种
+
+| 镜像 | 适用场景 | 大小 |
+|------|---------|------|
+| `gcr.io/distroless/static-debian12` | 纯静态二进制 | ~2MB |
+| `gcr.io/distroless/base-debian12` | 需要 glibc 的动态链接程序 | ~30MB |
+| `gcr.io/distroless/java17` | Java 应用(含 JRE) | ~150MB |
+| `gcr.io/distroless/nodejs18` | Node.js 应用 | ~100MB |
+
+## 镜像压缩对比参考
+
+| 基础镜像 | 典型 Go 服务大小 | 备注 |
+|---------|-----------------|------|
+| `ubuntu:22.04` | ~35 MB | 完整发行版 |
+| `debian:bookworm-slim` | ~20 MB | 裁剪后较常用 |
+| `alpine:3.19` | ~7 MB | musl libc,偶有兼容问题 |
+| `distroless/static` | ~5 MB | 无 shell,生产推荐 |
+| `scratch` | <取决于二进制 | 仅静态链接二进制可运行 |
+
+> [!warning] Alpine 与 musl libc
+>
+> Alpine 使用 musl libc 而非 glibc。某些 C 扩展(如 `node-gyp`、部分 Python wheel)可能无法编译或运行时行为不一致。Go 服务通过 `CGO_ENABLED=0` 规避了此问题,但 Node.js/Python 应用需谨慎评估。
+
+## 镜像仓库管理与流转
+
+```mermaid
+flowchart LR
+ Dev["开发者本地"] -->|"docker push"| DEV_REPO["dev 仓库
v1.2.3-dev"]
+
+ Staging["Staging 验证"] -->|"通过"| PROD_REPO["prod 仓库
v1.2.3"]
+
+ K8s["K8s Cluster"] -->|"pull"| PROD_REPO
+
+ PR["PR Merge"] --> Tag["打标签 v1.2.3"]
+ Tag --> ProdRepoMove["移动到 prod 仓库"]
+
+ style DEV_REPO fill:#fff3e0
+ style PROD_REPO fill:#e8f5e9
+```
+
+| 仓库方案 | 特点 | 适用场景 |
+|---------|------|---------|
+| **Harbor** | 自托管,RBAC + 扫描 | 国内企业首选 |
+| **ECR / ACR / GCR** | 云厂商托管 | 全云环境 |
+| **Docker Hub** | 公共免费 | 开源项目 |
+
+### Harbor 最佳实践
+
+| 实践 | 说明 |
+|------|------|
+| **项目隔离** | dev/prod 独立项目,不同团队有不同权限 |
+| **自动扫描** | 推送时自动触发 Trivy 扫描,阻断高危漏洞 |
+| **复制规则** | dev 仓库 → prod 仓库自动复制已通过测试的镜像 |
+| **生命周期策略** | 自动清理过期镜像和垃圾数据 |
+
+## 补充:镜像签名与 Cosign
+
+为了防止中间人攻击或被篡改的镜像被拉到集群,可以使用 Sigstore Cosign 对镜像签名:
+
+```bash
+# 签名镜像
+cosign sign --key cosign.key harbor.example.com/app/server:v1.2.3
+
+# 在 CI 中验证签名
+cosign verify --key cosign.pub harbor.example.com/app/server:v1.2.3
+
+# 不依赖密钥的验证( rekor 透明日志)
+cosign verify --certificate-identity=email@company.com \
+ --certificate-oidc-issuer=https://accounts.google.com \
+ harbor.example.com/app/server:v1.2.3
+```
+
+> [!info] 镜像签名 vs 漏洞扫描
+>
+> 两者解决不同问题:
+> - **漏洞扫描** = 这个镜像有没有已知漏洞?
+> - **镜像签名** = 这个镜像确实是我们构建的,没有被篡改?
+>
+> 生产环境应该两者都有。签名可以作为 Gatekeeper/Admission Webhook 的一部分,在 Pod 启动前强制验证镜像签名。
+
+## 关联笔记
+
+- [[../01-容器化/01-Dockerfile最佳实践]] — Dockerfile 中的安全加固
+- [[../01-容器化/02-多架构构建]] — Buildx 构建全平台镜像
+- [[../hhs/MS/05-部署运维/03-CICD与GitOps]] — CI 流水线中的安全扫描环节
diff --git a/hhs/MS/05-部署运维/02-Kubernetes.md b/hhs/MS/05-部署运维/02-Kubernetes.md
index 4e1d4cb..9f0e7bb 100644
--- a/hhs/MS/05-部署运维/02-Kubernetes.md
+++ b/hhs/MS/05-部署运维/02-Kubernetes.md
@@ -1,14 +1,16 @@
---
tags: [microservice, kubernetes, k8s, container-orchestration]
-create time: 2026-05-05
+create time: 2026-05-05 18:30
---
-# Kubernetes
+# Kubernetes — 参考手册
## 概述
Kubernetes (K8s) 是微服务架构的事实标准编排引擎。它将容器化的服务组织成声明式的资源对象,自动处理部署、扩展、故障恢复。
+本文档是总入口:顶层概念、决策矩阵、速查表见本页;详细教程和实操指南在子文档中。
+
```mermaid
graph TB
subgraph CLUSTER["K8s Cluster"]
@@ -41,226 +43,99 @@ graph TB
style N2 fill:#fff3e0
```
+## 快速导航
+
+| 主题 | 定位 | 文档 |
+|------|------|------|
+| 核心概念与 Deployment | **教程**:Pod/Deployment/Probe、滚动更新 | [[02-Kubernetes/01-核心概念与Deployment]] |
+| 配置管理 | **操作手册**:ConfigMap/Secret 注入、热更新 | [[02-Kubernetes/02-配置管理]] |
+| 网络与服务发现 | **教程**:Service 类型、Ingress、Headless | [[02-Kubernetes/03-网络与服务发现]] |
+| 扩缩容与有状态应用 | **教程**:HPA / VPA / StatefulSet | [[02-Kubernetes/04-扩缩容与有状态应用]] |
+| 调度控制 | **进阶**:Affinity/Taint/TopologySpread | [[02-Kubernetes/05-调度控制]] |
+| 资源与安全管控 | **安全**:Quota/LimitRange/NetworkPolicy | [[02-Kubernetes/06-资源与安全管控]] |
+| 运维排查 | **速查**:上线清单、诊断命令、FAQ | [[02-Kubernetes/07-运维排查]] |
+
## 核心概念速查
| K8s 对象 | 用途 | 类比 |
|---------|------|------|
| **Pod** | 最小部署单元,包含一个或多个容器 | 应用实例 |
| **Deployment** | 管理 Pod 的副本数和滚动更新 | 应用的"模板" |
-| **Service** | 稳定的网络入口,负载均衡 | 内部 VIP |
-| **Ingress** | HTTP/HTTPS 路由规则 | 外部网关 |
-| **ConfigMap** | 配置注入(明文) | 环境变量/配置文件 |
-| **Secret** | 敏感配置注入(base64) | 密码/API Key |
-| **HPA** | 根据指标自动扩缩容 | 弹性伸缩 |
-| **StatefulSet** | 有状态应用的有序管理 | DB、ZK |
-| **Job/CronJob** | 一次性任务 / 定时任务 | 批处理 |
+| **Service** | 稳定的网络入口,负载均衡 | 内部 VIP → [[02-Kubernetes/03-网络与服务发现]] |
+| **Ingress** | HTTP/HTTPS 路由规则 | 外部网关 → [[02-Kubernetes/03-网络与服务发现]] |
+| **ConfigMap** | 配置注入(明文) → [[02-Kubernetes/02-配置管理]] |
+| **Secret** | 敏感配置注入(base64)→ [[02-Kubernetes/02-配置管理]] |
+| **HPA** | 根据指标自动扩缩容 → [[02-Kubernetes/04-扩缩容与有状态应用]] |
+| **StatefulSet** | 有状态应用的有序管理 | DB、ZK → [[02-Kubernetes/04-扩缩容与有状态应用]] |
-## Deployment 详解
+## 部署驱动模式对比
-### 完整示例
+| 维度 | 传统 CI/CD (Push) | GitOps (Pull) |
+|------|------------------|---------------|
+| 部署驱动 | CI 服务器主动推送 | Git 仓库变动触发拉取 |
+| 状态源 | CI pipeline 历史 | Git commit history |
+| 漂移检测 | 通常无 | 持续比对,自动修复 |
+| 安全边界 | CI 需直连 K8s | ArgoCD 集群内运行 |
+| 回滚方式 | 回到上一次的 pipeline | `git revert` + 自动同步 |
+| 代表工具 | Jenkins, GitHub Actions | ArgoCD, Flux |
-```yaml
-apiVersion: apps/v1
-kind: Deployment
-metadata:
- name: order-service
- labels:
- app: order
- version: v1.2.3
-spec:
- replicas: 3 # 期望副本数
- strategy:
- type: RollingUpdate
- rollingUpdate:
- maxSurge: 1 # 最多超额 1 个 Pod
- maxUnavailable: 0 # 滚动更新期间不允许不可用
-
- selector:
- matchLabels:
- app: order
-
- template:
- metadata:
- labels:
- app: order
- version: v1.2.3
- spec:
- containers:
- - name: order-service
- image: registry.example.com/order:v1.2.3
- ports:
- - containerPort: 8080
-
- # ========== 资源配置 ==========
- resources:
- requests: # 调度依据:保证至少有这些
- cpu: "250m"
- memory: "256Mi"
- limits: # 硬上限:超过则 OOMKill/CPU Throttle
- cpu: "500m"
- memory: "512Mi"
-
- # ========== 探针 ==========
- livenessProbe:
- httpGet:
- path: /healthz
- port: 8080
- initialDelaySeconds: 15
- periodSeconds: 10
- failureThreshold: 3 # 连续失败 3 次才重启
-
- readinessProbe:
- httpGet:
- path: /ready
- port: 8080
- initialDelaySeconds: 5
- periodSeconds: 5
- failureThreshold: 3
-
- startupProbe:
- httpGet:
- path: /healthz
- port: 8080
- failureThreshold: 30 # 最长等待 300s (慢启动友好)
-
- # ========== 环境变量 & 挂载 ==========
- envFrom:
- - configMapRef:
- name: order-service-config
- - secretRef:
- name: order-service-secrets
-
- lifecycle:
- preStop:
- exec:
- command: ["sh", "-c", "sleep 15"] # 优雅退出,给 LB 摘流时间
+> **选型建议**:团队规模 < 20 人时 Push 模式足够。≥ 50 人或多环境场景建议迁移到 GitOps → [[../03-CICD与GitOps]]
+
+## Secret 管理方案对比
+
+| 方案 | 适用场景 | 优点 | 缺点 |
+|------|---------|------|------|
+| K8s 原生 Secret | 小规模内部团队 | 零成本,开箱即用 | etcd 明文存储 |
+| ESO (External Secrets) | 已有 Vault / AWS SSM | Git 中无密文 | 需维护额外组件 |
+| SOPS + sealed-secrets | ArgoCD 用户 | 加密文件可提交到 Git | 需管理 PKI |
+| 云厂商 Secret Manager | 深度绑定单一云平台 | 审计完善 | 平台锁定 |
+
+> **渐进路径**:K8s Secret → 接入云厂商 Secret Manager → 引入 ESO 实现多云解耦。
+
+## 编排工具选型
+
+| 服务规模 | 推荐方案 | 理由 |
+|----------|---------|------|
+| < 10 个服务 | Kustomize / 裸 YAML | 复杂度高于收益 |
+| 10~50 个服务 | Helm | 模板复用价值明显 |
+| > 50 个服务 | Helm + Kustomize overlays | Helm 管模板,Kustomize 管环境差异 |
+
+## 关键决策流程图
+
+### 安全选型决策
+
+```mermaid
+flowchart TD
+ START["开始选型"] --> SIZE["团队 < 20人?"]
+ SIZE -- 是 --> K8SSECRET["✅ K8s 原生 Secret"]
+ SIZE -- 否 --> CLOUD["深度绑定云厂商?"]
+ CLOUD -- 是 --> SECRETMGR["✅ Cloud Secret Manager"]
+ CLOUD -- 否 --> GITOPS["使用 ArgoCD?"]
+ GITOPS -- 是 --> SOPS["✅ SOPS + sealed-secrets"]
+ GITOPS -- 否 --> ESO["✅ External Secrets Operator"]
+
+ style K8SSECRET fill:#e8f5e9
+ style SECRETMGR fill:#e3f2fd
+ style SOPS fill:#fff3e0
+ style ESO fill:#f3e5f5
```
-### Probe 选择指南
+## 附录:常用命令速查
-| 探针类型 | 触发条件 | 动作 | 适用场景 |
-|---------|---------|------|---------|
-| **Liveness** | `/healthz` 返回非 2xx | 重启容器 | 死锁、无法恢复的崩溃 |
-| **Readiness** | `/ready` 返回非 2xx | 摘除 Service 流量 | 依赖未就绪、热加载中 |
-| **Startup** | 首次成功前持续失败 | 不重启,只等待 | 大模型/JVM 冷启动 |
+### kubectl 基础
+```bash
+kubectl set image deployment/order-service \
+ order=registry.example.com/order-service:v1.2.3 -n production
-> [!warning] 经典陷阱:CrashLoopBackOff
->
-> 如果 Liveness Probe 因为 DB 连接超时而返回 503,K8s 会认为容器挂了并反复重启它——这就是 CrashLoopBackOff。正确做法是:让 `/healthz` 做降级判断(DB 不可用时返回 200),用 `/ready` 来摘除流量。
-
-## Service 与 Ingress
-
-### Service 类型
-
-| 类型 | 特点 | 使用场景 |
-|------|------|---------|
-| **ClusterIP** | 集群内 IP,外部不可访问 | 默认,内部服务间调用 |
-| **NodePort** | 在每个 Node 上开端口 | 调试、临时访问 |
-| **LoadBalancer** | 云厂商分配公网 IP | 对外暴露的服务 |
-| **ExternalName** | CNAME 到外部域名 | 对接外部系统 |
-
-```yaml
-# ClusterIP Service — 服务发现的载体
-apiVersion: v1
-kind: Service
-metadata:
- name: order-service
-spec:
- selector:
- app: order
- ports:
- - port: 80
- targetPort: 8080
- protocol: TCP
- type: ClusterIP
+kubectl rollout status deployment/order-service -n production --timeout=120s
+kubectl rollout undo deployment/order-service -n production
```
-调用方只需 `http://order-service:80`,K8s 通过 iptables/IPVS 自动实现负载均衡。
+### 关联笔记
-### Ingress — HTTP 路由
-
-```yaml
-apiVersion: networking.k8s.io/v1
-kind: Ingress
-metadata:
- name: main-ingress
- annotations:
- nginx.ingress.kubernetes.io/rewrite-target: /
-spec:
- rules:
- - host: api.example.com
- http:
- paths:
- - path: /orders
- pathType: Prefix
- backend:
- service:
- name: order-service
- port:
- number: 80
- - path: /users
- pathType: Prefix
- backend:
- service:
- name: user-service
- port:
- number: 80
-```
-
-## HPA 弹性伸缩
-
-```yaml
-apiVersion: autoscaling/v2
-kind: HorizontalPodAutoscaler
-metadata:
- name: order-service-hpa
-spec:
- scaleTargetRef:
- apiVersion: apps/v1
- kind: Deployment
- name: order-service
- minReplicas: 3
- maxReplicas: 20
- metrics:
- - type: Resource
- resource:
- name: cpu
- target:
- type: Utilization
- averageUtilization: 70 # CPU > 70% 时扩容
- - type: Resource
- resource:
- name: memory
- target:
- type: Utilization
- averageUtilization: 80
- behavior:
- scaleUp:
- stabilizationWindowSeconds: 60 # 扩容稳定期
- policies:
- - type: Pods
- value: 2
- periodSeconds: 60 # 每分钟最多扩 2 个
- scaleDown:
- stabilizationWindowSeconds: 300 # 缩容稳定期 5min(防抖动)
-```
-
-## K8s 运维 Checklist
-
-每次上线前过一遍这个清单:
-
-| # | 检查项 | 说明 |
-|---|--------|------|
-| 1 | **Probe 已配置** | liveness/readiness/startup 都设定了阈值 |
-| 2 | **Resources Limits** | 防止单个 Pod OOMKill 拖垮整台机器 |
-| 3 | **日志输出到 stdout/stderr** | 可被采集器解析为 JSON |
-| 4 | **trace_id 透传** | 跨服务调用链 trace_id 不丢失 |
-| 5 | **回滚预案** | `kubectl rollout undo deployment/order-service` 能用 |
-| 6 | **告警已配置** | 关键指标异常时有人收到通知 |
-| 7 | **镜像 Tag** | 不用 latest,用语义化版本或 commit SHA |
-
-## 关联笔记
-
-- [[02-服务治理/04-服务发现]] — K8s Service 是服务端发现模式的代表
-- [[02-服务治理/08-流量治理]] — Istio VirtualService 在 K8s 上的高级路由
-- [[05-部署运维/04-SRE实践]] — SLO/Error Budget 在 K8s 中的落地
+- [[02-Kubernetes/01-核心概念与Deployment]] — Deployment 完整示例与 Probe 选择
+- [[hhs/MS/05-部署运维/01-容器化]] — Docker 镜像是 K8s Pod 的基础单元
+- [[hhs/MS/05-部署运维/03-CICD与GitOps]] — GitOps ArgoCD 操作 K8s manifest
+- [[hhs/MS/05-部署运维/04-SRE实践]] — SLO/Error Budget 在 K8s 中的落地
+- [[hhs/MS/02-服务治理/04-服务发现]] — K8s Service 是服务端发现模式的代表
+- [[hhs/MS/02-服务治理/08-流量治理]] — Istio VirtualService 在 K8s 上的高级路由
diff --git a/hhs/MS/05-部署运维/02-Kubernetes/01-核心概念与Deployment.md b/hhs/MS/05-部署运维/02-Kubernetes/01-核心概念与Deployment.md
new file mode 100644
index 0000000..0222c03
--- /dev/null
+++ b/hhs/MS/05-部署运维/02-Kubernetes/01-核心概念与Deployment.md
@@ -0,0 +1,202 @@
+---
+tags: [kubernetes, pod, deployment, probe, container-orchestration]
+create time: 2026-05-18 00:30
+---
+
+# K8s 核心概念与 Deployment — 教程
+
+## 概述
+
+本文档带你从零理解 Kubernetes 的核心对象模型,并掌握 Deployment 这个最常用的工作负载控制器。更多进阶主题(资源配置、服务发现、调度)分散在后续章节中。总览和决策矩阵见 [[../02-Kubernetes]]。
+
+## K8s 架构全景
+
+```mermaid
+graph TB
+ subgraph CONTROL["控制面 Control Plane"]
+ APISERVER["API Server
唯一入口"]
+ SCHEDULER["Scheduler
调度决策"]
+ CMGR["Controller Manager
状态协调"]
+ ETCD["etcd
分布式 KV 存储"]
+
+ APISERVER --> SCHEDULER
+ APISERVER --> CMGR
+ APISERVER --> ETCD
+ CMGR --> ETCD
+ end
+
+ subgraph WORKER["工作节点 Worker Node"]
+ N1["Node A
kubelet + kube-proxy"]
+ N2["Node B
kubelet + kube-proxy"]
+ end
+
+ APISERVER -.->|监听变动| N1
+ APISERVER -.->|监听变动| N2
+
+ style CONTROL fill:#e3f2fd
+ style ETCD fill:#c8e6c9
+ style WORKER fill:#fff3e0
+```
+
+> [!question] 为什么 K8s 需要这么多组件?
+>
+> 因为"声明式 API"的设计哲学——用户告诉 K8s **想要什么状态**(比如"我要 3 个订单服务实例"),控制面负责让实际状态持续逼近目标状态。任何偏离都会被自动修复,这也就是自愈能力的来源。
+
+### 核心对象速查表
+
+| K8s 对象 | 用途 | 类比 |
+|---------|------|------|
+| **Pod** | 最小部署单元,包含一个或多个容器 | 应用实例 |
+| **Deployment** | 管理 Pod 的副本数和滚动更新 | 应用的"模板" |
+| **Service** | 稳定的网络入口,负载均衡 | 内部 VIP → [[03-网络与服务发现]] |
+| **ConfigMap** | 配置注入(明文) → [[02-配置管理]] |
+| **Secret** | 敏感配置注入(base64)→ [[02-配置管理]] |
+| **HPA** | 根据指标自动扩缩容 → [[04-扩缩容与有状态应用]] |
+| **StatefulSet** | 有状态应用的有序管理 → [[04-扩缩容与有状态应用]] |
+| **NetworkPolicy** | L3/L4 网络安全策略 → [[06-资源与安全管控]] |
+
+## Deployment 详解
+
+### 完整示例
+
+```yaml
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: order-service
+ labels:
+ app: order
+ version: v1.2.3
+spec:
+ replicas: 3 # 期望副本数
+ strategy:
+ type: RollingUpdate
+ rollingUpdate:
+ maxSurge: 1 # 最多超额 1 个 Pod
+ maxUnavailable: 0 # 滚动更新期间不允许不可用
+
+ selector:
+ matchLabels:
+ app: order
+
+ template: # Pod 模板
+ metadata:
+ labels:
+ app: order
+ version: v1.2.3
+ spec:
+ containers:
+ - name: order-service
+ image: registry.example.com/order:v1.2.3
+ ports:
+ - containerPort: 8080
+
+ # ========== 资源配置 ==========
+ resources:
+ requests: # 调度依据:保证至少有这些
+ cpu: "250m"
+ memory: "256Mi"
+ limits: # 硬上限:超过则 OOMKill/CPU Throttle
+ cpu: "500m"
+ memory: "512Mi"
+
+ # ========== 探针 ==========
+ livenessProbe:
+ httpGet:
+ path: /healthz
+ port: 8080
+ initialDelaySeconds: 15
+ periodSeconds: 10
+ failureThreshold: 3 # 连续失败 3 次才重启
+
+ readinessProbe:
+ httpGet:
+ path: /ready
+ port: 8080
+ initialDelaySeconds: 5
+ periodSeconds: 5
+ failureThreshold: 3
+
+ startupProbe:
+ httpGet:
+ path: /healthz
+ port: 8080
+ failureThreshold: 30 # 最长等待 300s (慢启动友好)
+
+ # ========== 环境变量 & 挂载 ==========
+ envFrom:
+ - configMapRef:
+ name: order-service-config
+ - secretRef:
+ name: order-service-secrets
+
+ lifecycle:
+ preStop:
+ exec:
+ command: ["sh", "-c", "sleep 15"] # 优雅退出,给 LB 摘流时间
+```
+
+### Probe 选择指南
+
+| 探针类型 | 触发条件 | 动作 | 适用场景 |
+|---------|---------|------|---------|
+| **Liveness** | `/healthz` 返回非 2xx | 重启容器 | 死锁、无法恢复的崩溃 |
+| **Readiness** | `/ready` 返回非 2xx | 摘除 Service 流量 | 依赖未就绪、热加载中 |
+| **Startup** | 首次成功前持续失败 | 不重启,只等待 | 大模型/JVM 冷启动 |
+
+> [!warning] 常见陷阱:Liveness 误杀导致 CrashLoopBackOff
+>
+> 如果 Liveness Probe 因为 DB 连接超时而返回 503,K8s 会认为容器挂了并反复重启它。正确做法:让 `/healthz` 做降级判断(DB 不可用时返回 200),用 `/ready` 来摘除流量。
+
+### Deployment 更新策略
+
+```yaml
+strategy:
+ type: RollingUpdate # 逐步替换旧 Pod
+ rollingUpdate:
+ maxSurge: 1 # 新 Pod 数量可以超出期望值 1
+ maxUnavailable: 0 # 更新时不允许有空缺
+
+ # 另一种策略:先全部新建再销毁旧的(零停机但需 2x 资源)
+ # type: Recreate # ❌ 全量替换,会有短暂不可用
+```
+
+> [!tip] 如何安全地回滚 Deployment?
+>
+> ```bash
+> kubectl rollout undo deployment/order-service -n production # 回到上一版本
+> kubectl rollout undo deployment/order-service -n production --to-revision=3 # 回退到指定版本
+> kubectl rollout status deployment/order-service -n production # 查看进度
+> kubectl rollout history deployment/order-service -n production # 历史版本对比
+> ```
+>
+> K8s 自动保留每次 Deployment 变更后的 PodTemplateSpec,因此回滚是瞬时的,不需要手动备份 YAML。
+
+### Pod 生命周期简述
+
+```mermaid
+stateDiagram-v2
+ [*] --> Pending: Pod 被创建
+ Pending --> ContainerCreating: 调度成功,拉取镜像
+ ContainerCreating --> Running: 容器就绪
+ Running --> Waiting: Probe 失败或手动暂停
+ Waiting --> Running: 探针恢复
+ Running --> Terminating: 删除/更新触发
+ Terminating --> (*): 完全终止
+
+ state Waiting {
+ CrashLoopBackOff
+ ImagePullBackOff
+ }
+
+ note right of CrashLoopBackOff: Liveness 连续失败\n或进程异常退出
+ note right of ImagePullBackOff: 镜像不存在或仓库认证失败
+```
+
+## 关联笔记
+
+- [[../02-配置管理]] — ConfigMap / Secret 的配置注入
+- [[../03-网络与服务发现]] — Service / Ingress 网络路由
+- [[../04-扩缩容与有状态应用]] — HPA / StatefulSet
+- [[../hhs/MS/05-部署运维/01-容器化]] — Docker 镜像是 Pod 的基础单元
+- [[../hhs/MS/05-部署运维/03-CICD与GitOps]] — GitOps ArgoCD 操作 K8s manifest
diff --git a/hhs/MS/05-部署运维/02-Kubernetes/02-配置管理.md b/hhs/MS/05-部署运维/02-Kubernetes/02-配置管理.md
new file mode 100644
index 0000000..3702915
--- /dev/null
+++ b/hhs/MS/05-部署运维/02-Kubernetes/02-配置管理.md
@@ -0,0 +1,188 @@
+---
+tags: [kubernetes, configmap, secret, configuration-management]
+create time: 2026-05-18 00:30
+---
+
+# K8s 配置管理 — ConfigMap 与 Secret
+
+## 概述
+
+K8s 提供两种原生的配置注入机制:**ConfigMap**(普通配置)和 **Secret**(敏感信息)。理解它们的区别、注入方式和生效时机,是编写可维护 K8s manifest 的基础。更多安全方案(外部密钥管理)参见 [[../03-CICD与GitOps/04-安全与发布策略]]。
+
+## ConfigMap — 非敏感配置注入
+
+### 基本用法
+
+```yaml
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: order-service-config
+ namespace: production
+data:
+ # ========== 环境变量风格 ==========
+ DATABASE_HOST: "mysql.production.svc.cluster.local"
+ DATABASE_PORT: "3306"
+ LOG_LEVEL: "info"
+ MAX_CONNECTIONS: "100"
+
+ # ========== 文件风格 (挂载为 volume) ==========
+ application.yaml: |
+ server:
+ port: 8080
+ spring:
+ datasource:
+ url: jdbc:mysql://${DATABASE_HOST}:${DATABASE_PORT}/orders
+ pool-size: ${MAX_CONNECTIONS}
+ logging:
+ level:
+ root: ${LOG_LEVEL}
+```
+
+### 两种注入方式
+
+```yaml
+spec:
+ containers:
+ - name: order-service
+ # 方式一:环境变量注入(简单直接)
+ env:
+ - name: DATABASE_HOST
+ valueFrom:
+ configMapKeyRef:
+ name: order-service-config
+ key: DATABASE_HOST
+
+ # 方式二:volume 挂载配置文件(适合完整配置文件)
+ volumeMounts:
+ - name: config-volume
+ mountPath: /etc/app/config/application.yaml
+ subPath: application.yaml
+ volumes:
+ - name: config-volume
+ configMap:
+ name: order-service-config
+```
+
+> [!tip] 热更新 vs 重建 Pod
+>
+> - 通过 **环境变量** 注入的配置需要重建 Pod 才能生效
+> - 通过 **volume 挂载** 的 ConfigMap 在配置变更后,Pod 内文件会在约 1 分钟内自动更新(前提是应用本身支持配置热加载,如 Spring Cloud Context 的 `@RefreshScope`)
+
+## Secret — 敏感信息存储
+
+### 基本用法
+
+```yaml
+apiVersion: v1
+kind: Secret
+metadata:
+ name: order-service-secrets
+ namespace: production
+type: Opaque
+data:
+ # Base64 编码(注意:这不是加密!只是编码)
+ DB_PASSWORD: cGFzc3dvcmQxMjM=
+ API_KEY: bXktc2VjcmV0LWFwaS1rZXk=
+
+# 或者用 stringData(明文写入,K8s 自动编码)
+---
+apiVersion: v1
+kind: Secret
+metadata:
+ name: order-service-secrets-v2
+type: Opaque
+stringData:
+ DB_PASSWORD: password123 # K8s 自动转为 base64
+ API_KEY: my-secret-api-key
+```
+
+**使用 Secret 的方式:**
+
+```yaml
+# 方式一:作为环境变量注入
+env:
+- name: DB_PASSWORD
+ valueFrom:
+ secretKeyRef:
+ name: order-service-secrets
+ key: DB_PASSWORD
+
+# 方式二:挂载为卷中的文件(更安全,避免暴露在 ps 输出中)
+volumeMounts:
+- name: secrets-volume
+ readOnly: true
+ mountPath: /etc/secrets
+ defaultMode: 0400
+volumes:
+- name: secrets-volume
+ secret:
+ secretName: order-service-secrets
+```
+
+> [!warning] Secret 的安全性边界
+>
+> K8s Secret 默认以 **base64 编码** 存储在 etcd 中,任何人都可以解码。**Base64 ≠ 加密**。生产环境应启用以下至少一项:
+>
+> 1. **etcd 加密** — 开启 K8s EncryptionConfiguration,使 etcd 存储密文
+> 2. **外部密钥管理** — Sealed Secrets、External Secrets Operator 对接 AWS Secrets Manager / HashiCorp Vault
+> 3. **RBAC 严格管控** — 限制谁可以读取 Secret 资源
+
+### ConfigMap vs Secret 对比
+
+| 维度 | ConfigMap | Secret |
+|------|-----------|--------|
+| **用途** | 普通配置(URL、端口等) | 敏感信息(密码、Token、证书) |
+| **存储格式** | UTF-8 字符串 | Base64 编码 |
+| **挂载路径** | `/etc/config/...` | `/etc/secrets/...` |
+| **大小限制** | 1 MiB | 1 MiB |
+| **安全建议** | 正常 RBAC | 必须启用 etcd 加密或外置密钥管理 |
+
+## 补充:动态配置重载实战
+
+对于需要频繁变更的配置(如功能开关、灰度比例),可以通过 sidecar 模式实现自动重载:
+
+```yaml
+spec:
+ containers:
+ - name: app
+ image: myapp:latest
+ volumeMounts:
+ - name: config-volume
+ mountPath: /etc/app/config
+ readOnly: true
+ - name: reload-sidecar # Watch 配置变更并发送 SIGUSR1
+ image: busybox:latest
+ command: ["sh", "-c"]
+ args:
+ - |
+ LAST_MD5=""
+ while true; do
+ CURR_MD5=$(md5sum /etc/config/application.yaml | awk '{print $1}')
+ if [ "$CURR_MD5" != "$LAST_MD5" ]; then
+ kill -USR1 1 # 通知主进程重新加载配置
+ LAST_MD5="$CURR_MD5"
+ fi
+ sleep 5
+ done
+ volumeMounts:
+ - name: config-volume
+ mountPath: /etc/config
+ readOnly: true
+ volumes:
+ - name: config-volume
+ configMap:
+ name: order-service-config
+ - name: shared-config
+ emptyDir: {}
+```
+
+> [!note] sidecar 模式的权衡
+>
+> 优点:无需重启 Pod 即可热更新配置;缺点:增加了资源消耗和维护复杂度。Spring Boot 项目建议使用 Actuator `/refresh` 端点替代手动信号方案。
+
+## 关联笔记
+
+- [[../01-核心概念与Deployment]] — ConfigMap 在 Deployment 中的标准引用方式
+- [[../03-CICD与GitOps/04-安全与发布策略]] — 企业级 Secret 管理方案选型
+- [[../hhs/MS/02-服务治理/07-配置管理]] — 微服务级别的配置中心方案(Nacos / Apollo / Consul)
diff --git a/hhs/MS/05-部署运维/02-Kubernetes/03-网络与服务发现.md b/hhs/MS/05-部署运维/02-Kubernetes/03-网络与服务发现.md
new file mode 100644
index 0000000..073dd5d
--- /dev/null
+++ b/hhs/MS/05-部署运维/02-Kubernetes/03-网络与服务发现.md
@@ -0,0 +1,144 @@
+---
+tags: [kubernetes, service, ingress, networking, service-discovery]
+create time: 2026-05-18 00:30
+---
+
+# K8s 网络与服务发现 — Service 与 Ingress
+
+## 概述
+
+Kubernetes 的网络模型解决了容器 IP 频繁变动的问题。Service 提供**稳定的服务发现入口**,Ingress 提供**L7 HTTP 路由能力**。本文将梳理核心概念、典型模式和常见问题。
+
+## Service 类型
+
+| 类型 | 特点 | 使用场景 |
+|------|------|---------|
+| **ClusterIP** | 集群内 IP,外部不可访问 | 默认,内部服务间调用 |
+| **NodePort** | 在每个 Node 上开端口 | 调试、临时访问 |
+| **LoadBalancer** | 云厂商分配公网 IP | 对外暴露的服务 |
+| **ExternalName** | CNAME 到外部域名 | 对接外部系统 |
+
+### ClusterIP Service — 服务发现的载体
+
+```yaml
+apiVersion: v1
+kind: Service
+metadata:
+ name: order-service
+spec:
+ selector:
+ app: order # 匹配带此 label 的 Pod
+ ports:
+ - port: 80 # Service 端口(对外暴露)
+ targetPort: 8080 # 容器实际监听端口
+ protocol: TCP
+ type: ClusterIP
+```
+
+调用方只需 `http://order-service:80`,K8s 通过 iptables/IPVS 自动实现负载均衡。
+
+> [!question] Service 是怎么做到负载均衡的?
+>
+> 每个 Node 上的 kube-proxy 会监控 Service 的变动,自动生成 iptables 规则或 IPVS 虚拟服务器配置。当请求发往 ClusterIP 时,内核将其 DNAT 到后端 Pod IP 之一,负载均衡算法默认是 round-robin。K8s 1.14+ 推荐使用 IPVS 模式(`kube-proxy --proxy-mode=ipvs`),性能更高且支持更多算法。
+
+## Ingress — HTTP/HTTPS 路由
+
+```yaml
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: main-ingress
+ annotations:
+ nginx.ingress.kubernetes.io/rewrite-target: /
+spec:
+ rules:
+ - host: api.example.com
+ http:
+ paths:
+ - path: /orders
+ pathType: Prefix
+ backend:
+ service:
+ name: order-service
+ port:
+ number: 80
+ - path: /users
+ pathType: Prefix
+ backend:
+ service:
+ name: user-service
+ port:
+ number: 80
+```
+
+> [!info] Ingress Controller 是什么?
+>
+> Ingress 只是一个 API 对象,真正的 HTTP 路由由 Ingress Controller(如 NGINX、Traefik、Envoy)执行。安装后会在集群中运行一个 LoadBalancer 类型的 Pod 集群,监听 Ingress 资源变化并生成对应配置。
+
+### 常见 Ingress Annotation(NGINX 为例)
+
+| Annotation | 用途 | 示例值 |
+|------------|------|--------|
+| `rewrite-target` | URL 重写 | `/` |
+| `ssl-redirect` | 强制 HTTPS | `"true"` |
+| `rate-limit` | 限流 | `100` |
+| `whitelist-source-range` | IP 白名单 | `10.0.0.0/8` |
+| `client-max-body-size` | 上传文件大小限制 | `50m` |
+
+## Headless Service — 无头服务
+
+Headless Service (`ClusterIP: None`) 不分配虚拟 IP,而是返回所有匹配 Pod 的真实 IP。这是 StatefulSet 的灵魂搭档:
+
+```yaml
+apiVersion: v1
+kind: Service
+metadata:
+ name: mysql-headless
+spec:
+ clusterIP: None # 关键:不分配 ClusterIP
+ selector:
+ app: mysql
+ ports:
+ - port: 3306
+```
+
+通过 DNS 可以直接访问单个 Pod:
+- `mysql-cluster-0.mysql-headless.production.svc.cluster.local:3306`
+- `mysql-cluster-1.mysql-headless.production.svc.cluster.local:3306`
+
+## K8s 网络模型总结
+
+```mermaid
+flowchart LR
+ EXT["外部流量"] --> INGRESS["Ingress Controller"]
+
+ INGRESS --> SVC_A["order-service:80"]
+ INGRESS --> SVC_B["user-service:80"]
+
+ SVC_A --> Pod1["order-pod-1\n10.244.1.5:8080"]
+ SVC_A --> Pod2["order-pod-2\n10.244.2.3:8080"]
+
+ SVC_B --> Pod3["user-pod-1\n10.244.1.8:3000"]
+
+ Pod1 -->|DNS解析| COREDNS["CoreDNS
svc.cluster.local"]
+
+ style EXT fill:#e3f2fd
+ style INGRESS fill:#fff3e0
+ style SVC_A fill:#e8f5e9
+ style SVC_B fill:#fce4ec
+```
+
+> [!tip] 调试技巧:Service 不通怎么办?
+>
+> 1. `kubectl get svc ` 确认 Service 存在且有 ClusterIP
+> 2. `kubectl get endpoints ` 检查 Endpoint 列表是否为空
+> 3. Endpoint 为空 → 检查 selector 标签是否匹配 Pod
+> 4. Endpoint 有值但 curl 不通 → 进入 Pod `curl :` 验证
+> 5. ClusterIP 通但外部不通 → 检查 Ingress / LoadBalancer 配置
+
+## 关联笔记
+
+- [[../01-核心概念与Deployment]] — Deployment 通过 selector 与 Service 关联
+- [[../02-配置管理]] — Service 的端点配置不直接涉及 ConfigMap/Secret
+- [[../04-扩缩容与有状态应用]] — Headless Service + StatefulSet 配合
+- [[../hhs/MS/02-服务治理/04-服务发现]] — K8s Service 是服务端发现模式的代表
diff --git a/hhs/MS/05-部署运维/02-Kubernetes/04-扩缩容与有状态应用.md b/hhs/MS/05-部署运维/02-Kubernetes/04-扩缩容与有状态应用.md
new file mode 100644
index 0000000..202db17
--- /dev/null
+++ b/hhs/MS/05-部署运维/02-Kubernetes/04-扩缩容与有状态应用.md
@@ -0,0 +1,217 @@
+---
+tags: [kubernetes, hpa, autoscaling, statefulset, horizontal-pod-autoscaler]
+create time: 2026-05-18 00:30
+---
+
+# K8s 扩缩容与有状态应用 — HPA & StatefulSet
+
+## 概述
+
+K8s 提供多种扩缩容机制。**HPA** 基于 CPU/Memory 或其他自定义指标实现 Pod 级别的弹性伸缩,适用于无状态服务。**StatefulSet** 则是数据库、消息队列等有状态组件的理想选择。两者各有适用场景。
+
+## HPA 弹性伸缩
+
+### 基础示例
+
+```yaml
+apiVersion: autoscaling/v2
+kind: HorizontalPodAutoscaler
+metadata:
+ name: order-service-hpa
+spec:
+ scaleTargetRef:
+ apiVersion: apps/v1
+ kind: Deployment
+ name: order-service
+ minReplicas: 3
+ maxReplicas: 20
+ metrics:
+ - type: Resource
+ resource:
+ name: cpu
+ target:
+ type: Utilization
+ averageUtilization: 70 # CPU > 70% 时扩容
+ - type: Resource
+ resource:
+ name: memory
+ target:
+ type: Utilization
+ averageUtilization: 80
+ behavior:
+ scaleUp:
+ stabilizationWindowSeconds: 60 # 扩容稳定期
+ policies:
+ - type: Pods
+ value: 2
+ periodSeconds: 60 # 每分钟最多扩 2 个
+ scaleDown:
+ stabilizationWindowSeconds: 300 # 缩容稳定期 5min(防抖动)
+```
+
+### 扩展指标:基于自定义 Metric 扩缩容
+
+```yaml
+ metrics:
+ - type: Pods
+ pods:
+ metric:
+ name: http_requests_per_second
+ target:
+ type: AverageValue
+ averageValue: "100" # 每个 Pod 平均 100 QPS 时扩容
+ - type: Object
+ object:
+ metric:
+ name: active_users
+ describedObject:
+ apiVersion: apps/v1
+ kind: Deployment
+ name: web-app
+ target:
+ type: Value
+ value: "500" # 每 500 活跃用户触发一次扩容
+```
+
+> [!info] HPA 的工作机制
+>
+> HPA 控制器每 15 秒查询 Metrics Server(CPU/Memory)或自定义 Metrics Adapter(QPS 等),计算所需 Replica 数。公式:`ceil(当前指标值 / 目标指标值 × 当前副本数)`。为了防止抖动,scaleDown 的稳定窗口通常比 scaleUp 长很多。
+
+### HPA 调优最佳实践
+
+| 参数 | 推荐值 | 说明 |
+|------|--------|------|
+| `averageUtilization` (CPU) | 60~70% | 低于 60% 浪费资源,高于 80% 延迟风险大 |
+| `scaleUp.stabilizationWindow` | 0~60s | 突发流量快速响应 |
+| `scaleDown.stabilizationWindow` | 300~600s | 防止抖动导致的频繁扩缩 |
+| `pods[].periodSeconds` | 60s | 限制扩容频率,避免雪崩 |
+
+## StatefulSet — 有状态应用
+
+Deployment 适合无状态服务,而有状态服务(数据库、分布式协调器等)需要 **稳定的网络标识 + 有序的启停**。StatefulSet 就是为此而生的控制器。
+
+### 完整示例
+
+```yaml
+apiVersion: apps/v1
+kind: StatefulSet
+metadata:
+ name: mysql-cluster
+spec:
+ serviceName: mysql # 绑定 Headless Service
+ replicas: 3
+ podManagementPolicy: Parallel # 并行启动(生产建议 OrderedReady 逐步启动)
+
+ updateStrategy:
+ type: RollingUpdate
+ rollingUpdate:
+ partition: 0 # 从最后一个 Pod 开始逐步滚动
+
+ selector:
+ matchLabels:
+ app: mysql
+
+ template:
+ metadata:
+ labels:
+ app: mysql
+ spec:
+ containers:
+ - name: mysql
+ image: mysql:8.0
+ ports:
+ - containerPort: 3306
+ volumeMounts:
+ - name: data
+ mountPath: /var/lib/mysql
+
+ volumeClaimTemplates:
+ - metadata:
+ name: data
+ spec:
+ accessModes: ["ReadWriteOnce"]
+ resources:
+ requests:
+ storage: 20Gi
+```
+
+### StatefulSet 的核心特性
+
+| 特性 | 说明 |
+|------|------|
+| **稳定名称** | Pod 名为 `{statefulset-name}-{ordinal}`,如 `mysql-cluster-0` |
+| **有序部署** | 默认依次启动:0 → 1 → 2(设置 `Parallel` 可并行) |
+| **有序删除** | 反向删除:2 → 1 → 0(保证主从切换安全) |
+| **持久化存储** | `volumeClaimTemplates` 为每个 Pod 独立创建 PVC,Pod 重建后数据不丢失 |
+| **Headless Service** | 需搭配 `ClusterIP: None` 的 Service,每个 Pod 有独立的 DNS 记录 |
+
+### StatefulSet vs Deployment 对比
+
+| 维度 | Deployment | StatefulSet |
+|------|-----------|-------------|
+| **Pod 命名** | 随机后缀 | `{name}-{0,1,2,...}` |
+| **存储** | 共享 PVC 或不持久 | 每个 Pod 独立 PVC |
+| **启动顺序** | 并发 | 串行(默认) |
+| **删除顺序** | 随机 | 逆序 |
+| **DNS 稳定性** | 每次重建 IP 变 | 域名不变 |
+| **适用场景** | Web/API 无状态服务 | DB、ZooKeeper、Kafka |
+
+> [!note] 为什么有状态应用不适合 Deployment?
+>
+> Deployment 会随机销毁和重建 Pod——这意味着 PVC 会被重新绑定到不同的 Pod,存储卷中的数据和节点 ID 完全错位。StatefulSet 保证同一个 Pod 始终使用同一片存储,这对数据库、Kafka、ZooKeeper 等组件至关重要。
+
+### Headless Service — StatefulSet 的灵魂搭档
+
+```yaml
+apiVersion: v1
+kind: Service
+metadata:
+ name: mysql-headless
+spec:
+ clusterIP: None # 关键:不分配 ClusterIP
+ selector:
+ app: mysql
+ ports:
+ - port: 3306
+```
+
+通过 DNS 可以直接访问单个 Pod:
+- `mysql-cluster-0.mysql-headless.production.svc.cluster.local:3306`
+- `mysql-cluster-1.mysql-headless.production.svc.cluster.local:3306`
+
+## 补充:Vertical Pod Autoscaler (VPA)
+
+HPA 只管 Pod 数量,不管单 Pod 的资源配置。如果你经常遇到 OOMKill 或 CPU Throttling,可以尝试 VPA:
+
+```yaml
+apiVersion: autoscaling.k8s.io/v1
+kind: VerticalPodAutoscaler
+metadata:
+ name: order-service-vpa
+spec:
+ targetRef:
+ apiVersion: apps/v1
+ kind: Deployment
+ name: order-service
+ updatePolicy:
+ updateMode: "Auto" # Auto / Initial / Off
+ resourcePolicy:
+ containerPolicy:
+ minAllowed:
+ cpu: 100m
+ memory: 128Mi
+ maxAllowed:
+ cpu: "2"
+ memory: 4Gi
+```
+
+> [!warning] VPA 的限制
+>
+> VPA 在调整资源时会**驱逐并重建 Pod**。与 HPA 不同,VPA 不能无缝运行——建议在开发/测试环境中先用 `Initial` 模式观察推荐值,确定合理后再切到 `Auto`。生产环境通常结合 HPA(管数量)+ 手动调优 Resources(管质量)。
+
+## 关联笔记
+
+- [[../01-核心概念与Deployment]] — Deployment 基础,HPA 的目标控制器
+- [[../02-配置管理]] — StatefulSet 的数据库凭据通过 Secret 注入
+- [[../03-网络与服务发现]] — Headless Service 为 StatefulSet 提供 DNS 解析
+- [[../hhs/MS/05-部署运维/04-SRE实践]] — SLO/Error Budget 决定扩容阈值
diff --git a/hhs/MS/05-部署运维/02-Kubernetes/05-调度控制.md b/hhs/MS/05-部署运维/02-Kubernetes/05-调度控制.md
new file mode 100644
index 0000000..276de13
--- /dev/null
+++ b/hhs/MS/05-部署运维/02-Kubernetes/05-调度控制.md
@@ -0,0 +1,179 @@
+---
+tags: [kubernetes, scheduling, affinity, taints, tolerations, topology]
+create time: 2026-05-18 00:30
+---
+
+# K8s 调度控制 — Affinity、Taint & Topology
+
+## 概述
+
+默认调度器会把 Pod 随便分配到任意可用 Node。想让 Pod 之间"互相躲开"或"必须在一起"?需要使用 K8s 的调度控制机制:**Affinity/Anti-Affinity**(节点间关系)、**Taint/Toleration**(节点白名单)、**TopologySpreadConstraints**(跨可用区分布)。
+
+## Affinity / Anti-Affinity
+
+### Pod 反亲和性:分散到不同节点
+
+```yaml
+spec:
+ affinity:
+ # ========== 反亲和性:同一服务的 Pod 分散到不同节点 ==========
+ podAntiAffinity:
+ preferredDuringSchedulingIgnoredDuringExecution:
+ - weight: 100
+ podAffinityTerm:
+ labelSelector:
+ matchExpressions:
+ - key: app
+ operator: In
+ values:
+ - order-service
+ topologyKey: kubernetes.io/hostname
+ # ^^^ "尽量"把同 app 的 Pod 分配到不同机器
+```
+
+> [!question] 为什么要把同一个服务的 Pod 分散?
+>
+> 如果所有副本都在同一台物理机上,这台机器宕机就会导致服务完全不可用。**故障域隔离**是高可用的基础——把 Pod 分散到不同节点 = 把鸡蛋放进不同篮子。
+
+### 节点亲和性:只调度到特定标签的节点
+
+```yaml
+spec:
+ affinity:
+ # ========== 节点亲和性:只调度到特定标签的节点 ==========
+ nodeAffinity:
+ requiredDuringSchedulingIgnoredDuringExecution:
+ nodeSelectorTerms:
+ - matchExpressions:
+ - key: node-role
+ operator: In
+ values:
+ - high-memory # 只调度到有 high-memory 标签的节点
+```
+
+### 调度级别对比
+
+| 级别 | 含义 | 推荐度 |
+|---------|------|--------|
+| `requiredDuring...` | 硬约束,不满足就不调度 | 高(适用于特殊硬件节点) |
+| `preferredDuring...` | 软约束,尽力而为 | 高(适用于反亲和性拆分) |
+| `ignoringDuringExecution` | 调度时检查,运行时忽略变化 | 标准做法 |
+
+## Taint & Toleration — 节点级别的"白名单"
+
+Taint 设在 Node 上,Toleration 设在 Pod 上:
+
+```bash
+# 给节点加污点:只有带 db 容忍度的 Pod 才能调度上来
+kubectl taint nodes node1 dedicated=db:NoSchedule
+```
+
+```yaml
+spec:
+ tolerations:
+ - key: "dedicated"
+ operator: "Equal"
+ value: "db"
+ effect: "NoSchedule"
+```
+
+> [!info] Taint effect 三种类型
+>
+> | Effect | 含义 | 场景 |
+> |--------|------|------|
+> | `NoSchedule` | 不接受新 Pod | 专用节点 |
+> | `PreferNoSchedule` | 尽量避免但不强制 | 优先但不是硬性 |
+> | `NoExecute` | 驱逐已有的 Pod | 节点出问题时 |
+
+```mermaid
+flowchart LR
+ Node["Node 打 Taint
dedicated=db:NoSchedule"] --> Check{Pod 有 Toleration?}
+
+ Check -- 否 --> Reject["❌ 不允许调度"]
+ Check -- 是 --> Accept["✅ 允许调度"]
+
+ style Reject fill:#ffebee
+ style Accept fill:#e8f5e9
+```
+
+## TopologySpreadConstraints — K8s 1.19+ 推荐的跨可用区分布
+
+比 anti-affinity 更优雅的实现方式,特别适合多云和混合云架构:
+
+```yaml
+spec:
+ topologySpreadConstraints:
+ - maxSkew: 1 # 任何两个可用区最多差 1 个 Pod
+ topologyKey: topology.kubernetes.io/zone # 按可用区分组
+ whenUnsatisfiable: DoNotSchedule # 不满足则不调度
+ labelSelector:
+ matchLabels:
+ app: order-service
+```
+
+> [!tip] WhenUnsatisfiable 策略选择
+>
+> - `DoNotSchedule`:宁可等也不破坏分布均衡(严格一致)
+> - `ScheduleAnyway`:先调度上去,能分尽量分(可用性优先)
+>
+> **生产建议**:对外服务用 `ScheduleAnyway` + 反亲和性;内部有状态组件用 `DoNotSchedule`。
+
+## 补充:NodeSelector 最简单的限制
+
+```yaml
+spec:
+ nodeSelector:
+ disktype: ssd # 只调度到标注了 disktype=ssd 的节点
+```
+
+> [!note] NodeSelector vs Node Affinity
+>
+> NodeSelector 是最基本的调度方式——只能做相等匹配。如果需要 `In/NotIn/Exists` 等更复杂的逻辑,应使用 `nodeAffinity`。现代 K8s 项目中建议统一使用 `nodeAffinity` 以保持风格一致。
+
+## 组合使用示例
+
+```yaml
+# 一个典型的 Production Web 部署
+spec:
+ template:
+ spec:
+ # ① 必须运行在 Linux x86_64 节点上
+ affinity:
+ nodeAffinity:
+ requiredDuringSchedulingIgnoredDuringExecution:
+ nodeSelectorTerms:
+ - matchExpressions:
+ - key: kubernetes.io/os
+ operator: In
+ values: [linux]
+ - key: kubernetes.io/arch
+ operator: In
+ values: [amd64]
+
+ # ② 尽量分散在不同可用区
+ topologySpreadConstraints:
+ - maxSkew: 1
+ topologyKey: topology.kubernetes.io/zone
+ whenUnsatisfiable: ScheduleAnyway
+ labelSelector:
+ matchLabels:
+ app: web-frontend
+
+ # ③ 避免与后台批处理任务混在同一节点
+ podAntiAffinity:
+ preferredDuringSchedulingIgnoredDuringExecution:
+ - weight: 200
+ podAffinityTerm:
+ labelSelector:
+ matchExpressions:
+ - key: workload-type
+ operator: In
+ values: [batch-job]
+ topologyKey: kubernetes.io/hostname
+```
+
+## 关联笔记
+
+- [[../01-核心概念与Deployment]] — Deployment 中的 podTemplateSpec 位置
+- [[../06-资源与安全管控]] — ResourceQuota / NetworkPolicy 配合调度实现安全隔离
+- [[../hhs/MS/05-部署运维/02-Kubernetes]] — 调度失控时的常见排查技巧
diff --git a/hhs/MS/05-部署运维/02-Kubernetes/06-资源与安全管控.md b/hhs/MS/05-部署运维/02-Kubernetes/06-资源与安全管控.md
new file mode 100644
index 0000000..619bebd
--- /dev/null
+++ b/hhs/MS/05-部署运维/02-Kubernetes/06-资源与安全管控.md
@@ -0,0 +1,208 @@
+---
+tags: [kubernetes, resource-quota, limit-range, network-policy, security]
+create time: 2026-05-18 00:30
+---
+
+# K8s 资源与安全管控 — Quota、LimitRange & NetworkPolicy
+
+## 概述
+
+多人共享集群或多租户环境中,需要两道防线来保障稳定:**资源管控**防止单个团队耗尽集群,**网络策略**防止横向越权访问。这两者构成了 K8s 多环境的安全基线。
+
+## ResourceQuota — 命名空间资源总量上限
+
+```yaml
+apiVersion: v1
+kind: ResourceQuota
+metadata:
+ name: production-quota
+ namespace: production
+spec:
+ hard:
+ requests.cpu: "16" # 所有 Pod 的 CPU request 总和不超过 16核
+ requests.memory: 32Gi # 内存 request 不超过 32G
+ limits.cpu: "32" # 所有 Pod 的 CPU limit 不超过 32核
+ limits.memory: 64Gi # 内存 limit 不超过 64G
+ pods: "50" # 最多 50 个 Pod
+ services: "20" # 最多 20 个 Service
+ persistentvolumeclaims: "10" # 最多 10 个 PVC
+```
+
+> [!warning] 配额打满怎么办?
+>
+> 当某个命名空间的配额被占满时,该 namespace 内的新创建请求会直接失败(Return Error)。解决方法:清理不再使用的资源,或申请管理员增加配额 (`kubectl edit quota -n `)。
+
+## LimitRange — 单容器资源边界
+
+```yaml
+apiVersion: v1
+kind: LimitRange
+metadata:
+ name: default-limits
+ namespace: production
+spec:
+ limits:
+ # 默认 Limits/Requests(不指定的 Pod 自动套用)
+ - type: Container
+ default:
+ cpu: "500m"
+ memory: "512Mi"
+ defaultRequest:
+ cpu: "250m"
+ memory: "256Mi"
+ # 单个容器的边界
+ max:
+ cpu: "2"
+ memory: "4Gi"
+ min:
+ cpu: "50m"
+ memory: "64Mi"
+```
+
+> [!info] Quota vs LimitRange — 别搞混
+>
+> - **ResourceQuota** = namespace 总量天花板(整个房间能住多少人)
+> - **LimitRange** = 单容器边界(每人占多大床位)
+>
+> 两者配合使用,既能防止个体乱配资源,又能防止群体耗尽配额。
+
+## NetworkPolicy — 网络安全策略
+
+默认情况下,K8s 集群内所有 Pod 可以自由互访(零信任缺失)。NetworkPolicy 提供 L3/L4 层的双向流量控制:
+
+```yaml
+apiVersion: networking.k8s.io/v1
+kind: NetworkPolicy
+metadata:
+ name: order-service-policy
+ namespace: production
+spec:
+ podSelector:
+ matchLabels:
+ app: order-service
+ policyTypes:
+ - Ingress
+ - Egress
+
+ ingress:
+ # 允许来自 Ingress Controller 和 user-service 的流量
+ - from:
+ - namespaceSelector:
+ matchLabels:
+ name: ingress-ns
+ - podSelector:
+ matchLabels:
+ app: user-service
+ ports:
+ - protocol: TCP
+ port: 8080
+
+ egress:
+ # 允许 outbound DB 连接和 DNS 查询
+ - to:
+ - podSelector:
+ matchLabels:
+ app: mysql
+ ports:
+ - protocol: TCP
+ port: 3306
+ - to:
+ - namespaceSelector: {}
+ ports:
+ - protocol: UDP
+ port: 53 # DNS(必备!否则 Pod 无法解析域名)
+```
+
+### NetworkPolicy 最佳实践
+
+| 原则 | 说明 |
+|------|------|
+| **默认拒绝全部** | 先用 `podSelector: {}` + 空 ingress/egress 拦截一切 |
+| **白名单精确匹配** | 每个 Policy 只放开一个方向的流量 |
+| **DNS 永远放行** | 没有 egress DNS 规则,Service 发现全部失效 |
+| **跨命名空间要显式声明** | namespaceSelector 为空 `{}` 表示允许所有命名空间 |
+
+### 支持 NetworkPolicy 的网络插件
+
+> [!warning] NetworkPolicy 生效前提
+>
+> NetworkPolicy **需要集群网络插件支持**。默认的 Kubenet / Flannel 不一定支持,推荐使用:
+> - **Calico** — 功能最完整,支持 L7 策略(与 Istio 配合)
+> - **Cilium** — 基于 eBPF,性能最优,原生支持 L7 过滤
+> - **Antrea** — VMware 出品,兼容性好
+
+### 补充:Zero-Trust 默认策略模板
+
+```yaml
+# ========== 全局:默认拒绝所有入站 ==========
+apiVersion: networking.k8s.io/v1
+kind: NetworkPolicy
+metadata:
+ name: default-deny-all-ingress
+ namespace: production
+spec:
+ podSelector: {} # 选择所有 Pod
+ policyTypes:
+ - Ingress
+
+---
+# ========== 全局:默认拒绝所有出站 ==========
+apiVersion: networking.k8s.io/v1
+kind: NetworkPolicy
+metadata:
+ name: default-deny-all-egress
+ namespace: production
+spec:
+ podSelector: {}
+ policyTypes:
+ - Egress
+```
+
+> [!tip] 渐进式实施 NetworkPolicy
+>
+> 不要在已有集群上一夜之间切换所有策略——这会立刻打飞所有服务。正确的做法:
+>
+> 1. 先以 **audit** 模式运行 Calico/Cilium,观察现有流量
+> 2. 对每个服务编写 allowlist Policy,在 **测试环境** 验证
+> 3. 逐步推广到生产,从非关键服务开始
+> 4. 最终启用 deny-all 作为兜底
+
+## 补充:SecurityContext — 容器级安全加固
+
+```yaml
+spec:
+ containers:
+ - name: order-service
+ securityContext:
+ runAsNonRoot: true # 禁止 root 运行
+ readOnlyRootFilesystem: true # 根文件系统只读
+ allowPrivilegeEscalation: false # 禁止提权
+ capabilities:
+ drop: ["ALL"] # 丢弃所有内核能力
+ add: ["NET_BIND_SERVICE"] # 仅保留绑定 <1024 端口需要的能力
+
+ # Pod 级别的 DNS 配置
+ dnsPolicy: ClusterFirstWithHostNet # DNS 策略
+ automountServiceAccountToken: false # 不挂载 SA Token(不需要 API 时)
+```
+
+> [!warning] readOnlyRootFilesystem 的坑
+>
+> 某些应用(如 Python 的 pip 缓存、Java 的 temp 目录)会尝试写入 `/tmp` 或应用目录。解决方案:
+>
+> ```yaml
+> volumeMounts:
+> - name: tmp-volume
+> mountPath: /tmp
+> volumes:
+> - name: tmp-volume
+> emptyDir: {}
+> ```
+>
+> 通过 `emptyDir` 挂载临时卷,既保持了根文件系统只读,又给了应用必要的写权限。
+
+## 关联笔记
+
+- [[../02-配置管理]] — Secret 资源与 RBAC 共同构成安全基线
+- [[../05-调度控制]] — 调度控制 + 网络策略 = 纵深防御
+- [[../03-CICD与GitOps/04-安全与发布策略]] — GitOps 中的 Secret 安全
diff --git a/hhs/MS/05-部署运维/02-Kubernetes/07-运维排查.md b/hhs/MS/05-部署运维/02-Kubernetes/07-运维排查.md
new file mode 100644
index 0000000..e2224c8
--- /dev/null
+++ b/hhs/MS/05-部署运维/02-Kubernetes/07-运维排查.md
@@ -0,0 +1,143 @@
+---
+tags: [kubernetes, troubleshooting, diagnostics, ops-checklist]
+create time: 2026-05-18 00:30
+---
+
+# K8s 运维排查 — Checklist 与诊断速查
+
+## 概述
+
+每次上线前过一遍清单,遇到问题时有系统化的排查思路。本章整理了实战中最常用的诊断命令、高频问题对照表和有价值的调试技巧。更多 SRE 理念(错误预算、MTTR)参见 [[../04-SRE实践]]。
+
+## 上线前 Checklist
+
+| # | 检查项 | 说明 |
+|---|--------|------|
+| 1 | **Probe 已配置** | liveness/readiness/startup 都设定了阈值 |
+| 2 | **Resources Limits** | 防止单个 Pod OOMKill 拖垮整台机器 |
+| 3 | **日志输出到 stdout/stderr** | 可被采集器解析为 JSON |
+| 4 | **trace_id 透传** | 跨服务调用链 trace_id 不丢失 |
+| 5 | **回滚预案** | `kubectl rollout undo deployment/order-service` 能用 |
+| 6 | **告警已配置** | 关键指标异常时有人收到通知 |
+| 7 | **镜像 Tag** | 不用 latest,用语义化版本或 commit SHA |
+
+## 常用诊断命令
+
+```bash
+# 1. 查看 Pod 状态(为什么起不来?)
+kubectl get pods -n production
+
+# 2. 看单个 Pod 的详细事件
+kubectl describe pod -n production
+# ↑ 重点看 Events 区域的 LastState / State / Reason
+
+# 3. 看容器日志(含重启前的上一次输出)
+kubectl logs -n production --previous # 上次崩溃容器的日志
+kubectl logs -n production -c sidecar # 多容器指定 sidecar 名
+
+# 4. 进入运行中的容器调试
+kubectl exec -it -n production -- /bin/sh
+
+# 5. 查看滚动更新进度
+kubectl rollout status deployment/order-service -n production
+
+# 6. 回滚到上一个版本
+kubectl rollout undo deployment/order-service -n production
+
+# 7. 查看资源占用
+kubectl top pods -n production # Pod CPU/Memory
+kubectl top nodes # 节点级别
+```
+
+## 高频问题对照表
+
+| 症状 | 可能原因 | 排查步骤 |
+|------|---------|---------|
+| `ImagePullBackOff` | 镜像不存在、仓库认证失败、拼写错误 | `kubectl describe pod` 看 Normal Events;确认 registry 凭证 Secret |
+| `ErrImagePull` | 镜像 tag 不存在 | 检查 CI 是否成功 push;`docker pull` 在本地复现 |
+| `CrashLoopBackOff` | Liveness Probe 误杀、代码异常启动 | `kubectl logs --previous`;检查 `/healthz` 健康端点逻辑 |
+| `Pending` (调度中) | 资源不足、Affinity/Toleration 不满足 | `kubectl describe pod` 看 Warning 事件;检查节点可用资源 |
+| `OOMKilled` | memory limit 过小或内存泄漏 | 增大 limits;检查应用堆dump;JVM 需设 `-Xmx` |
+| Service 不通 | Selector 标签不匹配、端口配置错 | `kubectl get ep ` 看 Endpoint 列表;curl ClusterIP 验证 |
+| ConfigMap/Secret 未生效 | 重建了 Pod 但环境变量没更新 | 删除 Pod 让 Deployment 重建;ConfigMap volume 挂载会热更新 |
+| Ingress 无响应 | Ingress Controller 未安装、Backend 配置错 | `kubectl get pods -n ingress-nginx`;检查 annotation 语法 |
+| DNS 解析失败 | CoreDNS Pod 异常或 NetworkPolicy 限制 DNS egress | `nslookup kubernetes.default`;确保 DNS egress 端口 53 放行 |
+| Pod 被频繁驱逐 | Node 资源不足触发 Eviction | `kubectl describe node ` 看 MemoryPressure/DiskPressure |
+
+## 调试技巧:优雅地抓包与断点
+
+```bash
+# ========== Debugging Sidecar 模式 ==========
+# 给故障 Pod 附加一个临时调试容器,共享网络命名空间
+kubectl debug -it --image=nicolaka/netshoot --share-network
+
+# 进来了之后可以直接:
+# curl, nslookup, tcpdump, ping, tshark — 全套网络诊断工具
+
+# ========== 动态调整日志级别(无需重建 Pod)==========
+# 通过 port-forward 访问 kube-apiserver 的 debug endpoint
+kubectl port-forward svc/kube-apiserver 6443:443 -n default
+```
+
+> [!tip] 快速判断 K8s 问题的层级
+>
+> ```
+> Pod 起不来 → 查 Images / Resources / Probes
+> Pod 起来了但服务不通 → 查 Service Selector / Endpoints / Ingress
+> 服务通但有报错 → 查 App Logs / Metrics / Traces
+> 性能差 → 查 CPU Throttling / Disk IO / 连接池
+> ```
+>
+> 按这个顺序一层层定位,避免在日志里大海捞针。
+
+## 补充:kubectl 高阶用法
+
+```bash
+# 根据表达式筛选(比如只看处于 CrashLoop 的 Pod)
+kubectl get pods --field-selector=status.phase==Failed -A
+
+# 批量执行命令(在每个 Pod 中同时运行)
+kubectl exec deploy/api-server -- sh -c 'uptime; free -m'
+
+# 导出资源配置用于备份或审计
+kubectl get deployment -n production -o yaml > deploy-backup.yaml
+
+# 模拟变更效果(dry-run,不做实际修改)
+kubectl apply -f new-deployment.yaml --dry-run=server -o yaml
+
+# 查看哪个节点承载了某个 Pod
+kubectl get pods -o wide -n production | grep my-app
+
+# 持续监控 Pod 事件(实时流)
+kubectl get events -n production --sort-by=.lastTimestamp -w
+```
+
+> [!info] 理解 kubectl verbosity 级别
+>
+> `-v=6` 显示 HTTP 请求 headers;`-v=8` 额外返回响应 body;`-v=9` 逐行展开。调试 API 交互时通常 `-v=6` 就足够了,`-v=8` 以上会产生大量输出。
+
+## 补充:集群层面的健康检查
+
+```bash
+# 查看所有控制面组件状态
+kubectl get componentstatuses # K8s 1.19+ 已废弃,改用:
+kubectl get endpoints etcd -n kube-system
+
+# 检查 CoreDNS 健康(DNS 异常的起点)
+kubectl get pods -n kube-system -l k8s-app=kube-dns
+kubectl logs -n kube-system
+
+# 检查存储插件正常
+kubectl get storageclass
+
+# 查看当前活跃的资源配额使用情况
+kubectl describe quota -n production
+```
+
+## 关联笔记
+
+- [[../01-核心概念与Deployment]] — Deployment 回滚操作
+- [[../03-网络与服务发现]] — Service / Ingress 的调试方法
+- [[../05-调度控制]] — Pending Pod 与 Affinity/Toleration 的关系
+- [[../hhs/MS/05-部署运维/04-SRE实践]] — MTTR 指标与故障恢复
+- [[../hhs/MS/04-可观测性]] — Prometheus + Grafana 可视化排查
diff --git a/hhs/MS/05-部署运维/03-CICD与GitOps.md b/hhs/MS/05-部署运维/03-CICD与GitOps.md
index 7bedec1..f0a332d 100644
--- a/hhs/MS/05-部署运维/03-CICD与GitOps.md
+++ b/hhs/MS/05-部署运维/03-CICD与GitOps.md
@@ -1,176 +1,205 @@
---
-tags: [microservice, cicd, gitops, github-actions, argocd]
-create time: 2026-05-05
+tags: [microservice, cicd, gitops, github-actions, argocd, kubernetes, helm]
+create time: 2026-05-05 14:30
---
-# CI/CD 与 GitOps
+# CI/CD 与 GitOps — 参考手册
## 概述
-微服务需要**独立部署**。上百个服务的手工发布是不可想象的——必须用自动化流水线保证每次变更都能安全、快速地推送到生产环境。
+微服务架构下的自动化交付体系。**一次构建,多处部署**是核心哲学——镜像不随环境重新编译,只改 K8s ConfigMap。本文档是总入口:顶层概念、决策矩阵、速查表见本页;详细教程和实操指南在子文档中。
+
+---
+
+## 快速导航
+
+| 主题 | 定位 | 文档 |
+|------|------|------|
+| Pipeline 搭建 | **教程**:从 0 到 1 写出一个能跑的流水线 | [[03-CICD与GitOps/01-CICD基础与实践]] |
+| GitOps & ArgoCD | **教程**:Push → Pull 架构迁移、Application CRD | [[03-CICD与GitOps/02-GitOps与ArgoCD]] |
+| Helm 模板 | **操作手册**:values.yaml、Go 模板、多环境覆盖 | [[03-CICD与GitOps/03-Helm模板管理]] |
+| 安全 & 发布策略 | **决策指南**:Secret 选型、Canary vs Blue-Green | [[03-CICD与GitOps/04-安全与发布策略]] |
+
+---
+
+## 架构全景
```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
-```
+ Dev["开发者 push"] --> CI["GitHub Actions\n(CI: 测试→构建→Push镜像)"]
+ CI -->|"新镜像"| REG["Container Registry"]
-## CI/CD 设计原则
+ Dev2["开发者 PR manifest"] --> MANIFEST["Git Manifest Repo"]
-| 原则 | 说明 |
-|------|------|
-| **一次构建,多处部署** | 镜像不随环境重新编译,只改 K8s ConfigMap/环境变量 |
-| **语义化版本** | 镜像 tag 用 `v1.2.3`,tag 即版本溯源 |
-| **路径过滤** | 只对相关服务的代码变更触发构建 |
-| **Commit SHA 作为镜像 tag** | 保证精确回滚 |
-| **分阶段部署** | Staging → Production 的审批关卡不可跳过 |
-
-## GitHub Actions Pipeline 实战
-
-```yaml
-# .github/workflows/deploy.yml
-name: Deploy order-service
-on:
- push:
- branches: [main]
- paths:
- - "services/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
- 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
-
- # ========== Stage 3: Deploy to Production ==========
- deploy-production:
- needs: deploy-staging
- runs-on: ubuntu-latest
- environment: production
- steps:
- - name: Canary release (10% → 50% → 100%)
- run: |
- kubectl patch canary order-service --type merge \
- -p '{"spec":{"weight":10}}'
-
- # ... 等待监控确认,逐步放大流量
- echo "Monitor metrics before proceeding..."
-```
-
-## GitOps 工作流
-
-GitOps 的核心思想:**K8s 集群的状态 = Git 仓库中声明式配置的当前状态。**
-
-```mermaid
-flowchart LR
- Dev["开发者 PR"] -->|"修改 K8s manifest"| Git[(Git Repo)]
-
subgraph Cluster["K8s Cluster"]
- Argo["ArgoCD / Flux"] -->|同步| K8sState["Pod/Service/ConfigMap"]
+ Argo["ArgoCD"] -->|"自动同步"| WORKLOADS["Pod / Service / ..."]
end
-
- Git -.->|Webhook| Argo
-
- Argo -->|"检测到差异"| Diff{"状态一致?"}
- Diff -- 否 --> Sync["自动同步到 K8s ✅"]
- Diff -- 是 --> OK["已一致 ⏸️"]
-
- style Git fill:#e3f2fd
+
+ MANIFEST -.->|Watch 变动| Argo
+
+ style CI fill:#e8f5e9
style Argo fill:#fff3e0
- style K8sState fill:#e8f5e9
+ style WORKLOADS fill:#e3f2fd
```
-### 与传统 CI/CD 的区别
+**核心分工**:**CI/CD 管"怎么建"**(构建、扫描、推送镜像),**GitOps 管"怎么维持"**(Git 声明 → 集群自动同步)。
-| 维度 | 传统 CI/CD | GitOps |
-|------|-----------|--------|
-| **部署驱动** | CI 服务器主动推送 | Git 仓库变动触发拉取 |
-| **状态源** | CI pipeline 的历史记录 | Git commit history |
-| **回滚方式** | 回到上一次的 pipeline | `git revert` + 自动同步 |
-| **漂移检测** | 通常无 | 持续比对,自动修复不一致 |
-| **代表工具** | Jenkins / GitLab CI / GitHub Actions | ArgoCD / Flux |
+---
-### GitOps 的优势
+## 标签策略速查
-1. **审计完整** — 所有变更都在 Git 中可追溯
-2. **回滚简单** — `git revert` 就是回滚操作
-3. **自修复** — ArgoCD 持续监测并修复集群状态偏离
-4. **多人协作** — 通过 PR Review 流程管控配置变更
+| Tag | 示例 | 用途 | 安全性 |
+|-----|------|------|--------|
+| `latest` | `nginx:latest` | ❌ 开发调试 | ❌ 可被覆盖,不可回滚 |
+| 语义版本 | `v1.2.3` | ✅ 人类阅读、快速参考 | ⚠️ 仍可被更新覆盖 |
+| Commit SHA | `sha256:a1b2c3d` | ✅ 机器精确引用、精确回滚 | ✅ 全局唯一、不可变 |
+| Run Number | `v123` | ✅ GitHub Actions 快速参考 | ⚠️ 单次 workflow 内唯一 |
-## Helm — K8s 的包管理
+> **推荐**:同时打 `sha256:{commit}` + `v{run_number}`。生产回滚优先用 Commit SHA。
-当每个服务都有几十行 YAML 时,Helm 能大幅简化部署:
+---
+## Pipeline 阶段对照
+
+| 阶段 | 做什么 | 工具举例 | 详见 |
+|------|--------|---------|------|
+| 触发过滤 | 只对相关代码变更构建 | `paths`, `pull_request` | [[03-CICD与GitOps/01-CICD基础与实践]] |
+| 测试 | 单元测试 + 集成测试 | Jest, pytest, go test | [[03-CICD与GitOps/01-CICD基础与实践]] |
+| 安全扫描 | 镜像/依赖漏洞检测 | Trivy, Snyk | [[03-CICD与GitOps/04-安全与发布策略]] |
+| 构建+推送 | Docker image 构建并推仓库 | docker build/push | [[03-CICD与GitOps/01-CICD基础与实践]] |
+| Staging 验证 | 预发布环境冒烟测试 | kubectl set image | [[03-CICD与GitOps/01-CICD基础与实践]] |
+| Production | 灰度放量,人工审批关卡 | Canary, Blue-Green | [[03-CICD与GitOps/04-安全与发布策略]] |
+
+---
+
+## 部署驱动模式对比
+
+| 维度 | 传统 CI/CD (Push) | GitOps (Pull) |
+|------|------------------|---------------|
+| 部署驱动 | CI 服务器主动推送 | Git 仓库变动触发拉取 |
+| 状态源 | CI pipeline 历史 | Git commit history |
+| 漂移检测 | 通常无 | 持续比对,自动修复 |
+| 安全边界 | CI 需直连 K8s | ArgoCD 集群内运行 |
+| 回滚方式 | 回到上一次的 pipeline | `git revert` + 自动同步 |
+| 代表工具 | Jenkins, GitHub Actions | ArgoCD, Flux |
+
+> **选型建议**:团队规模 < 20 人时 Push 模式足够。≥ 50 人或多环境场景建议迁移到 GitOps。
+
+---
+
+## Secret 管理方案对比
+
+| 方案 | 适用场景 | 优点 | 缺点 |
+|------|---------|------|------|
+| K8s 原生 Secret | 小规模内部团队 | 零成本,开箱即用 | etcd 明文存储 |
+| ESO (External Secrets) | 已有 Vault / AWS SSM | Git 中无密文 | 需维护额外组件 |
+| SOPS + sealed-secrets | ArgoCD 用户 | 加密文件可提交到 Git | 需管理 PKI |
+| 云厂商 Secret Manager | 深度绑定单一云平台 | 审计完善 | 平台锁定 |
+
+> **渐进路径**:K8s Secret → 接入云厂商 Secret Manager → 引入 ESO 实现多云解耦。
+
+---
+
+## 发布策略对比
+
+| 维度 | Rolling Update | Canary | Blue-Green | Experiment |
+|------|---------------|--------|------------|------------|
+| 停机时间 | 可能有抖动 | 零 | 零 | 零 |
+| 复杂度 | 低 | 中 | 中高 | 高 |
+| 资源消耗 | 1x | 1.5x | 2x | 2x+ |
+| 回滚速度 | 慢(完整重建) | 快(切流量) | 秒级 | 秒级 |
+| 典型场景 | 内部服务 | 用户-facing | 重要版本 | A/B 测试 |
+
+---
+
+## 编排工具选型
+
+| 服务规模 | 推荐方案 | 理由 |
+|----------|---------|------|
+| < 10 个服务 | Kustomize / 裸 YAML | 复杂度高于收益 |
+| 10~50 个服务 | Helm | 模板复用价值明显 |
+| > 50 个服务 | Helm + Kustomize overlays | Helm 管模板,Kustomize 管环境差异 |
+
+---
+
+## 关键决策流程图
+
+### Secret 选型决策
+
+```mermaid
+flowchart TD
+ START["开始选型"] --> SIZE["团队 < 20人?"]
+ SIZE -- 是 --> K8SSECRET["✅ K8s 原生 Secret"]
+ SIZE -- 否 --> CLOUD["深度绑定云厂商?"]
+ CLOUD -- 是 --> SECRETMGR["✅ Cloud Secret Manager"]
+ CLOUD -- 否 --> GITOPS["使用 ArgoCD?"]
+ GITOPS -- 是 --> SOPS["✅ SOPS + sealed-secrets"]
+ GITOPS -- 否 --> ESO["✅ External Secrets Operator"]
+
+ style K8SSECRET fill:#e8f5e9
+ style SECRETMGR fill:#e3f2fd
+ style SOPS fill:#fff3e0
+ style ESO fill:#f3e5f5
+```
+
+### 渐进式采纳路径
+
+```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
+```
+
+---
+
+## 附录:常用命令速查
+
+### GitHub Actions
```bash
-# 创建 Chart 模板
-helm create order-service
+# 查看 workflow 运行记录
+gh run list --workflow deploy.yml
-# 使用 values.yaml 参数化部署
-helm upgrade --install order-service ./charts/order-service \
- --set image.tag=v1.2.3 \
- --set replicas=3 \
- --namespace=production
+# 手动触发 workflow
+gh workflow run deploy.yml --ref main
```
-> [!tip] 为什么需要 Helm?
->
-> 没有 Helm 时,每个服务都要手动维护 Deployment、Service、Ingress、ConfigMap 等几十个 YAML 文件。Helm 允许你把通用模板抽出来,只在 values.yaml 里改差异化配置。
+### kubectl
+```bash
+# 滚动更新指定镜像
+kubectl set image deployment/order-service \
+ order=registry.example.com/order-service:v1.2.3 -n production
+
+# 查看 rollout 状态
+kubectl rollout status deployment/order-service -n production --timeout=120s
+
+# 回滚到上一版本
+kubectl rollout undo deployment/order-service -n production
+```
+
+### ArgoCD CLI
+```bash
+argocd app sync order-service # 手动同步
+argocd app diff order-service # Git vs 实际差异
+argocd app history order-service # 同步历史
+argocd app rollback order-service # 回滚
+```
+
+### Helm
+```bash
+helm upgrade --install order-service ./charts/order-service -n production
+helm upgrade --install order-service ./charts/order-service -n staging --set image.tag=v1.2.4-dev
+helm template order-service ./charts/order-service # 仅渲染,不安装
+helm history order-service -n production # Release 历史
+```
+
+---
## 关联笔记
-- [[05-部署运维/01-容器化]] — Docker 镜像构建是 CI/CD 的第一步
-- [[05-部署运维/02-Kubernetes]] — K8s 是部署的目标平台
-- [[05-部署运维/04-SRE实践]] — 错误预算影响发布策略
+- [[01-容器化]] — Docker 镜像构建是 CI/CD 的第一步
+- [[02-Kubernetes]] — K8s 是部署的目标平台
+- [[04-SRE实践]] — 错误预算影响发布策略
+- [[05-监控告警]] — Prometheus + Alertmanager 为 Canary 提供指标支撑
diff --git a/hhs/MS/05-部署运维/03-CICD与GitOps/01-CICD基础与实践.md b/hhs/MS/05-部署运维/03-CICD与GitOps/01-CICD基础与实践.md
new file mode 100644
index 0000000..2cb1a7e
--- /dev/null
+++ b/hhs/MS/05-部署运维/03-CICD与GitOps/01-CICD基础与实践.md
@@ -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 ` |
+| **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]] — 参考手册与决策指南
diff --git a/hhs/MS/05-部署运维/03-CICD与GitOps/02-GitOps与ArgoCD.md b/hhs/MS/05-部署运维/03-CICD与GitOps/02-GitOps与ArgoCD.md
new file mode 100644
index 0000000..4d8dec1
--- /dev/null
+++ b/hhs/MS/05-部署运维/03-CICD与GitOps/02-GitOps与ArgoCD.md
@@ -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]] — 参考手册与决策指南
diff --git a/hhs/MS/05-部署运维/03-CICD与GitOps/03-Helm模板管理.md b/hhs/MS/05-部署运维/03-CICD与GitOps/03-Helm模板管理.md
new file mode 100644
index 0000000..dbb2e3b
--- /dev/null
+++ b/hhs/MS/05-部署运维/03-CICD与GitOps/03-Helm模板管理.md
@@ -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]] — 参考手册与决策指南
diff --git a/hhs/MS/05-部署运维/03-CICD与GitOps/04-安全与发布策略.md b/hhs/MS/05-部署运维/03-CICD与GitOps/04-安全与发布策略.md
new file mode 100644
index 0000000..441f07c
--- /dev/null
+++ b/hhs/MS/05-部署运维/03-CICD与GitOps/04-安全与发布策略.md
@@ -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]] — 参考手册与决策指南总入口
diff --git a/hhs/MS/05-部署运维/04-SRE实践.md b/hhs/MS/05-部署运维/04-SRE实践.md
index 1f78689..e72184d 100644
--- a/hhs/MS/05-部署运维/04-SRE实践.md
+++ b/hhs/MS/05-部署运维/04-SRE实践.md
@@ -3,11 +3,11 @@ tags: [microservice, sre, slo, error-budget, postmortem]
create time: 2026-05-05
---
-# SRE 实践
+# SRE 实践 — 参考手册
## 概述
-站点可靠性工程 (SRE) 把运维问题看作**软件工程问题**。它的核心理念是用 SLI/SLO/SLA 来量化服务质量,避免"我觉得系统很慢"这类模糊描述。
+站点可靠性工程 (SRE) 把运维问题看作**软件工程问题**。它的核心理念是用 SLI/SLO/SLA 来量化服务质量,避免"我觉得系统很慢"这类模糊描述。本文档是总入口:顶层概览、速查表见本页;详细教程和实操指南在子文档中。
```mermaid
flowchart LR
@@ -25,95 +25,15 @@ flowchart LR
style Stabilize fill:#ffebee
```
-## SLI / SLO / SLA 详解
+## 快速导航
-### 三层模型
+| 主题 | 定位 | 文档 |
+|------|------|------|
+| SLI / SLO / SLA 详解 | **基础**:三层模型、比例对照表、设定方法 | [[04-SRE实践/01-SLI-SLO-SLA详解]] |
+| Error Budget 深度解析 | **进阶**:计算方式、决策矩阵、告警联动 | [[04-SRE实践/02-错误预算深度解析]] |
+| SRE 心法与事故复盘 | **文化**:五条心法、Postmortem 模板、DORA Metrics | [[04-SRE实践/03-SRE心法与事故复盘]] |
-| 术语 | 全称 | 定义 | 谁制定 | 变更频率 |
-|------|------|------|--------|---------|
-| **SLI** | Service Level Indicator | 实际度量:用户请求的成功率是多少? | 观测系统自动产出 | 持续更新 |
-| **SLO** | Service Level Objective | 内部目标:我们承诺达到 99.9% 可用性 | SRE + 研发 | 季度回顾 |
-| **SLA** | Service Level Agreement | 对外契约:达不到就赔钱 | 法务 + 商务 | 按合同约定 |
-
-### 实用性比例对照表
-
-| SLO | 每年停机时间 | 每月停机时间 | 适用级别 |
-|-----|-------------|-------------|---------|
-| **99%** | ~87.6 小时 | ~7.2 小时 | 内部工具(几乎无意义) |
-| **99.9% ("三个九")** | ~8.76 小时 | ~43 分钟 | 大多数后端服务 ✅ |
-| **99.95%** | ~4.38 小时 | ~21 分钟 | 核心交易链路 |
-| **99.99% ("四个九")** | ~52.6 分钟 | ~4.3 分钟 | 金融级 / 支付系统 |
-| **99.999%** | ~5.26 分钟 | ~26 秒 | 电信级,极难实现 |
-
-> [!warning] "三个九"是底线
->
-> 如果团队宣称 SLO = 99%,这意味着每月可以容忍近 **7 小时的不可用**——这在生产环境中基本等于没有可用性目标。
-
-## Error Budget (错误预算) 深度解析
-
-### 计算方式
-
-```
-可用率 = (总时间 - 故障时间) / 总时间 × 100%
-错误预算 = 1 - SLO
-
-SLO = 99.9% → 错误预算 = 0.001
- 每月可容忍故障 = 30 × 24 × 60 × 0.001 = 43.2 分钟
- 每周可容忍故障 = 7 × 24 × 60 × 0.001 = 10.1 分钟
-
-SLO = 99.99% → 错误预算 = 0.0001
- 每月可容忍故障 ≈ 4.3 分钟
-```
-
-### 基于错误预算的决策矩阵
-
-```mermaid
-flowchart TD
- Ratio["错误预算使用率 = 已消耗 / 总预算"]
-
- Ratio -- "< 50%" --> Green["🟢 绿灯阶段
预算充足: 可以大胆发布
新功能、尝试激进方案
常规发布节奏"]
- Ratio -- "50~90%" --> Yellow["🟡 黄灯阶段
预算紧张: 收紧发布频率
增加人工审查
暂停非关键功能开发"]
- Ratio -- "\> 90%" --> Orange["🟠 橙灯阶段
严重不足: 冻结发布
专注稳定性修复
全员 On-Call"]
- Ratio -- "\> 100%" --> Red["🔴 红灯阶段
预算耗尽: 禁止所有
功能性变更
SLO 事故复盘"]
-
- style Green fill:#c8e6c9
- style Yellow fill:#fff9c4
- style Orange fill:#ffe0b2
- style Red fill:#ffcdd2
-```
-
-### 错误预算告警联动
-
-```yaml
-# Prometheus 告警规则示例
-groups:
- - name: error-budget
- rules:
- - alert: ErrorBudgetBurnRateHigh
- expr: |
- sum(rate(http_requests_total{status=~"5.."}[1h])) /
- sum(rate(http_requests_total[1h])) > 0.001
- for: 1h
- labels:
- severity: warning
- budget_phase: yellow
- annotations:
- summary: "错误预算消耗加速"
- message: "当前错误率 {{ $value }}% > SLO 阈值 0.1%"
-
- - alert: ErrorBudgetExhausted
- expr: |
- (sum(rate(http_requests_total{status=~"5.."}[24h])) /
- sum(rate(http_requests_total[24h]))) >= 0.001
- labels:
- severity: critical
- budget_phase: red
- annotations:
- summary: "错误预算已耗尽!"
- message: "本月 SLO 已破线,冻结非紧急变更"
-```
-
-## SRE 心法
+## SRE 五条心法
1. **用户视角定义 SLO** — 不是"API P99 < 200ms",而是"用户在正常网络下加载页面 < 2s"
2. **错误预算用完 = 停止功能开发** — 全力修 bug、加稳定性
@@ -121,29 +41,24 @@ groups:
4. **自动化一切重复劳动** — 手动操作一定会出错
5. **接受一定程度的失败** — 在可控范围内快速迭代,比追求完美更重要
-## Blameless Postmortem 模板
+## Error Budget 计算速查
-| 字段 | 填写内容 |
-|------|---------|
-| **事件名称** | `2026-05-05 支付服务超时事故` |
-| **影响范围** | 约 15% 的支付请求失败,持续 23 分钟 |
-| **发现时间** | 14:32 (On-Call 接到告警) |
-| **恢复时间** | 14:55 (回滚后确认恢复) |
-| **根本原因** | 某次变更后支付网关的连接池大小从 50 降到 10 |
-| **时间线** | 14:00 发布 v1.2.3 → 14:30 错误率开始升高 → 14:32 收到告警 → 14:35 开始排查 → 14:45 定位根因 → 14:50 回滚 → 14:55 完全恢复 |
-| **改进行动** | ① 连接池参数变动必须经过压测验证;② 监控中补充连接池活跃数指标 |
-| **跟进人** | @zhangsan (行动 ①)、@lisi (行动 ②) |
+| SLO | 月预算 | 周预算 | 日预算 |
+|-----|--------|--------|--------|
+| 99% | ~5h | ~72min | ~14min |
+| 99.9% | ~43min | ~10min | ~1.4min |
+| 99.95% | ~21min | ~5min | ~43s |
+| 99.99% | ~4.3min | ~1min | ~8.6s |
+| 99.999% | ~26s | ~6s | <1s |
## SRE Metrics 看板
-除了 SLO,SRE 还需要关注以下运营指标:
-
-| 指标 | 说明 | 目标 |
-|------|------|------|
-| **MTTR** | Mean Time To Recovery | 核心服务 < 30min |
+| 指标 | 说明 | 精英级目标 |
+|------|------|-----------|
+| **MTTR** | Mean Time To Recovery | < 1h |
| **Change Failure Rate** | 变更导致故障的比例 | < 5% |
-| **Lead Time for Changes** | 代码提交到上线的时间 | < 2h |
-| **Deployment Frequency** | 日均部署次数 | > 5 (大规模团队) |
+| **Lead Time for Changes** | 代码提交到上线的时间 | < 1h |
+| **Deployment Frequency** | 部署频率 | 每日多次 |
> [!tip] DORA Metrics
>
@@ -155,6 +70,7 @@ groups:
## 关联笔记
-- [[04-可观测性/04-告警管理]] — SLO/Error Budget 与告警体系的联动
-- [[05-部署运维/02-Kubernetes]] — K8s HPA 弹性伸缩支撑 SLO 保障
-- [[05-部署运维/03-CICD与GitOps]] — GitOps 支持安全的持续交付
+- [[04-SRE实践/01-SLI-SLO-SLA详解]] — SLI/SLO/SLA 三层模型基础
+- [[04-SRE实践/02-错误预算深度解析]] — Error Budget 告警规则示例
+- [[hhs/MS/05-部署运维/02-Kubernetes]] — K8s HPA 弹性伸缩支撑 SLO 保障
+- [[hhs/MS/05-部署运维/03-CICD与GitOps]] — GitOps 支持安全的持续交付
diff --git a/hhs/MS/05-部署运维/04-SRE实践/01-SLI-SLO-SLA详解.md b/hhs/MS/05-部署运维/04-SRE实践/01-SLI-SLO-SLA详解.md
new file mode 100644
index 0000000..5a9b88f
--- /dev/null
+++ b/hhs/MS/05-部署运维/04-SRE实践/01-SLI-SLO-SLA详解.md
@@ -0,0 +1,77 @@
+---
+tags: [sre, slo, sli, sla, service-level]
+create time: 2026-05-18 00:50
+---
+
+# SLI / SLO / SLA 详解 — 可靠性量化基础
+
+## 概述
+
+站点可靠性工程 (SRE) 把运维问题看作**软件工程问题**。它的核心理念是用 SLI/SLO/SLA 来量化服务质量,避免"我觉得系统很慢"这类模糊描述。本文将详细拆解三层模型和实际应用方法。
+
+```mermaid
+flowchart LR
+ User["用户体验"] --> SLI{"实际测量"}
+ SLI -->|"达标"| SLO_OK["✅ SLO 达成"]
+ SLI -->|"不达标"| Budget["消耗错误预算"]
+
+ Budget --> Left{"预算剩余?"}
+ Left -- "> 50%" --> Ship["快速迭代 🚀"]
+ Left -- "< 10%" --> Stabilize["稳定优先 ❄️"]
+
+ style User fill:#e3f2fd
+ style SLO_OK fill:#e8f5e9
+ style Ship fill:#fff3e0
+ style Stabilize fill:#ffebee
+```
+
+## SLI / SLO / SLA 三层模型
+
+| 术语 | 全称 | 定义 | 谁制定 | 变更频率 |
+|------|------|------|--------|---------|
+| **SLI** | Service Level Indicator | 实际度量:用户请求的成功率是多少? | 观测系统自动产出 | 持续更新 |
+| **SLO** | Service Level Objective | 内部目标:我们承诺达到 99.9% 可用性 | SRE + 研发 | 季度回顾 |
+| **SLA** | Service Level Agreement | 对外契约:达不到就赔钱 | 法务 + 商务 | 按合同约定 |
+
+> [!question] 三者有什么区别?
+>
+> - **SLI** = 仪表盘上的真实数字("今天成功率 99.87%")
+> - **SLO** = 内部团队的目标线("我们要 ≥ 99.9%")
+> - **SLA** = 给客户的承诺条款("达不到赔偿 10% 月费")
+>
+> SLI < SLO → 团队内部关注;SLI < SLA → 客户开始投诉。
+
+### 实用的类比
+
+| 层级 | 生活类比 | 技术场景 |
+|------|---------|---------|
+| SLI | "我体重秤显示 72kg" | Prometheus 返回的 P99 延迟 230ms |
+| SLO | "我要保持 70kg 以下" | 研发团队内部追求 P99 < 200ms |
+| SLA | "健身合约:每周少于 3 次到店扣费" | 对客户提供 99.9% 可用性,否则退款 |
+
+## 实用性比例对照表
+
+| SLO | 每年停机时间 | 每月停机时间 | 适用级别 |
+|-----|-------------|-------------|---------|
+| **99%** | ~87.6 小时 | ~7.2 小时 | 内部工具(几乎无意义) |
+| **99.9% ("三个九")** | ~8.76 小时 | ~43 分钟 | 大多数后端服务 ✅ |
+| **99.95%** | ~4.38 小时 | ~21 分钟 | 核心交易链路 |
+| **99.99% ("四个九")** | ~52.6 分钟 | ~4.3 分钟 | 金融级 / 支付系统 |
+| **99.999%** | ~5.26 分钟 | ~26 秒 | 电信级,极难实现 |
+
+> [!warning] "三个九"是底线
+>
+> 如果团队宣称 SLO = 99%,这意味着每月可以容忍近 **7 小时的不可用**——这在生产环境中基本等于没有可用性目标。
+
+## 补充:如何为你的服务设定合适的 SLO
+
+1. **从用户视角出发** — 不是"API P99 < 200ms",而是"用户在正常网络下加载页面 < 2s"
+2. **参考行业标准** — 内部工具 99.9%,用户-facing 服务 99.95%+
+3. **考虑成本收益** — 从 99.9% 到 99.99% 可能需要 5~10 倍的基础设施投入
+4. **先设定保守目标再逐步提升** — 一个达不到的 SLO 比没有 SLO 更有害(团队会习惯打破它)
+5. **绑定到具体指标** — 每个 SLO 必须有对应的 SLI 自动采集数据支撑
+
+## 关联笔记
+
+- [[../02-错误预算深度解析]] — Error Budget 的计算与应用
+- [[../hhs/MS/05-部署运维/04-SRE实践]] — SRE 总体概览
diff --git a/hhs/MS/05-部署运维/04-SRE实践/02-错误预算深度解析.md b/hhs/MS/05-部署运维/04-SRE实践/02-错误预算深度解析.md
new file mode 100644
index 0000000..9da85cf
--- /dev/null
+++ b/hhs/MS/05-部署运维/04-SRE实践/02-错误预算深度解析.md
@@ -0,0 +1,113 @@
+---
+tags: [sre, error-budget, budget-allocation, sre-alerting]
+create time: 2026-05-18 00:50
+---
+
+# Error Budget 深度解析 — 稳定性与速度的平衡杠杆
+
+## 概述
+
+Error Budget(错误预算)是 SRE 体系中最具实操价值的概念之一。它将抽象的"SLO 目标"转化为具体的可消耗量,为产品迭代的快慢提供量化依据。更多背景见 [[../04-SRE实践/01-SLI-SLO-SLA详解]]。
+
+## 计算方式
+
+```
+可用率 = (总时间 - 故障时间) / 总时间 × 100%
+错误预算 = 1 - SLO
+
+SLO = 99.9% → 错误预算 = 0.001
+ 每月可容忍故障 = 30 × 24 × 60 × 0.001 = 43.2 分钟
+ 每周可容忍故障 = 7 × 24 × 60 × 0.001 = 10.1 分钟
+
+SLO = 99.99% → 错误预算 = 0.0001
+ 每月可容忍故障 ≈ 4.3 分钟
+```
+
+> [!tip] 理解错误预算的本质
+>
+> 错误预算不是"允许出错的额度",而是"**允许不完美的空间**"。它回答了一个关键问题:在当前的可靠性要求下,团队还能冒多大的风险?
+
+## 基于错误预算的决策矩阵
+
+```mermaid
+flowchart TD
+ Ratio["错误预算使用率 = 已消耗 / 总预算"]
+
+ Ratio -- "< 50%" --> Green["🟢 绿灯阶段
预算充足: 可以大胆发布
新功能、尝试激进方案
常规发布节奏"]
+ Ratio -- "50~90%" --> Yellow["🟡 黄灯阶段
预算紧张: 收紧发布频率
增加人工审查
暂停非关键功能开发"]
+ Ratio -- "\> 90%" --> Orange["🟠 橙灯阶段
严重不足: 冻结发布
专注稳定性修复
全员 On-Call"]
+ Ratio -- "\> 100%" --> Red["🔴 红灯阶段
预算耗尽: 禁止所有
功能性变更
SLO 事故复盘"]
+
+ style Green fill:#c8e6c9
+ style Yellow fill:#fff9c4
+ style Orange fill:#ffe0b2
+ style Red fill:#ffcdd2
+```
+
+### 各阶段的典型行动清单
+
+| 阶段 | 功能发布 | 技术债处理 | 人员安排 |
+|------|---------|-----------|---------|
+| 🟢 绿灯 | 正常节奏 | 按计划推进 | 按需 On-Call |
+| 🟡 黄灯 | 减少 50%,需额外审批 | 启动专项清理 | 增加第二响应人 |
+| 🟠 橙灯 | 冻结(仅 Hotfix) | 全员投入修复 | 负责人值班 |
+| 🔴 红灯 | 全面冻结 | Postmortem + 专项改进 | 全员待命 |
+
+## 错误预算告警联动
+
+```yaml
+# Prometheus 告警规则示例
+groups:
+ - name: error-budget
+ rules:
+ - alert: ErrorBudgetBurnRateHigh
+ expr: |
+ sum(rate(http_requests_total{status=~"5.."}[1h])) /
+ sum(rate(http_requests_total[1h])) > 0.001
+ for: 1h
+ labels:
+ severity: warning
+ budget_phase: yellow
+ annotations:
+ summary: "错误预算消耗加速"
+ message: "当前错误率 {{ $value }}% > SLO 阈值 0.1%"
+
+ - alert: ErrorBudgetExhausted
+ expr: |
+ (sum(rate(http_requests_total{status=~"5.."}[24h])) /
+ sum(rate(http_requests_total[24h]))) >= 0.001
+ labels:
+ severity: critical
+ budget_phase: red
+ annotations:
+ summary: "错误预算已耗尽!"
+ message: "本月 SLO 已破线,冻结非紧急变更"
+```
+
+> [!info] Multi-window Burn Rate Alerting
+>
+> Google SRE 推荐的做法是使用**两个窗口**来检测预算消耗速度:
+> - **短窗口(1h/5m)**:检测突发大量错误的情况(立即触发黄灯)
+> - **长窗口(半天/一天)**:检测慢性缓慢泄漏的情况(防止温水煮青蛙)
+>
+> 只有当两个窗口同时超限时才亮红灯,这样可以避免误报导致的警报疲劳。
+
+## 补充:多服务 Error Budget 管理策略
+
+当集群中有几十上百个服务时,不能把所有预算分配给同一个 SLO。推荐分层策略:
+
+| 层级 | 示例 | 预算分配 |
+|------|------|---------|
+| P0 核心链路 | 支付、登录 | 99.99% → 极低容错 |
+| P1 重要服务 | 订单、搜索 | 99.95% → 中等容错 |
+| P2 一般服务 | 后台管理、报表 | 99.9% → 较高容错 |
+
+> [!tip] Error Budget 分配实战技巧
+>
+> 将月度预算**均匀摊分到每周**作为基线。例如月预算 43 分钟 → 每周预算约 10 分钟。这比月末一次性清算更有操作性——你可以在周报中直接看到本周预算余量。
+
+## 关联笔记
+
+- [[../04-SRE实践/01-SLI-SLO-SLA详解]] — SLI/SLO/SLA 三层模型基础
+- [[../04-SRE实践/03-SRE心法与事故复盘]] — Blameless Postmortem 模板
+- [[../hhs/MS/05-部署运维/02-Kubernetes]] — K8s HPA 弹性伸缩支撑 SLO 保障
diff --git a/hhs/MS/05-部署运维/04-SRE实践/03-SRE心法与事故复盘.md b/hhs/MS/05-部署运维/04-SRE实践/03-SRE心法与事故复盘.md
new file mode 100644
index 0000000..e7d7919
--- /dev/null
+++ b/hhs/MS/05-部署运维/04-SRE实践/03-SRE心法与事故复盘.md
@@ -0,0 +1,114 @@
+---
+tags: [sre, postmortem, blameless, dora-metrics, incident-management]
+create time: 2026-05-18 00:50
+---
+
+# SRE 心法与事故复盘 — 文化和方法论
+
+## 概述
+
+SRE 不仅是技术指标和告警规则,更是一种工作文化。本章介绍 Google SRE 提出的五条核心心法,以及事故复盘的标准流程和 DORA Metrics 效能评估框架。更多 SLO/Error Budget 理论参见 [[../04-SRE实践/01-SLI-SLO-SLA详解]]。
+
+## SRE 五条心法
+
+### 1. 用户视角定义 SLO
+
+> 不是"API P99 < 200ms",而是"用户在正常网络下加载页面 < 2s"。
+
+技术指标可能欺骗你——P99 延迟低不代表用户体验好。一个前端图片加载慢 3 秒的服务,后端再快也无法让用户满意。**永远从用户的感知出发定义质量**。
+
+### 2. 错误预算用完 = 停止功能开发
+
+当 Error Budget 耗尽时,团队必须全力修 bug、加稳定性,而不是继续发新功能。这不是惩罚,而是**资源重新分配的客观依据**。
+
+### 3. Blameless Postmortem
+
+不问"谁干的",问"流程哪里可以改进"。责备个人只会让团队成员隐瞒问题,最终酿成更大的事故。
+
+### 4. 自动化一切重复劳动
+
+手动操作一定会出错。无论是回滚、扩容还是配置变更,能脚本化的绝不人工执行。这不仅仅是效率问题,更是**可靠性问题**。
+
+### 5. 接受一定程度的失败
+
+在可控范围内快速迭代,比追求完美更重要。Google 的"可控失败"理念认为:**小规模的失败是发现系统性问题的最佳途径**。
+
+## Blameless Postmortem 模板
+
+| 字段 | 填写内容 |
+|------|---------|
+| **事件名称** | `2026-05-05 支付服务超时事故` |
+| **影响范围** | 约 15% 的支付请求失败,持续 23 分钟 |
+| **发现时间** | 14:32 (On-Call 接到告警) |
+| **恢复时间** | 14:55 (回滚后确认恢复) |
+| **根本原因** | 某次变更后支付网关的连接池大小从 50 降到 10 |
+| **时间线** | 14:00 发布 v1.2.3 → 14:30 错误率开始升高 → 14:32 收到告警 → 14:35 开始排查 → 14:45 定位根因 → 14:50 回滚 → 14:55 完全恢复 |
+| **改进行动** | ① 连接池参数变动必须经过压测验证;② 监控中补充连接池活跃数指标 |
+| **跟进人** | @zhangsan (行动 ①)、@lisi (行动 ②) |
+
+> [!tip] 什么是真正的 "Blameless"?
+>
+> Blameless ≠ 不追究责任。它的意思是:**聚焦于系统和流程缺陷,而非个人的失误**。人类会犯错,这是物理定律级别的现实。好的 Postmortem 应该揭示为什么系统设计允许一个简单的配置变更导致大规模故障。
+
+### Postmortem 写作原则
+
+| 原则 | 做法 |
+|------|------|
+| **事实先行** | 按时间线罗列发生了什么,不掺入主观判断 |
+| **5 Whys 分析法** | 连续问 5 次"为什么",直到触及根因 |
+| **行动项可追踪** | 每个改进措施都要有负责人和截止日期 |
+| **公开透明** | Postmortem 全文共享给全公司,不隐藏信息 |
+| **闭环验证** | 改进措施完成后回顾是否真正预防了同类问题 |
+
+## SRE Metrics 看板
+
+除了 SLO,SRE 还需要关注以下运营指标:
+
+| 指标 | 说明 | 目标 |
+|------|------|------|
+| **MTTR** | Mean Time To Recovery | 核心服务 < 30min |
+| **Change Failure Rate** | 变更导致故障的比例 | < 5% |
+| **Lead Time for Changes** | 代码提交到上线的时间 | < 2h |
+| **Deployment Frequency** | 日均部署次数 | > 5 (大规模团队) |
+
+> [!tip] DORA Metrics
+>
+> Google 提出的四大 DevOps 指标,广泛用于评估工程效能:
+> - **部署频率** — 交付速度
+> - **变更前置时间** — 从代码提交到生产部署需要多久
+> - **变更失败率** — 多少部署导致了故障或回滚
+> - **MTTR** — 恢复服务的平均时间
+
+### 四种绩效等级的 DORA 表现
+
+| 等级 | 部署频率 | 变更前置时间 | 变更失败率 | MTTR |
+|------|---------|-------------|-----------|------|
+| 🏆 精英 (Elite) | 每日多次 | < 1 小时 | < 5% | < 1 小时 |
+| 🥈 高 (High) | 每周 1-6 次 | < 1 周 | 6-15% | 1-7 天 |
+| 🥉 中 (Medium) | 每月 < 1 次 | 1-4 周 | 16-30% | > 1 个月 |
+| ⬜ 低 (Low) | < 每月 1 次 | > 6 个月 | > 30% | > 6 个月 |
+
+> [!info] 关于 DORA 的提醒
+>
+> DORA Metrics 的价值在于**纵向对比自身演进**,而非横向攀比。一个创业团队追求"精英级"可能过度工程化。关键是看趋势——如果你的 Change Failure Rate 从 20% 降到了 10%,这就是实质性的进步。
+
+## 补充:事故分级标准
+
+```mermaid
+flowchart TD
+ P0["🔴 P0 - 灾难性
全站不可用 / 资金损失
CTO 级别通知
2h内必须恢复"] --> P1
+ P1["🟠 P1 - 严重
核心功能受影响
负责人 + SRE 紧急会议
4h内必须恢复"] --> P2
+ P2["🟡 P2 - 一般
部分功能降级
工作时间处理
24h内必须恢复"] --> P3
+ P3["🟢 P3 - 轻微
非核心问题
下个迭代处理"]
+
+ style P0 fill:#ffcdd2
+ style P1 fill:#ffe0b2
+ style P2 fill:#fff9c4
+ style P3 fill:#c8e6c9
+```
+
+## 关联笔记
+
+- [[../04-SRE实践/01-SLI-SLO-SLA详解]] — SLI/SLO/SLA 三层模型
+- [[../04-SRE实践/02-错误预算深度解析]] — Error Budget 告警联动
+- [[../hhs/MS/05-部署运维/02-Kubernetes]] — K8s 自愈能力与 SLO 保障
diff --git a/hhs/MS/05-部署运维/README.md b/hhs/MS/05-部署运维/README.md
index 4490150..d6ad63e 100644
--- a/hhs/MS/05-部署运维/README.md
+++ b/hhs/MS/05-部署运维/README.md
@@ -23,20 +23,23 @@ graph LR
style D fill:#e8f5e9
```
-| # | 主题 | 核心问题 |
-|---|------|----------|
-| 1 | [[05-部署运维/01-容器化]] | Docker 镜像怎么优化?最佳实践有哪些? |
-| 2 | [[05-部署运维/02-Kubernetes]] | K8s 核心概念和资源管理怎么做? |
-| 3 | [[05-部署运维/03-CICD与GitOps]] | 自动化流水线怎么设计?GitOps 流程怎么走? |
-| 4 | [[05-部署运维/04-SRE实践]] | SLI/SLO/Error Budget 如何落地? |
+## 知识体系详情
+
+| # | 主题 | 核心问题 | 定位 |
+|---|------|----------|------|
+| 1 | [[05-部署运维/01-容器化]] | Docker 镜像怎么优化?多架构如何构建? | **枢纽页** → 3 个子文档(Dockerfile / 多架构 / 安全) |
+| 2 | [[05-部署运维/02-Kubernetes]] | K8s 核心概念和资源管理怎么做? | **枢纽页** → 7 个子文档(核心概念 / 配置 / 网络 / 扩缩容 / 调度 / 安全 / 排查) |
+| 3 | [[05-部署运维/03-CICD与GitOps]] | 自动化流水线怎么设计?GitOps 流程怎么走? | **枢纽页** → 4 个子文档(CICD / GitOps / Helm / 安全与发布) |
+| 4 | [[05-部署运维/04-SRE实践]] | SLI/SLO/Error Budget 如何落地?事故复盘怎么做? | **枢纽页** → 3 个子文档(SLI-SLO / ErrorBudget / SRE心法) |
### 学习建议
> [!tip] 学习路径
+>
> 先掌握容器化(Docker),再学 K8s 核心概念,CI/CD 和 SRE 可以在实践中逐步深入。
### 关联笔记
-- [[02-服务治理]] — K8s Service 提供原生的服务发现和负载均衡
-- [[04-可观测性]] — K8s Liveness/Readiness Probe 是可观测性的基础
+- [[hhs/MS/02-服务治理]] — K8s Service 提供原生的服务发现和负载均衡
+- [[hhs/MS/04-可观测性]] — K8s Liveness/Readiness Probe 是可观测性的基础
- [[hzh/MS/README.md]] — 完整微服务知识索引