使用 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