【问题标题】:How to install external modules in a Python Lambda Function created by AWS CDK?如何在 AWS CDK 创建的 Python Lambda 函数中安装外部模块?
【发布时间】:2020-03-10 08:24:37
【问题描述】:

我在 Cloud9 中使用 Python AWS CDK,并且我正在部署一个简单的 Lambda 函数,该函数应该向 Atlassian 的 API 发送 API 请求当对象上传到 S3 存储桶时(也由 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 安装 requests 模块?

I've already looked around online and found this。但它似乎直接修改了 lambda 函数,这会导致堆栈漂移(我被告知这对 IaaS 来说是坏的)。我也查看了 AWS CDK 文档,但没有发现任何外部模块/库的提及(我现在正在对其进行彻底检查)有人知道我该如何解决这个问题吗?

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

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

【问题讨论】:

    标签: python aws-lambda amazon-cloudformation python-module aws-cdk


    【解决方案1】:

    更新:

    现在看来,CDK 中有一种新类型的(实验性)Lambda 函数,称为 PythonFunctionPython docs for it are here。这包括对添加 requirements.txt 文件的支持,该文件使用 docker 容器将它们添加到您的函数中。 See more details on that here。具体来说:

    如果入口路径中存在 requirements.txt 或 Pipfile,则该构造将根据运行时将所有必需的模块安装到与 Lambda 兼容的 Docker 容器中。

    原答案:

    这是我的经理编写的一段很棒的代码,我们现在使用:

    
        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 类的一部分(不在 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)]
            )
    
    

    【讨论】:

    • 谢谢,我使用了一个 build.sh 脚本,我必须预先手动调用(在使用 cdk cli 部署之前)。该脚本或多或少与上述相同(对lamnda本身使用不同的requirements.txt并将其打包成一个zip。然后该zip就是我用from_asset()在顶部堆栈本身中引用的内容。但我不得不说,创建一个层并将其包含在 lambda 中感觉更好。肯定会看到我如何集成到我的解决方案中。
    【解决方案2】:

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

    在你的情况下,你应该在执行cdk deploy之前安装依赖requests和其他库。

    例如,

    pip install requests --target ./asset/package
    

    an example供参考。

    【讨论】:

    • 如果你使用 pipenv 运行以下命令:pipenv run pip install requestst --target ./asset/package
    • 我不确定我是否关注你。我是否需要使用lambda.Code.fromAsset 或以某种方式指示cdk 在哪里获取已安装的依赖项?还是cdk deploy默认取./asset/package上传到s3做其他魔术?
    • 您必须安装与您的处理程序一致的软件包。在 lamda 的文档中查看详细信息。
    • 我的经理实际上最终编写了一些非常简洁的代码,用于使用依赖层在 CDK + Lambda 环境中管理包。我会尽量记住在我回来工作时发布它。
    • 我整个上午都在试图弄清楚这一点。多谢你们。所以@Jamie 分享将不胜感激。虽然我自己会尝试使用构建脚本来生成一个部署包,然后我可以在堆栈 lambda 定义中引用它。
    【解决方案3】:

    甚至没有必要在 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"
                ],
            ),
        ),
    )
    

    【讨论】:

    • 是的......这是正确的答案。只为一些简单的包做一个层是不行的。
    • 第二。这个解决方案需要更多的支持,
    【解决方案4】:

    我也遇到了这个问题。当我在我的 ubuntu 机器上工作时,我使用了像 @Kane 和 @Jamie 这样的解决方案。但是,我在使用 MacOS 时遇到了问题。显然,如果将某些(全部?)python 包安装在不同的操作系统上,则它们不适用于 lambda(linux env)(请参阅 stackoverflow 帖子)

    我的解决方案是在 docker 容器中运行 pip install。这让我可以从我的 macbook 进行 cdk 部署,而不会在 lambda 中遇到我的 python 包的问题。

    假设您的 cdk 项目中有一个目录 lambda_layers/python,它将容纳 lambda 层的 python 包。

    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"))
    

    【讨论】:

    • 我更新了我的原始答案,现在包括来自 AWS 的官方(实验性)实现,其中结合了 docker 容器的使用。它可能是更适合您的简化解决方案!
    • 哦,太棒了!谢谢!
    猜你喜欢
    • 1970-01-01
    • 2021-04-08
    • 2020-03-13
    • 1970-01-01
    • 1970-01-01
    • 2020-03-18
    • 1970-01-01
    • 1970-01-01
    • 2021-02-03
    相关资源
    最近更新 更多