概述

凌晨三点被告警叫醒,面对一个不熟悉的服务,你能多快恢复?如果你需要翻聊天记录、问同事、翻代码才能弄清楚怎么处理,那说明你的团队缺一样东西——Runbook。

Runbook 是 SRE 体系中最基础但也最容易被忽视的工程实践。它是连接"告警"和"行动"的桥梁——告警告诉你"出问题了",Runbook 告诉你"该怎么办"。一个好的 Runbook 能让任何有基础技术能力的工程师在 On-Call 时有效响应告警,而不依赖特定人员的"脑内知识"。

从 Runbook 的作用、结构设计、自动化、版本管理、质量评审到实战模板,详细梳理如何编写和维护高质量的 Runbook。

关于 Runbook 的好的实践,可参考 Google SRE Workbook - Operational OverloadPagerDuty - 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 实践中有不同的定位:

维度RunbookPlaybook
范围单一告警/问题的处理步骤多步骤的复杂工作流程
触发告警触发计划性任务/人工触发
粒度细粒度的操作步骤粗粒度的流程编排
示例“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
  1. 验证所有扩展在新版本可用
  2. 通知相关团队维护窗口

阶段 3: 切换(T 日)

  1. [Runbook] 停止写入流量
  2. [Runbook] 等待复制延迟归零
  3. [Runbook] 验证数据一致性
  4. [Runbook] 切换应用连接到新实例
  5. [Runbook] 验证服务正常

阶段 4: 验证(T+1 天)

  1. 监控新实例性能指标
  2. 验证所有功能正常
  3. 保留旧实例 7 天作为回滚备份

回滚

如果切换后出现问题:

  1. [Runbook] 切换应用连接回旧实例
  2. [Runbook] 验证服务恢复
  3. 排查问题,重新安排升级

相关 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.0YYYY-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 体系中最基础但最高频使用的工程实践。核心要点:

  1. 每条告警都必须关联 Runbook——没有 Runbook 的告警等于"告诉你出问题了但不告诉你怎么办"
  2. 结构化设计——告警含义 → 影响 → 诊断步骤 → 处理步骤 → 验证 → 升级 → 相关资源
  3. 假设读者是新人——不依赖个人知识,每一步都给出可执行的命令和预期结果
  4. 逐步自动化——从文档型到脚本型到半自动到全自动,把人工操作逐步交给机器
  5. 持续维护——Runbook 是活文档,需要定期评审、随系统更新、使用后修正
  6. 度量驱动改进——用覆盖率、使用率、有效性等指标衡量 Runbook 体系的质量

记住:最好的 Runbook 是你凌晨三点被叫醒时,打开它照着做就能在 10 分钟内解决问题的那份文档。 如果做不到这一点,它就还不是一个合格的 Runbook。

参考资料与致谢

本文在撰写过程中参考了以下资料,感谢原作者的贡献:

  1. Google SRE Workbook - Operational Overload — Google SRE 团队,参考了Google SRE Workbook - Operational Overload相关内容
  2. PagerDuty - Runbook Guide — PagerDuty,参考了PagerDuty - Runbook Guide相关内容