AWS DynamoDB 多表跨账户备份到S3方案
方案概述
本方案利用AWS EventBridge定时触发Lambda函数,通过Python脚本调用DynamoDB API将指定表格数据导出为DynamoDB JSON格式,并存储到目标账户的S3存储桶中。所有配置参数均通过Lambda环境变量设置,确保灵活性和安全性。
架构设计
graph TD
A[EventBridge定时规则] --> B[触发Lambda函数]
B --> C{备份类型判断}
C -->|全量| D[调用DynamoDB全量导出API]
C -->|增量| E[调用DynamoDB增量导出API]
D --> F[数据导出到目标账户S3]
E --> F
F --> G[多表循环]
详细实现方案
1. Lambda环境变量配置
| 环境变量名称 | 描述 | 示例值 | 默认值 | 备注 |
|---|---|---|---|---|
SOURCE_TABLES |
要备份的DynamoDB表名列表(JSON格式) | ["table1", "table2"] |
无 | 必需 |
TARGET_BUCKET_NAME |
目标账户S3存储桶名称 | my-backup-bucket |
无 | 必需 |
TARGET_ACCOUNT_ID |
目标AWS账户ID | 123456789012 |
无 | 必需 |
ACCOUNT_ID |
源账户ID | 123456789012 |
无 | 必需 |
EXPORT_TYPE_CONFIG |
导出类型配置:FULL(全量)或INCREMENTAL(增量) | INCREMENTAL |
INCREMENTAL |
可选 |
AWS_PARTITION |
AWS分区标识 | aws-cn |
自动从Lambda Context获取 | 可选 |
2. Lambda函数代码(支持多表+备份类型配置)
import boto3
import os
import json
from datetime import datetime, timezone
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def lambda_handler(event, context):
# Get environment variable configuration
source_tables = json.loads(os.environ['SOURCE_TABLES'])
region = os.environ['AWS_REGION']
export_type_config = os.environ.get('EXPORT_TYPE_CONFIG', 'INCREMENTAL').upper()
partition = os.environ.get('AWS_PARTITION', context.invoked_function_arn.split(':')[1])
# Get account ID, use current account if not configured
account_id = os.environ.get('ACCOUNT_ID', context.invoked_function_arn.split(':')[4])
# Validate export type configuration
if export_type_config not in ['FULL', 'INCREMENTAL']:
error_msg = f"Invalid export type configuration: {export_type_config}. Must be 'FULL' or 'INCREMENTAL'"
logger.error(error_msg)
return {
'statusCode': 400,
'body': json.dumps({
'error': error_msg
})
}
logger.info(f"Export type configuration: {export_type_config}")
# Create DynamoDB client
dynamodb = boto3.client('dynamodb', region_name=region)
# Process each table
results = []
for table_name in source_tables:
table_arn = f"arn:{partition}:dynamodb:{region}:{account_id}:table/{table_name}"
# Determine export type and build export parameters
export_params = build_export_params(dynamodb, table_arn, table_name, export_type_config)
# Start export task
try:
response = dynamodb.export_table_to_point_in_time(**export_params)
export_arn = response['ExportDescription']['ExportArn']
results.append({
'table': table_name,
'status': 'STARTED',
'exportArn': export_arn,
'exportType': export_params.get('ExportType', 'FULL_EXPORT')
})
logger.info(
f"Table {table_name} export task started: {export_arn}, Type: {export_params.get('ExportType', 'FULL_EXPORT')}")
except Exception as e:
error_msg = f"Table {table_name} export failed: {str(e)}"
logger.error(error_msg)
results.append({
'table': table_name,
'status': 'FAILED',
'error': str(e)
})
return {
'statusCode': 200,
'body': json.dumps({
'message': 'Export tasks started',
'exportTypeConfig': export_type_config,
'results': results
})
}
def build_export_params(dynamodb, table_arn, table_name, export_type_config):
"""Build export parameters, correctly handling incremental export time ranges"""
base_params = {
'TableArn': table_arn,
'S3Bucket': os.environ['TARGET_BUCKET_NAME'],
'S3BucketOwner': os.environ['TARGET_ACCOUNT_ID'],
'S3Prefix': f"exports/{table_name}/",
'ExportFormat': 'DYNAMODB_JSON',
'S3SseAlgorithm': 'AES256'
}
# If configured for full export, return full export parameters directly
if export_type_config == 'FULL':
base_params['ExportType'] = 'FULL_EXPORT'
base_params['ExportTime'] = datetime.now(timezone.utc)
return base_params
# If configured for incremental export, check if it's the first export
try:
# If configured for incremental export, try to get the most recent successful export
last_successful_export = get_last_successful_export(dynamodb, table_arn)
# If there's a successful export record, build incremental export parameters
if last_successful_export:
export_detail = dynamodb.describe_export(ExportArn=last_successful_export)
last_export_time = export_detail['ExportDescription']['StartTime']
# Set incremental export parameters
base_params['ExportType'] = 'INCREMENTAL_EXPORT'
base_params['IncrementalExportSpecification'] = {
'ExportFromTime': last_export_time,
'ExportToTime': datetime.now(timezone.utc),
'ExportViewType': 'NEW_AND_OLD_IMAGES'
}
logger.info(f"Table {table_name} incremental export: from {last_export_time} to current time")
return base_params
# No successful history found, use full export
logger.info(f"Table {table_name} no successful export history found, using full export")
base_params['ExportType'] = 'FULL_EXPORT'
base_params['ExportTime'] = datetime.now(timezone.utc)
return base_params
except Exception as e:
logger.error(f"Error checking table {table_name} export history: {str(e)}")
# Use full export as a safe choice when an error occurs
base_params['ExportType'] = 'FULL_EXPORT'
base_params['ExportTime'] = datetime.now(timezone.utc)
return base_params
def get_last_successful_export(dynamodb, table_arn):
"""Get the most recent successful export record"""
try:
# Get export history
response = dynamodb.list_exports(
TableArn=table_arn,
MaxResults=10 # Get more records to improve the probability of finding a valid record
)
# Find the most recent successful export
last_successful_export_arn = None
if response.get('ExportSummaries'):
for export in response['ExportSummaries']:
# Only consider exports with status COMPLETED
if export.get('ExportStatus') == 'COMPLETED':
last_successful_export_arn = export['ExportArn']
break
return last_successful_export_arn
except Exception as e:
logger.error(f"Error getting export history: {str(e)}")
return None
3. 使用场景
- 首次部署:
- 设置
EXPORT_TYPE_CONFIG=INCREMENTAL - 首次运行会自动检测到没有历史导出记录,使用全量导出
- 后续运行会自动使用增量导出
- 设置
- 定期全量备份:
- 设置
EXPORT_TYPE_CONFIG=FULL - 每次运行都会执行全量导出
- 设置
- 日常增量备份:
- 设置
EXPORT_TYPE_CONFIG=INCREMENTAL - 系统会自动判断导出类型(首次全量,后续增量)
- 设置
4. Lambda执行角色权限
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowDynamoDBExport",
"Effect": "Allow",
"Action": [
"dynamodb:ExportTableToPointInTime",
"dynamodb:ListExports",
"dynamodb:DescribeExport"
],
"Resource": [
"arn:aws-cn:dynamodb:REGION:SOURCE_ACCOUNT_ID:table/table1",
"arn:aws-cn:dynamodb:REGION:SOURCE_ACCOUNT_ID:table/table2"
]
},
{
"Sid": "AllowS3Export",
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:PutObjectAcl",
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws-cn:s3:::TARGET_BUCKET_NAME/*"
]
},
{
"Sid": "AllowCloudWatchLogs",
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws-cn:logs:*:*:*"
}
]
}
5. 目标账户S3存储桶策略
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowCrossAccountBackupAccess",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::SOURCE_ACCOUNT_ID:role/LambdaDynamoDBExportRole"
},
"Action": [
"s3:PutObject",
"s3:PutObjectAcl",
"s3:ListBucket",
"s3:GetObject",
"s3:AbortMultipartUpload"
],
"Resource": [
"arn:aws:s3:::TARGET_BUCKET_NAME/*"
]
}
]
}
部署与测试建议
-
部署步骤:
- 在源账户创建导出角色:
- 创建IAM角色
LambdaDynamoDBExportRole - 配置权限策略
- 创建IAM角色
- 在目标账户配置S3桶:
- 创建S3存储桶(如果不存在)
- 配置桶策略允许源账户的导出角色访问
- 在源账户创建Lambda函数:
- 使用提供的Python代码
- 配置环境变量
- 设置适当的执行超时时间(建议5分钟)
- 配置Lambda执行角色为
LambdaDynamoDBExportRole
- 设置EventBridge定时触发:
- 创建EventBridge规则定时触发Lambda函数
- 在源账户创建导出角色:
-
测试建议:
- 验证多表备份是否正常工作
- 修改数据后测试增量备份功能
- 检查S3存储桶中的备份文件结构和内容
方案特点与优势
- 灵活的导出类型配置:
- 通过环境变量
EXPORT_TYPE_CONFIG控制导出类型 - 支持
FULL(全量导出)和INCREMENTAL(增量导出)两种模式
- 通过环境变量
- 智能的首次导出处理:
- 当配置为增量导出时,自动检测是否为首次导出
- 首次导出使用全量导出,后续使用增量导出
- 真正的跨账户备份:
- 使用DynamoDB原生导出功能
- 通过
TARGET_ACCOUNT_ID参数指定目标账户
- 完善的错误处理:
- 验证环境变量配置
- 处理检查导出历史时的异常
- 详细的日志记录
- 简化权限管理:
- 使用专门的导出角色
- 清晰的跨账户权限配置
- 最小权限原则
常见问题排查
- 导出任务失败:
- 检查DynamoDB表是否启用PITR
- 验证导出角色权限
- 检查S3桶策略
- 增量导出未生效:
- 确认有成功的历史导出记录
- 检查时间范围是否在PITR保留期内
- 控制台不显示导出记录:
- 确认使用原生导出API(非Scan方式)
- 检查导出任务状态是否为COMPLETED
注意事项
- PITR要求:
- 增量导出需要表启用PITR(时间点恢复)
- 确保所有要导出的表都已启用PITR
- 导出限制:
- 每个账户同时最多有10个导出任务
- 每个表每24小时最多导出4次
- 时间范围限制:
- 增量导出的时间范围必须在PITR保留期内(默认35天)
- 如果上次导出时间超过35天,增量导出会失败
- 监控和告警:
- 建议配置CloudWatch告警监控导出失败情况
- 监控Lambda函数的执行日志
此方案提供了灵活可靠的DynamoDB跨账户备份解决方案,支持多表备份和备份策略配置,满足中国区AWS环境下的特殊需求。

浙公网安备 33010602011771号