使用 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 的大小限制更不太可能碰触到。
还有几点要注意下:
- 即使有了层,解压后的 Lambda,包括层的总大小不能超过 250 MB,因为运行时所有自己编写的代码与层中的依赖都要被解压到一处
- 一个 Lambda 可以依赖于最多五个层,比如把不同的依赖组合做成不同的层,部署时可以选择多个
- 还有 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 包
1 2 3 4 5 6 7 8 9 10 11 |
python3 -m venv layer-venv cd layer-venv mkdir python source bin/activate pip install redis pip install requests cp -r lib/python3.7/site-packages/{redis,requests,certifi,idna,urllib3}* python zip -r python-redis-requests.zip python |
使用 Python 虚拟环境来构建永远是一个好习惯,如果不用虚拟环境,安装系统中已有的包会出现问题。
可用 unzip -l python-redis-requests.zip
来查看一下目录结构是否符合官方要求的,可能看到所有包都在 python
目录之下, python/redis
, python/requests
等,没有 site-packages
之类的。
为什么要 certifi
, idna
和urllib3
呢,因为 requests
依赖于它们,如用 pip freeze
会看到
1 2 3 4 5 6 |
certifi==2019.6.16 chardet==3.0.4 idna==2.8 redis==3.2.1 requests==2.22.0 urllib3==1.25.3 |
如果上面 pip freeze
的内容存成的 requirements.txt
文件,那么上面的 pip install redis requests
步骤可以替换为
pip install -r requrements.txt
上传生成的 python-redis-requests.zip 作为一个 Lambda 的层,为 Python Lambda 指定使用该层,这时候又能在 Lambda 控制台直接编辑 Python 代码引用 requests
了
1 2 3 4 5 |
import requests def lambda_handler(event, context): response = requests.get('https://yanbin.blog') print(response.text) |
制作 Python 依赖层方法二
像前面那样每次需要把在 Python 虚拟环境中用 pip
安装的组件(用 pip freeze
列出所有) 一一拷贝到 python 目录再压缩的方式有些罗嗦。我们知道用 pip
安装的组件会在虚拟环境的 lib/python3.7/site-packages
目录中,所以可以一股脑的把该目录中所有内容移到 python
目录,然后再剔除多余的 pip
和 setuptools
这两个体量不小还无用的目录 -- 这两目录压缩后有 3.4 MB,完整步骤如下
1 2 3 4 5 6 7 8 9 10 11 12 |
python3 -m venv layer-venv cd layer-venv mkdir python source bin/activate # 这些步骤是一样的 pip install -r requrements.txt cp -r lib/python3.7/site-packages/* python rm -r python/{pip*,setuptools*,easy_install.py,__pycache__,pkg_resources} zip -r python-redis-requests.zip python |
制作 Python 依赖层方法三
我们再进一步分析,Python 的 Lambda 层运行时其实是会被解压到 /opt
目录中,而 Lambda 会从 /opt/python
中寻找组件,这就是为什么我们的 zip 包中需要有 python
这一最外层目录。
我们可以做个测试,在 Lambda 中查看一下 Python 的模块搜索路径,用如下代码
1 2 3 4 |
import sys for line in sys.path print(f"'{line}'") |
可以得到如下输出
1 2 3 4 5 6 7 8 9 |
'/var/task' '/opt/python/lib/python3.7/site-packages' '/opt/python' '/var/runtime' '/var/lang/lib/python37.zip' '/var/lang/lib/python3.7' '/var/lang/lib/python3.7/lib-dynload' '/var/lang/lib/python3.7/site-packages' '/opt/python/lib/python3.7/site-packages' |
也就是说只要层中的内容能够被释放到相应的 /opt
目录中就行,有两个可选,分别是
/opt/python/lib/python3.7/site-packages
/opt/python
前面两种做法是让组件释放到 /opt/python
下,当然也可以释放到 /opt/python/lib/python3.7/site-package
中去,而这个目录名与我们的 Python 虚拟环境中安装的组件类似。因此另一种打包方式是
1 2 3 4 5 6 7 8 9 10 11 12 |
python3 -m venv layer-venv cd layer-venv mkdir python source bin/activate # 这些步骤是一样的 pip install -r requrements.txt cp -r lib python rm -r python/lib/python3.7/site-packages/{pip*,setuptools*,easy_install.py,__pycache__,pkg_resources}* zip -r python-redis-requests.zip 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
安装全局环境中已存在的依赖会提示错误,所以总是建议创建虚拟环境。
1 2 3 4 5 6 |
python3 -m venv layer-venv cd layer-venv source bin/activate mkdir python pip install --target=python -r requirements.txt zip -r python-redis-requests.zip python |
用 pip --target=python
安装的依赖直接在 python
目录中,像 python/requests
, python/redis
, 而不是中间隔着一层 lib/python/3.7/site-packages
目录。所以对 python
直接打包即可,里面不存在多余的 pip
, setuptools
等目录/文件。
但需要在 pip install
安装组件后还跑测试用例的话,还得往 sys.path
列表中加上 python
目录路径才成。
小结一下
最后就一句话,尽量使用官方要求的 Lambda 层的目录结构,所以这里建议用方法二和方法四,其中方法四的层会更干净。
链接:
- New for AWS Lambda - Use Any Programming Lanaguage and Share Common Components
- Exploring AWS Lambda Deployment Limits
- Structure your layer
- Getting started with AWS Lambda Layers for Python
- LAMBDA LAYERS FOR PYTHON RUNTIME
本文链接 https://yanbin.blog/aws-python-lambda-layer/, 来自 隔叶黄莺 Yanbin Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。