Lewati ke isi

14 · SRE & 事故响应

官方文档:https://sre.google/sre-book/table-of-contents/(Google SRE Book,免费) PagerDuty 指南:https://response.pagerduty.com/ iGaming 重点:零容忍宕机 · 大赛流量预案 · DDoS 应急 · 快速故障定位


1. 事故等级定义

P 级别标准(iGaming 场景)

P0 — 生产全面故障(最高级)
  触发条件:平台完全不可用 / 所有用户无法访问 / 投注系统宕机
  响应时间:5 分钟内响应,15 分钟内更新
  处理人:立即唤醒 On-call + CTO + 客户成功

P1 — 核心功能故障
  触发条件:支付失败 / 实时赔率停止更新 / 登录不可用
  响应时间:15 分钟内响应
  处理人:On-call 工程师 + Team Lead

P2 — 部分功能受损
  触发条件:单个客户受影响 / 特定功能降级 / 性能下降
  响应时间:1 小时内响应
  处理人:当班工程师

P3 — 低影响问题
  触发条件:告警误报 / 日志错误 / 非核心功能异常
  响应时间:下个工作日
  处理人:排期处理

2. On-call 值班体系

轮班设计

推荐模式(Follow the Sun):
  亚太时区(UTC+8): 08:00 - 20:00  → 亚马逊/本地团队
  欧洲时区(UTC+1): 08:00 - 20:00  → 欧洲团队

工具:PagerDuty / OpsGenie / Squadcast
值班日历应提前2周排好,节假日需替班安排

告警路由规则

# PagerDuty 路由示例(伪配置)
routes:
  - match: severity=P0
    action: page_all    # 同时 Call + SMS + App Push
    escalate_after: 5min → page_manager

  - match: severity=P1
    action: page_oncall  # App Push
    escalate_after: 15min → page_backup

  - match: severity=P2
    action: slack_alert  # 发 Slack 消息

3. Runbook 模板

标准 Runbook 格式

# Runbook: [故障类型名称]

**版本**: 1.0 | **最后更新**: 2025-04 | **所有者**: Platform Team

## 症状
- [ ] 告警名称:xxx
- [ ] 用户影响:xxx
- [ ] 常见触发原因:xxx

## 快速诊断(< 5 分钟)
1. 检查 Dashboard 链接:[Grafana 链接]
2. 运行诊断命令:`kubectl get pods -n production`
3. 查看最近部署:`git log --oneline -10`

## 处理步骤

### 情况 A:数据库连接耗尽
1. 检查连接数:`SELECT count(*) FROM pg_stat_activity;`
2. 临时扩容连接池:`kubectl scale deployment pgbouncer --replicas=5`
3. 找到僵尸连接并终止:
   `SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE wait_event_type='Lock';`

### 情况 B:...

## 回滚步骤
1. 回滚最近部署:`kubectl rollout undo deployment/app`
2. 验证回滚成功:`kubectl rollout status deployment/app`

## 升级联系人
- Team Lead: [姓名] [联系方式]
- DBA: [姓名]
- 客户成功: [姓名]

## 事后任务
- [ ] 撰写 Postmortem
- [ ] 更新本 Runbook
- [ ] 创建修复 Ticket

4. 事故响应流程

标准流程

发现告警
    ↓
确认是否真实问题(排除误报,< 3 分钟)
    ↓
声明事故等级(在 Slack #incidents 频道)
    ↓
指定事故指挥官(Incident Commander,通常是 On-call 工程师)
    ↓
快速诊断(查 Dashboard → 查日志 → 查最近变更)
    ↓
执行对应 Runbook
    ↓
持续在 Slack 更新进度(每 10-15 分钟一次)
    ↓
问题解决,发送恢复通知
    ↓
48 小时内完成 Postmortem

快速诊断命令集

# ===== 系统层 =====
# 服务器整体状态
uptime && free -h && df -hT

# 最近 10 分钟的系统日志
journalctl --since "10 minutes ago" -p err

# ===== 应用层 =====
# Kubernetes Pod 状态
kubectl get pods -n production --sort-by='.status.startTime' | tail -20
kubectl describe pod <pod-name> -n production
kubectl logs <pod-name> -n production --tail=100 --previous

# ===== 数据库层 =====
# PG 活跃连接
SELECT pid, state, wait_event_type, wait_event, query, now()-query_start as duration
FROM pg_stat_activity WHERE state != 'idle' ORDER BY duration DESC;

# Redis 状态
redis-cli -h $REDIS_HOST info stats | grep -E "total_commands|connected_clients|blocked_clients"

# ===== 网络层 =====
# Cloudflare 实时分析
open https://dash.cloudflare.com/  # Security → Analytics

# AWS ALB 5xx 错误率
aws cloudwatch get-metric-statistics \
  --namespace AWS/ApplicationELB \
  --metric-name HTTPCode_ELB_5XX_Count \
  --dimensions Name=LoadBalancer,Value=$ALB_ARN \
  --start-time $(date -d '30 minutes ago' -u +%Y-%m-%dT%H:%M:%SZ) \
  --end-time $(date -u +%Y-%m-%dT%H:%M:%SZ) \
  --period 60 --statistics Sum

5. DDoS 应急流程

# ===== 发现攻击 =====
# 特征:流量突增 + 5xx 增加 + Cloudflare Security Analytics 告警

# Step 1: 立即开启 Under Attack Mode
curl -X PATCH "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/settings/security_level" \
  -H "Authorization: Bearer $CF_TOKEN" \
  -d '{"value":"under_attack"}'
echo "✅ Under Attack Mode 已开启"

# Step 2: 分析攻击 IP 来源
# Cloudflare Dashboard → Security → Events → 查看 Top IPs

# Step 3: 封锁攻击来源(如果集中在某个 IP 段)
curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/firewall/rules" \
  -H "Authorization: Bearer $CF_TOKEN" \
  -d '[{"filter":{"expression":"ip.src in {1.2.3.0/24}"},"action":"block","description":"DDoS mitigation"}]'

# Step 4: 确认 Origin 是否暴露(直连 IP 是否泄露)
# 检查是否有直接访问 Origin IP 的流量
grep -v "103.21.244.0/22\|103.22.200.0/22\|103.31.4.0/22" /var/log/nginx/access.log | head -20
# 如果有,立即更换 Origin IP 并用 Cloudflare Tunnel 彻底隐藏

# Step 5: 攻击结束后 30 分钟恢复
curl -X PATCH "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/settings/security_level" \
  -H "Authorization: Bearer $CF_TOKEN" \
  -d '{"value":"medium"}'

6. 大赛流量预案(世界杯/超级碗)

赛前 48 小时 Checklist

# ===== 容量确认 ===== 确认 Auto Scaling 最大实例数(平时的 5-10 倍)
□ 提前预热 ASG(Pre-warm:提前扩容到预期峰值的 50%)
□ 确认 ElastiCache 集群节点数
□ 确认 RDS Read Replica 数量
□ 联系 Cloudflare 客户经理告知大流量预期

# ===== 功能确认 ===== 禁用非核心功能(减少负载)
□ 开启静态资源长期缓存
□ 确认 WebSocket 连接上限配置
□ 确认支付通道容量(联系 PSP 确认)

# ===== 监控确认 ===== Grafana 大屏准备就绪
□ PagerDuty On-call 人员确认
□ 告警阈值适当调高(避免告警风暴)
□ 准备好回滚脚本

# ===== 测试 ===== 运行压测(k6 模拟 5x 正常流量)
□ 测试 Auto Scaling 触发是否正常
□ 确认 DB 连接池在高并发下稳定

压测命令(k6)

// stress-test.js
// 官方文档:https://grafana.com/docs/k6/latest/
import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  stages: [
    { duration: '5m',  target: 1000 },   // 爬升到 1000 用户
    { duration: '10m', target: 5000 },   // 模拟大赛峰值
    { duration: '5m',  target: 1000 },   // 回落
  ],
  thresholds: {
    http_req_duration: ['p(95)<500'],    // 95% 请求 < 500ms
    http_req_failed: ['rate<0.01'],      // 错误率 < 1%
  },
};

export default function () {
  const res = http.get('https://api.example.com/odds/live');
  check(res, {
    'status is 200': (r) => r.status === 200,
    'response time < 200ms': (r) => r.timings.duration < 200,
  });
  sleep(1);
}
k6 run --out json=results.json stress-test.js

7. Postmortem 模板

# Postmortem: [事故标题]
**日期**: 2025-04-01 | **持续时间**: 2小时23分 | **严重程度**: P1
**撰写人**: [姓名] | **审阅人**: [姓名]

## 摘要
[2-3句话描述事故、影响范围、根本原因、已采取措施]

## 影响范围
- 受影响用户数:约 15,000 人
- 收入损失估算:$XX,XXX
- 持续时间:14:35 - 16:58 UTC

## 时间线

| 时间 | 事件 |
|------|------|
| 14:35 | PagerDuty 告警触发 |
| 14:38 | On-call 确认 P1 |
| 14:45 | 定位到 Redis 集群故障转移超时 |
| 15:10 | 临时方案上线(手动切换到备用集群) |
| 16:58 | 完全恢复 |

## 根本原因
Redis Cluster 在节点故障后自动故障转移时间为 45 秒,
超过了应用的连接超时(30 秒),导致大量连接失败。

## 直接原因
AWS ElastiCache 节点硬件故障,触发故障转移。

## 起因因素
1. 连接超时配置过短(30s 应改为 60s)
2. 没有连接重试机制
3. 健康检查间隔过长,未能提前发现节点降级

## 行动项

| 行动 | 负责人 | 截止日期 | 优先级 |
|------|--------|---------|--------|
| 调整 Redis 连接超时到 60s | @张三 | 2025-04-05 | P1 |
| 实现指数退避重试 | @李四 | 2025-04-10 | P1 |
| 添加 Redis 节点健康告警 | @王五 | 2025-04-07 | P2 |

## 经验教训
做得好:
- On-call 响应时间符合 SLA
- 临时方案决策速度快

可以改进:
- 缺少 Redis 故障转移的演练
- 没有标准的 Redis 故障 Runbook

## 无指责声明
本 Postmortem 的目的是改进系统,不追究个人责任。

8. SLO 定义与监控

# SLO 定义示例(iGaming 场景)
slos:
  - name: "投注 API 可用性"
    target: 99.95%        # 每月允许 21.9 分钟宕机
    sli_metric: "成功请求数 / 总请求数"
    measurement_window: 30d

  - name: "赔率更新延迟"
    target: 99%           # 99% 的赔率更新在 100ms 内完成
    sli_metric: "p99 延迟 < 100ms"

  - name: "支付成功率"
    target: 99.9%
    sli_metric: "支付成功数 / 支付请求数"
# Prometheus PromQL 查询 SLO
# 过去1小时可用性
sum(rate(http_requests_total{status!~"5.."}[1h])) 
/ sum(rate(http_requests_total[1h])) * 100

# Error Budget 消耗率
1 - (current_availability / target_availability)

官方文档 & 学习资源

资源 链接
Google SRE Book(免费) https://sre.google/sre-book/table-of-contents/
Google SRE Workbook(免费) https://sre.google/workbook/table-of-contents/
PagerDuty 事故响应指南 https://response.pagerduty.com/
Postmortem 案例库 https://github.com/danluu/post-mortems
k6 压测文档 https://grafana.com/docs/k6/latest/
Atlassian 事故管理 https://www.atlassian.com/incident-management

最后更新:2025-04