如何在 AWS CDK 创建的 Python Lambda 函数中安装外部模块?

How to install external modules in a Python Lambda Function created by AWS CDK?

我在 Cloud9 中使用 Python AWS CDK,我正在 部署一个简单的 Lambda 函数,它应该发送一个 API 请求 到 Atlassian API 当对象上传到 S3 Bucket(也是由 CDK 创建)时。这是我的 CDK 堆栈代码:

from aws_cdk import core
from aws_cdk import aws_s3
from aws_cdk import aws_lambda
from aws_cdk.aws_lambda_event_sources import S3EventSource


class JiraPythonStack(core.Stack):
    def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        # The code that defines your stack goes here
        jira_bucket = aws_s3.Bucket(self,
                                    "JiraBucket",
                                    encryption=aws_s3.BucketEncryption.KMS)

        event_lambda = aws_lambda.Function(
            self,
            "JiraFileLambda",
            code=aws_lambda.Code.asset("lambda"),
            handler='JiraFileLambda.handler',
            runtime=aws_lambda.Runtime.PYTHON_3_6,
            function_name="JiraPythonFromCDK")

        event_lambda.add_event_source(
            S3EventSource(jira_bucket,
                          events=[aws_s3.EventType.OBJECT_CREATED]))

lambda 函数代码使用了我导入的 requests 模块。但是,当我检查 CloudWatch 日志并测试 lambda 函数时,我得到:

无法导入模块 'JiraFileLambda': 没有名为 'requests'

的模块

我的问题是:如何通过 Python CDK 安装请求模块?

I've already looked around online and found this。但它似乎直接修改了 lambda 函数,这会导致 Stack Drift(有人告诉我这对 IaaS 来说是不好的)。我也查看了 AWS CDK 文档,但没有发现任何关于外部 modules/libraries 的提及(我现在正在对其进行彻底检查)有人知道我如何解决这个问题吗?

编辑: It would appear I'm not the only one looking for this.

Here's another GitHub issue that's been raised.

在通过 CDK 部署 lambda 之前,您应该在本地安装 lambda 的依赖项。 CDK 不知道如何安装依赖项以及应该安装哪些库。

在你的情况下,你应该在执行 cdk deploy.

之前安装依赖项 requests 和其他库

例如,

pip install requests --target ./asset/package

an example供参考

更新:

现在看来,CDK 中似乎有一种新型(实验性)Lambda 函数,称为 PythonFunctionPython docs for it are here. And this includes support for adding a requirements.txt file which uses a docker container to add them to your function. See more details on that here。具体来说:

If requirements.txt or Pipfile exists at the entry path, the construct will handle installing all required modules in a Lambda compatible Docker container according to the runtime.

原答案:

所以这是我的经理写的我们现在使用的很棒的代码:


    def create_dependencies_layer(self, project_name, function_name: str) -> aws_lambda.LayerVersion:
        requirements_file = "lambda_dependencies/" + function_name + ".txt"
        output_dir = ".lambda_dependencies/" + function_name
        
        # Install requirements for layer in the output_dir
        if not os.environ.get("SKIP_PIP"):
            # Note: Pip will create the output dir if it does not exist
            subprocess.check_call(
                f"pip install -r {requirements_file} -t {output_dir}/python".split()
            )
        return aws_lambda.LayerVersion(
            self,
            project_name + "-" + function_name + "-dependencies",
            code=aws_lambda.Code.from_asset(output_dir)
        )

它实际上是 Stack class 作为方法的一部分(不在 init 内部)。我们在这里设置它的方式是我们有一个名为 lambda_dependencies 的文件夹,其中包含我们正在部署的每个 lambda 函数的文本文件,其中只有一个依赖项列表,例如 requirements.txt.

为了利用这段代码,我们在 lambda 函数定义中包含如下内容:


        get_data_lambda = aws_lambda.Function(
            self,
            .....
            layers=[self.create_dependencies_layer(PROJECT_NAME, GET_DATA_LAMBDA_NAME)]
        )

我也运行关注这个问题。当我在我的 ubuntu 机器上工作时,我使用了@Kane 和@Jamie 建议的解决方案就好了。但是,我 运行 在 MacOS 上工作时遇到了问题。显然一些(全部?)python 包在 lambda(linux env)上不起作用,如果它们是 pip 安装在不同的 os 上(参见 Whosebug post)

我的解决方案是 运行 在 docker 容器中安装 pip。这允许我从我的 macbook 进行 cdk 部署,而不是 运行 进入 lambda 中的 python 包的问题。

suppos你的 cdk 项目中有一个目录 lambda_layers/python,它将存放你的 python lambda 层包。

current_path = str(pathlib.Path(__file__).parent.absolute())
pip_install_command = ("docker run --rm --entrypoint /bin/bash -v "
            + current_path
            + "/lambda_layers:/lambda_layers python:3.8 -c "
            + "'pip3 install Pillow==8.1.0 -t /lambda_layers/python'")
subprocess.run(pip_install_command, shell=True)
lambda_layer = aws_lambda.LayerVersion(
    self,
    "PIL-layer",
    compatible_runtimes=[aws_lambda.Runtime.PYTHON_3_8],
    code=aws_lambda.Code.asset("lambda_layers"))

甚至没有必要使用 CDK 中的实验性 PythonLambda 功能 - CDK 内置支持将依赖项构建到简单的 Lambda 包(不是 docker 图像)。它使用 docker 进行构建,但最终结果仍然是一个简单的文件压缩包。文档在此处显示:https://docs.aws.amazon.com/cdk/api/latest/docs/aws-lambda-readme.html#bundling-asset-code;要点是:

new Function(this, 'Function', {
  code: Code.fromAsset(path.join(__dirname, 'my-python-handler'), {
    bundling: {
      image: Runtime.PYTHON_3_9.bundlingImage,
      command: [
        'bash', '-c',
        'pip install -r requirements.txt -t /asset-output && cp -au . /asset-output'
      ],
    },
  }),
  runtime: Runtime.PYTHON_3_9,
  handler: 'index.handler',
});

我在我的 CDK 部署中使用了这个确切的配置,它运行良好。

而对于 Python,它只是

aws_lambda.Function(
    self,
    "Function",
    runtime=aws_lambda.Runtime.PYTHON_3_9,
    handler="index.handler",
    code=aws_lambda.Code.from_asset(
        "function_source_dir",
        bundling=core.BundlingOptions(
            image=aws_lambda.Runtime.PYTHON_3_9.bundling_image,
            command=[
                "bash", "-c",
                "pip install --no-cache -r requirements.txt -t /asset-output && cp -au . /asset-output"
            ],
        ),
    ),
)

想分享我为此制作的 2 个模板存储库(深受上述一些启发):

希望对大家有所帮助:)

最后;如果您想查看有关此主题的长篇文章,请参阅此处:https://github.com/aws/aws-cdk/issues/3660