使用 Cloudformation 部署 Lamba,包括函数将有权访问的参数
Using Cloudformation to Deploy Lamba, Including a Parameter that the function will have access to
我们有一个 API 将用于使用 Cloud Formation 在 AWS 中配置某些资源。这包括一个将事件发送到 S3 的 Lambda 函数,存储桶是可配置的。问题是,我们会在提供 lambda 时知道存储桶名称,而不是在 lambda 代码本身中。
据我所知,无法在配置时在 Cloud Formation 模板本身中注入 S3 存储桶名称。真的吗?
我能看到的唯一解决方案是动态生成函数代码,并将其嵌入到 Cloud Formation 模板中。这将使我们无法将任何 NPM 依赖项与函数代码一起使用。还有更好的选择吗?
目前无法将参数传递给事件本身旁边的 Lambda 函数。
如果您使用 CloudFormation 创建 Lambda 函数,您可以使用以下解决方法:
- 使用 Lambda 函数名称派生 CloudFormation 堆栈名称。
- 在执行 Lambda 函数时使用 CloudFormation 堆栈名称访问资源或堆栈参数。
所以,我意识到我从来没有用我的最终解决方案更新这个问题。我最终将一个代理 lambda 函数嵌入到 cloudformation 模板中,这使我能够注入模板参数。
示例:
{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "Creates a function to relay messages from a Kinesis instance to S3",
"Parameters": {
"S3Bucket" : {
"Type": "String",
"Description": "The name of the S3 bucket where the data will be stored"
},
"S3Key": {
"Type": "String",
"Description": "The key of the directory where the data will be stored"
}
},
"Resources": {
"mainLambda": {
"Type" : "AWS::Lambda::Function",
"Properties" : {
"Handler" : "index.handler",
"Description" : "Writes events to S3",
"Role" : { "Ref": "LambdaRoleARN" },
"Runtime" : "nodejs4.3",
"Code" : {
"S3Bucket": "streams-resources",
"S3Key": "astro-bass/${GIT_COMMIT}/lambda/astro-bass.zip"
}
}
},
"lambdaProxy": {
"Type" : "AWS::Lambda::Function",
"Properties" : {
"Handler" : "index.handler",
"Runtime" : "nodejs",
"Code" : {
"ZipFile": { "Fn::Join": ["", [
"var AWS = require('aws-sdk');",
"var lambda = new AWS.Lambda();",
"exports.handler = function(event, context) {",
"event.bundledParams = ['",
{ "Ref": "S3Bucket" },
"','",
{ "Ref": "S3Key" },
"'];",
"lambda.invoke({",
"FunctionName: '",
{ "Ref": "mainLambda" },
"',",
"Payload: JSON.stringify(event, null, 2),",
"InvocationType: 'Event'",
"}, function(err, data) {",
"if(err) {",
"context.fail(err);",
"}",
"context.done();",
"});",
"};"
]]}
}
}
},
},
...
}
代理函数将参数注入其代码 (s3bucket/key),然后它使用修改后的事件对象调用主 lambda。它有点不正统,但让我印象深刻的是它比其他可用的解决方案(例如解析 stacknames/etc)干净得多。到目前为止工作得很好。
请注意,此解决方案目前仅适用于遗留节点环境。这不是问题,但就此解决方案的寿命而言令人担忧。
更新:
我们 运行 陷入了先前解决方案的局限性,不得不设计另一种解决方案。我们最终使用描述字段的标签外用法来嵌入配置值。这是我们的 Lambda
'use strict';
var aws = require('aws-sdk');
var lambda = new aws.Lambda({apiVersion: '2014-11-11'});
let promise = lambda.getFunctionConfiguration({ FunctionName: process.env['AWS_LAMBDA_FUNCTION_NAME'] }).promise();
exports.handler = async function getTheConfig(event, context, cb) {
try {
let data = await promise;
cb(null, JSON.parse(data.Description).bucket);
} catch(e) {
cb(e);
}
};
然后,在描述字段中,您可以嵌入一个简单的 JSON 片段,如下所示:
{
"bucket": "bucket-name"
}
此外,此结构在处理程序外部使用 promise,将请求限制为仅在生成容器时发生 - 而不是针对每个单独的 lambda 执行。
这不是最干净的解决方案,但却是我们发现的最实用的解决方案。
我建议这样做。
首先创建一个 index.js 文件并添加此代码。
var AWS = require('aws-sdk');
const s3 = new AWS.S3();
const https = require('https');
exports.handler = (event, context, callback) => {
const options = {
hostname: process.env.ApiUrl,
port: 443,
path: '/todos',
method: 'GET'
};
const req = https.request(options, (res) => {
console.log('statusCode:', res.statusCode);
console.log('headers:', res.headers);
res.on('data', (d) => {
process.stdout.write(d);
});
});
req.on('error', (e) => {
console.error(e);
});
req.end();
};
压缩 index.js 文件并将其上传到与您的 lambda 函数位于同一区域的 S3 存储桶。
然后使用此 Cloudformation 模板确保指定正确的存储桶名称。
{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "ApiWorkflow",
"Metadata": {
},
"Parameters": {
"ApiUrl": {
"Description": "Specify the api url",
"Type": "String",
"Default": "jsonplaceholder.typicode.com"
},
},
"Mappings": {
},
"Conditions": {
},
"Resources": {
"lambdaVodFunction": {
"Type": "AWS::Lambda::Function",
"Properties": {
"Code": {
"S3Bucket": "lamdba-exec-tests",
"S3Key": "index.js.zip"
},
"Handler": "index.handler",
"Role": "arn:aws:iam::000000000:role/BasicLambdaExecRole",
"Runtime": "nodejs10.x",
"FunctionName": "ApiWorkflow",
"MemorySize": 128,
"Timeout": 5,
"Description": "Texting Lambda",
"Environment": {
"Variables": {
"ApiUrl": {
"Ref": "ApiUrl"
},
"Test2": "Hello World"
}
},
}
}
},
"Outputs": {
"ApiUrl": {
"Description": "Set api url",
"Value": {
"Ref": "ApiUrl"
}
}
}
}
您应该在模板中看到环境变量,您可以像这样在 NodeJS Lambda 函数中访问它们。
process.env.ApiUrl
我们有一个 API 将用于使用 Cloud Formation 在 AWS 中配置某些资源。这包括一个将事件发送到 S3 的 Lambda 函数,存储桶是可配置的。问题是,我们会在提供 lambda 时知道存储桶名称,而不是在 lambda 代码本身中。
据我所知,无法在配置时在 Cloud Formation 模板本身中注入 S3 存储桶名称。真的吗?
我能看到的唯一解决方案是动态生成函数代码,并将其嵌入到 Cloud Formation 模板中。这将使我们无法将任何 NPM 依赖项与函数代码一起使用。还有更好的选择吗?
目前无法将参数传递给事件本身旁边的 Lambda 函数。
如果您使用 CloudFormation 创建 Lambda 函数,您可以使用以下解决方法:
- 使用 Lambda 函数名称派生 CloudFormation 堆栈名称。
- 在执行 Lambda 函数时使用 CloudFormation 堆栈名称访问资源或堆栈参数。
所以,我意识到我从来没有用我的最终解决方案更新这个问题。我最终将一个代理 lambda 函数嵌入到 cloudformation 模板中,这使我能够注入模板参数。
示例:
{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "Creates a function to relay messages from a Kinesis instance to S3",
"Parameters": {
"S3Bucket" : {
"Type": "String",
"Description": "The name of the S3 bucket where the data will be stored"
},
"S3Key": {
"Type": "String",
"Description": "The key of the directory where the data will be stored"
}
},
"Resources": {
"mainLambda": {
"Type" : "AWS::Lambda::Function",
"Properties" : {
"Handler" : "index.handler",
"Description" : "Writes events to S3",
"Role" : { "Ref": "LambdaRoleARN" },
"Runtime" : "nodejs4.3",
"Code" : {
"S3Bucket": "streams-resources",
"S3Key": "astro-bass/${GIT_COMMIT}/lambda/astro-bass.zip"
}
}
},
"lambdaProxy": {
"Type" : "AWS::Lambda::Function",
"Properties" : {
"Handler" : "index.handler",
"Runtime" : "nodejs",
"Code" : {
"ZipFile": { "Fn::Join": ["", [
"var AWS = require('aws-sdk');",
"var lambda = new AWS.Lambda();",
"exports.handler = function(event, context) {",
"event.bundledParams = ['",
{ "Ref": "S3Bucket" },
"','",
{ "Ref": "S3Key" },
"'];",
"lambda.invoke({",
"FunctionName: '",
{ "Ref": "mainLambda" },
"',",
"Payload: JSON.stringify(event, null, 2),",
"InvocationType: 'Event'",
"}, function(err, data) {",
"if(err) {",
"context.fail(err);",
"}",
"context.done();",
"});",
"};"
]]}
}
}
},
},
...
}
代理函数将参数注入其代码 (s3bucket/key),然后它使用修改后的事件对象调用主 lambda。它有点不正统,但让我印象深刻的是它比其他可用的解决方案(例如解析 stacknames/etc)干净得多。到目前为止工作得很好。
请注意,此解决方案目前仅适用于遗留节点环境。这不是问题,但就此解决方案的寿命而言令人担忧。
更新: 我们 运行 陷入了先前解决方案的局限性,不得不设计另一种解决方案。我们最终使用描述字段的标签外用法来嵌入配置值。这是我们的 Lambda
'use strict';
var aws = require('aws-sdk');
var lambda = new aws.Lambda({apiVersion: '2014-11-11'});
let promise = lambda.getFunctionConfiguration({ FunctionName: process.env['AWS_LAMBDA_FUNCTION_NAME'] }).promise();
exports.handler = async function getTheConfig(event, context, cb) {
try {
let data = await promise;
cb(null, JSON.parse(data.Description).bucket);
} catch(e) {
cb(e);
}
};
然后,在描述字段中,您可以嵌入一个简单的 JSON 片段,如下所示:
{
"bucket": "bucket-name"
}
此外,此结构在处理程序外部使用 promise,将请求限制为仅在生成容器时发生 - 而不是针对每个单独的 lambda 执行。
这不是最干净的解决方案,但却是我们发现的最实用的解决方案。
我建议这样做。
首先创建一个 index.js 文件并添加此代码。
var AWS = require('aws-sdk');
const s3 = new AWS.S3();
const https = require('https');
exports.handler = (event, context, callback) => {
const options = {
hostname: process.env.ApiUrl,
port: 443,
path: '/todos',
method: 'GET'
};
const req = https.request(options, (res) => {
console.log('statusCode:', res.statusCode);
console.log('headers:', res.headers);
res.on('data', (d) => {
process.stdout.write(d);
});
});
req.on('error', (e) => {
console.error(e);
});
req.end();
};
压缩 index.js 文件并将其上传到与您的 lambda 函数位于同一区域的 S3 存储桶。
然后使用此 Cloudformation 模板确保指定正确的存储桶名称。
{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "ApiWorkflow",
"Metadata": {
},
"Parameters": {
"ApiUrl": {
"Description": "Specify the api url",
"Type": "String",
"Default": "jsonplaceholder.typicode.com"
},
},
"Mappings": {
},
"Conditions": {
},
"Resources": {
"lambdaVodFunction": {
"Type": "AWS::Lambda::Function",
"Properties": {
"Code": {
"S3Bucket": "lamdba-exec-tests",
"S3Key": "index.js.zip"
},
"Handler": "index.handler",
"Role": "arn:aws:iam::000000000:role/BasicLambdaExecRole",
"Runtime": "nodejs10.x",
"FunctionName": "ApiWorkflow",
"MemorySize": 128,
"Timeout": 5,
"Description": "Texting Lambda",
"Environment": {
"Variables": {
"ApiUrl": {
"Ref": "ApiUrl"
},
"Test2": "Hello World"
}
},
}
}
},
"Outputs": {
"ApiUrl": {
"Description": "Set api url",
"Value": {
"Ref": "ApiUrl"
}
}
}
}
您应该在模板中看到环境变量,您可以像这样在 NodeJS Lambda 函数中访问它们。
process.env.ApiUrl