4、AWS Secrets Manager 实战:自动轮换 RDS 数据库凭证

好,咱们进入实战环节。前面聊了那么多理论,说白了,密钥轮换这件事,最终还是要落到工具上。AWS Secrets Manager 是我个人非常喜欢的一个服务——它把「存、管、转」三件事打包好了,尤其是自动轮换 RDS 凭证这个能力,能省掉你大量运维时间。

我记得第一次在客户现场配置这个功能时,对方 DBA 一脸怀疑:「你们自动化轮换,万一把我库搞挂了怎么办?」嗯,这种担心很正常。但当你理解了它的底层机制后,你会发现——其实它比你手动改密码要安全得多。

4.1 核心架构:谁在驱动轮换?

先看一张整体流程图,帮你建立全局认知。

AWS Secrets Manager RDS 自动轮换架构 管理员 / 应用 AWS Secrets Manager Lambda 轮换函数 RDS 主实例 RDS 只读副本 轮换流程说明 ① 管理员启用自动轮换 → ② Secrets Manager 触发 Lambda → ③ Lambda 生成新密码并更新 RDS ④ Lambda 将新凭证写回 Secrets Manager → ⑤ 应用下次获取时自动拿到新密码

整个流程的核心是 Lambda 轮换函数。它就像一个「中间人」——Secrets Manager 只负责调度和存储,真正去改数据库密码的,是 Lambda。

4.2 轮换策略:四种阶段模型

AWS 定义了标准的轮换策略,分四个阶段。我刚开始接触时觉得有点绕,但用熟了会发现——每个阶段都有它的道理。

阶段 名称 做了什么 凭证状态
1 createSecret 生成新密码,存入暂存标签 旧凭证仍可用
2 setSecret 用新密码更新 RDS 数据库 新旧凭证同时有效
3 testSecret 用新密码测试连接 验证新凭证可用
4 finishSecret 将暂存标签标记为正式版本 旧凭证失效

关键设计思想:整个过程中,至少有一个有效凭证。阶段 2 完成后,新旧密码同时可用,应用不会因为轮换而断连。这就是「零停机轮换」的底气。

4.3 实战配置:一步步来

好,咱们动手。假设你有一个 RDS MySQL 实例,现在要让它每 30 天自动换一次密码。

4.3.1 创建 Secrets Manager 密钥

在 AWS 控制台里,找到 Secrets Manager,点「存储新密钥」。选择 RDS 数据库凭证 类型——这个选项会自动帮你生成 Lambda 轮换函数模板。

# 也可以用 AWS CLI 创建
aws secretsmanager create-secret \
    --name prod-db-secret \
    --description "Production RDS MySQL credentials" \
    --secret-string '{"username":"admin","password":"InitialP@ss123"}' \
    --tags Key=Environment,Value=Production

我的习惯:创建时先手动填一个初始密码,等轮换函数配置好后再触发一次手动轮换。这样能确保轮换流程从一开始就是通的。

4.3.2 配置自动轮换

在密钥详情页,找到「轮换配置」。你需要指定:

  • 轮换周期:比如 30 天。用 cron 表达式写:rate(30 days)
  • 轮换 Lambda 函数:选择「创建新函数」——AWS 会帮你生成一个基于 Python 的模板
  • IAM 角色:Lambda 需要权限去读 Secrets Manager 和改 RDS 密码

嗯,这里有个坑。我曾经遇到过 Lambda 执行超时——默认 3 分钟,但 RDS 如果网络延迟高,可能不够。我建议你改成 5 分钟。

4.3.3 轮换函数代码解读

AWS 生成的模板代码大概长这样。我挑核心部分讲:

def lambda_handler(event, context):
    # 阶段 1: 生成新密码
    if event['Step'] == 'createSecret':
        # 生成随机密码,存入 AWSPENDING 标签
        pass
    
    # 阶段 2: 更新 RDS
    elif event['Step'] == 'setSecret':
        # 用新密码 ALTER USER ... IDENTIFIED BY
        # 注意:这里要处理主实例和只读副本
        pass
    
    # 阶段 3: 验证
    elif event['Step'] == 'testSecret':
        # 用新密码建立连接,执行 SELECT 1
        pass
    
    # 阶段 4: 完成
    elif event['Step'] == 'finishSecret':
        # 将 AWSPENDING 标签标记为 AWSCURRENT
        pass

我曾经踩过的坑:如果 RDS 开启了 Multi-AZ,轮换函数默认只改主实例。但只读副本的密码不会自动同步——你需要手动在 setSecret 阶段里加上对只读副本的 ALTER USER 操作。否则副本会一直用旧密码,导致应用读请求失败。

4.4 应用端:如何无感切换?

轮换成功了,但应用怎么拿到新密码?

说白了,应用不能缓存密码。每次连接数据库前,都去 Secrets Manager 拉一次。AWS 提供了 SDK,代码很简单:

import boto3
import json
from botocore.exceptions import ClientError

def get_db_credentials():
    session = boto3.session.Session()
    client = session.client('secretsmanager')
    
    try:
        response = client.get_secret_value(SecretId='prod-db-secret')
        secret = json.loads(response['SecretString'])
        return secret['username'], secret['password']
    except ClientError as e:
        print(f"获取密钥失败: {e}")
        raise

性能优化建议:每次请求都去调 API 确实有延迟。我一般会在应用层做 本地缓存 + 定时刷新——比如每 5 分钟重新拉一次,缓存 10 分钟。这样既不会频繁调用 API,也能在轮换后 10 分钟内拿到新密码。

4.5 监控与告警:别让轮换静默失败

轮换失败怎么办?我见过最惨的情况是——轮换函数报错了,但没人知道,结果旧密码过期后应用全部断连。

所以,一定要配监控:

  • CloudWatch 告警:监控 Lambda 的 Errors 和 Throttles 指标
  • Secrets Manager 事件:订阅 RotationFailed 事件,发到 SNS 通知团队
  • 定期手动验证:我习惯每个月手动触发一次轮换,确保流程是活的

一句话总结:自动轮换不是「配完就忘」。你要把它当成一个生产服务来运维——有监控、有告警、有应急预案。

4.6 避坑清单

最后,把我这些年遇到过的坑列出来,你配置时对照着检查:

常见问题 原因 解决方案
轮换后应用报连接拒绝 应用缓存了旧密码 实现密码自动刷新机制
Lambda 执行超时 默认 3 分钟不够 调整为 5-10 分钟
只读副本密码未更新 轮换函数只改了主实例 手动添加副本 ALTER USER
IAM 权限不足 Lambda 角色缺少 rds:ModifyDBInstance 检查并补充权限策略
轮换频率过高 cron 表达式写错 用 rate(30 days) 而非 cron(0 0 * * ? *)