概述
凌晨三点被告警叫醒,面对一个不熟悉的服务,你能多快恢复?如果你需要翻聊天记录、问同事、翻代码才能弄清楚怎么处理,那说明你的团队缺一样东西——Runbook。
Runbook 是 SRE 体系中最基础但也最容易被忽视的工程实践。它是连接"告警"和"行动"的桥梁——告警告诉你"出问题了",Runbook 告诉你"该怎么办"。一个好的 Runbook 能让任何有基础技术能力的工程师在 On-Call 时有效响应告警,而不依赖特定人员的"脑内知识"。
从 Runbook 的作用、结构设计、自动化、版本管理、质量评审到实战模板,详细梳理如何编写和维护高质量的 Runbook。
关于 Runbook 的好的实践,可参考 Google SRE Workbook - Operational Overload 和 PagerDuty - Runbook Guide。
一、Runbook 的作用与价值
什么是 Runbook
Runbook 是一份标准化的操作手册,描述了如何诊断和处理特定的运维场景。它的核心特征是:
任何有基础技术能力的工程师,按照 Runbook 的步骤操作,都能正确处理对应的告警或运维任务——而不需要依赖特定人员的主观经验。
没有 Runbook 的代价
没有 Runbook 的故障响应:
告警触发 → On-Call 工程师看到 "Redis 内存使用率 90%"
→ 不知道该怎么办
→ 打电话问张三(张三写过这个服务)
→ 张三不在线
→ 打电话问李四
→ 李四说 "可能是某个 key 太大了,你看看"
→ 工程师花 20 分钟找大 key
→ 最终处理了,但用了 40 分钟
有 Runbook 的故障响应:
告警触发 → On-Call 工程师看到 "Redis 内存使用率 90%"
→ 告警中附带 Runbook 链接
→ 打开 Runbook,按步骤操作
→ Step 1: 执行 redis-cli --bigkeys 扫描大 key
→ Step 2: 发现 user_session:xxx 占用 500MB
→ Step 3: 执行 DEL user_session:xxx
→ 10 分钟恢复
Runbook 的价值
| 维度 | 价值 |
|---|---|
| 降低 MTTR | 按步骤操作比从零排查快得多 |
| 降低 On-Call 压力 | 有文档可依,不需要"什么都知道" |
| 知识沉淀 | 把个人经验转化为组织知识 |
| 新人友好 | 新人 On-Call 不再需要"跟班学习"数周 |
| 驱动自动化 | Runbook 中的步骤是自动化的候选对象 |
Runbook 与其他文档的区别
| 文档类型 | 目的 | 读者 | 场景 |
|---|---|---|---|
| Runbook | 处理特定告警/故障的操作步骤 | On-Call 工程师 | 告警触发时 |
| Architecture Doc | 描述系统架构和设计决策 | 所有工程师 | 理解系统设计 |
| API Doc | 描述 API 接口规范 | 开发者 | 对接 API |
| Postmortem | 分析故障根因和改进项 | 团队 | 故障复盘 |
| Playbook | 更广泛的工作流程指南 | 团队 | 执行复杂任务 |
二、Runbook 的结构设计
标准结构
一个好的 Runbook 应该包含以下部分:
# Runbook: [告警名称/场景名称]
## 基本信息
- 告警名称:RedisMemoryHigh
- 严重级别:SEV3
- 影响服务:user-service, session-service
- Runbook 负责人:张三
- 最后更新:2026-07-10
- 评审状态:已审核
## 告警含义
[这段告警是什么意思?什么指标超了?]
## 影响
[如果不处理会怎样?对用户和业务的影响是什么?]
## 诊断步骤
[按步骤排查问题的操作]
## 处理步骤
[按步骤解决问题的操作]
## 验证
[怎么确认问题已解决?]
## 升级
[如果以上步骤无效,该找谁?]
## 相关资源
[监控链接、日志查询、相关文档]
各部分详解
告警含义
用最简洁的语言解释告警的含义,让不熟悉该服务的工程师也能理解:
## 告警含义
Redis 实例 redis-prod-01 的内存使用率超过 90%,持续 5 分钟。
当前内存使用:4.5GB / 5GB(90%)
这意味着 Redis 可能很快会触发 OOM(Out of Memory),导致:
- Redis 被操作系统强制杀掉
- 所有依赖 Redis 的服务(用户会话、缓存)不可用
影响
## 影响
- **直接影响**:Redis OOM 后所有缓存失效,数据库压力骤增
- **用户影响**:用户可能被登出(会话丢失),页面加载变慢
- **业务影响**:如果持续超过 10 分钟,可能导致用户流失
- **不处理的风险**:Redis OOM → 服务雪崩 → SEV1 故障
诊断步骤
诊断步骤是 Runbook 的核心——它应该是一系列可执行的、有序的操作:
## 诊断步骤
### Step 1: 确认告警真实性
```bash
# 检查 Redis 当前内存使用
redis-cli -h redis-prod-01 -p 6379 INFO memory | grep used_memory_human
# 预期输出:used_memory_human:4.5G
# 检查内存使用率
redis-cli -h redis-prod-01 -p 6379 INFO memory | grep memory_fragmentation_ratio
如果内存使用率低于 80%,可能是误告警,检查 Prometheus 采集是否正常。
Step 2: 找出内存增长原因
# 扫描大 key(可能需要几秒到几十秒)
redis-cli -h redis-prod-01 -p 6379 --bigkeys
# 或者使用 memory usage 查看特定 key
redis-cli -h redis-prod-01 -p 6379 MEMORY USAGE user_session:xxx
常见原因:
| 原因 | 特征 | 处理方式 |
|---|---|---|
| 大 key | 单个 key 占用 >100MB | 见处理步骤 Step 1 |
| key 过期策略不当 | 大量 key 未设置 TTL | 见处理步骤 Step 2 |
| 内存碎片 | used_memory_rss 远大于 used_memory | 见处理步骤 Step 3 |
| 正常增长 | 数据量持续增长 | 见处理步骤 Step 4 |
Step 3: 检查最近变更
# 查看最近的部署
kubectl rollout history deployment/user-service -n production
# 查看最近的配置变更
git log --since="2 hours ago" --oneline -- configs/redis/
如果最近有变更,可能是变更引入的问题。
#### 处理步骤
```markdown
## 处理步骤
### 场景 A:大 key 导致
```bash
# 确认 key 的内容(不要直接删除,先确认)
redis-cli -h redis-prod-01 -p 6379 TYPE user_session:xxx
redis-cli -h redis-prod-01 -p 6379 TTL user_session:xxx
# 如果是过期的会话数据,可以安全删除
redis-cli -h redis-prod-01 -p 6379 DEL user_session:xxx
# 如果是不应该存在的 key,删除后排查来源
# 检查哪个服务写入的
grep "user_session:xxx" /var/log/apps/*.log
场景 B:key 未设置 TTL
# 找出未设置 TTL 的 key
redis-cli -h redis-prod-01 -p 6379 --scan --pattern '*' | while read key; do
ttl=$(redis-cli -h redis-prod-01 -p 6379 TTL "$key")
if [ "$ttl" -eq -1 ]; then
echo "$key (type: $(redis-cli -h redis-prod-01 -p 6379 TYPE $key))"
fi
done | head -20
# 为遗漏 TTL 的 key 设置过期时间
redis-cli -h redis-prod-01 -p 6379 EXPIRE cache:xxx 3600
场景 C:内存碎片
# 检查碎片率
redis-cli -h redis-prod-01 -p 6379 INFO memory | grep -E "used_memory_human|used_memory_rss_human|mem_fragmentation_ratio"
# 如果 mem_fragmentation_ratio > 1.5,执行碎片整理
redis-cli -h redis-prod-01 -p 6379 MEMORY PURGE
场景 D:正常增长,需要扩容
# 如果是正常数据增长,需要扩容 Redis
# 1. 申请新的更大内存 Redis 实例
# 2. 配置主从同步
# 3. 切换流量
# 4. 详细步骤见:https://wiki/ops/redis-migration
#### 验证
```markdown
## 验证
```bash
# 确认内存使用率已降低
redis-cli -h redis-prod-01 -p 6379 INFO memory | grep used_memory_human
# 预期:使用率 < 70%
# 确认服务正常
curl -s http://user-service:8080/healthz
# 预期:返回 200 OK
# 确认告警已清除
# 查看 Prometheus: redis_memory_used_bytes / redis_memory_max_bytes < 0.7
如果 10 分钟后内存再次升高,说明根因未消除,需要深入排查。
#### 升级
```markdown
## 升级
如果以上步骤均无效:
1. **联系 Redis DBA**:李四(电话:xxx,IM:@lisi)
2. **联系服务负责人**:张三(IM:@zhangsan)
3. **如果影响用户**:升级为 SEV2,按照故障响应流程处理
4. **War Room**:https://meet.example.com/emergency
相关资源
## 相关资源
- **监控仪表盘**:https://grafana.example.com/d/redis-overview
- **日志查询**:https://kibana.example.com/app/discover (索引:redis-*)
- **架构文档**:https://wiki/arch/redis-cluster
- **上次故障复盘**:https://wiki/postmortem/2026-05-redis-ooc
- **Redis 运维手册**:https://wiki/ops/redis-handbook
三、告警与 Runbook 的关联
每条告警都必须关联 Runbook
这是 Runbook 体系的基石——告警和 Runbook 必须一一对应:
# Prometheus 告警规则中的 Runbook 链接
groups:
- name: redis-alerts
rules:
- alert: RedisMemoryHigh
expr: |
redis_memory_used_bytes / redis_memory_max_bytes > 0.9
for: 5m
labels:
severity: warning
annotations:
summary: "Redis 内存使用率超过 90%"
description: "Redis {{ $labels.instance }} 内存使用率 {{ $value | humanizePercentage }}"
runbook: "https://wiki/runbooks/redis-memory-high"
# ↑ 每条告警都必须有 runbook 链接
- alert: RedisDown
expr: redis_up == 0
for: 1m
labels:
severity: critical
annotations:
summary: "Redis 不可用"
runbook: "https://wiki/runbooks/redis-down"
Runbook 链接的注意事项
# ❌ 错误:指向通用文档
runbook: "https://wiki/ops/redis" # 太泛了,不知道该看哪一段
# ❌ 错误:指向需要搜索的页面
runbook: "https://wiki/ops" # 要去搜索?凌晨3点谁有耐心?
# ✅ 正确:直接指向具体 Runbook
runbook: "https://wiki/runbooks/redis-memory-high"
四、自动化 Runbook
从手动到自动化的演进
Runbook 的终极目标是自动化——把人工操作步骤变成自动执行的脚本:
| 层次 | 特征 | 示例 |
|---|---|---|
| L1 文档型 | 纯文字描述步骤 | “执行 redis-cli –bigkeys” |
| L2 脚本型 | 附带可执行脚本 | 文档中嵌入 ./diagnose_redis.sh |
| L3 半自动 | 一键诊断,人工确认后执行修复 | 脚本诊断 + 人工确认 + 脚本修复 |
| L4 全自动 | 告警触发自动执行 | AlertManager → Webhook → 自动修复 |
自动诊断脚本
#!/usr/bin/env python3
"""
Redis 内存告警自动诊断脚本
用法: python3 diagnose_redis_high_memory.py --host redis-prod-01 --port 6379
"""
import argparse
import subprocess
import json
from typing import Dict, List
class RedisDiagnostic:
def __init__(self, host: str, port: int):
self.host = host
self.port = port
self.results = {}
def _redis_cli(self, *args) -> str:
"""执行 redis-cli 命令"""
cmd = ['redis-cli', '-h', self.host, '-p', str(self.port)] + list(args)
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
return result.stdout.strip()
def check_memory(self) -> Dict:
"""检查内存状态"""
info = self._redis_cli('INFO', 'memory')
memory_info = {}
for line in info.split('\n'):
if ':' in line and not line.startswith('#'):
key, value = line.split(':', 1)
memory_info[key] = value
used = int(memory_info.get('used_memory', 0))
max_mem = int(memory_info.get('maxmemory', 0))
rss = int(memory_info.get('used_memory_rss', 0))
self.results['memory'] = {
'used_human': memory_info.get('used_memory_human', 'unknown'),
'max_human': memory_info.get('maxmemory_human', 'unknown'),
'usage_ratio': f"{used/max_mem*100:.1f}%" if max_mem > 0 else 'unlimited',
'fragmentation_ratio': memory_info.get('mem_fragmentation_ratio', 'unknown'),
}
return self.results['memory']
def find_big_keys(self) -> List:
"""扫描大 key"""
print("Scanning big keys (this may take 10-30 seconds)...")
output = self._redis_cli('--bigkeys')
# 解析输出,提取大 key 信息
big_keys = []
for line in output.split('\n'):
if 'Biggest' in line and 'found' in line:
big_keys.append(line.strip())
self.results['big_keys'] = big_keys
return big_keys
def find_no_ttl_keys(self, sample_size=100) -> List:
"""查找未设置 TTL 的 key"""
keys = self._redis_cli('--scan', '--pattern', '*').split('\n')[:sample_size]
no_ttl = []
for key in keys:
if key:
ttl = self._redis_cli('TTL', key)
if ttl == '-1':
key_type = self._redis_cli('TYPE', key)
size = self._redis_cli('MEMORY', 'USAGE', key)
no_ttl.append({
'key': key,
'type': key_type,
'size': f"{size} bytes",
'ttl': 'none'
})
self.results['no_ttl_keys'] = no_ttl
return no_ttl
def generate_report(self) -> str:
"""生成诊断报告"""
report = f"""
=== Redis Memory Diagnostic Report ===
Host: {self.host}:{self.port}
Time: {datetime.now().isoformat()}
--- Memory Status ---
Used: {self.results.get('memory', {}).get('used_human', 'N/A')}
Max: {self.results.get('memory', {}).get('max_human', 'N/A')}
Usage: {self.results.get('memory', {}).get('usage_ratio', 'N/A')}
Fragmentation: {self.results.get('memory', {}).get('fragmentation_ratio', 'N/A')}
--- Big Keys ---
{chr(10).join(self.results.get('big_keys', ['No big keys found']))}
--- Keys Without TTL (sample) ---
"""
for key_info in self.results.get('no_ttl_keys', [])[:10]:
report += f" {key_info['key']} (type={key_info['type']}, size={key_info['size']})\n"
if not self.results.get('no_ttl_keys'):
report += " All sampled keys have TTL\n"
report += "\n=== Recommended Actions ===\n"
# 根据诊断结果给出建议
frag_ratio = float(self.results.get('memory', {}).get('fragmentation_ratio', 1))
if frag_ratio > 1.5:
report += "- High fragmentation ratio. Consider: MEMORY PURGE\n"
if self.results.get('big_keys'):
report += "- Big keys found. Consider: DEL or split the keys\n"
if self.results.get('no_ttl_keys'):
report += f"- {len(self.results.get('no_ttl_keys', []))} keys without TTL (in sample). Consider: EXPIRE\n"
return report
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--host', required=True)
parser.add_argument('--port', type=int, default=6379)
args = parser.parse_args()
diag = RedisDiagnostic(args.host, args.port)
diag.check_memory()
diag.find_big_keys()
diag.find_no_ttl_keys()
print(diag.generate_report())
告警触发的自动修复
# AlertManager Webhook 配置
route:
receiver: 'auto-remediation'
routes:
- match:
alertname: 'RedisMemoryHigh'
receiver: 'redis-auto-fix'
receivers:
- name: 'redis-auto-fix'
webhook_configs:
- url: 'http://auto-remediation:5000/redis/memory-high'
send_resolved: true
# 自动修复 Webhook
from flask import Flask, request, jsonify
import subprocess
app = Flask(__name__)
@app.route('/redis/memory-high', methods=['POST'])
def handle_redis_memory_high():
alert = request.json['alerts'][0]
instance = alert['labels']['instance']
host = instance.split(':')[0]
# 1. 自动诊断
diag = subprocess.run(
['python3', '/scripts/diagnose_redis.py', '--host', host],
capture_output=True, text=True
)
# 2. 根据诊断结果决定修复策略
diag_output = diag.stdout
if 'Big keys found' in diag_output:
# 如果是大 key,自动清理过期的会话 key
subprocess.run(['python3', '/scripts/cleanup_bigkeys.py', '--host', host])
action = "cleaned big keys"
elif 'High fragmentation' in diag_output:
# 如果是碎片,执行碎片整理
subprocess.run(['redis-cli', '-h', host, 'MEMORY', 'PURGE'])
action = "purged memory fragmentation"
else:
# 无法自动修复,通知人工
notify_oncall(f"Redis {host} memory high, auto-fix failed. Manual intervention needed.")
action = "escalated to human"
# 3. 记录操作
log_action(host, action, diag_output)
return jsonify({'status': 'ok', 'action': action})
五、Playbook 与 Runbook 的区别
概念区分
虽然 Runbook 和 Playbook 经常被混用,但它们在 SRE 实践中有不同的定位:
| 维度 | Runbook | Playbook |
|---|---|---|
| 范围 | 单一告警/问题的处理步骤 | 多步骤的复杂工作流程 |
| 触发 | 告警触发 | 计划性任务/人工触发 |
| 粒度 | 细粒度的操作步骤 | 粗粒度的流程编排 |
| 示例 | “Redis 内存高怎么处理” | “数据库从 5.x 升级到 6.x 的完整流程” |
| 自动化 | 逐步自动化 | 端到端编排 |
Playbook 示例
# Playbook: PostgreSQL 大版本升级
## 概述
从 PostgreSQL 13 升级到 16,涉及数据迁移、应用兼容性验证、流量切换。
## 前置条件
- [ ] 新版本已在测试环境验证通过
- [ ] 应用代码已适配新版本
- [ ] 备份策略已验证
- [ ] 回滚方案已准备
## 阶段 1: 准备(T-7 天)
1. 创建新版本 PostgreSQL 实例
2. 配置逻辑复制(从旧到新)
3. 验证复制延迟 < 1s
## 阶段 2: 预检查(T-1 天)
1. 运行兼容性检查脚本
```bash
./pg_upgrade_check.sh --old 13 --new 16
- 验证所有扩展在新版本可用
- 通知相关团队维护窗口
阶段 3: 切换(T 日)
- [Runbook] 停止写入流量
- [Runbook] 等待复制延迟归零
- [Runbook] 验证数据一致性
- [Runbook] 切换应用连接到新实例
- [Runbook] 验证服务正常
阶段 4: 验证(T+1 天)
- 监控新实例性能指标
- 验证所有功能正常
- 保留旧实例 7 天作为回滚备份
回滚
如果切换后出现问题:
- [Runbook] 切换应用连接回旧实例
- [Runbook] 验证服务恢复
- 排查问题,重新安排升级
相关 Runbook
可以看到,Playbook 编排了多个 Runbook,是一个更高层的工作流程文档。
## 六、版本管理与质量评审
### 版本管理
Runbook 是活文档,需要随系统变化持续更新。使用 Git 进行版本管理:
runbooks/ ├── redis/ │ ├── redis-memory-high.md │ ├── redis-down.md │ └── redis-replication-lag.md ├── postgres/ │ ├── postgres-high-cpu.md │ ├── postgres-connection-exhausted.md │ └── postgres-replication-lag.md ├── kubernetes/ │ ├── pod-crashloopbackoff.md │ ├── node-not-ready.md │ └── pvc-pending.md ├── _template/ │ └── runbook-template.md └── README.md
```yaml
# Runbook 元数据(嵌入文档头部)
---
runbook_id: RB-REDIS-001
title: "Redis 内存使用率过高"
alert_name: RedisMemoryHigh
severity: SEV3
owner: zhangsan
last_reviewed: 2026-07-10
next_review_due: 2026-10-10
version: 1.3
changelog:
- version: 1.3
date: 2026-07-10
author: zhangsan
change: "增加自动诊断脚本链接"
- version: 1.2
date: 2026-05-15
author: lisi
change: "修正 redis-cli 命令参数"
- version: 1.1
date: 2026-03-01
author: zhangsan
change: "增加大 key 场景的处理步骤"
---
质量评审标准
# Runbook 质量评审检查清单
runbook_quality_checklist:
completeness:
- "是否包含告警含义解释?"
- "是否描述了影响?"
- "诊断步骤是否完整?"
- "处理步骤是否覆盖常见场景?"
- "是否包含验证步骤?"
- "是否有升级路径?"
accuracy:
- "命令和脚本是否经过验证?"
- "链接是否有效?"
- "监控面板 URL 是否正确?"
- "联系人信息是否最新?"
usability:
- "步骤是否有序号?"
- "命令是否可以直接复制执行?"
- "是否有过多的模糊描述(如'检查一下')?"
- "新人能否看懂?"
automation:
- "是否有可执行的脚本?"
- "脚本是否参数化?"
- "是否有自动诊断能力?"
maintenance:
- "是否有负责人?"
- "是否有最后更新时间?"
- "是否定期评审?"
定期评审
# Runbook 评审计划
review_schedule:
frequency: "每季度"
trigger: "定时 + 事件驱动"
event_driven_review:
- "相关服务架构变更后"
- "相关告警规则调整后"
- "Runbook 被使用后(使用者反馈)"
- "故障复盘后(如果发现 Runbook 有缺陷)"
review_process:
1: "负责人检查内容的准确性和完整性"
2: "SRE 团队评审"
3: "验证所有命令和链接"
4: "更新 last_reviewed 日期"
5: "如有变更,通知团队"
# 过期检查
stale_check:
rule: "超过 6 个月未评审的 Runbook 标记为 stale"
action: "标记为需要评审,在告警中提示 'Runbook 可能过期'"
七、Runbook 模板
完整模板
---
runbook_id: RB-[SERVICE]-[NUMBER]
title: "[告警名称/场景名称]"
alert_name: "[对应的 AlertManager alert name]"
severity: "[SEV1/SEV2/SEV3/SEV4]"
owner: "[负责人]"
last_reviewed: [YYYY-MM-DD]
version: [x.y]
---
# [标题]
## 告警含义
[简洁解释告警含义,让不熟悉该服务的工程师也能理解]
**告警条件**:[Prometheus 表达式或触发条件]
**正常值**:[正常范围]
**告警阈值**:[触发阈值]
## 影响
| 维度 | 影响 |
|------|------|
| 直接影响 | [技术层面的影响] |
| 用户影响 | [用户感知到的影响] |
| 业务影响 | [对业务的影响] |
| 不处理风险 | [如果置之不理会怎样] |
## 诊断步骤
### Step 1: [步骤名称]
[操作描述]
```bash
# 可直接执行的命令
[命令]
预期结果:[正常情况下应该看到什么] 异常结果:[异常情况说明什么]
Step 2: [步骤名称]
…
处理步骤
场景 A: [场景描述]
[处理步骤]
场景 B: [场景描述]
[处理步骤]
验证
# 验证命令
[命令]
验证标准:[什么结果表示问题已解决]
升级
| 条件 | 联系人 | 联系方式 |
|---|---|---|
| 以上步骤无效 | [服务负责人] | [IM/电话] |
| 影响用户 | [SRE 负责人] | [IM/电话] |
| 需要紧急支援 | [DBA/专家] | [IM/电话] |
相关资源
- 监控面板:[Grafana 链接]
- 日志查询:[Kibana/Loki 链接]
- 架构文档:[Wiki 链接]
- 自动诊断脚本:[脚本路径或链接]
- 上次故障复盘:[Postmortem 链接]
变更记录
| 版本 | 日期 | 变更内容 | 作者 |
|---|---|---|---|
| 1.0 | YYYY-MM-DD | 初始版本 | [作者] |
## 八、Runbook 实践经验
### Runbook 编写的 "DO" 和 "DON'T"
```yaml
runbook_best_practices:
do:
- "假设读者是第一次 On-Call 的新人"
- "每一步都给出可执行的命令"
- "给出预期结果,让读者知道'正常'长什么样"
- "覆盖最常见的 2-3 种场景,不需要穷举所有可能"
- "保持简洁——On-Call 时没人看长篇大论"
- "附带监控和日志的快速链接"
- "每次使用后根据反馈更新"
dont:
- "不要写'检查一下系统状态'——检查什么?怎么看?"
- "不要假设读者知道上下文"
- "不要写模糊的步骤——'可能需要重启'到底重启不重启?"
- "不要包含过时的信息"
- "不要把 Runbook 写成架构文档"
- "不要依赖特定人员的个人知识"
Runbook 度量
runbook_metrics:
coverage:
name: "Runbook 覆盖率"
formula: "有 Runbook 的告警 / 总告警规则"
target: "> 95%"
usage:
name: "Runbook 使用率"
formula: "On-Call 时打开 Runbook 的次数 / On-Call 响应次数"
target: "> 80%"
effectiveness:
name: "Runbook 有效性"
formula: "按 Runbook 步骤解决问题的比例"
target: "> 70%"
freshness:
name: "Runbook 新鲜度"
formula: "6 个月内评审过的 Runbook / 总 Runbook"
target: "> 90%"
automation:
name: "Runbook 自动化率"
formula: "有自动化脚本的 Runbook / 总 Runbook"
target: "> 50%"
从 Runbook 到知识库
Runbook 是 SRE 知识管理体系的有机部分。随着 Runbook 积累,它自然构成了一个运维知识库:
知识管理体系:
Runbook(操作层)
→ "告警来了怎么做"
↓
Playbook(流程层)
→ "复杂任务怎么做"
↓
Architecture Doc(设计层)
→ "系统是怎么设计的"
↓
Postmortem(学习层)
→ "出了什么问题,学到了什么"
每一层文档互相引用、互相补充,形成完整的知识体系。Runbook 是这个体系中最频繁使用的部分——它是 On-Call 工程师的第一站。
总结
Runbook 是 SRE 体系中最基础但最高频使用的工程实践。核心要点:
- 每条告警都必须关联 Runbook——没有 Runbook 的告警等于"告诉你出问题了但不告诉你怎么办"
- 结构化设计——告警含义 → 影响 → 诊断步骤 → 处理步骤 → 验证 → 升级 → 相关资源
- 假设读者是新人——不依赖个人知识,每一步都给出可执行的命令和预期结果
- 逐步自动化——从文档型到脚本型到半自动到全自动,把人工操作逐步交给机器
- 持续维护——Runbook 是活文档,需要定期评审、随系统更新、使用后修正
- 度量驱动改进——用覆盖率、使用率、有效性等指标衡量 Runbook 体系的质量
记住:最好的 Runbook 是你凌晨三点被叫醒时,打开它照着做就能在 10 分钟内解决问题的那份文档。 如果做不到这一点,它就还不是一个合格的 Runbook。
参考资料与致谢
本文在撰写过程中参考了以下资料,感谢原作者的贡献:
- Google SRE Workbook - Operational Overload — Google SRE 团队,参考了Google SRE Workbook - Operational Overload相关内容
- PagerDuty - Runbook Guide — PagerDuty,参考了PagerDuty - Runbook Guide相关内容