使用 cloudformation 为 S3 存储桶启用 Lambda 函数
Posted
技术标签:
【中文标题】使用 cloudformation 为 S3 存储桶启用 Lambda 函数【英文标题】:Enable Lambda function to an S3 bucket using cloudformation 【发布时间】:2016-07-20 05:32:27 【问题描述】:我们正在使用 CloudFormation 模板创建一个 S3 存储桶。每当将文件添加到 S3 存储桶时,我想关联(向 S3 存储桶添加事件)一个 Lambda 函数。
如何通过 CloudFormation 模板实现。 CloudFormation 中需要用到哪些属性。
【问题讨论】:
您希望在创建存储桶或在存储桶中创建对象(PutObject)时触发 lambda? 【参考方案1】:这是一个完整的、自包含的 CloudFormation 模板,演示了如何在将文件添加到 S3 存储桶时触发 Lambda 函数:
Description: Upload an object to an S3 bucket, triggering a Lambda event, returning the object key as a Stack Output.
Parameters:
Key:
Description: S3 Object key
Type: String
Default: test
Body:
Description: S3 Object body content
Type: String
Default: TEST CONTENT
BucketName:
Description: S3 Bucket name
Type: String
Resources:
Bucket:
Type: AWS::S3::Bucket
DependsOn: BucketPermission
Properties:
BucketName: !Ref BucketName
NotificationConfiguration:
LambdaConfigurations:
- Event: 's3:ObjectCreated:*'
Function: !GetAtt BucketWatcher.Arn
BucketPermission:
Type: AWS::Lambda::Permission
Properties:
Action: 'lambda:InvokeFunction'
FunctionName: !Ref BucketWatcher
Principal: s3.amazonaws.com
SourceAccount: !Ref "AWS::AccountId"
SourceArn: !Sub "arn:aws:s3:::$BucketName"
BucketWatcher:
Type: AWS::Lambda::Function
Properties:
Description: Sends a Wait Condition signal to Handle when invoked
Handler: index.handler
Role: !GetAtt LambdaExecutionRole.Arn
Code:
ZipFile: !Sub |
exports.handler = function(event, context)
console.log("Request received:\n", JSON.stringify(event));
var responseBody = JSON.stringify(
"Status" : "SUCCESS",
"UniqueId" : "Key",
"Data" : event.Records[0].s3.object.key,
"Reason" : ""
);
var https = require("https");
var url = require("url");
var parsedUrl = url.parse('$Handle');
var options =
hostname: parsedUrl.hostname,
port: 443,
path: parsedUrl.path,
method: "PUT",
headers:
"content-type": "",
"content-length": responseBody.length
;
var request = https.request(options, function(response)
console.log("Status code: " + response.statusCode);
console.log("Status message: " + response.statusMessage);
context.done();
);
request.on("error", function(error)
console.log("send(..) failed executing https.request(..): " + error);
context.done();
);
request.write(responseBody);
request.end();
;
Timeout: 30
Runtime: nodejs4.3
Handle:
Type: AWS::CloudFormation::WaitConditionHandle
Wait:
Type: AWS::CloudFormation::WaitCondition
Properties:
Handle: !Ref Handle
Timeout: 300
S3Object:
Type: Custom::S3Object
Properties:
ServiceToken: !GetAtt S3ObjectFunction.Arn
Bucket: !Ref Bucket
Key: !Ref Key
Body: !Ref Body
S3ObjectFunction:
Type: AWS::Lambda::Function
Properties:
Description: S3 Object Custom Resource
Handler: index.handler
Role: !GetAtt LambdaExecutionRole.Arn
Code:
ZipFile: !Sub |
var response = require('cfn-response');
var AWS = require('aws-sdk');
var s3 = new AWS.S3();
exports.handler = function(event, context)
console.log("Request received:\n", JSON.stringify(event));
var responseData = ;
if (event.RequestType == 'Create')
var params =
Bucket: event.ResourceProperties.Bucket,
Key: event.ResourceProperties.Key,
Body: event.ResourceProperties.Body
;
s3.putObject(params).promise().then(function(data)
response.send(event, context, response.SUCCESS, responseData);
).catch(function(err)
console.log(JSON.stringify(err));
response.send(event, context, response.FAILED, responseData);
);
else if (event.RequestType == 'Delete')
var deleteParams =
Bucket: event.ResourceProperties.Bucket,
Key: event.ResourceProperties.Key
;
s3.deleteObject(deleteParams).promise().then(function(data)
response.send(event, context, response.SUCCESS, responseData);
).catch(function(err)
console.log(JSON.stringify(err));
response.send(event, context, response.FAILED, responseData);
);
else
response.send(event, context, response.SUCCESS, responseData);
;
Timeout: 30
Runtime: nodejs4.3
LambdaExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal: Service: [lambda.amazonaws.com]
Action: ['sts:AssumeRole']
Path: /
ManagedPolicyArns:
- "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
Policies:
- PolicyName: S3Policy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- 's3:PutObject'
- 'S3:DeleteObject'
Resource: !Sub "arn:aws:s3:::$BucketName/$Key"
Outputs:
Result:
Value: !GetAtt Wait.Data
【讨论】:
非常感谢您的工作示例。它节省了很多时间!但是你能告诉我为什么那个模板中有S3Object
吗?我没有看到它在任何地方被引用?
@Cherry S3Object
和 S3ObjectFunction
自动在 S3 存储桶中创建一个对象来触发 Lambda 函数,仅用于测试。通常这将由您手动上传的对象触发,因此在您自己的实现中不需要它。
@wjordan S3Object 是否有理由需要“ServiceToken”?每当我尝试删除更多文件时,都会调用“BucketWatcher”,但不会调用“S3ObjectFunction”。这是权限/角色问题吗?我正在手动将文本文件放入创建的“存储桶”中。
我意识到,该文件实际上需要命名为“test”才能正常工作。这就是为什么我的“S3ObjectFunction”没有被调用
2021 年 4 月:这个 sn-p 在我运行时不会创建任何函数。有谁知道如何让它工作?【参考方案2】:
您的 CloudFormation 模板中需要一个 NotificationConfiguration
属性。不幸的是,它似乎要求存储桶已经存在。为了解决这个问题,您可以创建一个初始堆栈,然后使用 NotificationConfiguration
对其进行更新。例如:
// template1.json
"AWSTemplateFormatVersion": "2010-09-09",
"Parameters":
"mylambda":
"Type": "String"
,
"Resources":
"bucketperm":
"Type": "AWS::Lambda::Permission",
"Properties" :
"Action": "lambda:InvokeFunction",
"FunctionName": "Ref": "mylambda",
"Principal": "s3.amazonaws.com",
"SourceAccount": "Ref": "AWS::AccountId",
"SourceArn": "Fn::Join": [":", [
"arn", "aws", "s3", "" , "", "Ref" : "mybucket"]]
,
"mybucket":
"Type": "AWS::S3::Bucket"
// template2.json -- adds the NotificationConfiguration
"AWSTemplateFormatVersion": "2010-09-09",
"Parameters":
"mylambda":
"Type": "String"
,
"Resources":
"bucketperm":
"Type": "AWS::Lambda::Permission",
"Properties" :
"Action": "lambda:InvokeFunction",
"FunctionName": "Ref": "mylambda",
"Principal": "s3.amazonaws.com",
"SourceAccount": "Ref": "AWS::AccountId",
"SourceArn": "Fn::Join": [":", [
"arn", "aws", "s3", "" , "", "Ref" : "mybucket"]]
,
"mybucket":
"Type": "AWS::S3::Bucket",
"Properties":
"NotificationConfiguration":
"LambdaConfigurations": [
"Event" : "s3:ObjectCreated:*",
"Function" : "Ref": "mylambda"
]
您可以使用 AWS CLI 工具来创建堆栈,如下所示:
$ aws cloudformation create-stack --stack-name mystack --template-body file://template1.json --parameters ParameterKey=mylambda,ParameterValue=<lambda arn>
# wait until stack is created
$ aws cloudformation update-stack --stack-name mystack --template-body file://template2.json --parameters ParameterKey=mylambda,ParameterValue=<lambda arn>
【讨论】:
我们是否需要在 CFT 的资源部分为 lambda 编写代码?正如你所指的 mylambda?? 问题是如何在创建存储桶时触发 lambda,而不是在将新文件添加到存储桶时。 不必在同一个模板中定义 lambda。存储桶配置只需要 lambda ARN。如果您的 lambda 设置在其他地方,只需将 ARN 作为模板参数传入即可。 @ataylor 你能在你的代码中提到如何将 ARN 作为模板参数传递.. 我已经更新了我的答案,将 lambda ARN 作为参数。【参考方案3】:我在用于创建 S3 存储桶的 cloudformation 中添加了下面的存储桶 perm 以及通知配置。它起作用了!!
"bucketperm":
"Type": "AWS::Lambda::Permission",
"Properties":
"Action": "lambda:invokeFunction",
"FunctionName": "<arnvalue>",
"Principal": "s3.amazonaws.com"
【讨论】:
这似乎不起作用。这是必需的,但即使docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/… 的文档也提到这是问题的一部分。【参考方案4】:是的,可以通过 Cloudformation 实现,您需要配置的是:
1) AWS::S3::Bucket
资源和,
2) NotificationConfiguration
配置(在本例中使用 LambdaConfigurations
)用于上述 s3 资源。
您需要的相关文档:
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket.html#cfn-s3-bucket-notification
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket-notificationconfig.html
【讨论】:
【参考方案5】:在 AWS 文档中明确指出 AWS::S3::Bucket 用于创建资源,如果我们有一个已经存在的存储桶,我们不能修改它来添加 NotificationConfiguration。 因此,上述模板必须不存在 S3 存储桶才能正常工作。 让 CloudFormation 创建包括 S3 存储桶在内的所有资源。
【讨论】:
我用 NotificationConfiguration 和 Lambda Permission 更新了一个现有的存储桶,它工作正常。以上是关于使用 cloudformation 为 S3 存储桶启用 Lambda 函数的主要内容,如果未能解决你的问题,请参考以下文章
使用 CloudFormation 在 S3 存储桶中创建 Lambda 通知
CloudFormation 模板设置 S3 存储桶默认加密 [重复]
CloudFormation 中如何派生 S3 存储桶名称?