10个 AWS 生产事故教会我真实世界的 SRE

发布: (2026年1月9日 GMT+8 00:25)
13 min read
原文: Dev.to

Source: Dev.to

请提供您想要翻译的正文内容,我将按照要求保留源链接、格式和技术术语,仅翻译文本部分。谢谢!

1️⃣ 3 AM Wake‑up Call – 403 Errors

症状

  • CloudWatch 警报:4XX 错误升高。
  • 流量看起来正常,但 ≈ 30 % 的请求返回 403

我的猜测

  • API Gateway 限流或 IAM 权限问题。

实际原因

  • 代码部署更改了 JWT 验证逻辑。
  • 来自旧版移动应用(仍有约 30 % 用户在使用)的令牌被拒绝。

解决方案

# Roll back the problematic deployment
aws deploy rollback \
    --application-name my-app \
    --deployment-group-name prod

# Add backward‑compatible token validation
# (code change – omitted for brevity)

快速行动

  • 回滚了部署。
  • 为旧令牌添加兼容性。
  • 设置了 CloudWatch 指标来监控 应用版本分布

2️⃣ 5XX 高峰流量期间的激增

症状

  • 5XX 错误激增;负载均衡器健康检查通过。
  • ≈ 15 % 的请求失败。

我的猜想

  • 后端服务不堪重负。

实际原因

  • 在流量激增期间,Lambda 函数因 冷启动 超时,API Gateway 返回 504 Gateway Timeout

解决方案

# 为热点函数启用预置并发
aws lambda put-provisioned-concurrency-config \
    --function-name my-function \
    --qualifier $LATEST \
    --provisioned-concurrent-executions 100
  • 在 API Gateway 集成中实现指数退避。

快速行动

  • 为对流量敏感的 Lambda 启用预置并发。
  • 为并发执行接近限制添加 CloudWatch 警报。

3️⃣ Route 53 故障转移至未就绪的次要区域

症状

  • 下午2点,Route 53 故障转移将所有流量路由到次要区域,导致该区域迅速超负荷。

我的猜测

  • 认为主区域出现了故障。

实际原因

  • 安全组的更改阻塞了 Route 53 健康检查端点
  • 服务本身是健康的,但 Route 53 无法验证。

解决方案

# Allow Route 53 health‑checker IP ranges
curl https://ip-ranges.amazonaws.com/ip-ranges.json |
jq -r '.prefixes[] | select(.service=="ROUTE53") | .ip_prefix' |
while read cidr; do
    aws ec2 authorize-security-group-ingress \
        --group-id sg-xxxxxx \
        --protocol tcp \
        --port 443 \
        --cidr "$cidr"
done
# Quick health‑check test
curl -v https://api.example.com/health

快速行动

  • 将 Route 53 健康检查器的 IP 范围添加到安全组。
  • 实施了内部健康检查,既验证端点可访问性 并且 实际服务健康状态。

4️⃣ “Connection Pool Exhausted” Errors – RDS

症状

  • 应用日志:“connection pool exhausted”
  • RDS 指标:CPU ≈ 20 %,连接数远低于 max_connections

我的想法

  • 需要在数据库上增加 max_connections

实际原因

  • 应用在异常后未释放连接,导致池中出现 僵尸连接

解决方案

# Example context manager to ensure proper cleanup
from contextlib import contextmanager

@contextmanager
def db_cursor(conn):
    cur = conn.cursor()
    try:
        yield cur
    finally:
        cur.close()
        conn.commit()
  • 添加了连接超时设置、熔断器逻辑,以及用于监控池健康的 CloudWatch 仪表盘。

快速行动

  • 实现了上述上下文管理器。
  • 将警报阈值设为 70 % 的池利用率(而不是 95 %)。

5️⃣ Lambda “Rate Exceeded” Errors During a Batch Job

症状

  • Lambda 函数在处理批处理作业时出现 Rate exceeded 错误。
  • 作业完全停止。

我的猜想

  • 我们触及了 AWS 服务限制。

实际原因

  • 批处理作业在没有退避的情况下执行了 10 000 并发 DynamoDB 写入,在几秒钟内耗尽了表的写入容量。

解决办法

import time, random
from botocore.config import Config

def exponential_backoff_retry(func, max_retries=5):
    for attempt in range(max_retries):
        try:
            return func()
        except Exception as e:
            backoff = (2 ** attempt) + random.random()
            time.sleep(backoff)
            if attempt == max_retries - 1:
                raise e
# Use the built‑in retry config for the AWS SDK
config = Config(
    retries={
        'max_attempts': 10,
        'mode': 'standard'
    }
)

快速行动

  • 使用重试辅助函数包装 DynamoDB 写入。
  • 为写入容量启用 DynamoDB 自动扩缩。

6️⃣ ALB 将健康实例标记为不健康

症状

  • ALB 偶尔将实例标记为不健康 → 某些请求出现 502 错误。

我的想法

  • 实例在负载下确实出现故障。

实际原因

  • 健康检查间隔为 5 秒,超时时间为 2 秒
  • 短暂的 CPU 峰值导致健康检查未能响应,产生误报(假阴性)。

解决方案

# Adjust target‑group health‑check settings
aws elbv2 modify-target-group \
    --target-group-arn arn:aws:elasticloadbalancing:... \
    --health-check-interval-seconds 30 \
    --health-check-timeout-seconds 5 \
    --healthy-threshold-count 3 \
    --unhealthy-threshold-count 3
  • 将健康检查端点设为 轻量级(不进行数据库查询)。

最佳实践

✅ 做❌ 不做
只验证进程存活的健康检查(例如 /ping)。执行耗时操作的健康检查(例如完整的数据库查询)。

快速行动

  • 更新了健康检查配置。
  • 部署了轻量级的 /ping 端点。

7️⃣ P99 延迟峰值至 8 秒 在低流量期间

症状

  • P99 延迟飙升至 8 秒,而 P50 仍保持在 200 毫秒,在安静时段。

我的猜测

  • 后端数据库性能下降。

实际原因

  • Lambda 冷启动。函数在空闲期间被终止,导致下一个请求的启动时间很长。

解决方案

# 为对延迟敏感的函数启用预置并发
aws lambda put-provisioned-concurrency-config \
    --function-name api-handler \
    --qualifier $LATEST \
    --provisioned-concurrent-executions 200
# 使用 EventBridge 调度(每 5 分钟一次的 cron 表达式)保持函数热启动
aws events put-rule \
    --name WarmLambdaRule \
    --schedule-expression "rate(5 minutes)"
  • 将部署包大小减少了 ≈60 %(移除未使用的库)。

快速行动

  • 为面向用户的 API 应用了预置并发。
  • 安排周期性的 “ping” 调用。
  • 优化了包大小。

8️⃣ DynamoDB ProvisionedThroughputExceededException 在报告生成期间

症状

  • 写入成功,但在每日报告生成时读取失败,出现 ProvisionedThroughputExceededException

我认为

  • 需要提升读取容量单位。

实际情况

  • 报告使用了 Scan 操作且未进行分页,导致产生 热点分区,在几秒钟内耗尽所有读取容量。

解决方案

def paginated_query(table, key_condition):
    items = []
    last_evaluated_key = None

    while True:
        if last_evaluated_key:
            response = table.query(
                KeyConditionExpression=key_condition,
                ExclusiveStartKey=last_evaluated_key
            )
        else:
            response = table.query(
                KeyConditionExpression=key_condition
            )

        items.extend(response["Items"])
        last_evaluated_key = response.get("LastEvaluatedKey")
        if not last_evaluated_key:
            break

    return items
  • 在可能的情况下改用 Query
  • 实现分页和指数退避。
  • 启用 DynamoDB 自动扩缩容,并添加 复合排序键 以实现高效的访问模式。

快速行动

  • 将报告任务重构为使用上述分页查询。
  • 为表开启自动扩缩容。

9️⃣ 过于激进的健康检查导致误报

  • 过于激进的健康检查间隔可能导致本应健康的实例被标记为不健康。
  • 平衡:频率要足以捕捉真实故障,但又不能过于紧密,以致瞬时峰值触发误报。

🔟 总体经验与要点

事件核心教训
1 – JWT validation版本兼容性 很重要;在发布过程中始终支持旧版客户端。
2 – Lambda cold starts预置并发 + 预热调度可缓解延迟峰值。
3 – Route 53 health checks安全组规则必须允许健康检查器的 IP 范围。
4 – DB connection pools强制正确清理;在池子饱和前充分监控使用情况。
5 – DynamoDB rate limits第 1 天 起就实现指数退避和重试。
6 – ALB health checks保持健康检查端点轻量化;将深度检查与路由检查分离。
7 – P99 latency冷启动主导尾部延迟;预置并发是解药。
8 – DynamoDB hot partitions优先使用 Query 而非 Scan,分页,并设计为键分布均匀。
9 – Aggressive health checks间隔过紧会导致误报;调节阈值和超时。
10 – (Overall)可观测性优先——在问题发生前进行监控、告警和假设验证。

通过把每一次事件视为学习机会并采用有纪律、可观测的修复措施,你可以将混乱的生产抢修转变为可预测、弹性的运维。

事件 1 – 蓝绿部署期间的 502 错误

症状

  • 5 % 的请求在每次部署时都因 502 Bad Gateway 错误而失败,尽管使用了蓝绿部署策略。

初始假设

  • 实例关闭得太快。

根本原因

  • ALB 上的 连接排空超时(注销延迟)设置为 30 秒,而某些 API 调用耗时可达 60 秒
  • ALB 在请求进行中终止了这些连接,导致 502 错误。

解决方案

# 增加连接排空超时(注销延迟)
aws elbv2 modify-target-group-attributes \
    --target-group-arn arn:aws:elasticloadbalancing:region:account-id:targetgroup/name/xxxxxxxxxxxx \
    --attributes Key=deregistration_delay.timeout_seconds,Value=120
  • 添加了一个部署健康检查,以验证没有正在进行的请求被丢弃。

已采取的快速行动

  1. 增加了注销延迟。
  2. 在应用程序中实现了优雅关闭流程(停止接受新请求,完成已有请求)。
  3. 添加了部署前验证步骤。

教训

  • 连接排空超时必须 长于最长请求延迟
  • 定期监控 P99 延迟并相应地设置超时。

Source:

事件 2 – 部署脚本导致安全组状态不一致

症状

  • 部署脚本在执行到一半时失败,导致安全组处于不一致状态。
  • 无法 SSH 登录实例,也无法回滚部署。

我的初步想法

  • 手动修复安全组。

实际原因

  • 自动化脚本 没有回滚机制,并在未测试的情况下直接修改了生产环境的安全组。

解决办法

# 使用 Session Manager 会话连接到受影响的实例
aws ssm start-session --target i-1234567890abcdef0
  1. 描述当前安全组(供参考)。

  2. 原子化地进行更改——例如,添加所需的规则:

    aws ec2 authorize-security-group-ingress \
        --group-id sg-12345 \
        --ip-permissions IpProtocol=tcp,FromPort=443,ToPort=443,IpRanges='[{CidrIp=0.0.0.0/0}]'
  3. 验证 更改是否生效。

  4. 在验证成功后 再删除旧规则。

更佳做法

  • 使用 AWS CloudFormation(或其他 IaC 工具)来管理安全组,以确保更新具备原子性和版本控制。

快速采取的措施

  • 在所有实例上启用 Systems Manager Session Manager
  • 将安全组管理切换至 CloudFormation。
  • 实施变更审批工作流。

教训

  • 切勿在生产环境中手动修改安全组,单一次错误就可能导致被锁定。
  • 使用 基础设施即代码(Infrastructure‑as‑Code) 并借助 Session Manager 作为安全网。

让这更容易的工具

当事故发生时,速度至关重要。我编写了一个 Incident Helper 脚本,用于自动化事故响应中的重复性工作:

  • 收集相关的 CloudWatch 日志。
  • 检查服务健康状态。
  • 识别常见的 AWS 配置错误。

真正的教训

  • 记录 每个事件。
  • 构建并维护 runbooks
  • 定期 测试 fail‑over 程序。
  • 每周与团队进行事后回顾讨论。

下一个事件已经排定,只是你不知道何时。做好准备才是关键。

Back to Blog

相关文章

阅读更多 »

从零到 SQS Lambda 只需 15 分钟

我的 AWS 之旅 我在加入当前组织时开始了我的 AWS 之旅。之前,我对使用 VPS 和 bare‑metal servers 感到非常满意。长话短说……