教程:将 AWS Lambda 与 Amazon Simple Notification Service 结合使用 - AWS Lambda

教程:将 AWS Lambda 与 Amazon Simple Notification Service 结合使用

在本教程中,您将使用某个 AWS 账户 中的 Lambda 函数,订阅独立 AWS 账户 中的 Amazon Simple Notification Service(Amazon SNS)主题。将消息发布到 Amazon SNS 主题时,Lambda 函数会读取消息内容并将其输出到 Amazon CloudWatch Logs。要完成此教程,需要使用 AWS Command Line Interface(AWS CLI)。

该图显示了本教程中使用的服务:连接到与 CloudWatch Logs 日志组连接的 Lambda 函数的 Amazon SNS 主题。

要完成本教程,请执行以下步骤:

  • 账户 A 中,创建 Amazon SNS 主题。

  • 账户 B 中,创建可从该主题读取消息的 Lambda 函数。

  • 账户 B 中,创建该主题的订阅。

  • 账户 A 中将消息发布到 Amazon SNS 主题,并确认账户 B 中的 Lambda 函数将其输出到 CloudWatch Logs。

通过完成这些步骤,您将了解如何配置 Amazon SNS 主题以调用 Lambda 函数。您还将了解如何创建 AWS Identity and Access Management(IAM)策略,向其他 AWS 账户 中的资源授予调用 Lambda 的权限。

在本教程中,您将使用两个独立的 AWS 账户。AWS CLI 命令通过以下方式对此进行说明:使用两个名为 accountAaccountB 的命名配置文件,每个文件配置为用于不同的 AWS 账户。要了解如何配置 AWS CLI 以使用不同的配置文件,请参阅《AWS Command Line Interface User Guide for Version 2》中的 Configuration and credential file settings。确保为两个配置文件配置相同的默认值 AWS 区域。

如果您为两个 AWS 账户 创建的 AWS CLI 配置文件使用不同名称,或者使用默认配置文件和一个命名配置文件,请根据需要按照以下步骤修改 AWS CLI 命令。

先决条件

如果您还没有 AWS 账户,请完成以下步骤来创建一个。

注册 AWS 账户
  1. 打开 https://portal.aws.amazon.com/billing/signup

  2. 按照屏幕上的说明进行操作。

    在注册时,将接到一通电话,要求使用电话键盘输入一个验证码。

    当您注册 AWS 账户时,系统将会创建一个 AWS 账户根用户。根用户有权访问该账户中的所有 AWS 服务 和资源。作为安全最佳实践,请为用户分配管理访问权限,并且只使用根用户来执行需要根用户访问权限的任务

注册过程完成后,AWS 会向您发送一封确认电子邮件。在任何时候,您都可以通过转至 https://aws.amazon.com/ 并选择我的账户来查看当前的账户活动并管理您的账户。

注册 AWS 账户 后,请保护好您的 AWS 账户根用户,启用 AWS IAM Identity Center,并创建一个管理用户,以避免使用根用户执行日常任务。

保护您的 AWS 账户根用户
  1. 选择根用户并输入您的 AWS 账户电子邮件地址,以账户拥有者身份登录 AWS Management Console。在下一页上,输入您的密码。

    要获取使用根用户登录方面的帮助,请参阅《AWS 登录 用户指南》中的以根用户身份登录

  2. 为您的根用户启用多重身份验证 (MFA)。

    有关说明,请参阅《IAM 用户指南》中的为 AWS 账户根用户启用虚拟 MFA 设备(控制台)

创建具有管理访问权限的用户
  1. 启用 IAM Identity Center。

    有关说明,请参阅《AWS IAM Identity Center 用户指南》中的启用 AWS IAM Identity Center

  2. 在 IAM Identity Center 中,为用户授予管理访问权限。

    有关如何使用 IAM Identity Center 目录 作为身份源的教程,请参阅《AWS IAM Identity Center 用户指南》中的使用默认的 IAM Identity Center 目录 配置用户访问权限

以具有管理访问权限的用户身份登录
  • 要使用您的 IAM Identity Center 用户身份登录,请使用您在创建 IAM Identity Center 用户时发送到您的电子邮件地址的登录网址。

    要获取使用 IAM Identity Center 用户登录方面的帮助,请参阅《AWS 登录 用户指南》中的 登录 AWS 访问门户

将访问权限分配给其他用户
  1. 在 IAM Identity Center 中,创建一个权限集,该权限集遵循应用最低权限的最佳做法。

    有关说明,请参阅《AWS IAM Identity Center 用户指南》中的创建权限集

  2. 将用户分配到一个组,然后为该组分配单点登录访问权限。

    有关说明,请参阅《AWS IAM Identity Center 用户指南》中的添加组

如果您尚未安装 AWS Command Line Interface,请按照安装或更新最新版本的 AWS CLI 中的步骤进行安装。

本教程需要命令行终端或 Shell 来运行命令。在 Linux 和 macOS 中,可使用您首选的 Shell 和程序包管理器。

注意

在 Windows 中,操作系统的内置终端不支持您经常与 Lambda 一起使用的某些 Bash CLI 命令(例如 zip)。安装 Windows Subsystem for Linux,获取 Ubuntu 和 Bash 与 Windows 集成的版本。

创建 Amazon SNS 主题(账户 A)

显示您正在 Amazon SNS 主题步骤中创建主题的教程工作流程图
创建 主题
  • 账户 A 中,使用以下 AWS CLI 命令创建 Amazon SNS 标准主题。

    aws sns create-topic --name sns-topic-for-lambda --profile accountA

    您应该可以看到类似于如下所示的输出内容。

    { "TopicArn": "arn:aws:sns:us-west-2:123456789012:sns-topic-for-lambda" }

    记下主题的 Amazon 资源名称(ARN)。稍后在本教程中向 Lambda 函数添加权限以订阅主题时,将需要使用此 ARN。

创建函数执行角色(账户 B)

显示您正在函数步骤中创建执行角色的教程工作流程图

执行角色是一个 IAM 角色,用于向 Lambda 函数授予访问 AWS 服务和资源的权限。在账户 B 中创建函数之前,您需要创建一个角色,向该函数授予将日志写入 CloudWatch Logs 的基本权限。我们将在后续步骤中添加读取 Amazon SNS 主题的权限。

创建执行角色
  1. 账户 B 中,打开 IAM 控制台中的角色页面

  2. 选择 Create role(创建角色)。

  3. 可信实体类型中选择 AWS 服务

  4. 使用案例中选择 Lambda

  5. 选择下一步

  6. 通过执行以下操作,向角色添加基本权限策略:

    1. 权限策略搜索框中输入 AWSLambdaBasicExecutionRole

    2. 选择下一步

  7. 通过执行以下操作,完成角色创建:

    1. 角色详细信息下的角色名称中输入 lambda-sns-role

    2. 选择 Create role(创建角色)。

创建 Lambda 函数(账户 B)

显示您正在函数步骤中创建函数的教程工作流程图

创建一个处理您的 Amazon SNS 消息的 Lambda 函数。函数代码会将每条记录的消息内容记录到 Amazon CloudWatch Logs 中。

本教程使用 Node.js 18.x 运行时系统,但我们还提供了其他运行时系统语言的示例代码。您可以选择以下框中的选项卡,查看适用于您感兴趣的运行时系统的代码。您将在此步骤中使用的 JavaScript 代码是 JavaScript 选项卡中显示的第一个示例。

.NET
AWS SDK for .NET
注意

在 GitHub 上查看更多内容。在无服务器示例存储库中查找完整示例,并了解如何进行设置和运行。

使用 .NET 将 SNS 事件与 Lambda 结合使用。

// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 using Amazon.Lambda.Core; using Amazon.Lambda.SNSEvents; // Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class. [assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] namespace SnsIntegration; public class Function { public async Task FunctionHandler(SNSEvent evnt, ILambdaContext context) { foreach (var record in evnt.Records) { await ProcessRecordAsync(record, context); } context.Logger.LogInformation("done"); } private async Task ProcessRecordAsync(SNSEvent.SNSRecord record, ILambdaContext context) { try { context.Logger.LogInformation($"Processed record {record.Sns.Message}"); // TODO: Do interesting work based on the new message await Task.CompletedTask; } catch (Exception e) { //You can use Dead Letter Queue to handle failures. By configuring a Lambda DLQ. context.Logger.LogError($"An error occurred"); throw; } } }
Go
适用于 Go V2 的 SDK
注意

在 GitHub 上查看更多内容。在无服务器示例存储库中查找完整示例,并了解如何进行设置和运行。

使用 Go 将 SNS 事件与 Lambda 结合使用。

// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package main import ( "context" "fmt" "github.com/aws/aws-lambda-go/events" "github.com/aws/aws-lambda-go/lambda" ) func handler(ctx context.Context, snsEvent events.SNSEvent) { for _, record := range snsEvent.Records { processMessage(record) } fmt.Println("done") } func processMessage(record events.SNSEventRecord) { message := record.SNS.Message fmt.Printf("Processed message: %s\n", message) // TODO: Process your record here } func main() { lambda.Start(handler) }
Java
SDK for Java 2.x
注意

在 GitHub 上查看更多内容。在无服务器示例存储库中查找完整示例,并了解如何进行设置和运行。

通过 Java 将 SNS 事件与 Lambda 结合使用。

// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package example; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.LambdaLogger; import com.amazonaws.services.lambda.runtime.RequestHandler; import com.amazonaws.services.lambda.runtime.events.SNSEvent; import com.amazonaws.services.lambda.runtime.events.SNSEvent.SNSRecord; import java.util.Iterator; import java.util.List; public class SNSEventHandler implements RequestHandler<SNSEvent, Boolean> { LambdaLogger logger; @Override public Boolean handleRequest(SNSEvent event, Context context) { logger = context.getLogger(); List<SNSRecord> records = event.getRecords(); if (!records.isEmpty()) { Iterator<SNSRecord> recordsIter = records.iterator(); while (recordsIter.hasNext()) { processRecord(recordsIter.next()); } } return Boolean.TRUE; } public void processRecord(SNSRecord record) { try { String message = record.getSNS().getMessage(); logger.log("message: " + message); } catch (Exception e) { throw new RuntimeException(e); } } }
JavaScript
适用于 JavaScript 的 SDK (v3)
注意

在 GitHub 上查看更多内容。在无服务器示例存储库中查找完整示例,并了解如何进行设置和运行。

使用 JavaScript 将 SNS 事件与 Lambda 结合使用。

// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 exports.handler = async (event, context) => { for (const record of event.Records) { await processMessageAsync(record); } console.info("done"); }; async function processMessageAsync(record) { try { const message = JSON.stringify(record.Sns.Message); console.log(`Processed message ${message}`); await Promise.resolve(1); //Placeholder for actual async work } catch (err) { console.error("An error occurred"); throw err; } }

使用 TypeScript 将 SNS 事件与 Lambda 结合使用。

// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 import { SNSEvent, Context, SNSHandler, SNSEventRecord } from "aws-lambda"; export const functionHandler: SNSHandler = async ( event: SNSEvent, context: Context ): Promise<void> => { for (const record of event.Records) { await processMessageAsync(record); } console.info("done"); }; async function processMessageAsync(record: SNSEventRecord): Promise<any> { try { const message: string = JSON.stringify(record.Sns.Message); console.log(`Processed message ${message}`); await Promise.resolve(1); //Placeholder for actual async work } catch (err) { console.error("An error occurred"); throw err; } }
PHP
适用于 PHP 的 SDK
注意

在 GitHub 上查看更多内容。在无服务器示例存储库中查找完整示例,并了解如何进行设置和运行。

通过 PHP 将 SNS 事件与 Lambda 结合使用。

// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 <?php /* Since native PHP support for AWS Lambda is not available, we are utilizing Bref's PHP functions runtime for AWS Lambda. For more information on Bref's PHP runtime for Lambda, refer to: https://bref.sh/docs/runtimes/function Another approach would be to create a custom runtime. A practical example can be found here: https://aws.amazon.com/blogs/apn/aws-lambda-custom-runtime-for-php-a-practical-example/ */ // Additional composer packages may be required when using Bref or any other PHP functions runtime. // require __DIR__ . '/vendor/autoload.php'; use Bref\Context\Context; use Bref\Event\Sns\SnsEvent; use Bref\Event\Sns\SnsHandler; class Handler extends SnsHandler { public function handleSns(SnsEvent $event, Context $context): void { foreach ($event->getRecords() as $record) { $message = $record->getMessage(); // TODO: Implement your custom processing logic here // Any exception thrown will be logged and the invocation will be marked as failed echo "Processed Message: $message" . PHP_EOL; } } } return new Handler();
Python
SDK for Python(Boto3)
注意

在 GitHub 上查看更多内容。在无服务器示例存储库中查找完整示例,并了解如何进行设置和运行。

使用 Python 将 SNS 事件与 Lambda 结合使用。

# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 def lambda_handler(event, context): for record in event['Records']: process_message(record) print("done") def process_message(record): try: message = record['Sns']['Message'] print(f"Processed message {message}") # TODO; Process your record here except Exception as e: print("An error occurred") raise e
Ruby
适用于 Ruby 的 SDK
注意

在 GitHub 上查看更多内容。在无服务器示例存储库中查找完整示例,并了解如何进行设置和运行。

通过 Ruby 将 SNS 事件与 Lambda 结合使用。

# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 def lambda_handler(event:, context:) event['Records'].map { |record| process_message(record) } end def process_message(record) message = record['Sns']['Message'] puts("Processing message: #{message}") rescue StandardError => e puts("Error processing message: #{e}") raise end
Rust
适用于 Rust 的 SDK
注意

在 GitHub 上查看更多内容。在无服务器示例存储库中查找完整示例,并了解如何进行设置和运行。

通过 Rust 将 SNS 事件与 Lambda 结合使用。

// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use aws_lambda_events::event::sns::SnsEvent; use aws_lambda_events::sns::SnsRecord; use lambda_runtime::{run, service_fn, Error, LambdaEvent}; use tracing::info; // Built with the following dependencies: // aws_lambda_events = { version = "0.10.0", default-features = false, features = ["sns"] } // lambda_runtime = "0.8.1" // tokio = { version = "1", features = ["macros"] } // tracing = { version = "0.1", features = ["log"] } // tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } async fn function_handler(event: LambdaEvent<SnsEvent>) -> Result<(), Error> { for event in event.payload.records { process_record(&event)?; } Ok(()) } fn process_record(record: &SnsRecord) -> Result<(), Error> { info!("Processing SNS Message: {}", record.sns.message); // Implement your record handling code here. Ok(()) } #[tokio::main] async fn main() -> Result<(), Error> { tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) .with_target(false) .without_time() .init(); run(service_fn(function_handler)).await }
创建函数
  1. 为项目创建一个目录,然后切换到该目录。

    mkdir sns-tutorial cd sns-tutorial
  2. 将示例 JavaScript 代码复制到名为 index.js 的新文件中。

  3. 使用以下 zip 命令创建部署包。

    zip function.zip index.js
  4. 运行以下 AWS CLI 命令,在账户 B 中创建 Lambda 函数。

    aws lambda create-function --function-name Function-With-SNS \ --zip-file fileb://function.zip --handler index.handler --runtime nodejs18.x \ --role arn:aws:iam::<AccountB_ID>:role/lambda-sns-role \ --timeout 60 --profile accountB

    您应该可以看到类似于如下所示的输出内容。

    { "FunctionName": "Function-With-SNS", "FunctionArn": "arn:aws:lambda:us-west-2:123456789012:function:Function-With-SNS", "Runtime": "nodejs18.x", "Role": "arn:aws:iam::123456789012:role/lambda_basic_role", "Handler": "index.handler", ... "RuntimeVersionConfig": { "RuntimeVersionArn": "arn:aws:lambda:us-west-2::runtime:7d5f06b69c951da8a48b926ce280a9daf2e8bb1a74fc4a2672580c787d608206" } }
  5. 记下函数的 Amazon 资源名称(ARN)。稍后在本教程中添加权限以允许 Amazon SNS 调用函数时,将需要使用此 ARN。

向函数添加权限(账户 B)

显示您正在函数步骤中添加权限的教程工作流程图

要让 Amazon SNS 调用函数,您需要在基于资源的策略语句中向其授予权限。您可以使用 AWS CLI add-permission 命令添加此语句。

授予 Amazon SNS 调用函数的权限
  • 账户 B 中,使用之前记下的 Amazon SNS 主题的 ARN 运行以下 AWS CLI 命令。

    aws lambda add-permission --function-name Function-With-SNS \ --source-arn arn:aws:sns:us-east-1:<AccountA_ID>:sns-topic-for-lambda \ --statement-id function-with-sns --action "lambda:InvokeFunction" \ --principal sns.amazonaws.com --profile accountB

    您应该可以看到类似于如下所示的输出内容。

    { "Statement": "{\"Condition\":{\"ArnLike\":{\"AWS:SourceArn\": \"arn:aws:sns:us-east-1:<AccountA_ID>:sns-topic-for-lambda\"}}, \"Action\":[\"lambda:InvokeFunction\"], \"Resource\":\"arn:aws:lambda:us-east-1:<AccountB_ID>:function:Function-With-SNS\", \"Effect\":\"Allow\",\"Principal\":{\"Service\":\"sns.amazonaws.com\"}, \"Sid\":\"function-with-sns\"}" }
注意

如果在选择加入型 AWS 区域 中托管具有 Amazon SNS 主题的账户,则需要在主体中指定区域。例如,假设您正在亚太地区(香港)区域使用一个 Amazon SNS 主题,则需要为主体指定 sns.ap-east-1.amazonaws.com,而不是 sns.amazonaws.com

授予 Amazon SNS 订阅的跨账户权限(账户 A)

显示您正在订阅步骤中授予权限的教程工作流程图

要让账户 B 中的 Lambda 函数订阅您在账户 A 中创建的 Amazon SNS 主题,您需要向账户 B 授予订阅主题的权限。您可以使用 AWS CLI add-permission 命令授予此权限。

授予账户 B 订阅主题的权限
  • 账户 A 中,运行以下 AWS CLI 命令。使用之前记下的 Amazon SNS 主题的 ARN。

    aws sns add-permission --label lambda-access --aws-account-id <AccountB_ID> \ --topic-arn arn:aws:sns:us-east-1:<AccountA_ID>:sns-topic-for-lambda \ --action-name Subscribe ListSubscriptionsByTopic --profile accountA

创建订阅(账户 B)

显示您正在订阅步骤中创建订阅的教程工作流程图

账户 B 中,您现在将 Lambda 函数订阅到教程开始时您在账户 A 中创建的 Amazon SNS 主题。当消息发送到该主题 (sns-topic-for-lambda) 后,Amazon SNS 会调用账户 B 中的 Lambda 函数 Function-With-SNS

创建订阅
  • 账户 B 中,运行以下 AWS CLI 命令。使用您在其中创建主题的默认区域以及主题和 Lambda 函数的 ARN。

    aws sns subscribe --protocol lambda \ --region us-east-1 \ --topic-arn arn:aws:sns:us-east-1:<AccountA_ID>:sns-topic-for-lambda \ --notification-endpoint arn:aws:lambda:us-east-1:<AccountB_ID>:function:Function-With-SNS \ --profile accountB

    您应该可以看到类似于如下所示的输出内容。

    { "SubscriptionArn": "arn:aws:sns:us-east-1:<AccountA_ID>:sns-topic-for-lambda:5d906xxxx-7c8x-45dx-a9dx-0484e31c98xx" }

将消息发布到主题(账户 A 和账户 B)

显示您正在发布步骤中发布消息的教程工作流程图

账户 B 中的 Lambda 函数现已订阅账户 A 中的 Amazon SNS 主题,现在可以通过将消息发布到主题来测试设置了。要确认 Amazon SNS 是否已调用 Lambda 函数,可以使用 CloudWatch Logs 查看函数的输出。

将消息发布到主题并查看函数的输出
  1. 在文本文件中输入 Hello World,并将其另存为 message.txt

  2. 在保存文本文件的同一目录中,在账户 A 中运行以下 AWS CLI 命令。将 ARN 用于您自己的主题。

    aws sns publish --message file://message.txt --subject Test \ --topic-arn arn:aws:sns:us-east-1:<AccountA_ID>:sns-topic-for-lambda \ --profile accountA

    这将返回一个具有唯一标识符的消息 ID,表明 Amazon SNS 服务已接受该消息。然后,Amazon SNS 则尝试将消息传输给主题订阅用户。要确认 Amazon SNS 是否已调用 Lambda 函数,可以使用 CloudWatch Logs 查看函数的输出:

  3. 账户 B 中,打开 Amazon CloudWatch 控制台的日志组页面。

  4. 选择函数 (/aws/lambda/Function-With-SNS) 的日志组。

  5. 选择最新的日志流。

  6. 如果函数已正确调用,您将看到类似于以下内容的输出,其中会显示发布到主题的消息内容。

    2023-07-31T21:42:51.250Z c1cba6b8-ade9-4380-aa32-d1a225da0e48 INFO Processed message Hello World 2023-07-31T21:42:51.250Z c1cba6b8-ade9-4380-aa32-d1a225da0e48 INFO done

清除资源

除非您想要保留为本教程创建的资源,否则可立即将其删除。通过删除您不再使用的 AWS 资源,可防止您的 AWS 账户 产生不必要的费用。

账户 A 中,清理您的 Amazon SNS 主题。

删除 Amazon SNS 主题
  1. 打开 Amazon SNS 控制台中的 Topics(主题)页面

  2. 选择您已创建的主题。

  3. 选择 删除

  4. 在文本输入字段中输入 delete me

  5. 选择 删除

账户 B 中,清理您的执行角色、Lambda 函数和 Amazon SNS 订阅。

删除执行角色
  1. 打开 IAM 控制台的角色页面

  2. 选择您创建的执行角色。

  3. 选择删除

  4. 在文本输入字段中输入角色名称,然后选择 Delete(删除)。

删除 Lambda 函数
  1. 打开 Lamba 控制台的 Functions(函数)页面

  2. 选择您创建的函数。

  3. 依次选择操作删除

  4. 在文本输入字段中键入 delete,然后选择 Delete(删除)。

删除 Amazon SNS 订阅
  1. 在 Amazon SNS 控制台中打开 Subscription(订阅)页面

  2. 选择您已创建的订阅。

  3. 选择 Delete(删除),Delete(删除)。