vault backup: 2026-05-18 00:17:59

This commit is contained in:
hhs
2026-05-18 00:17:59 +08:00
parent bbea71f62b
commit f8bcbb9d37
26 changed files with 4828 additions and 712 deletions
+1 -1
View File
@@ -195,7 +195,7 @@ public class OrderController {
if (newOrderFlowEnabled) {
// 新版流程
}
return orderService.list();
return orderService.list()
}
}
```
+517 -124
View File
@@ -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["📦 订单服务<br/>MySQL"]
S2["👤 用户服务<br/>PostgreSQL"]
S3["📦 库存服务<br/>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 水平拆分
拆分不是选择题,而是**两步走**:第一步按业务域垂直拆分(微服务化的标配),第二步当单个表太大时再水平拆分(分库分表)。
### 垂直拆分(按业务域)
### 第一步:垂直拆分 — 按业务域独立建库
按 **微服务边界** 拆库——这是微服务的标配。
```mermaid
graph TB
subgraph "DB-Order"
orders[orders]
order_items[order_items]
order_status[order_status]
end
subgraph "DB-User"
users[users]
user_profiles[user_profiles]
user_addresses[user_addresses]
end
subgraph "DB-Product"
products[products]
categories[categories]
product_images[product_images]
end
```
| 特点 | 说明 |
|------|------|
| 每个服务独占一个数据库 | 物理隔离,互不干扰 |
| 可异构选型 | 订单用 MySQL,用户用 PostgreSQL,搜索用 Elasticsearch |
| 天然解耦 | 服务间不能直接查对方库 |
### 水平拆分(分库分表)
当单个表的记录量达到千万级以上,需要进一步拆分。
```mermaid
graph TB
subgraph "分库策略"
DB1[(DB-01)]
DB2[(DB-02)]
DB3[(DB-03)]
end
subgraph "order_0001 表"
Row1[userId=1 → order_0001]
Row2[userId=4 → order_0001]
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
```
### 常见分片策略
| 策略 | 哈希公式 | 优点 | 缺点 |
|------|---------|------|------|
| **Hash Mod** | `user_id % N` | 简单高效,路由确定 | 扩缩容困难,数据迁移成本高 |
| **Range** | `user_id BETWEEN x AND y` | 范围查询友好 | 热点用户集中到单分片 |
| **Time-based** | `year_month` | 按生命周期管理 | 新分片写入压力大 |
| **Geo-based** | 按地域分片 | 本地化访问,延迟低 | 跨区域操作复杂 |
### ShardingSphere / MyCat
> [!tip] 推荐中间件
> [!question] 思考一下
>
> **Apache ShardingSphere** 是国内使用最广泛的分库分表方案:
> - 支持 JDBC / Proxy / Sidecar 三种部署模式
> - 内置分片算法:Mod、Range、Hash、Tag
> - 分布式主键生成器(Snowflake)原生集成
> - 读写分离、强制路由、广播表等高级特性
> 如果一个"用户下单"操作需要同时访问订单表和商品信息表——这说明这两个表应该属于同一个数据库吗?
>
> **答案是否定的。** 商品不会因为你改了订单就跟着变。真正的判断标准是:**哪些表经常一起被修改?** 如果一组表的变更总是由同一个业务逻辑触发,它们就属于同一个限界上下文,应该放在同一个库里。
#### ShardingSphere 配置示例
核心做法:以 **微服务边界** 为基准,将相关表打包到一个数据库中。
```mermaid
flowchart TB
subgraph DBOrder["DB-Order:订单域"]
direction LR
T1[orders]
T2[order_items]
T3[order_status_log]
end
subgraph DBUser["DB-User:用户域"]
direction LR
U1[users]
U2[user_profiles]
U3[user_addresses]
end
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(事务强),搜索用 Elasticsearch,缓存用 Redis |
| **天然解耦** | 服务之间不能直连对方数据库,只能通过 API 通信 |
> [!tip] 拆分的粒度
>
> 不要把一张表里的字段都拆到不同库里——那叫"过度拆分"。**以表为单位**进行垂直拆分是最常见的做法。一个微服务对应一个库,一个库包含多个相关表。
### 垂直拆分的判断标准(实操 checklist)
当你在犹豫某张表应该留在这个库还是挪到另一个库时,用下面几个维度来判断:
```mermaid
flowchart TD
START["开始判断"] --> A["这张表和当前库里的表<br/>是否经常被同一个事务修改?"]
A -->|是| SAME_DB["留在同一库 ✅"]
A -->|否| B["它们是否属于同一个<br/>业务限界上下文?"]
B -->|是| SAME_CTX["考虑放在同一库<br/>(降低跨库调用成本)"]
B -->|否| DIFF_CTX["必须独立建库 ✅"]
SAME_CTX --> C["变更频率差异大吗?"]
C -->|高频 vs 低频| SPLIT["建议拆分<br/>(避免互相影响)✅"]
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 "拆分前:一张表扛所有"
BIG_TABLE[(order 表 1 亿行)]
end
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
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 % 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 | 降低跨地域延迟 | 跨区域操作复杂 |
> [!important] 扩容陷阱
>
> Hash Mod 最容易踩坑:当你从 4 个分片扩展到 8 个分片时,`% 4` 变成 `% 8`,**几乎所有数据的新归属都变了**,需要大规模数据迁移。这是一个需要提前规划的重大决策。
>
> 如果扩容是高频需求,建议从一开始就用一致性哈希或预留足够多的槽位。
### 分片扩容方案:不停机迁移
生产环境的扩容不能停服停机——你需要一个 **双写 + 历史数据回迁** 的渐进式流程:
```mermaid
flowchart TD
A["当前状态: N 个分片<br/>hash % N"] --> B["第一步: 新增 M 个分片<br/>总容量变为 N+M"]
B --> C["第二步: 双写阶段<br/>新写入同时写到旧分片和新分片"]
C --> D["第三步: 历史数据回迁<br/>按分片逐个搬移存量数据"]
D --> E{"全部搬完?"}
E -->|否| D
E -->|是| F["第四步: 校验数据一致性<br/>checksum 对比"]
F --> G["第五步: 切读流量<br/>新请求读新分片"]
G --> H["第六步: 关闭双写<br/>恢复到单写单读"]
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["符号位<br/>1 bit"]
T["时间戳<br/>41 bits"]
D["机器 ID<br/>10 bits"]
SQ["序列号<br/>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] 冗余数据的维护
> [!note] 关键理解:快照语义
>
> 用户改名字了怎么办?**不改订单表**。订单上的 username 是该时刻的"快照"——它反映的是下单时的状态,不是当前状态。这符合业务语义。
> 用户后来改了名字、换了手机号,**不改订单表**。订单上的 `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: 宽表包含了订单 + 用户 + 商品的冗余字段<br/>只读,专为查询优化
```
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 万行<br/>单库单机足够"]
START --> MEDIUM["100 万 ~ 2000 万行<br/>考虑读写分离"]
START --> LARGE["> 2000 万行<br/>考虑分片"]
SMALL --> S1["✅ 先优化索引"]
S1 --> S2["✅ 加缓存层 Redis"]
S2 --> S3["✅ 读写分离<br/>一主多从"]
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["💻 开发环境<br/>git commit SQL 文件"] -->|"CI/CD 自动执行"| Stage["🧪 Staging<br/>flyway migrate"]
Stage -->|"人工审批"| Prod["🚀 Production<br/>flyway migrate"]
Prod -->|"校验"| Check["🔒 flyway validate<br/>确认无漂移"]
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**:迁移脚本只做"升级",回滚通过发版解决
## 关联笔记
+320 -21
View File
@@ -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<br/>(Seata Server)
participant TM as Transaction Manager<br/>(应用 A)
participant RS1 as Resource 1<br/>(订单 DB)
participant RS2 as Resource 2<br/>(库存 DB)
TM->>TC: 开启全局事务 (xid)
TM->>RS1: BEGIN; UPDATE orders SET ...
Note over RS1: 自动捕获 BEFORE/AFTER 镜像<br/>写入 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["延迟重试<br/>指数退避"]
DELAY --> C
RETRY -- 否 --> DLQ["进入死信队列<br/>人工介入"]
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-数据库拆分]] — 数据库拆分是分布式事务的前提
+1 -1
View File
@@ -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 生成
+57 -104
View File
@@ -1,18 +1,21 @@
---
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["镜像仓库<br/>Harbor / ECR / ACR"]
BUILD --> SCAN["安全扫描<br/>Trivy / Snyk"]
SCAN --> REGISTRY["镜像仓库<br/>Harbor / ECR / ACR"]
REGISTRY --> K8s["Kubernetes 部署"]
style BUILD fill:#e3f2fd
@@ -20,57 +23,15 @@ graph TB
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{"漏洞等级?"}
> **经常变动的内容靠近 COPY,少变的放前面**。
>
> ```dockerfile
> # ✅ 好:只有 go.mod/go.sum 变化时才重新下载依赖
> COPY go.mod go.sum ./
> RUN go mod download
> COPY . .
> RUN go build
> ```
Clean -- "CRITICAL/HIGH" --> Block["❌ 阻止推送"]
Clean -- "MEDIUM" --> Review["⚠️ 人工审核"]
Clean -- "LOW/INFO" --> Allow["✅ 允许推送"]
## 基础镜像选型
style Block fill:#ffebee
style Review fill:#fff3e0
style Allow fill:#e8f5e9
```
| 基础镜像 | 典型 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` | <取决于二进制 | 仅静态链接二进制可运行 |
### 安全检查清单
> [!warning] Alpine 与 musl libc
>
> Alpine 使用 musl libc 而非 glibc。某些 C 扩展(如 `node-gyp`、部分 Python wheel)可能无法编译或运行时行为不一致。Go 服务通过 `CGO_ENABLED=0` 规避了此问题,但 Node.js/Python 应用需谨慎评估。
- [ ] 不使用 `latest` tag(永远用具体版本号)
- [ ] 不安装不必要的软件包(`apk del --purge .build-deps`)
- [ ] 定期更新基础镜像(修复 CVE)
- [ ] 使用非 root 用户运行
- [ ] 镜像不包含密钥、密码、私钥
- [ ] 使用最小基础镜像(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)。
## 镜像仓库管理
```mermaid
flowchart LR
Dev["开发者本地"] -->|"docker push"| DEV_REPO["dev 仓库<br/>v1.2.3-dev"]
Staging["Staging 验证"] -->|"通过"| PROD_REPO["prod 仓库<br/>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 流水线中的镜像构建环节
@@ -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
@@ -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 <image>` 查看完整列表。
## 不同场景的构建策略
| 场景 | 策略 | 命令 |
|------|------|------|
| 开发阶段 | 只构建本机架构 (`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 节点混合架构时的调度考量
@@ -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 仓库<br/>v1.2.3-dev"]
Staging["Staging 验证"] -->|"通过"| PROD_REPO["prod 仓库<br/>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 流水线中的安全扫描环节
+76 -201
View File
@@ -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 # 滚动更新期间不允许不可用
> **选型建议**:团队规模 < 20 人时 Push 模式足够。≥ 50 人或多环境场景建议迁移到 GitOps → [[../03-CICD与GitOps]]
selector:
matchLabels:
app: order
## Secret 管理方案对比
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
| 方案 | 适用场景 | 优点 | 缺点 |
|------|---------|------|------|
| K8s 原生 Secret | 小规模内部团队 | 零成本,开箱即用 | etcd 明文存储 |
| ESO (External Secrets) | 已有 Vault / AWS SSM | Git 中无密文 | 需维护额外组件 |
| SOPS + sealed-secrets | ArgoCD 用户 | 加密文件可提交到 Git | 需管理 PKI |
| 云厂商 Secret Manager | 深度绑定单一云平台 | 审计完善 | 平台锁定 |
# ========== 资源配置 ==========
resources:
requests: # 调度依据:保证至少有这些
cpu: "250m"
memory: "256Mi"
limits: # 硬上限:超过则 OOMKill/CPU Throttle
cpu: "500m"
memory: "512Mi"
> **渐进路径**:K8s Secret → 接入云厂商 Secret Manager → 引入 ESO 实现多云解耦。
# ========== 探针 ==========
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
| 服务规模 | 推荐方案 | 理由 |
|----------|---------|------|
| < 10 个服务 | Kustomize / 裸 YAML | 复杂度高于收益 |
| 10~50 个服务 | Helm | 模板复用价值明显 |
| > 50 个服务 | Helm + Kustomize overlays | Helm 管模板,Kustomize 管环境差异 |
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 摘流时间
```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 上的高级路由
@@ -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<br/>唯一入口"]
SCHEDULER["Scheduler<br/>调度决策"]
CMGR["Controller Manager<br/>状态协调"]
ETCD["etcd<br/>分布式 KV 存储"]
APISERVER --> SCHEDULER
APISERVER --> CMGR
APISERVER --> ETCD
CMGR --> ETCD
end
subgraph WORKER["工作节点 Worker Node"]
N1["Node A<br/>kubelet + kube-proxy"]
N2["Node B<br/>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
@@ -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)
@@ -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<br/>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 <name>` 确认 Service 存在且有 ClusterIP
> 2. `kubectl get endpoints <name>` 检查 Endpoint 列表是否为空
> 3. Endpoint 为空 → 检查 selector 标签是否匹配 Pod
> 4. Endpoint 有值但 curl 不通 → 进入 Pod `curl <ClusterIP>:<port>` 验证
> 5. ClusterIP 通但外部不通 → 检查 Ingress / LoadBalancer 配置
## 关联笔记
- [[../01-核心概念与Deployment]] — Deployment 通过 selector 与 Service 关联
- [[../02-配置管理]] — Service 的端点配置不直接涉及 ConfigMap/Secret
- [[../04-扩缩容与有状态应用]] — Headless Service + StatefulSet 配合
- [[../hhs/MS/02-服务治理/04-服务发现]] — K8s Service 是服务端发现模式的代表
@@ -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 决定扩容阈值
@@ -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<br/>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]] — 调度失控时的常见排查技巧
@@ -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 <name> -n <namespace>`)。
## 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 安全
@@ -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 <pod-name> -n production
# ↑ 重点看 Events 区域的 LastState / State / Reason
# 3. 看容器日志(含重启前的上一次输出)
kubectl logs <pod-name> -n production --previous # 上次崩溃容器的日志
kubectl logs <pod-name> -n production -c sidecar # 多容器指定 sidecar 名
# 4. 进入运行中的容器调试
kubectl exec -it <pod-name> -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 <svc>` 看 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 <node>` 看 MemoryPressure/DiskPressure |
## 调试技巧:优雅地抓包与断点
```bash
# ========== Debugging Sidecar 模式 ==========
# 给故障 Pod 附加一个临时调试容器,共享网络命名空间
kubectl debug <pod-name> -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 <coredns-pod-name>
# 检查存储插件正常
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 可视化排查
+174 -145
View File
@@ -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 部署"]
Dev["开发者 push"] --> CI["GitHub Actions\n(CI: 测试→构建→Push镜像)"]
CI -->|"新镜像"| REG["Container Registry"]
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
```
## CI/CD 设计原则
| 原则 | 说明 |
|------|------|
| **一次构建,多处部署** | 镜像不随环境重新编译,只改 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)]
Dev2["开发者 PR manifest"] --> MANIFEST["Git Manifest Repo"]
subgraph Cluster["K8s Cluster"]
Argo["ArgoCD / Flux"] -->|同步| K8sState["Pod/Service/ConfigMap"]
Argo["ArgoCD"] -->|"自动同步"| WORKLOADS["Pod / Service / ..."]
end
Git -.->|Webhook| Argo
MANIFEST -.->|Watch 变动| Argo
Argo -->|"检测到差异"| Diff{"状态一致?"}
Diff -- 否 --> Sync["自动同步到 K8s ✅"]
Diff -- 是 --> OK["已一致 ⏸️"]
style Git fill:#e3f2fd
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 提供指标支撑
@@ -0,0 +1,299 @@
---
tags: [cicd, github-actions, docker, kubernetes, pipeline]
create time: 2026-05-17 10:00
---
# CI/CD 基础与实践
## 概述
本文档带你从零搭建微服务的自动化发布流水线。内容覆盖:从代码提交到镜像构建、安全扫描、Staging 验证、灰度上线的完整流程,以 GitHub Actions 为例进行实战讲解。更多决策分析(如工具选型)参见 [[../03-CICD与GitOps]]。
## 为什么需要 CI/CD?
> [!question] 想象一下
>
> 你有 50 个微服务,每次发布都需要手动 SSH 到服务器、停旧启新、检查日志——如果出错了还得回滚。**人工操作**在这个规模下就是最大的风险源。
>
> CI/CD 的核心目标:**消除发布日的手工操作**,让任何一次 commit 都可以被安全地部署。
```mermaid
flowchart LR
CODE["代码提交"] --> TEST["测试 & 静态分析"]
TEST --> SCAN["安全扫描"]
SCAN --> BUILD["构建镜像"]
BUILD --> PUSH["推送仓库"]
PUSH --> STAGING["Staging 验证"]
STAGING -->|"人工审批"| PROD["Production 部署"]
style CODE fill:#e3f2fd
style TEST fill:#fff3e0
style SCAN fill:#fce4ec
style BUILD fill:#e8f5e9
style PUSH fill:#f3e5f5
style STAGING fill:#e0f7fa
style PROD fill:#c8e6c9
```
## 核心设计原则
### 一次构建,多处部署
镜像不随环境重新编译,只改 K8s ConfigMap 或环境变量。Staging 和 Production 用的是**同一个镜像**。
```mermaid
flowchart LR
DEV["开发机"] --> BUILD["构建一次"]
BUILD --> REGISTRY["Docker Registry"]
REGISTRY --> STAGING["Staging 环境"]
REGISTRY --> PROD["Production 环境"]
STAGING -. "同一镜像" .- PROD
style DEV fill:#e3f2fd
style BUILD fill:#e8f5e9
style REGISTRY fill:#f3e5f5
style STAGING fill:#fff3e0
style PROD fill:#c8e6c9
```
### 语义化版本 + Commit SHA 双标签
| Tag 类型 | 示例 | 用途 |
|----------|------|------|
| **Commit SHA** | `sha256:a1b2c3d` | 机器精确引用,保证可回滚 |
| **运行序号** | `v123` | 人类快速参考,方便手动回滚 |
> [!tip] 永远不要用 `latest` 做生产环境的镜像标签。`latest` 可被覆盖,回滚时你不知道回滚到哪一刻的镜像。
### 路径过滤
只对相关服务的代码变更触发构建。`order-service` 改了代码,不应该触发 `payment-service` 的 pipeline。
### 流水线即代码
Pipeline 配置写在 Git 中(`.github/workflows/`),和源码一起 Review。没人知道它长什么样,就是最好的理由。
## Pipeline 实战:单服务完整流水线
以下是一个微服务(以 `order-service` 为例)的 CI/CD 流水线。核心思路:**代码推送到 main 分支 → 测试 → 构建镜像 → 推到 Staging → 人工审批 → 灰度上线**。
```yaml
# .github/workflows/deploy.yml
name: Deploy order-service
on:
push:
branches: [main]
paths:
- "services/order/**" # 只在 order 服务有变更时触发
env:
REGISTRY: registry.example.com
IMAGE: order-service
jobs:
# ========== Stage 1: Build & Test ==========
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run tests
run: make test
- name: Security scan (filesystem)
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
severity: 'CRITICAL,HIGH'
- name: Build Docker image
run: |
docker build \
-t ${{ env.REGISTRY }}/${{ env.IMAGE }}:${{ github.sha }} \
-t ${{ env.REGISTRY }}/${{ env.IMAGE }}:v${{ github.run_number }} \
-f services/order/Dockerfile \
services/order
- name: Push to registry
run: |
echo "${{ secrets.REGISTRY_PASSWORD }}" | \
docker login -u ${{ secrets.REGISTRY_USER }} --password-stdin
docker push ${{ env.REGISTRY }}/${{ env.IMAGE }}:${{ github.sha }}
docker push ${{ env.REGISTRY }}/${{ env.IMAGE }}:v${{ github.run_number }}
# ========== Stage 2: Deploy to Staging ==========
deploy-staging:
needs: build-and-test
runs-on: ubuntu-latest
environment: staging
steps:
- name: Deploy to staging
run: |
kubectl set image deployment/order-service \
order=${{ env.REGISTRY }}/${{ env.IMAGE }}:${{ github.sha }} \
--namespace=staging
kubectl rollout status deployment/order-service \
--namespace=staging --timeout=120s
- name: Smoke test
run: |
# 等待 Pod Ready 后发送探测请求
sleep 10
ENDPOINT=$(kubectl get ingress order-service \
-o jsonpath='{.spec.rules[0].host}' -n staging)
curl -f http://$ENDPOINT/health || exit 1
# ========== Stage 3: Deploy to Production ==========
deploy-production:
needs: deploy-staging
runs-on: ubuntu-latest
environment: production # 需人工 Approval Gate
steps:
- name: Canary release (10% → 50% → 100%)
run: |
kubectl patch canary order-service --type merge \
-p '{"spec":{"weight":10}}'
# 等待 10 分钟,检查 error rate / latency
echo "Monitor metrics before proceeding..."
- name: Promote to 50%
if: success()
run: |
kubectl patch canary order-service --type merge \
-p '{"spec":{"weight":50}}'
- name: Full promotion
if: success()
run: |
kubectl patch canary order-service --type merge \
-p '{"spec":{"weight":100}}'
```
> [!info] Secret 管理原则
>
> 所有敏感信息通过 **GitHub Actions Secrets** 存储,按环境分离:
>
> | Secret 名称 | 用途 | 存放位置 |
> |------------|------|---------|
> | `REGISTRY_USER` / `REGISTRY_PASSWORD` | 镜像仓库认证 | Repository Settings → Secrets |
> | `KUBECONFIG_STAGING` | K8s 集群配置 | Environment: staging |
> | `KUBECONFIG_PRODUCTION` | K8s 集群配置 | Environment: production |
>
> > [!tip] 不要在 YAML 中明文写密码,哪怕加了 `${{ secrets.XXX }}` 的 workflow 在 fork 的 PR 中仍然有泄露风险。对 fork PR 使用 `environment` 保护可以阻止 secrets 暴露。
## Pipeline 逐阶段解读
| 阶段 | 做什么 | 为什么重要 |
|------|--------|-----------|
| **路径过滤** | `paths` 指定只对特定目录变更触发 | 避免每次 PR 都重建所有服务的镜像 |
| **Secrets 管理** | GitHub Secrets 按环境分离存储 | 防止密钥泄露到 fork PR 或日志中 |
| **安全扫描** | Trivy 在构建前后扫描漏洞 | 把安全问题挡在镜像层 |
| **双 Tag 策略** | 同时打 Commit SHA + run number | SHA 用于精确操作,run number 用于快速参考 |
| **Environment 保护** | `environment: production` 配置审批人 | GitHub 级别的 gates |
| **冒烟测试** | 部署后发送健康探测请求 | 确认 Pod 真正 Ready,而非只等 rollout 完成 |
| **Canary 发布** | 10% → 50% → 100% 逐步放量 | 有 Bug 只影响 10% 用户 |
## 回滚与 Rollout
> [!question] 上线后发现 Bug,怎么最快恢复?
>
> 答案不是「快速修好再发一次」——而是**先回滚,后修复**。MTTR(平均恢复时间)比根因分析优先级更高。
### K8s Rollout 模式对比
```mermaid
flowchart LR
RU["RollingUpdate\n滚动更新"] --> P1["逐步替换旧 Pod"]
RU --> P2["期间服务不中断"]
RU --> P3["支持 ProgressDeadline"]
RC["Recreate\n全部重建"] --> R1["先杀所有旧 Pod"]
RC --> R2["再启新 Pod"]
RC --> R3["有短暂不可用窗口"]
style RU fill:#e8f5e9
style RC fill:#fff3e0
```
| 特性 | RollingUpdate | Recreate |
|------|--------------|----------|
| **可用性** | 零停机 | 短暂中断 |
| **资源峰值** | Old + New 并存 | 只有一个版本运行 |
| **适用场景** | 绝大多数微服务 | 独占存储卷、单写数据库 |
| **回滚速度** | `kubectl rollout undo` 秒级恢复 | 重新 deploy 上一个版本 |
### 回滚命令速查
```bash
# 查看当前 rollout 状态
kubectl rollout status deployment/order-service -n production
# 回滚到上一个版本
kubectl rollout undo deployment/order-service -n production
# 查看历史版本列表
kubectl rollout history deployment/order-service -n production
# 回滚到指定 revision
kubectl rollout undo deployment/order-service -n production --to-revision=3
# 暂停/恢复 rollout(适合批量变更)
kubectl rollout pause deployment/order-service -n production
# ... 应用多个变更 ...
kubectl rollout resume deployment/order-service -n production
```
> [!tip] 如果使用了 Canary / Istio 等流量治理工具,回滚步骤变为:
> 1. **先把流量切回稳定版本**(改 weight = 0)— 秒级止血
> 2. **再删除问题部署** — 释放资源
> 3. **最后排查和修复**
>
> 流量切换比 Pod 重建更快,这是灰度发布最大的安全优势。
## 生产级增强
上面的示例做了精简,真实环境中通常还会加入:
| 增强项 | 说明 | 工具 / 做法 |
|--------|------|------------|
| **Dependency Lock** | `go mod tidy` 后提交 `go.sum`,确保每次依赖一致 | `go.sum` / `package-lock.json` |
| **Trivy Image Scan** | 镜像推送后再扫描一次镜像内漏洞 | `trivy image <image>` |
| **OpenTelemetry Trace ID** | 注入 trace ID,追踪本次构建产生的请求 | 环境变量注入 `OTEL_SERVICE_VERSION` |
| **Slack 通知** | 每阶段完成后通过 Webhook 通知团队 | GitHub Actions `slack/notification` |
| **代码覆盖率门控** | 低于阈值直接失败(coverage < 80% = fail) | `gocover` / `codecov` |
| **SBOM 生成** | 用 `syft` 生成软件物料清单,满足合规要求 | `syft dir:. -o cyclonedx-json > sbom.json` |
| **Docker BuildKit** | 利用缓存层加速重建 | `DOCKER_BUILDKIT=1 docker build` |
| **并发测试** | 单元测试和 lint 并行执行缩短流水线时间 | GitHub Actions `strategy.matrix` |
```bash
# Docker BuildKit 加速构建
DOCKER_BUILDKIT=1 docker build \
--cache-from ${{ env.REGISTRY }}/${{ env.IMAGE }}:${{ github.sha }} \
-t ${{ env.REGISTRY }}/${{ env.IMAGE }}:${{ github.sha }} \
services/order
# SBOM 生成
syft dir:. -o cyclonedx-json > sbom.json
```
## 常见陷阱
> [!warning] 这些坑踩过一次就记住了
1. **不要在 pipeline 里用 `latest` tag** — 不同环境编译行为不一致
2. **不要让 Staging 和 Production 各自 `docker build`** — 这是最常见的一致性 bug 来源
3. **不要跳过 Staging 直连 Production** — 哪怕你的测试写得再好,也需要一个集成验证环节
4. **不要硬编码 Secret 到 YAML 或代码里** — 参考 [[04-安全与发布策略]]
5. **不要忽略 `kubectl rollout status`** — 不检查 rollout 状态等于盲发
6. **不要把日志级别开到 DEBUG 上生产** — 不仅浪费存储,还会暴露敏感数据
7. **不要用 cron 做主触发器替代 push** — 定时构建无法响应具体 commit,丢失审计链
8. **不要忘记配置 `ProgressDeadlineSeconds`** — 一个卡在 Pending 状态的 rollout 不会被自动中止
## 关联笔记
- [[02-GitOps与ArgoCD]] — GitOps 模式下的部署工作流
- [[03-Helm模板管理]] — Helm Chart 编写与管理
- [[04-安全与发布策略]] — 安全管理和发布策略决策
- [[../03-CICD与GitOps]] — 参考手册与决策指南
@@ -0,0 +1,449 @@
---
tags: [gitops, argocd, flux, kubernetes, deployment, cicd, app-of-apps]
create time: 2026-05-17 10:00
update time: 2026-05-17 10:30
---
# GitOps 与 ArgoCD
## 概述
本文档深入讲解 GitOps 工作流和 ArgoCD 的使用。内容覆盖:从"传统 CI/CD 的痛点"到 "Git 即真相源",ArgoCD 架构解析、Application CRD 编写、App of Apps 模式、Helm/Kustomize 混合使用、同步钩子与安全实践。工具选型对比参见 [[../03-CICD与GitOps]]。
## 核心思想:让集群自己"拉取"状态
> [!question] 谁来"推动"变更到集群?
>
> 传统模式下,Jenkins 拿着 kubeconfig SSH 到 K8s 执行 `kubectl apply`。但问题来了:如果有人在集群里直接改了配置(比如手动 `kubectl edit deployment`),Jenkins 并不知道——这就是**配置漂移**。
>
> GitOps 的答案:**让 K8s 集群自己"拉取"自己的状态。** Git 仓库是唯一真相源,任何变更都通过 PR 进入 Git,工具自动同步到集群。
```mermaid
flowchart LR
Dev["开发者 PR"] -->|"修改 K8s manifest"| Git[(Git Repo)]
subgraph Cluster["K8s Cluster"]
Argo["ArgoCD / Flux"] -->|同步| K8sState["Pod/Service/ConfigMap"]
end
Git -.->|Webhook Polling| Argo
Argo -->|"检测到差异"| Diff{"状态一致?"}
Diff -- 否 --> Sync["自动同步到 K8s ✅"]
Diff -- 是 --> OK["已一致 ⏸️"]
style Git fill:#e3f2fd
style Argo fill:#fff3e0
style K8sState fill:#e8f5e9
```
## Push vs Pull:本质区别
| | 传统 CI/CD (Push) | GitOps (Pull) |
|---|---|---|
| **部署驱动** | CI 服务器主动推送 | Git 仓库变动触发拉取 |
| **安全边界** | CI 需要直连 K8s(暴露 kubeconfig) | ArgoCD 在集群内运行,无需外部访问 |
| **漂移检测** | 通常无 | 持续比对,自动修复 |
| **回滚方式** | 回到上一次 pipeline | `git revert` + 自动同步 |
> [!tip] Push vs Pull 的安全含义
>
> - **Push**:CI 服务器持有集群凭据,主动向 K8s 发请求。凭据泄露 = 集群沦陷。
> - **Pull**:ArgoCD 在集群内部以 Pod 运行,只需读取 Git 的只读权限。即使 Git 凭据泄露,攻击者也无法写入集群——他们无法改变 Git 中的 YAML。
## ArgoCD 架构深度解析
ArgoCD 是 CNCF 级别的 GitOps 持续交付工具。
```mermaid
flowchart TB
Git[(Git Repository)]
subgraph ArgoCDServer["ArgoCD Server (集群外)"]
UI["Web UI"]
API["API"]
end
subgraph ArgoCDController["ArgoCD Controller (集群内)"]
SyncLoop["同步循环\n(每 3 分钟)"]
Reconcile["Reconcile: Git Manifest vs K8s 实际状态"]
end
Git -->|只读 access_token| SyncLoop
SyncLoop --> Reconcile
Reconcile -->|"发现不一致"| Apply["kubectl apply"]
Apply --> K8s["K8s Cluster"]
K8s -->|"读取实际状态"| SyncLoop
style Git fill:#e3f2fd
style ArgoCDServer fill:#fff3e0
style ArgoCDController fill:#e8f5e9
```
### 关键概念
| 术语 | 说明 |
|------|------|
| **Application** | ArgoCD 管理的核心资源,定义"从哪个 Git 路径同步到哪个命名空间" |
| **Sync Policy** | 决定是自动同步还是手动触发;以及同步策略(Prune resources, Self-heal) |
| **Health Status** | ArgoCD 对资源的健康检查(Deployment 是否有可用副本、Pod 是否 Running 等) |
| **Drift Detection** | 对比 Git 中声明的配置与实际 K8s 状态的差异 |
| **App of Apps** | 用 Application 来管理 Application,实现分层编排 |
## Application 配置实战
### 典型 Application
```yaml
# apps/order-service.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: order-service
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/team/k8s-manifests.git
targetRevision: main
path: overlays/production/order-service
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true # 删除 Git 中不存在的资源
selfHeal: true # 自动修复被手动改动的资源
syncOptions:
- CreateNamespace=true # 目标命名空间不存在时自动创建
```
**字段速查**:
- `prune: true` — Git 里删了的东西,集群上也删掉
- `selfHeal: true` — 有人手动改了 Deployment?下次同步循环自动改回来
- `CreateNamespace=true` — 不需要提前手动创建命名空间
### App of Apps:多层级应用编排
当有几十上百个服务时,用一个 Application 管理一个服务太繁琐了。ArgoCD 支持 **"应用的 Application"** 模式:
```yaml
# root-application.yaml —— 根级别 Application
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: cluster-services
spec:
sources:
- repoURL: https://github.com/team/k8s-manifests.git
targetRevision: main
path: apps/order-service
- repoURL: https://github.com/team/k8s-manifests.git
targetRevision: main
path: apps/payment-service
- repoURL: https://github.com/team/k8s-manifests.git
targetRevision: main
path: apps/user-service
```
这种模式下,开发者只需要往 `apps/` 目录下新增文件夹并提交 PR,根 Application 就会自动把新服务拉到集群。
> [!tip] App of Apps 的最佳实践
>
> - 按团队或环境划分层级:`root-app → team-a-services / team-b-services`
> - 每个子 Application 可以有自己的 `syncPolicy` 和 `namespace`
> - 不要超过 3 层嵌套,调试会变得困难
## 进阶:中间件集成
### Helm + Kustomize 混合使用
ArgoCD **同时支持** Helm 和 Kustomize 作为 source plugin。实际工程中,最常见的组合是:
> CI pipeline 用 Helm build chart → ArgoCD 通过 Helm source 读取并部署到集群。
```yaml
# apps/order-service.yaml —— 使用 Helm Source
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: order-service
spec:
project: default
source:
repoURL: https://github.com/team/k8s-manifests.git
targetRevision: main
path: charts/order-service # Helm Chart 目录
helm:
valueFiles:
- values.yaml # 默认值文件
- values-production.yaml # 生产环境覆盖
parameters: # 命令行参数覆盖(优先级最高)
- name: replicas
value: "3"
- name: image.tag
value: "{{ .Values.image.sha }}" # 引用 CI 写入的 commit SHA
destination:
server: https://kubernetes.default.svc
namespace: production
```
> [!question] 为什么不只用纯 YAML?
>
> 想象一下:每个环境的 Deployment 只有 `replicas`、`resources`、`imageTag` 不同,其余完全一样。如果全部写为裸 YAML,维护 5 个环境 = 维护 5 份几乎相同的文件。Helm/Kustomize 让你用 **模板化 + 差异覆盖** 解决这个 DRY 问题。
### ArgoCD 原生 Kustomize 示例
```yaml
# 直接用 kustomize 作为 source
spec:
source:
repoURL: https://github.com/team/k8s-manifests.git
targetRevision: main
path: overlays/production/order-service # kustomize overlay 路径
kustomize:
images:
- name: myregistry/order-service
newTag: v1.2.3 # CI 注入的镜像版本
```
> [!note] 选择建议
>
> | 场景 | 推荐 |
> |------|------|
> | 需要复杂的条件渲染和复用 | Helm |
> | 简单的环境覆盖(改几个字段) | Kustomize |
> | ArgoCD 原生支持两者,可以混用(多源模式已在 App of Apps 中展示) | — |
## 同步工作流:Hook 与健康检查
### 同步生命周期
```mermaid
flowchart LR
A["Git 提交"] --> B{"ArgoCD\n检测到差异"}
B -->|"执行 PreSync Hook"| C["PreSync"]
C -->|"迁移 DB Schema"| D[健康检查]
D -->|"前向兼容? "| E["Sync"]
E -->|"部署新版本"| F["PostSync Hook"]
F -->|"灰度验证 / 通知"| G["Health Status"]
style C fill:#fff3e0
style E fill:#e3f2fd
style F fill:#e8f5e9
style G fill:#f3e5f5
```
### PreSync Hook:数据库迁移示例
```yaml
# hooks/db-migration.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: db-migrate
annotations:
argocd.argoproj.io/hook: PreSync # Sync 之前执行
argocd.argoproj.io/hook-delete-policy: HookSucceeded # 成功后自动清理
spec:
template:
spec:
containers:
- name: migrate
image: myregistry/order-service:v1.2.3
command: ["./migrate", "--up"]
restartPolicy: Never
```
**常用 Hook 阶段**:
| 阶段 | 时机 | 典型用途 |
|------|------|---------|
| `PreSync` | Sync 之前 | DB 迁移、缓存预热 |
| `Sync` | 替代默认同步行为 | 复杂的多步骤部署 |
| `PostSync` | Sync 之后 | 灰度验证、发 Slack 通知 |
| `SyncFail` | Sync 失败时 | 回滚、告警 |
> [!important] Hook 注意事项
>
> - `hook-delete-policy: HookSucceeded` — 避免残留大量已完成的历史 Job
> - PreSync Hook 本身也有健康检查机制,Job 必须 Running → Succeeded 才算通过
> - Hook Job 和目标 Application 必须在同一个 Kubernetes 集群
### 自定义健康检查
ArgoCD 内置了对 Deployment、StatefulSet、Service 等资源的健康检查。对于自定义 CRD(比如 CassandraCluster),可以通过 Script Health Check 实现:
```yaml
# ConfigMap 挂载到 ArgoCD Server
apiVersion: v1
kind: ConfigMap
metadata:
name: resource-customizations
namespace: argocd
data:
myapp.example.com_CassandraCluster.health.lua: |
local status = {}
if obj.status ~= nil then
if obj.status.readyNodes ~= nil then
if obj.status.readyNodes >= 3 then
status.status = "Healthy"
else
status.status = "Progressing"
status.message = "Only " .. tostring(obj.status.readyNodes) .. " ready nodes"
end
end
end
return status
```
> [!tip] 常见内置资源健康状态速查
>
> | 资源类型 | Healthy 条件 | Progressing 条件 |
> |----------|-------------|-----------------|
> | Deployment | 所有 replicas Ready 且当前 | replicaReadyCount < desired |
> | StatefulSet | 所有 Pod Running | Pending 或 replica 不足 |
> | Service | 存在即 Healthy | —(通常不 Progressing) |
> | Ingress | 有 backend 即 Healthy | — |
> | ConfigMap/Secret | 存在即 Healthy | — |
## 日志调试与故障排查
### ArgoCD CLI 常用命令
```bash
# 实时日志
argocd app logs order-service --follow
# 指定Pod日志(查看特定容器)
argocd app logs order-service -p order-service-pod-abc123 -c sidecar
# 查看应用事件(谁触发的同步、为什么失败)
argocd app events order-service
# 强制刷新(跳过缓存)
argocd app get order-service --refresh
```
### 常见故障排查流程
```mermaid
flowchart TD
A["Application 显示 OutOfSync"] --> B{手动 sync 是否成功?}
B -- 是 --> C["✅ 可能是临时网络抖动, Watch 恢复即可"]
B -- 否 --> D["查看 Events\nargocd app events"]
D --> E{"错误原因?"}
E -- "ImagePullBackOff" --> F["镜像仓库不可达/凭据过期\n→ 检查 ImagePullSecrets"]
E -- "CrashLoopBackOff" --> G["应用启动失败\n→ 查看 Pod 日志\nargocd app logs"]
E -- "ResourceQuota exceeded" --> H["命名空间配额不足\n→ 调整 Quota 或精简资源"]
E -- "RBAC denied" --> I["ArgoCD SA 权限不足\n→ 检查 ClusterRole Binding"]
E -- "Custom health check failing" --> J["自定义健康脚本语法错误\n→ 检查 resource-customizations ConfigMap"]
style F fill:#ffebee
style G fill:#e3f2fd
style H fill:#fff3e0
style I fill:#f3e5f5
style J fill:#e8f5e9
```
### 调试 Checklist
- [ ] `kubectl get application -n argocd` — 确认 Application CR 状态
- [ ] `kubectl describe application xxx -n argocd` — 查看 Conditions 和最后同步信息
- [ ] `argocd app logs xxx --tail 100` — Controller 日志,搜索 Error 关键字
- [ ] 确认 Git Repo URL 和 token 可用:手动 curl 测试
- [ ] 如果是 self-heal 反复触发:检查是否有外部控制器在修改资源
## 安全要点
### RBAC 配置
```yaml
# argocd-rbac-cm.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-rbac-cm
namespace: argocd
data:
policy.default: role:readonly # 默认只读
policy.csv: |
# g, github-org:team-name, role:admin # 团队级管理
p, role:order-service-deployer, applications, sync, */order-service, allow
g, github:alice, role:order-service-deployer # alice 可同步 order-service
scopes: '[groups]' # 从 OIDC token 获取 groups
```
### Secrets 管理建议
| 方式 | 适用场景 | 备注 |
|------|---------|------|
| **ArgoCD Secret** | Git 仓库凭据、K8s cluster 注册 | 存储为 SealedSecret/ExternalSecret |
| **External Secrets Operator** | RDS 密码、API Key | 从 AWS Secrets Manager / Vault 拉取 |
| **SealedSecret** | 跨集群复用加密 secrets | 公钥加密,仅 controller 可解密 |
> [!warning] 常见误区
>
> - **不要把 Git SSH Private Key 存入集群的普通 Secret** — 应该用 `argocd reponame` 配置项统一管理
> - **Application 的 namespace 要单独隔离** — `argocd` 命名空间不应和生产共享
> - **启用 OIDC + RBAC scopes** — 让团队只能看到自己管理的 Application
---
现在回到日常操作:
## 常见运维场景
### 手动触发同步
当 Git 已更新但 ArgoCD 未自动同步时:
```bash
argocd app sync order-service --force
```
### 查看同步状态和历史
```bash
argocd app get order-service # 当前状态
argocd app history order-service # 同步历史
argocd app diff order-service # Git vs 实际的差异
```
### 回滚到上一个版本
```bash
argocd app rollback order-service
# 或直接 git revert 对应的 commit,ArgoCD 自动同步
```
### 解除锁定(Stuck App 恢复)
```bash
argocd app unlock order-service
```
## 工具选型:ArgoCD vs Flux
| 特性 | ArgoCD | Flux v2 |
|------|--------|---------|
| **UI** | 丰富的 Web UI,可视化对比 | CLI + Kubernetes-native,轻量 UI |
| **通知** | Slack/Discord/GitHub native | Event controller + Webhook |
| **Helm** | 内置 Helm Source 插件 | 一等公民(HelmRelease) |
| **多集群** | 多集群统一管理 | 需配合 Image Automation |
| **适合场景** | 可视化管理需求强的团队 | 纯 GitOps 极客团队 |
> [!note] 选型建议
>
> - 如果你已经重度使用 Helm:Flux 的 HelmRelease CRD 更自然
> - 如果你希望可视化查看每次部署的差异:ArgoCD 的 diff view 是杀手功能
> - 两者可以并存——比如 ArgoCD 管应用层,Flux 管基础设施层(CNI、CSI 等)
## 关联笔记
- [[01-CICD基础与实践]] — CI/CD Pipeline 的搭建与实践
- [[03-Helm模板管理]] — Helm Chart 编写与管理
- [[04-安全与发布策略]] — 安全管理与发布决策
- [[../03-CICD与GitOps]] — 参考手册与决策指南
@@ -0,0 +1,454 @@
---
tags: [helm, kubernetes, templating, kustomize, devops]
create time: 2026-05-17 10:00
---
# Helm 模板管理
## 概述
本文档系统讲解 Helm Chart 的编写与管理实践。内容覆盖:Chart 骨架、values.yaml 参数化、Go 模板语法、多环境值覆盖、Chart 依赖管理、Hooks 机制,以及与 ArgoCD 的集成方式。架构决策参见 [[../03-CICD与GitOps]]。
## 为什么需要 Helm?
> [!question] 每个服务几十个 YAML 文件,怎么管理?
>
> 一个典型的 `order-service` 部署包含:Deployment、Service、Ingress、ConfigMap、HPA、NetworkPolicy、PDB……当你有 50 个服务,每个都要维护这么一堆模板——**这是重复劳动的噩梦**。
Helm 提供了两个核心抽象:
| 概念 | 类比 | 说明 |
|------|------|------|
| **Chart** | NPM 包 / Maven jar | 打包格式,定义"我要部署什么" |
| **Release** | npm install 的实例 | 运行实例,同一个 Chart 可以有多个 Release(staging/prod) |
```bash
# 创建 Chart 骨架
helm create order-service
# 目录结构一览
charts/order-service/
├── Chart.yaml # 元信息 (name, version, description)
├── values.yaml # 默认值 —— 你唯一需要经常改的文件
├── templates/ # Go 模板文件
│ ├── deployment.yaml
│ ├── service.yaml
│ └── ingress.yaml
└── .helmignore # 类似 .gitignore
```
### Helm Release 生命周期
```mermaid
flowchart LR
A["install"] --> B["running"]
C["upgrade"] --> B
D["rollback"] --> B
E["uninstall"] --> B
style A fill:#e1f5fe
style C fill:#fff3e0
style D fill:#fce4ec
style E fill:#f3e5f5
```
## 第一步:让配置全部参数化
**values.yaml** 中存放所有可变参数,遵循"一处定义、多处引用"原则:
```yaml
# values.yaml
replicaCount: 2
image:
repository: registry.example.com/order-service
tag: "v1.2.3"
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 8080
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 20
targetCPUUtilizationPercentage: 75
```
**templates/deployment.yaml** 中使用 `{{ }}` 引用:
```yaml
# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}
namespace: {{ .Release.Namespace }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app: {{ .Release.Name }}
template:
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
resources: {{ toYaml .Values.resources | nindent 12 }}
```
> [!tip] 两个关键技巧
>
> 1. **`toYaml .X | nindent 12`**:将嵌套的对象序列化为 YAML 并缩进。如果直接写 `{{ .Values.resources }}`,输出会压缩成一行,Kubernetes 无法解析。
> 2. **`-` 修剪空白**:`{{- if ...` 和 `{{- end }}` 中的 `-` 会消除模板前后多余的换行符,避免生成空行导致 YAML 格式错误。
### Helm 内置对象速查
| Helm 对象 | 访问方式 | 用途 |
|-----------|----------|------|
| `.Release` | `.Release.Name`, `.Release.Namespace` | 当前发布实例的信息 |
| `.Chart` | `.Chart.Name`, `.Chart.Version` | Chart 本身的元信息 |
| `.Values` | `.Values.replicaCount` 等 | 用户传入的值(合并了 values.yaml + --set) |
| `.Capabilities` | `.Capabilities.KubeVersion` | 集群 API 版本信息 |
| `.Files` | `.Files.Get "config.ini"` | 读取同目录下的非模板文件 |
| `.Notes` | 渲染后显示给用户的提示文本 | Post-install 使用说明 |
## 第二步:多环境值覆盖
通过 **values 文件叠加**实现不同环境的差异化:
```bash
# Staging 使用命令行临时覆盖
helm upgrade --install order-service ./charts/order-service \
--namespace=staging \
--set image.tag=v1.2.4-dev
# Production 叠加额外值文件
helm upgrade --install order-service ./charts/order-service \
--namespace=production \
--values charts/order-service/values.prod.yaml \
--set image.tag=v1.2.3
```
典型 `values.prod.yaml`(只写与默认值不同的部分):
```yaml
replicaCount: 5
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: "2"
memory: 2Gi
autoscaling:
minReplicas: 5
maxReplicas: 50
```
> [!tip] Values 文件合并优先级(低 → 高)
>
> 1. `values.yaml` — 默认值
> 2. `values.prod.yaml` — 环境变量覆盖文件
> 3. `--set image.tag=xxx` — 命令行参数(最高优先级)
```mermaid
flowchart TD
A["values.yaml"] --> D["合并结果"]
B["values.prod.yaml"] --> D
C["--set image.tag=v1.2.3"] --> D
D --> E[".Values"]
```
> [!warning] 常见陷阱:JSON Patch
>
> 当使用 `--set` 修改嵌套字段时,Helm 使用 JSON Patch,意味着它会**替换整个对象**而非合并。例如 `--set resources.limits.cpu="1"` 会将 `memory` 字段丢弃。对于复杂嵌套,优先使用独立的 values 文件。
## 第三步:条件渲染与循环
### 条件渲染
用 Helm 的条件语法处理可选资源:
```yaml
# templates/ingress.yaml
{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ .Release.Name }}
spec:
rules:
- host: {{ .Values.ingress.host | quote }}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: {{ .Release.Name }}
port:
number: {{ .Values.service.port }}
{{- end }}
```
> [!note] `quote` 管道函数
>
> 当值的类型不确定时(可能是字符串也可能是数字),加 `| quote` 确保输出带引号,避免 `true/false` 被 YAML 解析为布尔值。
### Range 循环生成多个资源
当需要批量创建多个 ServiceMonitor、ConfigMap 等时,`range` 非常有用:
```yaml
# templates/servicemonitor.yaml
{{- range $key, $value := .Values.extraServices }}
---
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: {{ $.Release.Name }}-{{ $key }}
spec:
selector:
matchLabels:
app: {{ $.Release.Name }}
endpoints:
- port: {{ $value.port }}
interval: {{ $value.interval | default "30s" }}
{{- end }}
```
对应的 values:
```yaml
extraServices:
grpc-exporter:
port: grpc
interval: 15s
prometheus-path:
port: metrics
interval: 10s
```
> [!example] 生成结果
>
> 上面这段模板会为每个 `extraServices` 条目生成一个独立的 ServiceMonitor。注意 `$key` 绑定到键名(`grpc-exporter`),而 `$value` 绑定到对应对象。使用 `$.Release.Name`(带美元前缀)是因为在 range 内部 `.` 已被重绑定。
## 第四步:复用模板片段
随着 Chart 膨胀,`deployment.yaml` 和 `service.yaml` 之间会产生大量重复逻辑。Helm 提供 `_helpers.tpl` 来统一管理可复用的模板片段。
### _helpers.tpl 标准写法
```yaml
{{/*
Common labels — 所有资源统一标注 */}}
{{- define "order.labels" -}}
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end -}}
{{/*
Selector labels — Deployment selector 必须使用固定值 */}}
{{- define "order.selectorLabels" -}}
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end -}}
```
### 在模板中引用
```yaml
# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}
labels:
{{- include "order.labels" . | nindent 4 }}
spec:
selector:
matchLabels:
{{- include "order.selectorLabels" . | nindent 6 }}
```
> [!tip] include vs template
>
> - `include "name" .`:**返回值**,可通过 `| nindent` 继续管道处理 —— 适合注入 label、annotation
> - `template "name" .`:**直接输出**,不返回结果 —— 很少用,通常只在特殊情况下需要
## Chart 依赖与仓库管理
大型项目往往不会从零编写所有模板,而是复用社区已有的 Chart。
### 声明子依赖
```yaml
# Chart.yaml
apiVersion: v2
name: order-service
type: application
version: 1.2.0
appVersion: "1.2.3"
dependencies:
- name: postgresql
version: "15.0.0"
repository: https://charts.bitnami.com/bitnami
condition: postgresql.enabled
- name: redis
version: "18.0.0"
repository: https://charts.bitnami.com/bitnami
condition: redis.enabled
```
- `condition` 告诉 Helm:当 values 中对应路径为 `false` 时,跳过安装该子 Chart。
- 子 Chart 的 values 通过 `postgresql.xxx` / `redis.xxx` 命名空间传入。
### 常用命令
```bash
helm dependency update ./charts/order-service # 拉取 / 更新子依赖
helm dependency build ./charts/order-service # 仅从 Chart.lock 构建(无网络变更时使用)
helm repo add bitnami https://charts.bitnami.com/bitnami
helm search repo prometheus # 搜索公开 Chart
```
> [!note] Chart.yaml 的 apiVersion
>
> - `apiVersion: v1`(Helm 2):依赖写在 `dependencies:` 数组里,语义不同
> - `apiVersion: v2`(Helm 3):引入 `requirements.yaml` 的概念整合到 Chart.yaml 自身,推荐使用 v2
## 与 ArgoCD 集成
ArgoCD 原生支持将 Helm Chart 作为 Application source,自动解析 `values.yaml`:
```yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: order-service
spec:
source:
repoURL: https://github.com/team/k8s-manifests.git
targetRevision: main
path: charts/order-service
helm:
parameters:
- name: image.tag
value: v1.2.3
- name: replicaCount
value: "5"
```
> [!tip] ArgoCD + Helm 进阶用法
>
> - **`helm.valueFrom`**:从 ConfigMap 或 Secret 取值,避免把敏感值暴露在 Git 中
> - **`helm.fileParameters`**:从文件加载大段配置值,适合 CI/CD 流水线场景
> - **`helm.ignoreMissingValueFiles: true`**:允许某些 values 文件按环境选择性存在
## Hooks:在关键时机执行自定义逻辑
Helm Hooks 允许你在 Release 的生命周期事件中挂载自定义 Job 或 Pod:
```yaml
# templates/migrate-db.yaml
{{- if .Values.dbMigration.enabled }}
apiVersion: batch/v1
kind: Job
metadata:
name: {{ .Release.Name }}-db-migrate
annotations:
"hooks.helm.sh/hook": pre-upgrade,pre-install
"hooks.helm.sh/hook-delete-policy": hook-succeeded
spec:
template:
spec:
containers:
- name: migrate
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
command: ["./migrate", "up"]
restartPolicy: Never
{{- end }}
```
| Hook 事件 | 触发时机 |
|-----------|----------|
| `pre-install` | 资源创建之前(如数据库迁移) |
| `post-install` | 资源创建之后(如初始化数据) |
| `pre-upgrade` | 升级操作之前 |
| `post-upgrade` | 升级操作之后 |
| `pre-delete` | 删除操作之前(如优雅停机通知) |
| `post-delete` | 删除操作之后 |
## 调试与测试
### 可视化渲染结果
```bash
# 查看渲染后的纯 YAML(不做实际部署)
helm template my-release ./charts/order-service
# 指定命名空间和 values 文件
helm template my-release ./charts/order-service \
--namespace=staging \
-f values.staging.yaml
```
> [!tip] 调试三件套
>
> 1. **`helm lint ./charts/order-service`**:静态检查,快速定位语法问题
> 2. **`helm template`**:看渲染后的全量 YAML,逐段排查问题
> 3. **`helm diff upgrade --install ...`**:配合 [helm-diff 插件](https://github.com/helm/diff),对比升级前后的差异
### Dry Run
```bash
# 模拟部署(服务端校验,但不会真正改变集群状态)
helm upgrade --install my-release ./charts/order-service \
--dry-run --debug
```
## 最佳实践
| 原则 | 具体做法 |
|------|---------|
| **单一真相源** | 所有可配参数集中在 values.yaml,模板层只做引用不做硬编码 |
| **Helper 集中管理** | 公共 labels / selectors / annotations 一律放在 `_helpers.tpl` |
| **条件最小化** | 能用 defaults 解决的就不加 `if`;减少分支能大幅降低测试复杂度 |
| **版本号分离** | `Chart.yaml` 的 `version` 管 Chart 本身迭代,`appVersion` 管应用版本 |
| **先 lint 再 push** | CI 中加入 `helm lint` + `helm template --strict` 门禁 |
| **值文件只写差异** | `values.prod.yaml` 只覆盖与默认值不同的字段,保持可读性 |
## 何时用裸 YAML vs Helm vs Kustomize?
| 场景 | 推荐方案 | 理由 |
|------|---------|------|
| < 10 个服务 | Kustomize / 裸 YAML | 复杂度高于收益 |
| 10~50 个服务 | Helm | 模板复用价值明显 |
| > 50 个服务 | Helm + Kustomize overlays | Helm 管模板,Kustomize 管环境差异 |
> [!note] Kustomize 的定位
>
> Kustomize 不依赖 Go templating,更轻量。适合"基于同一套模板,按环境覆盖差异配置"的场景。
>
> **常见组合**:Helm 生成基础模板,Kustomize overlay 处理 staging/prod 差异。
## 关联笔记
- [[01-CICD基础与实践]] — CI/CD Pipeline 的搭建与实践
- [[02-GitOps与ArgoCD]] — GitOps 工作流与 ArgoCD
- [[04-安全与发布策略]] — 安全管理与发布决策
- [[../03-CICD与GitOps]] — 参考手册与决策指南
@@ -0,0 +1,470 @@
---
tags: [security, secrets-management, canary, rollback, deployment-strategy, feature-flag, database-migration]
create time: 2026-05-18 14:30
---
# 安全与发布策略
## 概述
本文档聚焦生产级部署中的两大决策点:**敏感信息管理**和**发布策略选择**。提供方案对比、实操检查清单以及零停机发布的数据库迁移策略。架构概览参见 [[../03-CICD与GitOps]]。
## Secret 管理:分层决策树
```mermaid
flowchart TD
START["开始选型"] --> SIZE["团队规模?"]
SIZE -- "< 20人" --> K8SSECRET["K8s 原生 Secret\n✅ 开箱即用"]
SIZE -- "≥ 20人" --> CLOUD["是否深度绑定云厂商?"]
CLOUD -- "是 (AWS/GCP/Azure)" --> SECRETMGR["云厂商 Secret Manager\n✅ 集成度高,审计完善"]
CLOUD -- "否 / 多云" --> ESOOROPS["需要 GitOps?"]
ESOOROPS -- "是 (ArgoCD)" --> SOPS["SOPS + sealed-secrets\n✅ 加密文件可提交到 Git"]
ESOOROPS -- "否 / 已有 Vault" --> ESO["External Secrets Operator\n✅ Git 中不存任何密文"]
style K8SSECRET fill:#e8f5e9
style SECRETMGR fill:#e3f2fd
style SOPS fill:#fff3e0
style ESO fill:#f3e5f5
```
### 方案速查表
| 方案 | 适用场景 | 优点 | 缺点 |
|------|---------|------|------|
| **K8s 原生 Secret** | 小规模、内部团队 | 零额外成本,开箱即用 | 明文存储在 etcd,RBAC 控制有限 |
| **External Secrets Operator (ESO)** | 已有 Vault / AWS SSM | Git 中不存任何密文 | 需维护额外组件 |
| **SOPS + sealed-secrets** | ArgoCD 用户 | 加密文件可提交到 Git | 需管理公钥基础设施 |
| **AWS Secrets Manager / GCP Secret Manager** | 云厂商深度绑定 | 集成度高,审计完善 | 锁定特定云平台 |
### ❌ 绝对不要做的事
```yaml
# 错误示范:明文写密码
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
DB_PASSWORD: "my-secret-password" # 绝对不要这样做!
```
### ✅ 正确做法
```yaml
# 使用 Secret(stringData 接收明文,API Server 自动 Base64)
apiVersion: v1
kind: Secret
metadata:
name: app-secrets
namespace: production
type: Opaque
stringData:
DB_PASSWORD: "${DB_PASSWORD}" # 通过 CI secrets 注入
API_KEY: "${API_KEY}"
---
# 在 Deployment 中引用
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
containers:
- name: order-service
envFrom:
- secretRef:
name: app-secrets
```
> [!warning] 为什么不能把 Secret 明文放进 Git?
>
> 即使使用 GitOps(ArgoCD),也不建议将明文密码提交到仓库。一旦权限管控疏漏——比如某个离职员工的账号未被撤销——整个公司的数据库密码就暴露了。使用 ESO 或 sealed-secrets,**Git commit 历史中永远不会有明文密钥**。
### Secret 进阶实践
#### 1. RBAC:最小权限原则
```yaml
# 限制只有特定 ServiceAccount 可以读取特定 Secret
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: order-service-read-secret
namespace: production
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: secret-reader # 仅限 Read,禁止 Create/Update/Delete
subjects:
- kind: ServiceAccount
name: order-service-sa # 仅该 SA 有权读取
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: secret-reader
namespace: production
rules:
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["app-secrets"] # 按名称限定,不可通读所有 Secret
verbs: ["get", "list"]
```
> [!tip] `resourceNames` 是什么?
> K8s RBAC 默认是资源级别的(你能读这个 namespace 下**所有** Secret)。加上 `resourceNames` 后变成**细粒度**控制——只能读指定的几个 Secret 名。这是防止越权访问的关键手段。
#### 2. 开启 etcd 加密
K8s 的 Secret 在 etcd 中以 Base64 编码存储,而非加密。建议在集群层面启用 EncryptionConfiguration:
```yaml
# encryption-config.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- aescbc: # 首选 AES-CBC 加密
keys:
- secret: $(ENCRYPTION_KEY) # 从环境变量注入,不要硬编码
- identity: # fallback:旧数据用明文
```
> [!note] 为什么要开启 etcd 加密?
> 如果不加密, anyone 能访问 etcd(比如 K8s admin 角色的人、云厂商控制台的操作员)就能直接读取所有 Secret 明文。启用后,etcd 中实际存储的是密文。⚠️ **加密必须一次性完成**——一旦写入数据,后续切换加密 provider 会导致旧数据丢失。
#### 3. 审计日志
确保 kube-apiserver 启用了审计策略,记录所有 Secret 的读写操作:
```yaml
# audit-policy.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: Metadata # Secret 只记录元数据,不记录内容
resources:
- group: "" # core API group
resources: ["secrets"]
verbs: ["get", "list", "watch"]
- level: None
resources: ["secrets"]
verbs: ["update", "patch", "delete"] # 修改类操作也需记录
```
## 数据库迁移策略
> [!question] 发布新版本时,代码先上还是数据库先改?
> 答案:**两边要同时准备好**——但「先扩后缩」是核心原则。
零停机发布的最大陷阱是**数据库 schema 变更**。如果代码 A 版本写了字段 X,而代码 B 版本还没发布,此时如果删掉字段 X,代码 A 就会崩溃。遵循以下原则:
### 向前兼容原则
```mermaid
flowchart LR
subgraph Phase1["Phase 1: 加字段(向后兼容)"]
P1A[添加新字段(可为 NULL)] --> P1B[部署新代码(双写新旧字段)]
end
subgraph Phase2["Phase 2: 填存量数据"]
P2A[后台任务回填历史数据] --> P2B[验证数据一致性]
end
subgraph Phase3["Phase 3: 切换到新字段"]
P3A[部署代码(仅读新字段)] --> P3B[删除旧字段]
end
P1B --> P2A
P2B --> P3A
style Phase1 fill:#e8f5e9
style Phase2 fill:#fff3e0
style Phase3 fill:#fce4ec
```
**三阶段详解:**
| 阶段 | 动作 | 兼容性保证 |
|------|------|-----------|
| **Phase 1** | `ALTER TABLE` 新增列(NOT NULL 不行!设为可 NULL)+ 新代码同时写入新旧字段 | 旧代码继续读旧字段,正常运行 |
| **Phase 2** | 用后台 Job 批量填充新字段的存量数据,直到全量一致 | 旧代码仍然正常,新代码已具备降级能力 |
| **Phase 3** | 部署仅读新字段的代码 → 确认无误后 `ALTER TABLE DROP` 旧字段 | 完全切换到新模式 |
> [!example] 具体例子:给订单表增加 `status_v2` 枚举字段
>
> **Phase 1 — 加列 + 双写:**
> ```sql
> ALTER TABLE orders ADD COLUMN status_v2 VARCHAR(20) NULL;
> ```
> ```go
> // 新代码:写入两个字段
> db.Exec("UPDATE orders SET status=?, status_v2=? WHERE id=?", oldStatus, newV2, orderId)
> ```
>
> **Phase 2 — 回填存量:**
> ```go
> func migrateLegacyStatus() {
> batch := 1000
> offset := 0
> for {
> rows, _ := db.Query("SELECT id, status FROM orders LIMIT $1 OFFSET $2", batch, offset)
> // ... 转换 status -> status_v2 并 UPDATE ...
> if rows.Count() < batch { break }
> offset += batch
> }
> }
> ```
>
> **Phase 3 — 切流 + 清理:**
> ```go
> // 新代码:只读写 status_v2
> db.Query("SELECT * FROM orders WHERE status_v2='paid'")
> // 确认全量稳定运行一周后执行:
> ALTER TABLE orders DROP COLUMN status;
> ```
### 关键注意事项
- **永远不要在同一个 deploy 中「删字段 + 上线读该字段的代码」** ——这必出事故
- `ALTER TABLE` 对大表是重操作:使用 `pt-online-schema-change`(MySQL)或 `gh-ost` 避免锁表
- DML(增删改行)可以用分批 + delay 方式;DDL(改结构)一定要离线窗口或使用在线工具
- 每次迁移前备份:`mysqldump` 或快照,回滚有退路
## 回滚策略与机制
> [!question] 发布后发现 Bug,怎么办?
> 最快的回滚不是「修代码再发布」,而是「切回上一个版本」。
### 回滚时机判断
```mermaid
flowchart TD
ALERT["告警触发 / 监控异常"] --> THRESHOLD{"指标超过阈值?"}
THRESHOLD -- "Error Rate > 目标值" --> AUTO_ROLLBACK["⚡ 自动回滚 Canary"]
THRESHOLD -- "Latency p99 升高" --> MANUAL["人工评估是否需要回滚"]
THRESHOLD -- "业务指标下降但不紧急" --> MONITOR["持续观察,暂不回滚"]
AUTO_ROLLBACK --> LOG["通知 Team + 记录事件"]
MANUAL -- "决定回滚" --> LOG
MANUAL -- "决定观察" --> MONITOR
LOG --> POSTMORTEM["事后复盘 Post-mortem"]
style AUTO_ROLLBACK fill:#ffebee
style MANUAL fill:#fff3e0
style MONITOR fill:#e8f5e9
style POSTMORTEM fill:#e3f2fd
```
### Rolling Update 的回滚
```bash
# kubectl rollout 是最基础的回滚手段
kubectl rollout status deployment/order-service # 查看当前状态
kubectl rollout undo deployment/order-service # 回滚到上一版本
kubectl rollout history deployment/order-service # 查看所有 revision
kubectl rollout undo deployment/order-service --to-revision=3 # 回滚到指定版本
# 也可以直接用之前验证过的镜像重新 apply
kubectl set image deployment/order-service order-service=myregistry/app:v1.2.3@sha256:...
```
### Blue-Green 的回滚
Blue-Green 天然支持秒级回滚——只需把 Service 的 selector 切回蓝环境即可:
```yaml
# 当前绿环境在服务,切回蓝环境:
apiVersion: v1
kind: Service
metadata:
name: order-service
spec:
selector:
app: order-service
version: blue # ← 从 green 改成 blue
ports:
- port: 80
targetPort: 8080
```
> [!tip] 如何做到无缝切换?
> 配合 **外部 DNS**(Route53 / CloudFlare)可以跨 Kubernetes Service 实现全局流量切换。Service Level Switching 毫秒级,DNS TTL 通常分钟级。关键是把流量入口层和服务层都做好双写。
### ArgoCD 一键回滚
```yaml
# ArgoCD Application 定义
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: order-service
spec:
project: default
source:
repoURL: https://github.com/team/k8s-manifests.git
targetRevision: v1.2.3 # ← 改到这个 tag 即回滚
path: overlays/production
syncPolicy:
automated:
prune: true # 自动删除不在 Git 中的资源
selfHeal: true # 检测到漂移自动修复
```
ArgoCD 的回滚就是「改 Git 里的目标版本 + push」——这就是 GitOps 的魅力:**回滚也是提交**。
## 特性开关(Feature Flag)
> [!question] 有没有一种方法可以让新功能「已经部署到生产」但「对用户还不可见」?
> 答案是 Feature Flag —— **代码提交 ≠ 功能上线**。
### 典型使用模式
```go
// feature_flags.go
package flags
import "github.com/thomaspoison/go-feature-flag/provider/fileprovider"
func init() {
ffclient.Init(ffconfig.Config{
FilePath: "features.yaml", // 配置文件路径
PollInterval: 30, // 每 30 秒刷新
})
}
// 调用处
if flags.Value("enable-new-checkout", false) {
// 新功能逻辑
return NewCheckoutFlow(ctx)
}
return LegacyCheckoutFlow(ctx) // 兜底:传统流程
```
对应配置文件 `features.yaml`:
```yaml
enable-new-checkout:
percentage: 0 # 初始为 0%,对所有人关闭
rollout: gradual # 渐进式放量
rules:
- attribute: env
operator: equal
value: production
invert: false
values: [50] # 灰度到 50% 的用户
```
### 何时使用 Feature Flag
| 场景 | 推荐度 | 理由 |
|------|--------|------|
| 复杂业务逻辑开关 | ⭐⭐⭐⭐⭐ | 无需重新部署即可上线下线功能 |
| A/B 测试 | ⭐⭐⭐⭐⭐ | 按用户维度控制实验组/对照组 |
| 简单配置项 | ⭐⭐ | 用 ConfigMap 就够了,Flag 太重 |
| 临时 Debug | ⭐⭐⭐ | 用环境变量更直接 |
### Feature Flag vs Release Branch
```mermaid
flowchart LR
trunk["主分支 main/master\n🟢 始终可构建"] --> CI["CI 自动化构建"]
subgraph flag["Feature Flag 方式"]
F1["新功能开发完成"] --> F2["合并到 main\nFlag = OFF"] --> F3["发布到生产"] --> F4["调试后 Flag = ON"]
end
subgraph branch["Release Branch 方式"]
B1["创建 release/v2.0 分支"] --> B2["隔离新功能"] --> B3["发布时合入 main"]
end
CI --> F1
CI --> B2
style trunk fill:#e8f5e9
style flag fill:#f3e5f5
style branch fill:#fff3e9
```
核心差异:**Feature Flag 保持单一 main 分支,Release Branch 产生长期隔离分支**。现代 GitOps 实践更推荐 Flag,因为可以避免 merge conflict 堆积和重复测试。
## 发布前检查清单
```mermaid
flowchart TD
PRE["发布前检查"] --> T1{"镜像 Tag 是否精确?"}
T1 -- ✅ SHA-based --> T2{"Staging 是否已通过?"}
T1 -- ❌ latest / head --> FAIL1["❌ 拒绝部署"]
T2 -- ✅ Passed --> T3{"Prod Approval 是否完成?"}
T2 -- ❌ Failed/Pending --> FAIL2["❌ 暂停等待"]
T3 -- ✅ Approved --> T4{"Canary 指标是否正常?"}
T3 -- ❌ Rejected --> FAIL2
T4 -- ✅ All Green --> SUCCESS["✅ 全量发布"]
T4 -- ❌ Error Rate 升高 --> ROLLBACK["⚠️ 自动回滚"]
style FAIL1 fill:#ffebee
style FAIL2 fill:#fff3e0
style SUCCESS fill:#e8f5e9
style ROLLBACK fill:#fce4ec
```
| # | 检查项 | 工具/方法 |
|---|--------|-----------|
| 1 | 镜像 Tag 使用 Commit SHA | GitHub Actions `${{ github.sha }}` |
| 2 | 单元测试覆盖率达标 | `make test` + coverage threshold |
| 3 | 安全扫描无 HIGH/CRITICAL | Trivy / Snyk |
| 4 | Staging 环境冒烟测试通过 | 自动化 health check |
| 5 | Prod 审批人工确认 | GitHub Environment Protection Rule |
| 6 | Canary 阶段 error rate < 0.1% | Prometheus + Alertmanager |
| 7 | Git manifest 已更新并 merged | ArgoCD Application status = Synced |
## 渐进式采纳路径
如果你的团队还在手工部署,不必一步到位。建议分三步走:
```mermaid
flowchart LR
STEP1[("1. 自动化构建+Push")] --> STEP2[("2. Staging + 审批关卡")] --> STEP3[("3. GitOps, Git 即真相源")]
style STEP1 fill:#e8f5e9
style STEP2 fill:#fff3e0
style STEP3 fill:#e3f2fd
```
1. **第一步:用 GitHub Actions/GitLab CI 实现自动化构建 + push** — 推荐立即做
2. **第二步:引入 Staging 环境和审批关卡** — 降低上线风险
3. **第三步:迁移到 GitOps(ArgoCD)** — 让 Git 成为唯一真相源
实际落地时,很多团队的最终架构是 **CI/CD + GitOps 结合**:
```mermaid
flowchart LR
Dev["开发者 push 代码"] --> CI["GitHub Actions\n(CI: 测试→构建→Push镜像)"]
CI -->|"推送新镜像"| IMGREG["Container Registry"]
Dev2["开发者 PR 修改 K8s manifest"] --> GIT[(Git Manifest Repo)]
subgraph Cluster["K8s Cluster"]
Argo["ArgoCD"] -->|"自动同步"| K8s["Pod/Service/ConfigMap"]
end
GIT -.->|Watch 变动| Argo
style CI fill:#e8f5e9
style Argo fill:#fff3e0
style K8s fill:#e3f2fd
```
核心分工:**CI/CD 管"怎么建",GitOps 管"怎么维持"**。两者合在一起,才构成了完整的现代软件交付链。
## 关联笔记
- [[01-CICD基础与实践]] — CI/CD Pipeline 的搭建与实践
- [[02-GitOps与ArgoCD]] — GitOps 工作流与 ArgoCD
- [[03-Helm模板管理]] — Helm Chart 编写与管理
- [[../03-CICD与GitOps]] — 参考手册与决策指南总入口
+26 -110
View File
@@ -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["🟢 绿灯阶段<br/>预算充足: 可以大胆发布<br/>新功能、尝试激进方案<br/>常规发布节奏"]
Ratio -- "50~90%" --> Yellow["🟡 黄灯阶段<br/>预算紧张: 收紧发布频率<br/>增加人工审查<br/>暂停非关键功能开发"]
Ratio -- "\> 90%" --> Orange["🟠 橙灯阶段<br/>严重不足: 冻结发布<br/>专注稳定性修复<br/>全员 On-Call"]
Ratio -- "\> 100%" --> Red["🔴 红灯阶段<br/>预算耗尽: 禁止所有<br/>功能性变更<br/>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 支持安全的持续交付
@@ -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 总体概览
@@ -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["🟢 绿灯阶段<br/>预算充足: 可以大胆发布<br/>新功能、尝试激进方案<br/>常规发布节奏"]
Ratio -- "50~90%" --> Yellow["🟡 黄灯阶段<br/>预算紧张: 收紧发布频率<br/>增加人工审查<br/>暂停非关键功能开发"]
Ratio -- "\> 90%" --> Orange["🟠 橙灯阶段<br/>严重不足: 冻结发布<br/>专注稳定性修复<br/>全员 On-Call"]
Ratio -- "\> 100%" --> Red["🔴 红灯阶段<br/>预算耗尽: 禁止所有<br/>功能性变更<br/>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 保障
@@ -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 - 灾难性<br/>全站不可用 / 资金损失<br/>CTO 级别通知<br/>2h内必须恢复"] --> P1
P1["🟠 P1 - 严重<br/>核心功能受影响<br/>负责人 + SRE 紧急会议<br/>4h内必须恢复"] --> P2
P2["🟡 P2 - 一般<br/>部分功能降级<br/>工作时间处理<br/>24h内必须恢复"] --> P3
P3["🟢 P3 - 轻微<br/>非核心问题<br/>下个迭代处理"]
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 保障
+11 -8
View File
@@ -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]] — 完整微服务知识索引