亚马逊AWS官方博客

使用 Amazon Lambda 简化跨账号管理多个亚马逊云科技账号资源

简介

随着业务规模的扩大,许多公司会采用多个亚马逊云科技账号的策略以便隔离资源,降低运维爆炸半径。在这种环境下,资源管理,特别是在业务高峰期,可能变得复杂和费时。考虑到需要反复登录多个账号,自动化和程序化的资源管理显得尤为关键。但多账号环境也带来了复杂的授权逻辑,或因账号之间的关联性增加了爆炸半径。笔者曾经使用 Terraform 构建过跨账号的大型基础设施,涉足过多套账号精细化授权和密钥管理问题的解决。但由于 Terraform 本身的模块化功能不够灵活,在自动化构建或者灾难恢复时回滚速度慢。因此,笔者之后选择了使用亚马逊云科技的 Lambda 服务构建自定义来管理云上资源,同时利用 IAM Role 在账号间实现安全的无密码授权,完成了对超大型资源的灵活管理,以及管理功能模块化的敏捷开发迭代。本文将探讨如何利用无服务器技术简化跨账号的资源管理任务。

背景

大促销季节即将到来,您的电商平台预计会有数百万的流量。为了保证用户体验,您需要及时地清除  CloudFront 缓存来确保网站内容是最新的。然而,不同国家和地区的平台资源可能分散在不同的亚马逊云科技账号,手动或通过 Terraform 管理多个账号中的 CloudFront 会非常麻烦,并且 Terraform 本身也不支持使得 CloudFront 缓存失效的功能。因此,如何轻松、高效地管理多个账号 CloudFront 资源就变得非常重要。本文将为您展示如何模拟从运维中心发送任务到一个中心化的 SQS 队列,并利用亚马逊云科技 Lambda 服务来跨账号管理 CloudFront,使得缓存失效。在自动化管理资源的情况下,我们会面临两个问题:1)由于亚马逊云科技服务的 API 会有 limit,例如操作 CloudFront 时某些 API 的限制是每秒一次,那我们该如何有效的下发这些指令呢? 2)接受指令之后,使用什么办法可以安全的跨账号授权操作,设置一个安全边界,保证即使在发生 bug 情况下也能控制爆炸半径,从而有效的保护基础设施。

解决方案概览

为了实现跨多账号的资源管理,我们首先使用 SQS 队列来发送和接收实时更新 CloudFront 的任务。这些任务包含了必要的信息,如账号 ID、CloudFront 分发 ID 和失效路径。随后,我们部署一个 Amazon Lambda 函数,该函数持续监听 SQS 队列并自动响应新任务。一旦 Lambda 函数接收到任务,它会使用 IAM AssumeRole 功能进行跨账号操作,从而管理在目标账号中的 CloudFront 资源。这种架构不仅简化了资源管理,还确保了内容的实时更新,提高了效率。您只需要在下图所示的 Ops Prod 账号(运维中心)中的 Lambda 使用 IAM STS 无密码授权到各个账号,操作其他账号资源,从而提高了效率和安全性。

详细步骤

  1. 登录 Ops Prod 账号管理控制台,进入 SQS 服务,创建一个 SQS 队列。在开发过程中建议您将消息保留周期设置为1分钟,以便自动清除因 Lambda 处理失败自动返回 SQS 的消息,其它参数默认即可。
  1. 进入 Lambda 管理控制台,创建一个新的 Lambda 函数。可以选择所需的运行时环境,如 js、Python 等。编写 Lambda 函数代码,处理 SQS 消息事件。这里以 Python 为例选择 3.11 运行时,并把 Lambda 的运行超时时长设置成一分钟:
  1. 添加 Lambda 权限:
    1. a. 在 IAM 控制台中找到你的 Lambda 执行角色,点击 “添加权限”
    2. b. 点击 “创建内联策略”,切换到 JSON 标签页
    3. c. 需要 Resource 部分替换目标账号 ID、队列名称、角色等信息。目标账号也需要相应的信任策略和资源访问策略
    4. 这个策略包含了:1)SQS 队列的访问权限。允许接收、删除消息以及获取队列属性;2)跨账号访问权限。允许 Assume Role 目标账号中的指定角色。

      配置这个策略到 Lambda 执行角色后, Lambda 就可以:1)接收 SQS 队列消息触发执行;2)在执行时跨账号访问目标账号资源。

      {
        "Version": "2012-10-17",
        "Statement": [
          {
            "Effect": "Allow",
            "Action": [
              "sqs:ReceiveMessage", 
              "sqs:DeleteMessage",
              "sqs:GetQueueAttributes"
            ],
            "Resource": "arn:aws:sqs:region:target-account-id:queue-name"
          },
          {
            "Effect": "Allow",
            "Action": "sts:AssumeRole",
            "Resource": "arn:aws:iam::target-account-id:role/role-name"
          }
        ]
      }
      
  1. 配置 Lambda 触发器:
    1. a. 在 Lambda 函数的“配置”页面,选择“添加触发器”
    2. b. 在触发器配置页面选择触发器类型为“SQS”
    3. c. 选择之前创建的 SQS 队列作为事件源
    4. d. 完成配置后,队列收到消息就会触发 Lambda
  1. 修改 Lambda 代码,处理来自 SQS 的请求,以下示例根据本文背景模拟了 Lambda 函数处理 SQS 消息并跨账号执行 CloudFront 缓存失效的请求。你可以:
    1. 在 Lambda 控制台,选择要修改的函数,进入函数代码页面
    2. 在代码编辑器中可以看到函数的 Python 代码,直接在这里进行代码修改
    3. 贴入以下代码,然后点击 “Deploy” 或者 “保存”

注意:替换掉以下 YourCrossAccountRoleName,在本文应该是 “ CloudFrontInvalidationRole ”,这个 Role 会在下一步创建

import json
import uuid
import boto3

def lambda_handler(event, context):
    sts_client = boto3.client('sts')
    cloudfront_client = boto3.client('cloudfront')
    
    for record in event['Records']:
        message_body = record['body']
        message = json.loads(message_body)
        
        for entry in message['entries']:
            aws_account_id = entry['aws_account_id']
            cloudfront_distribution_id = entry['cloudfront_distribution_id']
            invalidation_path = entry['invalidation_path']
            caller_reference = str(uuid.uuid4()) 
            
            assumed_role = sts_client.assume_role(
                RoleArn=f"arn:aws:iam::{aws_account_id}:role/YourCrossAccountRoleName",
                RoleSessionName="CloudFrontInvalidationSession"
            )
            
            cross_account_cloudfront = boto3.client(
                'cloudfront',
                aws_access_key_id=assumed_role['Credentials']['AccessKeyId'],
                aws_secret_access_key=assumed_role['Credentials']['SecretAccessKey'],
                aws_session_token=assumed_role['Credentials']['SessionToken']
            )
            
            cross_account_cloudfront.create_invalidation(
                DistributionId=cloudfront_distribution_id,
                InvalidationBatch={
                    'Paths': {
                        'Quantity': 1,
                        'Items': [invalidation_path]
                    },
                    'CallerReference': caller_reference
                }
            )
  1. 更换账号,在目标账号 A/B/C 中创建一个名为 CloudFrontInvalidationRole 的角色,用于允许来自 Lambda 账号的访问:
    1. 在目标账号的 IAM  控制台中,创建一个新的角色,比如名为 CloudFrontInvalidationRole
    2. 创建的 Role 类型选择“自定义信任策略”,注意替换 lambda-account-id 和 lambda-execution-role 为真实的信息,也就是 Ops Prod 账户中创建的 Lambda Role
    3. 附加一个访问所需资源的访问策略,这里可以选择 CloudFrontFullAccess
    4. 在角色的信任关系策略中,添加允许来自 Lambda 账号中的执行角色进行跨账号访问的策略
      {
        "Version": "2012-10-17",
        "Statement": [
          {
            "Effect": "Allow",
            "Principal": {
              "AWS": "arn:aws:iam::lambda-account-id:role/lambda-execution-role"
            },
            "Action": "sts:AssumeRole"
          }
        ]
      }
      

模拟从运维中心发送任务

现在开始模拟测试,通过 Python 脚本向 SQS 队列发送多个 CloudFront 失效缓存请求。在这里使用 Python 和 boto3 库,发送包含亚马逊云科技账号 ID、CloudFront 分发 ID 和失效路径的消息到 SQS 队列。创建 CloudFront 缓存失效需要指定 distributionId 和 paths 参数,paths 包含想要失效的路径。值得注意的是 paths 也就是下述测试代码的 “invalidation_path” 参数中的路径必须以 / 开头,例如:/images/cat.jpg。

import boto3
import json

sqs = boto3.client('sqs', region_name='YOUR_REGION', 
                   aws_access_key_id='YOUR_ACCESS_KEY', 
                   aws_secret_access_key='YOUR_SECRET_KEY')

queue_url = 'YOUR_SQS_QUEUE_URL'

message = {
    "entries": [
        {
            "aws_account_id": "YOUR_AWS_ACCOUNT_ID_1",
            "cloudfront_distribution_id": "YOUR_CLOUDFRONT_DISTRIBUTION_ID_1",
            "invalidation_path": "YOUR_INVALIDATION_PATH_1"
        },
        {
            "aws_account_id": "YOUR_AWS_ACCOUNT_ID_2",
            "cloudfront_distribution_id": "YOUR_CLOUDFRONT_DISTRIBUTION_ID_2",
            "invalidation_path": "YOUR_INVALIDATION_PATH_2"
        }
    ]
}

response = sqs.send_message(
    QueueUrl=queue_url,
    MessageBody=json.dumps(message)
)

print(response)

通过测试,您将在 Lambda CloudWatch 日志中看到触发记录,并在目标账号查看到 CloudFront 缓存失效记录。

最佳实践

A. 调整 Lambda 并发

假设您在测试环境中多次触发上述测试脚本发送失效请求,您应该会在 Lambda 中查看到在调用 CloudFront 的 CreateInvalidation API 时遇到了 HTTP 429 Too Many Requests Throttling 错误:

[ERROR] ClientError: An error occurred (Throttling) when calling the CreateInvalidation operation (reached max retries: 4): Rate exceeded
Traceback (most recent call last):

这是因为 CloudFront 的 CreateInvalidation API 有每秒请求数(RPS)的限制,如果超过这个限制就会被限流,返回 Throttling 错误。类似的问题可能在其他 API 出现,例如更新 WAF 规则时,需要先调用一个 API 获取 ChangeToken,然后将这个 token 作为参数传递给后续的更新 API 调用。在这过程中,也可能触发 429 这样的错误。这是由于 SQS 瞬时堆积过多数据,在 1-2 秒触发了多个 Lambda 运行环境导致了并发执行。此时我们需要考虑为 Lambda 函数调整并发:

1)适当的增大 SQS visibility timeout 时间可以使同一消息在更长时间内不可见,防止其他 Lambda 实例同时处理该消息,从而降低 Lambda 处理速度,避免出现 429 错误的现象。在消费速度允许的情况下,可以配置 visibility timeout >= Lambda Timeout 时间,以便平滑地控制 Lambda 并发处理消息的速度。

2)通过为 Lambda 函数设置 Reserved Concurrency 来限制其最大并发执行数量,从而降低其总体处理能力。在 Lambda 控制台中,选择要限制的函数,进入配置页面。找到“并发”部分,点击“编辑”。在“预留并发”处输入想要的最大并发数,例如 10。这样就设置了该函数的 Reserved Concurrency 为 10。这表示该函数最多只能同时处理 10 个事件,多余的事件请求将会被排队。通过降低 Reserved Concurrency 的设置,可以限制函数的最大并发,从而达到降低其总体处理能力和吞吐量的效果。

3)除了使用 CreateInvalidation API 之外,调用 WAF API 更新 ip set 频率过高也会触发 429 错误。为了减少触发 API 的次数,可以聚合相同操作到 DynamoDB。例如可以聚合 CloudFront CallerReference / WAF ChangeToken 发送请求,既保证调用 API 的幂等性也保证了安全触发。

B. 安全和审计

1)考虑到运维工具的安全性是保证系统稳定性的关键。在使用 Lambda 时,可以通过限制其权限来确保代码安全。尤其是与在生成式 AI 普及环境下,LLM 交互的 Agents 代码越来越多,其代码边界越来越模糊,代码意图可能不明确。此时,可以把 Agents 部署在受限权限的 Lambda 中,有效控制其边界。这也是本文背景下,使用 Lambda 及 IAM Role 作为容错机制,保护系统不受破坏的做法。

2)本文为了简单和敏捷度,在一个 Lambda 函数中同时实现了控制平面和数据平面。但此种方式在审计上,会带来一定的复杂性。如果需要追溯跨账号授权的过程和结果,可能需要同时获得两个账号的日志。基于这种考虑,笔者建议可以在把数据平面的操作放在对应账号,也就是在以上的 A、B、C 账号中创建 Lambda 然后通过 API gateway 授权给 Ops Prod 账号。此外,因为 Amazon API Gateway 拥有丰富多样的授权方式,API Gateway 甚至支持在跨账号的私有网络交互,以及完善的审计,大大的提高了审计的可视性,同时保留了原有的模块化,降低运维开发复杂度。

结论与展望

随着业务扩大,使用多个亚马逊账号进行资源隔离变得普遍,但同时也增加了跨账号资源管理的复杂性。本文探讨了如何通过 SQS+Lambda 架构实现了跨账号资源管理的自动化、安全性与灵活性,使复杂环境下的管理变得简单高效。希望这篇文章能帮助您简化亚马逊云资源管理,并确保在关键时刻为用户提供最佳体验。

参考链接:

本篇作者

谢佰臻

亚马逊云科技解决方案架构师,负责基于云计算方案架构的咨询和设计,目前专注于 Serverless、DevSecOps。

郭松

亚马逊云科技解决方案架构师,负责企业级客户的架构咨询及设计优化,同时致力于 AWS IoT 和存储服务在国内和全球企业客户的应用和推广。加入亚马逊云科技之前在 EMC 研发中心担任系统工程师,对企业级存储应用的高可用架构,方案及性能调优有深入研究。