4、AWS Secrets Manager 实战:自动轮换 RDS 数据库凭证
好,咱们进入实战环节。前面聊了那么多理论,说白了,密钥轮换这件事,最终还是要落到工具上。AWS Secrets Manager 是我个人非常喜欢的一个服务——它把「存、管、转」三件事打包好了,尤其是自动轮换 RDS 凭证这个能力,能省掉你大量运维时间。
我记得第一次在客户现场配置这个功能时,对方 DBA 一脸怀疑:「你们自动化轮换,万一把我库搞挂了怎么办?」嗯,这种担心很正常。但当你理解了它的底层机制后,你会发现——其实它比你手动改密码要安全得多。
4.1 核心架构:谁在驱动轮换?
先看一张整体流程图,帮你建立全局认知。
整个流程的核心是 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 * * ? *) |