AWS Python Lambda 使用 Layer

使用 Python 书写 AWS Lambda 的一个好处就是能够在控制台中直接编辑源代码,非常方便进行快速验证测试 AWS 环境相关的。这只限于使用 AWS 为 Python Lambda 运行时提供的默认组件(比如 boto3),尚若需要在自己的 Python Lambda 中使用其他的组件(如 redis), 就不得不把自己的代码及依赖打成一个 zip 包再部署,这时候就无法在控制台直接编辑代码了,也只能坠入本地修改代码,重新打包上传测试的循环当中。

欲了解 Python Lambda 中除了 boto3 外还能直接使用别的什么组件,可点击此链接 https://gist.github.com/gene1wood/4a052f39490fae00e0c3 查看当前。该 gist 也还提供了代码 code to run in Lambda 来获得所有依赖。试了下在 Python Lambda 中,用通常的

help('modules')    # 或
help('modules package')

竟然连大名鼎鼎的 boto3 都无法列出来。

回到正题来,如果既想用第三方的依赖,又想要在控制台中直接编辑代码进行测试,是否有他法呢?有,那就是 AWS 在 2018 年 11 月推出的 Lambda 层。见 AWS Lambda Now Supports Custom Runtimes and Enables Sharing Common Code Between Functions, 这里的层除了能用来提供 Python 依赖,还许自定义运行时,如 C++ 或 Rust 等写 Lambda 都不是梦。

AWS 的服务就像个大口袋,何时偷偷的加添了什么服务,或出了什么新的我,不时关注它的 What's New with AWS 必是个好习惯。

Lamba 使用层的好处

Lambda 层的引入让多个 Lambda 中公共的依赖不需要重复的包含在每一个单独 Lambda 部署包中,相关联的依赖可组合做成一个个层,部署时只需要指定依赖于哪些个层,待到 Lambda 运行时就能使用到指定的依赖层。

Lambda 层可以让我们突破以往的一些限制 AWS Lambda Limits,比如单个 Lambda 部署压缩包有 50MB 大小限制,现在把共享依赖做成层的话,部署包只需要包含自己的实现代码部份。想像一下比如一个 Java 写的 Lambda,如果包含所有依赖打成一个包的话可能达到 30MB,而要是第三方依赖全部放到一个自定义层中,那么自己代码实现部分打成部署包或许只有几十 K 大小。单 AWS 帐号 Lambda 总的 75 GB 的大小限制更不太可能碰触到。

还有几点要注意下:

  1. 即使有了层,解压后的 Lambda,包括层的总大小不能超过 250 MB,因为运行时所有自己编写的代码与层中的依赖都要被解压到一处
  2. 一个 Lambda 可以依赖于最多五个层,比如把不同的依赖组合做成不同的层,部署时可以选择多个
  3. 还有 Python 控制台能直接编辑的代码大小不能超过 3 MB, 这个应该好控制

创建层只要到 AWS Lambda / Layers 中上为每个层制作符合规格的 zip 包上传就行了,层还支持版本。下面会讲到 Python 依赖层 zip 包的内部目录结构要求。下图直观的显示了 Lambda 怎么选择需要的层

这样的话,Lambda 只需要包含自己写的代码,无须把 Python 代码打包成 zip,从而达到了既能控制台编辑代码,又来使用第三方组件的要求。不同层的假如存在相同的依赖,解压顺序是由上至下,也就是说下面的会覆盖上方层的依赖。

制作 Python 依赖层方法 一

现才是本文重要的地方,即如何制作 Python 的 Lambda 依赖层。关于各种 Lambda 运行时层 zip 包的目录结构请参考官方的 Structure your layer. 其中 Python 层的例子如下:

pillow.zip
│ python/PIL
└ python/Pillow-5.3.0.dist-info

具体说,假如我们要制作一个包含了 redis 和 requests 的,如果谨尊以上目录结构,我们可用通过以下步骤来生成一个 python-redis-requests.zip 包

使用 Python 虚拟环境来构建永远是一个好习惯,如果不用虚拟环境,安装系统中已有的包会出现问题。

可用 unzip -l python-redis-requests.zip 来查看一下目录结构是否符合官方要求的,可能看到所有包都在 python 目录之下, python/redis, python/requests 等,没有 site-packages 之类的。

为什么要 certifi, idnaurllib3 呢,因为 requests 依赖于它们,如用 pip freeze 会看到

如果上面 pip freeze 的内容存成的 requirements.txt 文件,那么上面的 pip install redis requests 步骤可以替换为

pip install -r requrements.txt

上传生成的 python-redis-requests.zip 作为一个 Lambda 的层,为 Python Lambda 指定使用该层,这时候又能在 Lambda 控制台直接编辑 Python 代码引用 requests

制作 Python 依赖层方法二

像前面那样每次需要把在 Python 虚拟环境中用 pip 安装的组件(用 pip freeze 列出所有) 一一拷贝到 python 目录再压缩的方式有些罗嗦。我们知道用 pip 安装的组件会在虚拟环境的 lib/python3.7/site-packages 目录中,所以可以一股脑的把该目录中所有内容移到 python 目录,然后再剔除多余的 pipsetuptools 这两个体量不小还无用的目录 -- 这两目录压缩后有 3.4 MB,完整步骤如下

制作 Python 依赖层方法三

我们再进一步分析,Python 的 Lambda 层运行时其实是会被解压到 /opt 目录中,而 Lambda 会从 /opt/python 中寻找组件,这就是为什么我们的 zip 包中需要有 python 这一最外层目录。

我们可以做个测试,在 Lambda 中查看一下 Python 的模块搜索路径,用如下代码

可以得到如下输出

也就是说只要层中的内容能够被释放到相应的 /opt 目录中就行,有两个可选,分别是

/opt/python/lib/python3.7/site-packages
/opt/python

前面两种做法是让组件释放到  /opt/python 下,当然也可以释放到 /opt/python/lib/python3.7/site-package 中去,而这个目录名与我们的 Python 虚拟环境中安装的组件类似。因此另一种打包方式是

这样的 python-redis-requests.zip 层中的组件也可以在 Lambda 被引用到,但是有一个问题,它与 Lambda 默认的包混同在一块,很可能会产生冲突,把默认的同名组件覆盖了去。由于从路径搜索顺序来看 /opt/python/lib/python3.7/site-packages 是排在 /opt/python 前面的,因此还是建议按照官方的要求把自己的 Python Lambda 依赖层中组件运行时解压到 /opt/python 目录中。

制作 Python 依赖层方法四

为了让 Lambda 解压层中的依赖到 /opt/python,压缩包的顶层目录必须是 python,目前没有发现 zip 命令如何在 python 目录不实际存在的情况下,如何在打成的包中加上 python  目录。退而求其次,那么能不能直接把依赖安装到 python 目录中呢?试过

pip install --install-option="--prefix=python" redis

它只会在 python 目录中安装 python/lib/python3.7/site-packages/redis-3.2.1-py3.7.egg-info,能用的文件却没见着。可能还是哪里没用对。

好在 pip 还有一个更好的 --target 参数,有了这个参数甚至连创建虚拟环境的过程都可以省去。pip 安装全局环境中已存在的依赖会提示错误,所以总是建议创建虚拟环境。

pip --target=python 安装的依赖直接在 python 目录中,像 python/requests, python/redis, 而不是中间隔着一层 lib/python/3.7/site-packages 目录。所以对 python 直接打包即可,里面不存在多余的 pip, setuptools 等目录/文件。

但需要在 pip install 安装组件后还跑测试用例的话,还得往 sys.path 列表中加上 python 目录路径才成。

小结一下

最后就一句话,尽量使用官方要求的 Lambda 层的目录结构,所以这里建议用方法二和方法四,其中方法四的层会更干净。

链接:

  1. New for AWS Lambda - Use Any Programming Lanaguage and Share Common Components
  2. Exploring AWS Lambda Deployment Limits
  3. Structure your layer
  4. Getting started with AWS Lambda Layers for Python
  5. LAMBDA LAYERS FOR PYTHON RUNTIME

类别: AWS, Python. 标签: , . 阅读(28). 订阅评论. TrackBack.

Leave a Reply

avatar