配置 AWS Lambda 函数以使用最新版本的层
Configure AWS Lambda function to use latest version of a Layer
我在开发应用程序中有 20 多个 lambda 函数。还有一个包含大量通用代码的 lambda 层。
一个Lambda函数,将它挂接到层的特定版本,每次更新层时,它都会生成一个新版本。由于它是一个开发中的应用程序,我几乎每天都有新版本的层。这在每天必须接触的 lambda 函数上造成了混乱 - 升级层版本。
我知道在生产中冻结 lambda 函数的代码很重要,并且必须将一个版本的 lambda 函数挂接到该层的一个版本。
但是,对于开发环境,是否可以避免每次更新图层时都生成新的图层版本?或者配置 lambda 函数,使最新的 lambda 版本始终引用最新的层版本?
遗憾的是,目前无法引用最新版本,并且图层版本没有别名的概念。
最好的建议是自动执行此操作,这样每当您创建新的 Lambda 层版本时,它都会更新当前包含此 Lambda 层的所有 Lambda 函数。
要创建此事件触发器,请创建一个使用其事件侦听 PublishLayerVersion
事件的 CloudWatch 函数。
然后让它触发一个 Lambda,该 Lambda 会触发每个 Lambda 的 update-function-layers 函数,用新层替换它的层。
从@Chris 的回答中得到增强,您还可以在堆栈中使用 lambda-backed 自定义资源,并使用此 lambda 来更新带有新层 ARN 的目标配置。我注意到这一点,以防几天前我发现这个帖子时有人有类似的需求。
这个解决方案有一些注意事项:
- 客户资源的 lambda 必须将状态响应发送回触发器 CloudFormation (CFN) 端点,否则 CFN 堆栈将挂起直到超时(大约一个小时或更长时间,如果遇到问题,这是一个痛苦的过程在这个 lambda 上,要小心)
- 发送回响应的简单方法,您可以使用 cfnresponse(pythonic 方式),当您使用 CFN lambda 内联代码时,此库神奇地可用(CFN 在使用内联代码处理 CFN 时设置此库)并且必须有一行'import cfnresponse':D
- CFN 创建后不会触及自定义资源,因此当您为新层更改更新堆栈时,lambda 不会触发。让它移动的一个技巧是将自定义资源与自定义 属性 一起使用,然后你将更改此 属性 每次执行堆栈时都会更改一些内容,层版本 arn。所以这个自定义资源会被更新,意味着这个资源的lambda会在堆栈更新时被触发。
- 不确定为什么 lambda 层的逻辑名称更改为 AWS::Serverless:Layer 所以我不能依赖该层逻辑名称但我仍然有 !Ref 其 ARN
这是一个示例代码
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
myshared-libraries layer
Resources:
LambdaLayer:
Type: AWS::Serverless::LayerVersion
Properties:
LayerName: !Sub MyLambdaLayer
Description: Shared library layer
ContentUri: my_layer/layerlib.zip
CompatibleRuntimes:
- python3.7
ConsumerUpdaterLambda:
Type: AWS::Serverless::Function
Properties:
FunctionName: consumer-updater
InlineCode: |
import os, boto3, json
import cfnresponse
def handler(event, context):
print('EVENT:[{}]'.format(event))
if event['RequestType'].upper() == 'UPDATE':
shared_layer = os.getenv("DB_LAYER")
lambda_client = boto3.client('lambda')
consumer_lambda_list = ["target_lamda"]
for consumer in consumer_lambda_list:
try:
lambda_name = consumer.split(':')[-1]
lambda_client.update_function_configuration(FunctionName=consumer, Layers=[shared_layer])
print("Updated Lambda function: '{0}' with new layer: {1}".format(lambda_name, shared_layer))
except Exception as e:
print("Lambda function: '{0}' has exception: {1}".format(lambda_name, str(e)))
responseValue = 120
responseData = {}
responseData['Data'] = responseValue
cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData)
Handler: index.handler
Runtime: python3.7
Role: !GetAtt ConsumerUpdaterRole.Arn
Environment:
Variables:
DB_LAYER: !Ref LambdaLayer
ConsumerUpdaterRole:
Type: AWS::IAM::Role
Properties:
Path: /
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- Fn::Sub: arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Policies:
- PolicyName:
Fn::Sub: updater-lambda-configuration-policy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- lambda:GetFunction
- lambda:GetFunctionConfiguration
- lambda:UpdateFunctionConfiguration
- lambda:GetLayerVersion
- logs:DescribeLogGroups
- logs:CreateLogGroup
Resource: "*"
ConsumerUpdaterMacro:
DependsOn: ConsumerUpdaterLambda
Type: Custom::ConsumerUpdater
Properties:
ServiceToken: !GetAtt ConsumerUpdaterLambda.Arn
DBLayer: !Ref LambdaLayer
Outputs:
SharedLayer:
Value: !Ref LambdaLayer
Export:
Name: MySharedLayer
另一种选择是使用堆栈通知 ARN,它将所有堆栈事件发送到定义的 SNS,您将在其中使用它来触发更新 lambda。在您的 lambda 中,您将使用 AWS::Lambda::Layer 资源过滤 SNS 消息正文(这是一个可读的 json 喜欢的格式字符串),然后获取层 ARN 的 PhysicalResourceId。如何将 SNS 主题加入您的堆栈,使用 CLI sam/cloudformation 部署 --notification-arns 选项。不幸的是,CodePipeline 不支持此配置选项,因此您只能使用 CLI
用于 extract/filter 带有资源数据的 SNS 消息正文的 lambda 示例代码
import os, boto3, json
def handler(event, context):
print('EVENT:[{}]'.format(event))
resource_data = extract_subscription_msg(event['Records'][0]['Sns']['Message'])
layer_arn = ''
if len(resource_data) > 0:
if resource_data['ResourceStatus'] == 'CREATE_COMPLETE' and resource_data['ResourceType'] == 'AWS::Lambda::LayerVersion':
layer_arn = resource_data['PhysicalResourceId']
if layer_arn != '':
lambda_client = boto3.client('lambda')
consumer_lambda_list = ["target_lambda"]
for consumer in consumer_lambda_list:
lambda_name = consumer.split(':')[-1]
try:
lambda_client.update_function_configuration(FunctionName=consumer, Layers=[layer_arn])
print("Update Lambda: '{0}' to layer: {1}".format(lambda_name, layer_arn))
except Exception as e:
print("Lambda function: '{0}' has exception: {1}".format(lambda_name, str(e)))
return
def extract_subscription_msg(msg_body):
result = {}
if msg_body != '':
attributes = msg_body.split('\n')
for attr in attributes:
if attr != '':
items = attr.split('=')
if items[0] in ['PhysicalResourceId', 'ResourceStatus', 'ResourceType']:
result[items[0]] = items[1].replace('\'', '')
return result
根据 https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/lambda_layer_version
, 可以使用附加数据语句导出层的最新版本号
因此在您的定义模块中,您将拥有原始图层资源定义
resource "aws_lambda_layer_version" "layer_mylib" {
filename = "layer_mylib.zip"
layer_name = "layer_mylib"
compatible_runtimes = ["python3.6", "python3.7", "python3.8"]
}
然后要获取最新版本的 ARN,请使用
data "aws_lambda_layer_version" "mylatest" {
layer_name = aws_lambda_layer_version.layer_mylib.layer_name
}
然后data.aws_lambda_layer_version.mylatest.arn
会给出包含最新版本号的参考,可以通过放置
查看
output {
value = data.aws_lambda_layer_version.mylatest.arn
}
在你的common.tf
我在开发应用程序中有 20 多个 lambda 函数。还有一个包含大量通用代码的 lambda 层。
一个Lambda函数,将它挂接到层的特定版本,每次更新层时,它都会生成一个新版本。由于它是一个开发中的应用程序,我几乎每天都有新版本的层。这在每天必须接触的 lambda 函数上造成了混乱 - 升级层版本。
我知道在生产中冻结 lambda 函数的代码很重要,并且必须将一个版本的 lambda 函数挂接到该层的一个版本。
但是,对于开发环境,是否可以避免每次更新图层时都生成新的图层版本?或者配置 lambda 函数,使最新的 lambda 版本始终引用最新的层版本?
遗憾的是,目前无法引用最新版本,并且图层版本没有别名的概念。
最好的建议是自动执行此操作,这样每当您创建新的 Lambda 层版本时,它都会更新当前包含此 Lambda 层的所有 Lambda 函数。
要创建此事件触发器,请创建一个使用其事件侦听 PublishLayerVersion
事件的 CloudWatch 函数。
然后让它触发一个 Lambda,该 Lambda 会触发每个 Lambda 的 update-function-layers 函数,用新层替换它的层。
从@Chris 的回答中得到增强,您还可以在堆栈中使用 lambda-backed 自定义资源,并使用此 lambda 来更新带有新层 ARN 的目标配置。我注意到这一点,以防几天前我发现这个帖子时有人有类似的需求。
这个解决方案有一些注意事项:
- 客户资源的 lambda 必须将状态响应发送回触发器 CloudFormation (CFN) 端点,否则 CFN 堆栈将挂起直到超时(大约一个小时或更长时间,如果遇到问题,这是一个痛苦的过程在这个 lambda 上,要小心)
- 发送回响应的简单方法,您可以使用 cfnresponse(pythonic 方式),当您使用 CFN lambda 内联代码时,此库神奇地可用(CFN 在使用内联代码处理 CFN 时设置此库)并且必须有一行'import cfnresponse':D
- CFN 创建后不会触及自定义资源,因此当您为新层更改更新堆栈时,lambda 不会触发。让它移动的一个技巧是将自定义资源与自定义 属性 一起使用,然后你将更改此 属性 每次执行堆栈时都会更改一些内容,层版本 arn。所以这个自定义资源会被更新,意味着这个资源的lambda会在堆栈更新时被触发。
- 不确定为什么 lambda 层的逻辑名称更改为 AWS::Serverless:Layer 所以我不能依赖该层逻辑名称但我仍然有 !Ref 其 ARN
这是一个示例代码
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
myshared-libraries layer
Resources:
LambdaLayer:
Type: AWS::Serverless::LayerVersion
Properties:
LayerName: !Sub MyLambdaLayer
Description: Shared library layer
ContentUri: my_layer/layerlib.zip
CompatibleRuntimes:
- python3.7
ConsumerUpdaterLambda:
Type: AWS::Serverless::Function
Properties:
FunctionName: consumer-updater
InlineCode: |
import os, boto3, json
import cfnresponse
def handler(event, context):
print('EVENT:[{}]'.format(event))
if event['RequestType'].upper() == 'UPDATE':
shared_layer = os.getenv("DB_LAYER")
lambda_client = boto3.client('lambda')
consumer_lambda_list = ["target_lamda"]
for consumer in consumer_lambda_list:
try:
lambda_name = consumer.split(':')[-1]
lambda_client.update_function_configuration(FunctionName=consumer, Layers=[shared_layer])
print("Updated Lambda function: '{0}' with new layer: {1}".format(lambda_name, shared_layer))
except Exception as e:
print("Lambda function: '{0}' has exception: {1}".format(lambda_name, str(e)))
responseValue = 120
responseData = {}
responseData['Data'] = responseValue
cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData)
Handler: index.handler
Runtime: python3.7
Role: !GetAtt ConsumerUpdaterRole.Arn
Environment:
Variables:
DB_LAYER: !Ref LambdaLayer
ConsumerUpdaterRole:
Type: AWS::IAM::Role
Properties:
Path: /
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- Fn::Sub: arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Policies:
- PolicyName:
Fn::Sub: updater-lambda-configuration-policy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- lambda:GetFunction
- lambda:GetFunctionConfiguration
- lambda:UpdateFunctionConfiguration
- lambda:GetLayerVersion
- logs:DescribeLogGroups
- logs:CreateLogGroup
Resource: "*"
ConsumerUpdaterMacro:
DependsOn: ConsumerUpdaterLambda
Type: Custom::ConsumerUpdater
Properties:
ServiceToken: !GetAtt ConsumerUpdaterLambda.Arn
DBLayer: !Ref LambdaLayer
Outputs:
SharedLayer:
Value: !Ref LambdaLayer
Export:
Name: MySharedLayer
另一种选择是使用堆栈通知 ARN,它将所有堆栈事件发送到定义的 SNS,您将在其中使用它来触发更新 lambda。在您的 lambda 中,您将使用 AWS::Lambda::Layer 资源过滤 SNS 消息正文(这是一个可读的 json 喜欢的格式字符串),然后获取层 ARN 的 PhysicalResourceId。如何将 SNS 主题加入您的堆栈,使用 CLI sam/cloudformation 部署 --notification-arns 选项。不幸的是,CodePipeline 不支持此配置选项,因此您只能使用 CLI
用于 extract/filter 带有资源数据的 SNS 消息正文的 lambda 示例代码
import os, boto3, json
def handler(event, context):
print('EVENT:[{}]'.format(event))
resource_data = extract_subscription_msg(event['Records'][0]['Sns']['Message'])
layer_arn = ''
if len(resource_data) > 0:
if resource_data['ResourceStatus'] == 'CREATE_COMPLETE' and resource_data['ResourceType'] == 'AWS::Lambda::LayerVersion':
layer_arn = resource_data['PhysicalResourceId']
if layer_arn != '':
lambda_client = boto3.client('lambda')
consumer_lambda_list = ["target_lambda"]
for consumer in consumer_lambda_list:
lambda_name = consumer.split(':')[-1]
try:
lambda_client.update_function_configuration(FunctionName=consumer, Layers=[layer_arn])
print("Update Lambda: '{0}' to layer: {1}".format(lambda_name, layer_arn))
except Exception as e:
print("Lambda function: '{0}' has exception: {1}".format(lambda_name, str(e)))
return
def extract_subscription_msg(msg_body):
result = {}
if msg_body != '':
attributes = msg_body.split('\n')
for attr in attributes:
if attr != '':
items = attr.split('=')
if items[0] in ['PhysicalResourceId', 'ResourceStatus', 'ResourceType']:
result[items[0]] = items[1].replace('\'', '')
return result
根据 https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/lambda_layer_version
, 可以使用附加数据语句导出层的最新版本号因此在您的定义模块中,您将拥有原始图层资源定义
resource "aws_lambda_layer_version" "layer_mylib" {
filename = "layer_mylib.zip"
layer_name = "layer_mylib"
compatible_runtimes = ["python3.6", "python3.7", "python3.8"]
}
然后要获取最新版本的 ARN,请使用
data "aws_lambda_layer_version" "mylatest" {
layer_name = aws_lambda_layer_version.layer_mylib.layer_name
}
然后data.aws_lambda_layer_version.mylatest.arn
会给出包含最新版本号的参考,可以通过放置
查看output {
value = data.aws_lambda_layer_version.mylatest.arn
}
在你的common.tf