Vitest Testing
提供 Vitest 单元测试与集成测试的模式与最佳实践,涵盖断言、异步测试与模拟方法。
提供完整的可观测性与可靠性工程框架,涵盖日志、指标、链路追踪等核心能力。
openclaw skills install @1kalin/afrexai-observability-engine命令、参数、文件名以原文为准
构建可观测、高可靠服务的完整体系 —— 从结构化日志到事件响应,再到以 SLO 为导向的开发。
评估您当前的可观测性水平:
| 信号 | 健康 (2) | 薄弱 (1) | 缺失 (0) |
|---|---|---|---|
| 结构化日志 | 包含 trace_id 关联的 JSON 日志 | 日志存在但非结构化 | console.log / print 语句 |
| 指标采集 | RED/USE 指标并配有仪表盘 | 有部分指标,无仪表盘 | 无指标 |
| 分布式追踪 | 完整请求链路且有采样 | 部分追踪,仅关键服务 | 无追踪 |
| 告警机制 | 基于 SLO 的告警 + 运维手册 | 阈值告警,部分运维手册 | 无告警或全是噪音 |
| 事件响应 | 有明确流程、角色分工及事后复盘 | 临时响应,部分文档 | “谁发现谁修” |
| SLO 定义 | 有 SLO 并每周跟踪错误预算 | 非正式可用性目标 | 无可靠性目标 |
| 应急轮班 | 有结构化的轮班与升级机制 | 非正式“叫人”方式 | 无应急轮班 |
| 成本管理 | 观测性预算每月跟踪 | 对成本有一定意识 | 不清楚实际支出 |
12-16: 生产就绪。重点优化。
8-11: 基础已建立。系统性填补短板。
4-7: 风险显著。优先完善告警与事件响应。
0-3: 盲目运行。立即启动第一阶段。
应用 → 结构化 JSON → 日志路由器 → 存储 → 查询引擎
↓
告警处理管道| 字段 | 类型 | 用途 | 示例 |
|---|---|---|---|
timestamp | ISO-8601 UTC | 时间戳 | 2026-02-22T18:30:00.123Z |
level | 枚举 | 严重程度 | info, warn, error, fatal |
service | 字符串 | 所属服务 | payment-api |
version | 字符串 | 当前部署版本 | v2.3.1 |
environment | 字符串 | 环境标识 | production |
message | 字符串 | 发生了什么 | 支付处理成功 |
trace_id | 字符串 | 请求关联标识 | abc123def456 |
span_id | 字符串 | 跟踪中的操作标识 | span_789 |
duration_ms | 数字 | 执行耗时(毫秒) | 142 |
# HTTP 请求上下文
http:
method: POST
path: /api/v1/orders
status: 201
client_ip: 203.0.113.42 # 如需保护隐私可匿名化
user_agent: "Mozilla/5.0..."
request_id: "req_abc123"
# 业务上下文
business:
user_id: "usr_456"
tenant_id: "tenant_789"
order_id: "ord_012"
action: "checkout"
amount_cents: 4999
currency: "USD"
# 错误上下文
error:
type: "PaymentDeclinedError"
message: "卡被拒:余额不足"
code: "CARD_DECLINED"
stack: "..." # 仅在非生产环境或 DEBUG 级别启用
retry_count: 2
retryable: true是否即将崩溃?
→ FATAL(记录后退出)
是否有操作失败需要人工介入?
→ ERROR(通知相关人员或创建工单)
是否有意外情况但已恢复?
→ WARN(每日排查中关注)
是否为正常业务事件值得记录?
→ INFO(审计轨迹、业务指标)
是否对调试有用但在生产中过于嘈杂?
→ DEBUG(生产关闭,预发开启)
是否仅在逐步调试代码时有用?
→ TRACE(永远不在生产环境使用)scrub_patterns:
# 始终脱敏
- field_patterns: ["password", "secret", "token", "api_key", "authorization"]
action: replace_with_redacted
# 哈希处理,用于关联而不暴露
- field_patterns: ["email", "phone", "ssn", "national_id"]
action: sha256_hash
# 部分掩码
- field_patterns: ["credit_card", "card_number"]
action: mask_last_4 # "****-****-****-1234"
# IP 地址匿名化
- field_patterns: ["client_ip", "ip_address"]
action: zero_last_octet # 203.0.113.0Node.js (Pino):
import pino from 'pino';
import { AsyncLocalStorage } from 'node:async_hooks';
const als = new AsyncLocalStorage<Record<string, string>>();
const logger = pino({
level: process.env.LOG_LEVEL || 'info',
formatters: {
level: (label) => ({ level: label }),
},
mixin: () => als.getStore() ?? {},
redact: ['req.headers.authorization', '*.password', '*.token'],
timestamp: pino.stdTimeFunctions.isoTime,
});
// 中间件:注入上下文
app.use((req, res, next) => {
const ctx = {
trace_id: req.headers['x-trace-id'] || crypto.randomUUID(),
request_id: crypto.randomUUID(),
service: 'payment-api',
version: process.env.APP_VERSION,
};
als.run(ctx, () => next());
});Python (structlog):
import structlog
structlog.configure(
processors=[
structlog.contextvars.merge_contextvars,
structlog.processors.add_log_level,
structlog.processors.TimeStamper(fmt="iso", utc=True),
structlog.processors.JSONRenderer(),
],
)
log = structlog.get_logger()
# 绑定每请求上下文:
structlog.contextvars.bind_contextvars(trace_id=trace_id, user_id=user_id)Go (zerolog):
log := zerolog.New(os.Stdout).With().
Timestamp().
Str("service", "payment-api").
Str("version", version).
Logger()
// 每个请求:
reqLog := log.With().Str("trace_id", traceID).Logger()| 体积 | 解决方案 | 保留策略 | 成本 |
|---|---|---|---|
| <10 GB/天 | Loki + Grafana | 30天热存储,90天冷存储 | 低 |
| 10-100 GB/天 | Elasticsearch / OpenSearch | 14天热存储,90天 S3 冷存储 | 中等 |
| 100+ GB/天 | ClickHouse 或 Datadog | 7天热存储,30天归档 | 高 |
| 预算受限 | Loki + S3 后端 | 90天全冷存储 | 极低 |
| # | 反模式 | 修复建议 |
|---|---|---|
| 1 | log.error(err) 无上下文信息 | 始终包含:操作内容、输入数据、系统状态 |
| 2 | 记录请求/响应体 | 仅在 DEBUG 级别记录;敏感字段需脱敏 |
| 3 | 日志消息使用字符串拼接 | 使用结构化字段:log.info("processed", { order_id, amount }) |
| 4 | 捕获异常后记录并重新抛出 | 在处理层记录日志,而非每一层都记录 |
| 5 | 不同服务使用不同日志格式 | 所有服务统一日志 schema |
| 6 | 无日志轮转或保留策略 | 设置最大大小 + TTL;归档至冷存储 |
| 7 | 在热点路径中频繁日志输出 | 聚合日志:每 N 条记录或每隔一段时间汇总一次 |
| 8 | 缺失关联 ID(Correlation ID) | 从入口点传播 trace_id 到所有服务 |
| 9 | 使用布尔型日志级别(如 verbose: true) | 使用标准日志级别,并支持最小级别配置 |
| 10 | 明文记录 PII 信息 | 在日志器层级实现数据清洗 |
针对每个服务接口,应监控以下指标:
| 指标 | 含义 | Prometheus 示例 |
|---|---|---|
| Rate | 每秒请求量 | http_requests_total{method, path, status} |
| Errors | 每秒失败请求数 | http_requests_total{status=~"5.."} / 总数 |
| Duration | 延迟分布 | http_request_duration_seconds{method, path}(直方图) |
针对每个资源(CPU、内存、磁盘、网络):
| 指标 | 含义 | 示例 |
|---|---|---|
| Utilization | 资源使用率(百分比) | CPU 使用率 78% |
| Saturation | 队列深度 / 背压情况 | 当前排队 12 个请求 |
| Errors | 资源错误数量 | 3 次磁盘 I/O 错误 |
| 信号 | 含义 | 数据来源 |
|---|---|---|
| 延迟 | 请求响应时间 | RED Duration |
| 流量 | 系统负载需求 | RED Rate |
| 错误 | 失败请求数率 | RED Errors |
| 饱和度 | 服务“满载”程度 | USE Saturation |
| 类型 | 使用场景 | 示例 |
|---|---|---|
| Counter | 只增不减的数值 | 总请求数、错误数、发送字节数 |
| Gauge | 上下波动的当前值 | 活跃连接数、队列深度、温度 |
| Histogram | 数值分布统计 | 请求延迟、响应大小 |
| Summary | 预计算的分位数 | 客户端延迟(需要精确分位数时) |
规则: 多数情况下优先使用直方图而非 Summary —— 因为直方图可在实例间聚合。
# 格式: <命名空间>_<子系统>_<名称>_<单位>
http_server_request_duration_seconds
http_server_requests_total
db_pool_connections_active
queue_messages_pending
cache_hit_ratio
# 规则:
# 1. 使用 snake_case
# 2. 包含单位后缀(_seconds, _bytes, _total)
# 3. 计数器使用 _total 后缀
# 4. 不在指标名中包含标签名称
# 5. 使用基础单位(秒而非毫秒,字节而非千字节)| 规则 | 原因 | 示例 |
|---|---|---|
| 单个标签基数 <100 | 高基数会严重影响性能 | status="200" 而非 status="200 OK" |
| 标签中禁止使用用户 ID | 会导致基数无限增长 | 使用日志关联替代 |
| 请求路径中禁止包含 ID | /api/users/123 会产生数百万个时间序列 | 正常化为 /api/users/:id |
| 每个指标最多 5-7 个标签 | 每个组合即为一个时间序列 | {method, path, status, service} |
application_metrics:
# HTTP 层
- http_request_duration_seconds: histogram {method, path, status}
- http_request_size_bytes: histogram {method, path}
- http_response_size_bytes: histogram {method, path}
- http_requests_in_flight: gauge
# 业务逻辑
- orders_processed_total: counter {status, payment_method}
- order_value_dollars: histogram {payment_method}
- user_signups_total: counter {source}
# 依赖项
- db_query_duration_seconds: histogram {query_type, table}
- db_connections_active: gauge {pool}
- db_connections_idle: gauge {pool}
- cache_requests_total: counter {result: hit|miss}
- external_api_duration_seconds: histogram {service, endpoint}
- external_api_errors_total: counter {service, error_type}
# 队列 / 异步处理
- queue_messages_published_total: counter {queue}
- queue_messages_consumed_total: counter {queue, status}
- queue_processing_duration_seconds: histogram {queue}
- queue_depth: gauge {queue}
- queue_consumer_lag: gauge {queue, consumer_group}
infrastructure_metrics:
# Node Exporter / cAdvisor 自动提供
- cpu_usage_percent: gauge {instance}
- memory_usage_bytes: gauge {instance}
- disk_usage_bytes: gauge {instance, mount}
- disk_io_seconds: counter {instance, device}
- network_bytes: counter {instance, direction}
- container_cpu_usage: gauge {pod, container}
- container_memory_usage: gauge {pod, container}| 组件 | 可选方案 | 推荐方案 |
|---|---|---|
| 采集 | Prometheus、OTEL Collector、Datadog Agent | Prometheus(免费)或 OTEL Collector(厂商中立) |
| 存储 | Prometheus、Thanos、Mimir、VictoriaMetrics | VictoriaMetrics(性价比最优)或 Mimir(Grafana 生态) |
| 可视化 | Grafana、Datadog、New Relic | Grafana(免费、可扩展) |
| 告警 | Alertmanager、Grafana Alerting、PagerDuty | Alertmanager + PagerDuty 路由 |
客户端请求
→ API 网关(根跨度)
→ 认证服务(子跨度)
→ 订单服务(子跨度)
→ 数据库查询(子跨度)
→ 支付服务(子跨度)
→ Stripe API(子跨度)
→ 通知服务(子跨度)
→ 邮件服务商(子跨度)自动注入(Node.js):
// tracing.ts — 在其他任何导入之前引入
import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
const sdk = new NodeSDK({
traceExporter: new OTLPTraceExporter({
url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT || 'http://localhost:4318/v1/traces',
}),
instrumentations: [getNodeAutoInstrumentations({
'@opentelemetry/instrumentation-http': { ignoreIncomingPaths: ['/health', '/ready'] },
'@opentelemetry/instrumentation-express': { enabled: true },
})],
serviceName: process.env.OTEL_SERVICE_NAME || 'payment-api',
});
sdk.start();自定义跨度用于业务逻辑:
import { trace, SpanStatusCode } from '@opentelemetry/api';
const tracer = trace.getTracer('payment-service');
async function processPayment(order: Order) {
return tracer.startActiveSpan('process-payment', async (span) => {
span.setAttributes({
'order.id': order.id,
'order.amount_cents': order.amountCents,
'payment.method': order.paymentMethod,
});
try {
const result = await chargeCard(order);
span.setAttributes({ 'payment.status': result.status });
return result;
} catch (err) {
span.setStatus({ code: SpanStatusCode.ERROR, message: err.message });
span.recordException(err);
throw err;
} finally {
span.end();
}
});
}| 策略 | 适用场景 | 配置 |
|---|---|---|
| 始终开启 | 开发/预发布环境,低流量(<100 rps) | ratio: 1.0 |
| 概率采样 | 中等流量(100-1000 rps) | ratio: 0.1(10%) |
| 速率限制 | 高流量(>1000 rps) | max_traces_per_second: 100 |
| 尾部采样 | 希望捕获所有错误和慢请求 | 由收集器端决定:若存在错误或持续时间超过 p99 则保留 |
| 父级基于 | 尊重上游的采样决策 | 若父跨度被采样,则子跨度也采样 |
建议: 初始采用父级基于 + 概率采样(10%)。在收集器端添加尾部采样以确保捕获所有错误。
| 头部 | 标准 | 格式 |
|---|---|---|
traceparent | W3C Trace Context | 00-{trace_id}-{span_id}-{flags} |
tracestate | W3C Trace Context | 供应商特定的键值对 |
b3 | Zipkin B3 | {trace_id}-{span_id}-{sampled} |
规则: 优先使用 W3C Trace Context(traceparent)。为兼容旧版 Zipkin 系统,支持 B3 格式。
| 数据量 | 解决方案 | 保留周期 |
|---|---|---|
| <50 GB/天 | Jaeger + Elasticsearch | 7 天 |
| 50-500 GB/天 | Tempo + S3 | 14 天 |
| 500+ GB/天 | Tempo + S3 并配合激进采样 | 7 天 |
| 预算受限 | Jaeger + Badger(本地磁盘) | 3 天 |
| 服务类型 | 主要 SLI | 次要 SLI | 测量方式 |
|---|---|---|---|
| API / Web | 可用性 + 延迟 | 错误率 | 服务端 + 合成监控 |
| 数据管道 | 新鲜度 + 正确性 | 吞吐量 | 管道时间戳 + 校验和 |
| 存储 | 持久性 + 可用性 | 延迟 | 校验和 + 运行时监控 |
| 流式处理 | 吞吐量 + 延迟 | 消息丢失率 | 消费者延迟 + 端到端延迟 |
| 批处理任务 | 成功率 + 新鲜度 | 执行时长 | 作业调度器指标 |
slo:
name: "支付 API 可用性"
service: payment-api
owner: payments-team
sli:
type: availability
definition: "非 5xx 响应的比例"
measurement: |
sum(rate(http_requests_total{service="payment-api",status!~"5.."}[5m]))
/
sum(rate(http_requests_total{service="payment-api"}[5m]))
target: 99.95% # 每月最多 21.9 分钟停机
window: rolling_30d
error_budget:
total_minutes: 21.9 # 每 30 天
burn_rate_alerts:
- severity: critical
burn_rate: 14.4x # 预算在 2 小时内耗尽
short_window: 5m
long_window: 1h
- severity: warning
burn_rate: 6x # 预算在 5 天内耗尽
short_window: 30m
long_window: 6h
- severity: ticket
burn_rate: 1x # 预算在 30 天内耗尽
short_window: 6h
long_window: 3d
consequences:
budget_remaining_above_50pct: "正常开发速度"
budget_remaining_20_to_50pct: "优先处理可靠性工作"
budget_remaining_below_20pct: "暂停新功能;仅聚焦可靠性"
budget_exhausted: "全员投入可靠性,直至预算恢复"| 服务层级 | 可用性 | p50 延迟 | p99 延迟 | 每月停机时间 |
|---|---|---|---|---|
| Tier 0(支付、认证) | 99.99% | <100ms | <500ms | 4.3 分钟 |
| Tier 1(核心 API) | 99.95% | <200ms | <1s | 21.9 分钟 |
| Tier 2(非关键) | 99.9% | <500ms | <2s | 43.8 分钟 |
| Tier 3(内部工具) | 99.5% | <1s | <5s | 3.6 小时 |
| 批处理 / 管道 | 99%(成功率) | N/A | N/A | N/A |
# 每周错误预算审查模板
error_budget_review:
week: "2026-W08"
service: payment-api
slo_target: 99.95%
budget:
total_minutes_this_period: 21.9
consumed_minutes: 8.2
remaining_minutes: 13.7
remaining_percent: 62.6%
incidents_consuming_budget:
- date: "2026-02-18"
duration_minutes: 5.1
cause: "数据库连接池耗尽"
preventable: true
action: "增加连接池大小 + 添加饱和度告警"
- date: "2026-02-20"
duration_minutes: 3.1
cause: "上游支付服务商超时"
preventable: false
action: "添加熔断机制并启用降级方案"
velocity_decision: "正常 — 剩余预算 62.6%"
reliability_work_this_week:
- "添加连接池饱和度告警"
- "为支付服务商实现熔断机制"
---
## 阶段 5:告警设计
### 告警质量原则
1. **每个告警必须可操作** — 如果无人需要响应,就不是告警
2. **每个告警需有运行手册(Runbook)** — 在告警注解中直接链接
3. **基于症状而非原因** — 告警应关注“用户无法下单”,而非“CPU 升高”
4. **多窗口燃烧速率** — 不使用静态阈值(参见 SLO 告警)
5. **告警异常状态,而非正常状态** — “15 分钟内无订单” 可捕捉静默故障
### 告警严重等级
| 严重等级 | 响应时间 | 通知渠道 | 责任人 | 示例 |
|----------|----------|----------|--------|------|
| **P0 — 关键** | <5 分钟 | 页面通知(PagerDuty/Opsgenie) | 当班工程师 | 支付系统宕机 |
| **P1 — 高** | <30 分钟 | 工作时间页面通知,全天候 Slack | 当班工程师 | 错误率 >5% 持续 10 分钟 |
| **P2 — 中** | <4 小时 | Slack 频道 | 团队 | p99 延迟升高 2 倍 |
| **P3 — 低** | 下一个工作日 | 自动创建工单 | 团队待办事项 | 磁盘使用率 >80% |
| **信息** | N/A | 仅仪表板显示 | 无人 | 部署完成 |
### 告警设计反模式
| 反模式 | 问题 | 解决方案 |
|--------|------|----------|
| 静态 CPU/内存阈值 | 产生大量噪音,与用户体验无关 | 使用基于 SLO 的燃烧速率告警 |
| 每实例独立告警 | 50 个实例 = 50 条相同告警 | 聚合:在服务级别监控错误率 |
| 无去重机制 | 同一告警触发 100 次 | 按服务 + 告警名称分组;设置重复间隔 |
| 缺少运行手册 | 工程师被唤醒却不知如何处理 | 每条告警必须链接运行手册 |
| 阈值过于敏感 | 对短暂波动即触发 | 使用 `for: 5m` 要求持续条件满足 |
| P0 告警过多 | 告警疲劳 → 忽略真实事件 | 每月审计;对噪音告警降级或移除 |
### 告警模板(Prometheus Alertmanager)groups:
- name: payment-api-slo
rules:
- alert: PaymentAPIHighErrorRate
expr: |
(
sum(rate(http_requests_total{service="payment-api",status=~"5.."}[5m]))
/
sum(rate(http_requests_total{service="payment-api"}[5m]))
) > 0.01
for: 5m
labels:
severity: critical
service: payment-api
team: payments
annotations:
summary: "支付 API 错误率 {{ $value | humanizePercentage }} (>1%)"
description: "5xx 错误率已超过 1%,持续 5 分钟"
runbook: "https://wiki.internal/runbooks/payment-api-errors"
dashboard: "https://grafana.internal/d/payment-api"
- alert: PaymentAPINoTraffic
expr: |
sum(rate(http_requests_total{service="payment-api"}[15m])) == 0
for: 5m
labels:
severity: critical
service: payment-api
annotations:
summary: "支付 API 连续 5 分钟无流量"
runbook: "https://wiki.internal/runbooks/payment-api-no-traffic"
- alert: PaymentAPILatencyHigh
expr: |
histogram_quantile(0.99,
sum(rate(http_request_duration_seconds_bucket{service="payment-api"}[5m])) by (le)
) > 2
for: 10m
labels:
severity: warning
annotations:
summary: "支付 API p99 延迟 {{ $value }}s (>2s 持续 10 分钟)"
runbook: "https://wiki.internal/runbooks/payment-api-latency"
### 运行手册模板支付 API 在 5 分钟窗口内返回的 5xx 错误率超过 1%。
用户可能无法完成支付流程。
kubectl rollout history deployment/payment-api- 数据库:[仪表板链接]
- Stripe API:[状态页]
- Redis 缓存:[仪表板链接]
kubectl logs -l app=payment-api --since=10m | jq 'select(.level=="error")'| 原因 | 诊断方法 | 解决方案 |
|---|---|---|
| 部署异常 | 错误出现时间与部署时间一致 | kubectl rollout undo deployment/payment-api |
| 数据库连接耗尽 | db_connections_active 达到上限 | 滚动重启 Pod + 增加连接池大小 |
| Stripe 服务中断 | Stripe 状态页显示红色 | 启用备用支付处理器 |
| 内存泄漏 | 内存持续上升,出现 OOMKilled 事件 | 滚动重启 + 进行排查 |
---
## 阶段 6:仪表板架构
### 仪表板层级结构
L1:管理层/业务仪表板(非技术干系人)
↓
L2:服务概览仪表板(值班人员、快速排查)
↓
L3:服务深度分析仪表板(定位特定服务问题)
↓
L4:基础设施仪表板(资源级别详情)
### L1:业务仪表板panels:
- title: "每分钟收入"
type: stat
query: "sum(rate(orders_total{status='completed'}[5m])) * avg(order_value_dollars)"
- title: "活跃用户数(5分钟)"
type: stat
query: "count(count by (user_id) (http_requests_total{...}[5m]))"
- title: "结账成功率"
type: gauge
query: "sum(rate(checkout_total{status='success'}[1h])) / sum(rate(checkout_total[1h]))"
thresholds: [95, 98, 99.5]
- title: "错误预算剩余"
type: gauge
query: "1 - (error_budget_consumed / error_budget_total)"
### L2:服务概览仪表板
每个服务都配备一个结构一致的仪表板:row_1_traffic:
- "请求速率(rps)" — 时间序列,按状态码分组
- "错误率(%)" — 时间序列,设置 SLO 阈值线
- "当前活跃请求数" — 指标仪表盘
row_2_latency:
- "延迟分布" — 热力图
- "p50 / p95 / p99" — 时间序列,设置阈值线
- "各端点延迟" — 表格,按 p99 排序
row_3_dependencies:
- "下游延迟" — 按依赖项的时间序列
- "下游错误率" — 按依赖项的时间序列
- "数据库查询耗时" — 按查询类型的时间序列
row_4_resources:
- "CPU 使用率" — 按 Pod 的时间序列
- "内存使用率" — 按 Pod 的时间序列
- "Pod 重启次数" — 统计指标
row_5_business:
- "业务指标 1" — 服务特有
- "业务指标 2" — 服务特有
### 仪表板规则
1. **默认时间范围:最近 1 小时** — 大多数排查发生在近期
2. **顶部设置变量选择器**:环境、服务、实例
3. **统一颜色编码**:绿色=正常,黄色=降级,红色=异常,所有仪表板保持一致
4. **告警链接至仪表板** — 每个告警注释中包含仪表板 URL
5. **单个仪表板不超过 15 个面板** — 若超出则拆分为 L3 仪表板
6. **包含“截至”时间戳** — 确保事故截图可追溯
7. **仪表板即代码** — 将 Grafana JSON 存储在 Git 中,通过 API 自动化部署
---
## 阶段 7:事故响应
### 事故严重性分级
| 严重性 | 判定标准 | 响应要求 | 通知策略 |
|--------|----------|----------|----------|
| **SEV-1** | 服务不可用,数据丢失风险,安全事件 | 全员响应,设立应急指挥室 | 状态页每 15 分钟更新一次 |
| **SEV-2** | 服务降级,SLO 受到威胁,部分中断 | 值班人员 + 备岗支持 | 状态页每 30 分钟更新一次 |
| **SEV-3** | 轻微降级,存在临时解决方案 | 值班人员在工作时间内响应 | 内部 Slack 更新 |
| **SEV-4** | 外观问题,影响极小 | 下个迭代处理 | 无需通知 |
### 事故角色职责
| 角色 | 职责 | 人选 |
|------|------|------|
| **事故指挥官(IC)** | 主导事故处理。协调各方。做出关键决策。 | 值班负责人 |
| **技术负责人** | 诊断并修复问题。向 IC 汇报技术进展。 | 高级工程师 |
| **沟通负责人** | 更新状态页、Slack、外部干系人。 | 产品/支持团队 |
| **记录员** | 实时记录时间线、行动和决策。 | 任意可用成员 |
### 事故响应流程- 告警触发 → 值班人员被通知
- 客户报告 → 支持团队升级
- 内部发现 → 工程师主动上报
- 确认问题真实存在(排除误报)
- 判定严重等级(SEV-1 至 SEV-4)
- 开启事故频道:#inc-YYYY-MM-DD-简短描述
- 分配角色(IC、技术负责人、沟通负责人)
- 目标:立即止血,而非立即定位根因
- 可选操作(按顺序尝试):
a. 回滚最近一次发布
b. 扩容或重启 Pod
c. 关闭功能开关
d. 流量重定向或启用备用方案
e. 手动数据修复
- 记录每一步操作及时间戳
- 确认缓解措施生效(指标恢复正常)
- 监控 15–30 分钟,确认无复发
- 更新状态页:“正在监控修复”
- 确认所有指标持续健康超过 30 分钟
- 更新状态页:“已解决”
- 安排事后复盘(SEV-1/2 类事故需在 48 小时内完成)
- 向干系人发送内部总结报告
### 事故频道模板📋 事故:支付 API 5xx 错误
🔴 严重性:SEV-2
🕐 开始时间:2026-02-22 14:23 UTC
👤 指挥官:@alice
🔧 技术负责人:@bob
📢 沟通负责人:@charlie
状态:正在处置
影响范围:约 5% 的结账请求失败
是否影响客户:是
时间线:
14:23 — 告警触发:PaymentAPIHighErrorRate
14:25 — 指挥官 @alice 确认,通过仪表板验证问题真实
14:28 — 技术负责人发现错误日志显示部署后连接池耗尽
14:31 — 回滚版本 v2.3.1 → v2.3.0
14:35 — 错误率下降,开始监控
14:50 — 错误率 <0.1%,标记为已解决
---
## 阶段 8:事后复盘框架
### 无责复盘模板
yaml
post_mortem:
title: "支付 API 连接池耗尽"
date: "2026-02-22"
severity: SEV-2
duration: 27 分钟(14:23 — 14:50 UTC)
authors: ["@alice", "@bob"]
reviewers: ["@engineering-leads"]
status: action_items_in_progress
summary: |
14:15 的部署引入了支付 API 中的连接泄漏问题。
连接池于 14:23 耗尽,导致约 5% 的结账请求出现 5xx 错误。
14:31 开始回滚;14:50 恢复正常。
impact:
user_impact: "~340 名用户在 27 分钟内遭遇结账失败"
revenue_impact: "$2,100 估算(基于平均订单金额 × 失败的结账次数)"
slo_impact: "消耗了每月 21.9 分钟错误预算中的 5.1 分钟(占 23%)"
data_impact: "无数据丢失。12 笔订单失败;用户可重试并成功完成"
timeline:
- time: "14:15"
event: "部署 v2.3.1(3/3 个 Pod 已更新)"
- time: "14:23"
event: "PaymentAPIHighErrorRate 告警触发"
- time: "14:25"
event: "应急负责人(IC)指派,通过仪表盘确认"
- time: "14:28"
event: "根因定位:新 ORM 查询未释放连接"
- time: "14:31"
event: "开始回滚:v2.3.1 → v2.3.0"
- time: "14:35"
event: "错误率下降"
- time: "14:50"
event: "已解决:错误率持续低于 0.1%"
root_cause: |
v2.3.1 部署引入了订单验证路径中的新数据库查询。
该查询使用了原始连接而非池管理的客户端,导致连接被获取但未释放。
在负载下,连接池在 8 分钟内即耗尽。
contributing_factors:
- "缺乏对连接池行为在负载下的集成测试"
- "连接池饱和指标存在,但未配置告警"
- "代码审查未发现原始连接的使用"
what_went_well:
- "部署后 8 分钟内触发告警"
- "应急负责人 2 分钟内指派"
- "根因在 3 分钟内识别(日志清晰)"
- "回滚操作顺利执行"
what_went_wrong:
- "部署后存在 8 分钟的检测延迟"
- "未采用灰度发布,在全量上线前未能发现问题"
- "连接池饱和状态无告警"
action_items:
- action: "添加连接池饱和告警(>80% 持续 2 分钟)"
owner: "@bob"
priority: P1
due: "2026-02-25"
status: in_progress
ticket: "ENG-1234"
- action: "为 payment-api 启用灰度发布"
owner: "@alice"
priority: P1
due: "2026-03-01"
ticket: "ENG-1235"
- action: "添加 linting 规则:禁止应用代码中使用原始数据库连接"
owner: "@charlie"
priority: P2
due: "2026-03-07"
ticket: "ENG-1236"
- action: "在预发环境对 payment-api 连接池进行负载测试"
owner: "@bob"
priority: P2
due: "2026-03-07"
ticket: "ENG-1237"
lessons_learned:
- "资源饱和指标需要告警,而不仅仅是仪表盘展示"
- "对于 Tier 0 服务,灰度发布是强制要求"
- "ORM 抽象不能保证连接安全——需审查原始查询"
---
### 事后分析会议议程(60 分钟)---
### 五个为什么分析问题:支付 API 出现 5xx 错误
为什么 1:数据库连接被耗尽
为什么 2:新查询获取了连接但未释放
为什么 3:查询使用了原始连接而非连接池管理器
为什么 4:ORM 的原始查询 API 不会自动释放(设计如此)
为什么 5:我们没有针对原始连接使用的 linting 规则或代码审查清单
根因:应用代码中缺少对原始连接使用的防护机制
系统性修复:增加 linting 规则 + 连接池饱和告警机制
---
## 第九阶段:值班运营
### 值班结构on_call:
rotation: 每周轮换
handoff_day: 周一 10:00 UTC
primary:
response_time: 5 分钟(SEV-1/2),30 分钟(SEV-3)
escalation_after: 15 分钟未确认响应
secondary:
response_time: 15 分钟(SEV-1),1 小时(SEV-2/3)
escalation_after: 30 分钟未确认响应
manager_escalation:
trigger: SEV-1 事件在 30 分钟内未解决
handoff_checklist:
- 查看当前待处理事件和活跃告警
- 检查所有服务的错误预算状态
- 阅读上周的事故复盘报告
- 确认 PagerDuty 排班和联系信息准确
- 测试告警路由(发送测试通知)
### 值班健康指标
| 指标 | 健康 | 需关注 | 不健康 |
|------|------|--------|--------|
| 每周告警次数 | <5 | 5-15 | >15 |
| 每周非工作时间告警次数 | <2 | 2-5 | >5 |
| 误报率 | <10% | 10-30% | >30% |
| 平均响应时间 | <5 分钟 | 5-15 分钟 | >15 分钟 |
| 平均解决时间 | <30 分钟 | 30-120 分钟 | >120 分钟 |
| 事务性工作比例(手动 vs 自动化) | <30% | 30-60% | >60% |
### 每周值班复盘模板on_call_review:
week: "2026-W08"
engineer: "@bob"
incidents:
total: 7
sev_1: 0
sev_2: 1
sev_3: 4
false_positives: 2
after_hours: 3
time_spent:
incident_response: "4.5 小时"
toil_automation: "2 小时"
runbook_updates: "1 小时"
improvements_made:
- "静音开发环境磁盘告警"
- "为 Pod 重启阈值添加自动修复功能"
improvements_needed:
- "缓存过期告警每周二凌晨 03:00 触发 —— 需调查原因"
- "支付重试逻辑需引入熔断机制(导致 3 次告警)"
handoff_notes: |
关注 payment-api 的 p99 延迟 —— 自周三起持续上升。
Stripe 更改了沙箱端点;预发环境可能出现错误。
chaos_experiment:
name: "支付数据库故障转移"
hypothesis: "如果主数据库不可用,流量应在 30 秒内切换到副本,且错误率上升低于 1%"
steady_state:
- metric: "checkout_success_rate"
expected: ">99.5%"
- metric: "db_query_duration_p99"
expected: "<200ms"
injection:
type: "network_partition"
target: "payment-db-primary"
duration: "5 minutes"
blast_radius: "single AZ"
abort_conditions:
- "checkout_success_rate < 95% 持续超过 60 秒"
- "每分钟收入下降超过 50%"
- "任何 SEV-1 事件被宣布"
results:
failover_time: "22 seconds"
error_spike: "0.3% 持续 25 秒"
hypothesis_confirmed: true
follow_up_actions:
- "在运行手册中记录故障转移行为"
- "将故障转移时间作为 SLI(目标:<30s)"| 等级 | 测试内容 | 工具 |
|---|---|---|
| 1:手动 | 手动删除 Pod,观察系统反应 | kubectl delete pod |
| 2:自动化 | 定期删除 Pod、网络延迟注入 | Chaos Monkey, Litmus |
| 3:演练日 | 多故障场景下的团队协作演练 | 自定义脚本 + 协调机制 |
| 4:持续性 | 生产环境中自动执行混沌并支持自动回滚 | Gremlin, Chaos Mesh |
| # | 驱动因素 | 典型占比 | 优化建议 |
|---|---|---|---|
| 1 | 日志量 | 40-60% | 降低日志级别,丢弃 DEBUG 信息,对重复日志采样 |
| 2 | 指标基数 | 15-25% | 清理无用指标,限制标签数量 |
| 3 | 跟踪数据量 | 10-20% | 采用采样策略,使用尾部采样 |
| 4 | 数据保留周期 | 10-15% | 分层存储(热 → 温 → 冷) |
| 5 | 查询成本 | 5-10% | 优化仪表盘查询,设置最大扫描限制 |
cost_optimization:
logs:
- action: "在生产环境丢弃 DEBUG/TRACE 日志"
savings: "日志量减少 30-50%"
- action: "对健康检查日志进行采样(1:100)"
savings: "日志量减少 5-15%"
- action: "去重相同错误的集中爆发"
savings: "故障期间减少 10-20%"
- action: "将超过 7 天的日志移至 S3/冷存储"
savings: "存储成本降低 60-80%"
- action: "关闭请求/响应体日志记录"
savings: "日志量减少 20-40%"
metrics:
- action: "审计未使用的指标(无仪表盘、无告警)"
savings: "指标系列减少 10-30%"
- action: "减少直方图桶数量(默认 11 → 8)"
savings: "直方图系列减少约 27%"
- action: "移除高基数标签"
savings: "可变 —— 降幅可能极大"
- action: "提高非关键指标的采集间隔(15s → 60s)"
savings: "相关指标数据点减少 75%"
traces:
- action: "实施尾部采样"
savings: "跟踪数据量减少 80-95%"
- action: "丢弃内部健康检查的跟踪数据"
savings: "跟踪数据量减少 5-20%"
- action: "减小跨度属性大小(截断长字符串)"
savings: "跟踪存储减少 10-30%"
general:
- action: "每季度审查并合理调整保留策略"
- action: "在仪表盘中设置查询超时和结果上限"
- action: "对高开销查询使用记录规则"observability_cost_review:
month: "2026年2月"
total_cost: "$X,XXX"
breakdown:
logs: { volume: "X TB", cost: "$X", pct: "X%" }
metrics: { series: "X 百万", cost: "$X", pct: "X%" }
traces: { volume: "X TB", cost: "$X", pct: "X%" }
infrastructure: { instances: X, cost: "$X", pct: "X%" }
cost_per:
request: "$0.000X"
service: "$X 平均"
engineer: "$X 每位工程师"
optimizations_applied: []
optimizations_planned: []
budget_status: "on_track | over_budget | under_budget"每个日志条目包含:trace_id, span_id
每个跟踪跨度包含:service, operation
每个指标包含:service 标签
关联路径:
告警触发(指标)→ 点击 → 仪表盘(指标)→ 按时间窗口筛选
→ 跟踪搜索(同服务 + 同时间)→ 定位失败的跟踪
→ 日志(按 trace_id 过滤)→ 查看具体错误信息
支持工单(用户反馈)→ 在日志中查找 request_id
→ 提取 trace_id → 查看完整跟踪 → 定位慢的跨度
→ 检查该跨度的服务指标 → 验证模式是否存在synthetic_checks:
- name: "结账流程"
type: browser
frequency: 5m
locations: [us-east, eu-west, ap-southeast]
steps:
- navigate: "https://app.example.com/products"
- click: "加入购物车"
- click: "结账"
- assert: "订单确认页面加载时间 <3秒"
alert_on: "同一位置连续两次失败"
- name: "API 健康状态"
type: api
frequency: 1m
endpoints:
- url: "https://api.example.com/health"
expected_status: 200
max_latency_ms: 500
- url: "https://api.example.com/v1/products?limit=1"
expected_status: 200
max_latency_ms: 1000# 将特性开关与指标关联
feature_flag_monitoring:
- flag: "new_checkout_flow"
metrics_to_compare:
- "checkout_conversion_rate" # 按开关版本对比
- "checkout_error_rate"
- "checkout_latency_p99"
alerts:
- "若新版本错误率 > 控制版本的 2 倍,则自动禁用该特性开关"| 维度 | 等级 1 | 等级 2 | 等级 3 | 等级 4 |
|---|---|---|---|---|
| 日志 | 非结构化日志 | 结构化 JSON,集中存储 | 与链路追踪关联 | 自动化日志分析 |
| 指标 | 基础基础设施指标 | 服务级 RED/USE 指标 | 基于 SLO 的指标,含错误预算 | 预测性(异常检测) |
| 链路追踪 | 无追踪 | 关键服务已接入 | 全链路分布式追踪 | 基于追踪的测试 |
| 告警 | 固定阈值 | 多信号告警 | 基于 SLO 的燃烧率 | 自动修复 |
| 事故响应 | 临时处理 | 有流程和角色定义 | 有行动项跟踪的复盘报告 | 生产环境混沌工程 |
| 文化 | “运维团队负责” | 共同责任(你构建,你运行) | 基于 SLO 的开发速度 | 可靠性作为功能特性 |
| 维度 | 权重 | 0 | 5 | 10 |
|---|---|---|---|---|
| 日志质量 | 15% | 非结构化,无关联 | 结构化 JSON,字段缺失 | 完整 Schema,支持链路关联,敏感信息脱敏 |
| 指标覆盖 | 15% | 无指标 | 仅使用 RED 或 USE | RED + USE + 业务指标 + 自定义指标 |
| 链路追踪完整性 | 10% | 无追踪 | 关键服务 | 全路径覆盖,采样策略,尾部采样 |
| SLO 成熟度 | 15% | 无可靠性目标 | 非正式目标 | 包含错误预算、燃烧率告警、每周评审 |
| 告警质量 | 15% | 噪音大或缺失 | 可操作,部分运行手册 | 基于 SLO,完整运行手册,低误报率 |
| 事故响应 | 10% | 临时处理 | 有流程 | 完整流程、角色分工、复盘报告、混沌工程 |
| 仪表板设计 | 10% | 无仪表板 | 基础面板 | 分层 L1-L4,风格统一,与告警联动 |
| 成本效率 | 10% | 成本未知 | 已跟踪 | 优化并每月审查,控制在预算内 |
90-100:世界级水平。可指导他人。
70-89:生产就绪。填补特定短板。
50-69:基本可用但脆弱。
<50:存在重大可靠性风险。
trace_id 将日志、链路和指标串联起来| 指令 | 功能说明 |
|---|---|
| "审计我们的可观测性" | 执行 /16 健康检查,评估各维度得分,识别优先改进项 |
| "为 [服务] 设计日志方案" | 生成该服务的结构化日志 Schema,包含上下文字段 |
| "为 [服务] 设置指标" | 制定 RED + USE + 业务指标的埋点实施计划 |
| "为 [服务] 创建 SLO" | 定义 SLI、目标值、错误预算及燃烧率告警规则 |
| "为 [服务] 设计告警" | 创建带严重等级、阈值和运行手册模板的告警规则 |
| "为 [服务] 构建仪表板" | 设计 L2 级服务概览仪表板,明确面板配置 |
| "为 [告警] 编写运行手册" | 生成结构化运行手册,包含诊断步骤与解决方案 |
| "为 [事故] 进行复盘" | 生成无责复盘文档,包含时间线与待办事项 |
| "为 [团队] 设置值班制度" | 设计轮班机制、升级策略与交接清单 |
| "规划 [场景] 的混沌实验" | 设计实验,包含假设、注入方式与终止条件 |
| "优化可观测性成本" | 审计当前支出,识别主要节省点,制定降本计划 |
| "为 [系统] 设计链路追踪" | 制定 OpenTelemetry 埋点方案,含采样策略 |
本技能提供方法论。如需行业落地模式:
afrexai-devops-engine — CI/CD、基础设施、部署策略afrexai-api-architect — API 设计、安全、版本管理afrexai-database-engineering — Schema 设计、查询优化、迁移afrexai-code-reviewer — 使用 SPEAR 框架的代码审查方法afrexai-prompt-engineering — 系统提示词设计、测试与优化浏览全部 AfrexAI 技能:[clawhub.com](https://clawhub.com) | [完整商品页](https://afrexai-cto.github.io/context-packs/)
已收录 14 个 Skill