- 引言
- 一、阿里云函数计算是什么?
- 开发流程
- 函数计算的触发调用
- 函数计算运行实例的生命周期
- 二、示例应用的架构及简介
- 三、具体开发部署步骤所遇到的坑和~~避坑指南~~
- 坑1. 超大依赖包的部署
- 坑2. Python 依赖包所需的额外工具库的正确打开方式
- 总结
- 参考资源
引言
无服务器计算(Serverless)是新近比较火的云计算范式。通过采用无服务器计算,开发者可以无需关心基础设施(如数据库、服务器等等),只专注于应用的业务逻辑。这种新的云计算范式极大地简化了云计算的部署和运维,尤其适合缺乏部署运维能力的中小企业。
Serverless 与 FaaS(函数即服务)通常被视为可以互换的术语,但这并不准确。Serverless 是一种抽象层次更高的架构模式,而“FaaS + BaaS”只是 Serverless 这种架构模式的一种实现。【1】
其中,FaaS 是一种特定类型的服务,例如 AWS Lambda、Google Cloud Functions、Azure Functions、阿里云函数计算和腾讯云云函数等等;而 BaaS(后端即服务)可以理解为其他类型的托管服务,例如数据库服务,对象存储服务和日志服务等等。【1】
本文将基于阿里云函数计算,实现一个无服务器 Python 应用,来对音频文件进行后期处理。其中将会涉及到 PyTorch 深度学习框架、librosa 音频处理库等超大依赖包在阿里云函数计算方式下的安装部署方法,特别是阿里云函数计算官方技术参考文档没有准确揭示的技术细节,以及连阿里云工单技术支持团队都毫无办法进行解答的技术难点。供大家在构建阿里云函数计算 Python 应用作为避坑指南参考。
一、阿里云函数计算是什么?
依据阿里云帮助中心产品文档的介绍,“函数计算是事件驱动的全托管计算服务。使用函数计算,您无需采购与管理服务器等基础设施,只需编写并上传代码。函数计算为您准备好计算资源,弹性地、可靠地运行任务,并提供日志查询、性能监控和报警等功能。”【2】
借助阿里云函数计算,开发者可以快速构建任何类型的应用和服务,并且只需为任务实际消耗的资源付费。【2】
函数计算的这种灵活性及按量计费的方式,非常适合中小微企业用于实现低成本的服务。尤其适合闲时用户量几乎没有,忙时用户量峰值极大的服务模式,从而省去租用服务器和自行维护基础设施的成本负担。
可以通过以下视频快速了解阿里云函数计算:
阿里云函数计算简介视频【2】
开发流程阿里云函数计算的工作流程请参见下图:
图源【2】:https://help-static-aliyun-doc.aliyuncs.com/assets/img/zh-CN/0087187951/p96487.png
从上述流程及步骤可以看出,阿里云函数计算有三种开发部署模式。【2】【3】
- 开发者通过阿里云函数计算控制台的网页版界面在线编写或上传已写好的代码;
- 离线先在本地搭建的 Serverless Devs 环境里编写代码并部署到阿里云函数计算环境;
- 在代码中通过阿里云函数计算 API 或 SDK 直接部署。
第一种方式适用于轻量级代码的函数计算部署,在代码层级较多、所需要的依赖包体积较大等情形下,就需要通过第二和第三种方式来部署了。第三种方式适用于需要通过自动化方式来部署函数计算方案的方式,使用方式较为复杂,属于高端技能。本文将基于第一种和第二种方式来部署示例 Python 应用,帮助快速了解函数计算的开发部署方式。
函数计算的触发调用在完成应用代码的上传部署后,理论上就已经完成了函数计算应用的部署。如果代码没有 bug,应用已经处于上线可触发调用的状态。此时通过阿里云函数计算所提供的调用方式,即可激活函数计算应用并返回执行结果。
如果代码有 bug,就需要根据阿里云函数计算接口所返回到错误信息进行 debug。例如由于不熟悉无服务器应用开发的特点,按照常规方式写代码,虽然在普通云服务器上部署可以正常运行,但部署到阿里云函数计算环境后出现错误。
阿里云函数计算目前提供的函数调用方式主要有两种,HTTP函数及事件函数,其相应的函数调用触发处理方式如下。【3】【4】【5】
- HTTP请求处理程序(HTTP Request Handler),通过函数计算的 URI 发起 HTTP 请求或 WebSocket 请求来对函数进行调用,该方式比较接近于常见的 Web 服务 API。
- 事件请求处理程序(Event Handler),通过指定触发函数的事件,在事件发生并满足设定条件时,由阿里云函数计算环境自动触发函数调用,该方式适用于处理除HTTP触发器以外的各种事件源触发的事件请求,例如难以主动预测到的数据库、网络存储事件等。
本文出于简化示例的复杂性,采用比较直观的 HTTP 请求处理方式来开发该函数计算应用。
函数计算运行实例的生命周期阿里云函数计算环境可以动态处理请求,并弹性安排计算资源,依据请求的并发情况动态创建函数计算运行实例;并在每个函数计算运行时实例长时间没有新增请求时,销毁函数计算运行实例,以节省资源和计费。下图展示了函数计算运行实例的生命周期。
图源:本文原创
二、示例应用的架构及简介本文的示例应用为一个基于 PyTorch 深度学习框架和 librosa 音频处理库的音频处理应用,可以用于对音频进行后期处理、音频分类、音乐信息检索等方面。
示例应用的主要架构如下图:
图源:本文原创
从示例应用的架构图可以看出,本应用符合无状态服务(stateless service)的定义——对单次请求的处理,不依赖其他请求,也就是说,处理一次请求所需的全部信息,要么都包含在这个请求里,要么可以从外部获取到(比如说数据库),服务器本身不存储任何信息。也因此非常适合采用函数计算的方式来实现本示例应用,在调用过后函数计算运行实例可以转入执行下一个服务请求或是销毁函数计算实例。而在没有新增服务请求后,可以销毁函数计算运行时实例,以节省资源和计费。
当然,上述应用架构中也可以加入存储机制或数据持久层,用以保存用户请求处理结果。本文从简化示例的角度出发,省去这些环节,以无状态服务的形式来实现应用。
三、具体开发部署步骤所遇到的坑和要基于阿里云函数计算环境开发一个函数计算应用,首先需要开通阿里云函数计算服务,并创建函数。这些前序操作步骤可以参考阿里云函数计算官方技术文档,本文在此不再赘述。【6】【7】
在创建好函数计算服务后,每一个函数计算服务中的创建的函数都具有类似以下示例的基本目录结构(以 Python 应用为例)。
--project_root
|--s.yaml
|--.fcignore
|--code
|--dependency_modules
|--index.py
...
其中 s.yaml 为函数计算的配置文件,.fcignore 为部署函数时需要忽略的本地目录或文件清单,code 目录下的 dependency_modules 指代用户业务代码所需要的依赖包,index.py 为 Python 版函数计算的服务入口。下文将会一一提到这些文件。
对于代码量小,以及连同所需要的依赖包(dependency modules)合并打包后 zip 文件小于 100MB 的应用来说,就可以通过阿里云函数计算 Web 版的控制台或 Serverless Devs 环境来上传代码包了。但如果遇到代码包超过 100MB,就会遇到一系列的函数计算环境限制,无法直接部署应用。
下文的重点将就代码包超过 100MB 的情形,指出阿里云函数计算官方技术参考文档没有准确揭示的技术细节,以及连阿里云工单技术支持团队都毫无办法进行解答的技术难点。
坑1. 超大依赖包的部署基于深度学习的 Python 应用通常需要调用 PyTorch、TensorFlow 等深度学习框架,而这些深度框架通常需要的存储空间都非常大。在基于常见云服务器或 Docker、kubernets(K8S)的部署方式里,开发者自行安装上述所需要的深度学习框架。在阿里云函数计算环境里,也同样需要开发者事先将所需的深度学习框架部署到阿里云函数计算环境中。
而依据阿里云函数计算环境的基本条件,函数计算实例规格有存储容量限制(下表中所列的数据为截止 2020 年 6 月 22 日所查询的结果)。【8】
| 实例类型 | 代码包大小上限 | 磁盘大小上限 |
|---|---|---|
| 弹性实例 | 100MB | 512MB |
| 性能实例 | 500MB | 10GB |
上述两类阿里云函数计算实例规格对于本文示例 Python 应用所用到的 PyTorch 深度学习框架而言(1.11.0 版本,安装后磁盘 torch 目录占用空间为 1.6GB,zip 打包后也将近 1GB),阿里云函数计算所提供的代码包大小上限完全不够用于安装 PyTorch 所需磁盘空间。
在实际部署时,无论通过阿里云函数计算 Web 控制台还是 Serverless Devs 环境进行部署,都会提示以下错误:
{"ErrorCode":"InvalidArgument","ErrorMessage":"Code size exceeds the maximum allowed length (max: 104857600, actual: '919937679')"}
详细查阅了阿里云函数计算产品文档后,没有找到解决办法。经开工单进行咨询,客服给出的解决方案是采用 Nas 文件系统来部署超大代码包。【9】
而要使用阿里云 Nas 文件存储,需要先开通 Nas 服务,详细的操作步骤可参阅阿里云 Nas 产品文档。【10】
在开通好 Nas 服务后,就可以在函数计算的函数配置中添加 Nas 路径了(注意,阿里云函数计算环境只允许 Nas 路径为以 /home/ 或者 /mnt/ 开头的子目录,且该 Nas 路径的访问权限必须为 777)。并在对应路径下安装 PyTorch 以及其它超大的依赖包,如本文所需用到的 librosa 音频处理包。
采用 Nas 文件系统安装超大依赖包,第一种也是最简便易行的方式就是通过将函数计算所用到的 Nas 文件系统挂载到一台阿里云 ECS 服务器上(操作系统最好为与阿里云函数计算环境所创建的运行实例一致的 Debian9 Linux),然后在该服务器上对挂载的 Nas 文件系统进行管理。例如,直接在这台 ECS 服务器上提前安装依赖包,例如安装本文所用到的 PyTorch。需要注意的是,阿里云 Nas 文件系统不允许跨地域挂载,因此开通的 Nas 文件系统服务与 ECS 服务器及函数计算实例必须在同一个地域内,否则无法挂载。
此外,在 ECS 服务器上对挂载 Nas 文件系统进行操作时,路径及文件访问权限必须为 777。因此,在该服务器上安装依赖包后,要注意查看文件访问权限是否为 777,否则可能导致函数计算运行实例在调用依赖包时因为访问权限出错。
当然,依赖包还可以采取第二种方式,即在开发人员的本地环境进行安装,然后通过阿里云函数计算的 Serverless Devs 环境来上传超大代码包。但这种命令行方式耗时时间长,需要稳定的网络连接,超大代码包发生传输中断后会很麻烦;而且最令人烦恼的是命令行终端没有及时的上传进度反馈,无法及时判断究竟是网络传输中断还是传输速度太慢。
无论采用上述两种安装部署依赖包的方式的哪一种,在通过 pip 安装 PyTorch 或其它依赖包时,需要通过-t参数指定安装路径,而不是让 pip 将依赖包安装到默认的本地 Python 环境中。因为安装到本地 Python 环境中的依赖包,只能在本地调用,不会被打包上传到函数计算环境中去。将会导致在函数计算运行时实例中将无法调用依赖包,因为函数计算运行时实例的 Python 环境默认是空的。最简单的路径设定为制定当前目录 .(即让实际运行代码在其所在目录中直接调用 PyTorch),并在打包上传依赖包时上传到所创建的函数计算函数实例所指定的 Nas 路径。以下对上述两种方式的具体操作给出示例。
- 登陆挂载 Nas 文件系统的 ECS 服务器进行安装 PyTorch 包。
首先需要切换到函数计算所配置指定的 Nas 目录中,例如本文例子的路径为:/mnt/aliyunfcdemo,在其中执行 pip 安装命令,并用-t参数指定安装路径为当前目录。
cd /mnt/aliyunfcdemo pip install -t . torch
如果 pip 下载速度过慢,可以通过-i参数引用国内镜像进行加速,例如:-i https://pypi.tuna.tsinghua.edu.cn/simple
安装好后,Nas 路径对应的目录结构应该如下:
/mnt
└── aliyunfcdemo
├── torch
└── torch-1.11.0.dist-info
- 通过 Serverless Devs 环境上传在本地开发环境安装的 PyTorch 包。
这种方式可以在本地选择一个全新的目录,在其中执行 pip 安装命令,并用-t参数指定安装路径为当前目录。
pip install -t . torch
同样可以引用国内镜像进行加速,例如:-i https://pypi.tuna.tsinghua.edu.cn/simple
安装完毕后,通过 Serverless Devs 环境上传安装好后的 PyTorch 目录。
s fc-deploy-test nas upload -r ./torch /mnt/aliyunfcdemo s fc-deploy-test nas upload -r ./torch-1.11.0.dist-info /mnt/aliyunfcdemo
也可以在本地由 Serverless Devs 环境为函数计算项目所创建的 s.yaml 文件所在的目录下用 pip 安装 PyTorch 包到该目录。为了方便管理,也可以将该目录创建为与函数计算所设定一致的路径中安装,例如与本文例子一致的/mnt/aliyunfcdemo。但需要注意的是,要在由 Serverless Devs 环境为函数计算项目所创建的 .fcignore 文件中将 PyTorch 目录忽略,否则在后续部署函数代码时会连同 PyTorch 相关目录一同打包为函数代码包,再次遇到因为打包后的代码包超过 100MB 限制导致上传出错。
/mnt
└── aliyunfcdemo
├── .fcignore
├── s.yaml
├── torch
├── torch-1.11.0.dist-info
└── index.py
直接将需要忽略的目录名或文件名写入到 .fcignore 文件里即可,其内容如以下编写示例:
torch torch-1.11.0.dist-info
这样在通过 Serverless Devs 环境上传安装好后的 PyTorch 目录,或是后续部署函数代码时都不会冲突了。
坑2. Python 依赖包所需的额外工具库的正确打开方式本文所需的另外一个 Python 依赖包为语音信号处理库 librosa,也采用同上面 PyTorch 一样的方式安装。
pip install -t . librosa
上传方式参考上面上传 PyTorch 的过程即可。
但本文走到这里就出现了第二个坑!librosa 库在 Linux 环境中使用时,需要搭配安装开源的音频编解码库 libsndfile。在普通的云服务器部署方式中,要自行通过apt install libsndfile1来安装这个由 C 语言开发的库。如果缺失这个库,Python 代码在import librosa时就会报错。例如直接在函数计算接口 index.py 文件中import librosa,在触发执行函数后,就会得到以下错误信息:
OSError: sndfile library not found
显然,阿里云函数计算环境所创建的 Debian9 Linux 运行实例里不会预先安装有 libsndfile 库,因此必须自行安装该库。详细查阅阿里云函数计算文档库没有解决方案后,再次开工单咨询客服。客服给出的解决方案是过 apt-get.list 进行安装,创建一个 apt-get.list 文件,里面写上需要安装的额外工具库,然后执行在 Serverless Devs 环境执行s build -d命令,所需的工具库就会被自动安装。这真是一个超级隐藏菜单!【11】
于是本文按照客服介绍的方法在 Serverless Devs 环境所创建 s.yaml 同级目录里创建了 apt-get.list 文件。
/mnt
└── aliyunfcdemo
├── .fcignore
├── apt-get.list
├── s.yaml
├── torch
├── torch-1.11.0.dist-info
└── index.py
apt-get.list 文件的内容为libsndfile1,即正常命令行执行apt-get install libsndfile1时使用的安装包名,其内容即以下示例:
libsndfile1
然后在 Serverless Devs 环境里执行部署命令s build -d(即—use—docker参数)。这里补充说明的是,由于阿里云函数计算的运行环境(Debian9)与用户本地的开发环境可能存在比较大的不同,会导致一部分用户本地安装/构建的依赖、代码包等,在线上无法正常运行。因此,阿里云函数计算所使用的 Serverless Devs 环境可以在开发本地环境中创建虚拟环境,例如先通过 Docker 容器进行本地构建,以尽可能地保证构建出来的依赖/产物,部署到线上可以得到良好的使用。具体的操作过程可以参阅阿里云函数计算 gitee 文档中对 Build 命令的说明。【11】【12】
但是很遗憾,按上述客服建议创建 apt-get.list 文件并在 Serverless Devs 环境里执行部署命令s build -d后仍然报错说 libsndfile 库没有安装。
OSError: sndfile library not found
而且由于阿里云函数计算环境返回的出错信息非常有限,很难判断究竟是哪里出了问题。但是一个基本的判断就是通过 apt-get.list 方式安装的 libsndfile 没有正确安装或者安装在非常见的路径当中,从而导致 Python 代码无法正确调用到 libsndfile 库。
通过漫长地试错过程,以及查看 Serverless Devs 环境在本地构建的 Docker 容器,最后发现阿里云函数计算实际上是将 apt-get.list 里指定安装的软件安装到函数计算运行环境 Debian9 运行实例的一个特别目录中:
/code . ├── .s │ └── root │ └── usr │ ├── bin │ │ └── tree │ ├── lib │ │ └── x86_64-linux-gnu │ │ ├── libFLAC.so.8 -> libFLAC.so.8.3.0 │ │ ├── libFLAC.so.8.3.0 │ │ ├── libogg.so.0 -> libogg.so.0.8.2 │ │ ├── libogg.so.0.8.2 │ │ ├── libsndfile.so.1 -> libsndfile.so.1.0.27 │ │ ├── libsndfile.so.1.0.27 │ │ ├─
然后期望在由 Serverless Devs 环境所创建的函数设置文件 s.yaml 中设定environmentVariables的LD_LIBRARY_PATH: 来实现对 apt-get.list 里指定安装的软件的引用。
edition: 1.0.0 # 命令行YAML规范版本,遵循语义化版本(Semantic Versioning)规范
name: aliyunfcdemo # 项目名称
access: "luo20220415" # 密钥别名
services:
aliyunfcdemo: # 服务名称
component: devsapp/fc # 组件名称
props: # 组件的属性值
...
environmentVariables:
PYTHONUSERBASE: /mnt/aliyunfcdemo
LD_LIBRARY_PATH: /code/.s/root/usr/local/lib:/code/.s/root/usr/lib:/code/.s/root/usr/lib/x86_64-linux-gnu:/code/.s/root/usr/lib64:/code/.s/root/lib:/code/.s/root/lib/x86_64-linux-gnu:/code/.s/root/python/lib/python2.7/site-packages:/code/.s/root/python/lib/python3.6/site-packages:/code:/code/lib:/usr/local/lib
经过试错,本文发现对于阿里云函数计算运行环境 Debian9 实例没有预安装的命令行工具 tree,通过上述方式安装确实可以正常使用;但是对于 C 语言库 libsndfile 就是无法调用。
再次开工单咨询阿里云客服团队,他们也说没有其它办法了,并给出了一个“终极秘籍”——登陆到函数计算的运行示例 Debian9 服务器里查看问题所在。【13】
本文再次按照阿里云工单客服团队的上述建议,在通过 HTTP/HTTPS 请求触发激活一个函数实例后,通过上述“终极秘籍”登陆到对应的函数计算的运行实例的 Debian9 服务器里进行查看。此时函数计算首次返回的执行结果仍然是 libsndfile 库不存在:
OSError: sndfile library not found
再次的试错表明,如果在运行示例 Debian9 服务器通过在终端命令行中以 root 身份执行apt-get install libsndfile1操作后可以实现对 libsndfile 库的热修复(hot-fix)。Python 业务代码在import librosa时不再提示找不到 libsndfile 库的错误。通过实际查看 Debian9 系统正常情况下安装工具库的默认路径/usr/lib/x86_64-linux-gnu/目录,经过上述热修复操作后,libsndfile 库被正确装上了。也就是说,阿里云函数计算运行实例在函数触发时,librosa 引用 libsndfile 的路径依然是系统默认的路径/usr/lib/x86_64-linux-gnu//libsndfile.so.1,而并不是 apt-get.list 所安装的/code/.s/root/usr/lib/x86_64-linux-gnu/libsndfile.so.1。 Serverless Devs 环境在s build --use-docker和s deploy过程中所添加的LD_LIBRARY_PATH和PATH对于 libsndfile 这类的函数库也没有按预设想的那样发挥作用。阿里云给出的“终极秘籍”只能是登陆到对应的函数计算的运行实例的 Debian9 服务器里进行热修复!
但显然这种热修复的方式在生产环境是不现实的,因为每一次触发激活新的阿里云函数计算运行实例都需要进行上述热修复,这将会大大增加运行实例的冷启动时间,而且还容易发生由于网络错误等原因造成不可控的失误。
在阿里云工单服务团队也束手无策,同时不论是阿里云产品文档和网络搜索都没能找到有用信息后,本文只能自行思考解决方案。毕竟,如果因为这样一个小错误就导致阿里云函数计算无法正常运行的话,那这真是个鸡肋。
在后续的试错过程中,既然通过阿里云函数计算环境本身无法让 Python 业务代码正确找到 libsndfile 库,那么就本文尝试从 Python 本身去寻找解决方案。功夫不负有心人,本文最终在 Python 自身找到了非常简单易行的解决方案——真的是“踏破铁鞋无觅处,得来全不费功夫”!
对于 Python 而言,查找依赖库一般是通过 ctypes.util 包的 find_library 方法在系统里查找依赖包和 C/C++ 动态链接库。但是截止目前最新版的ctypes.util.find_library只会在 Linux 系统默认路径中查找相应 C/C++ 动态链接库,而且即便找到后仅返回文件名,不包括完整路径。【14】
也因此导致 Python 代码无法通过 Serverless Devs 环境在s deploy过程中设置的LD_LIBRARY_PATH和PATH变量来定位到所需调用的 C/C++ 动态链接库,也就是本文中所报告的 libsndfile 库无法正确调用的根本原因。开发者需要根据具体的情况,改造通过ctypes.util.find_library查找所需用的工具库的代码。
本文最终找到的最简捷的解决方案是将 Serverless Devs 环境在s build -d; s deploy过程中所安装到/code/.s/root/usr/lib/x86_64-linux-gnu/路径中的 C/C++ 动态链接库直接硬编码到相应的 Python 依赖包的源代码当中。
就本文而言,就是在 librosa 依赖包的源代码 soundfile.py 文件中修改添加以下代码:
_libname = "/code/.s/root/usr/lib/x86_64-linux-gnu/libsndfile.so.1"
至此,本文的 Python 函数计算示例应用业务代码终于可以像原来部署在普通云服务器上一样正常运行并返回处理结果了!
总结
在前前后后折腾了将近二十天后,本文得出的总结是:
- 阿里云函数计算的实现架构由各自独立又相互关联的三个部分组成:虚拟化的最小 Debian9 Linux 操作系统、函数计算的函数触发机制,以及通过 Serverless Devs 环境构建的部署代码。用户在构建函数计算应用时,必须依据上述组成方式安排和部署业务代码,而不能像在普通云服务器上一样的方式去构想和开发部署业务代码。
- 作为新兴的云计算模式,无服务器计算确实与普通云计算模式有着很大的不同,目前各大厂商所给出的无服务器产品无论在技术体系或技术细节上都存在着巨大的差异。在一个厂商上实现的无服务器计算方案,并不能直接迁移到另一厂商的无服务器平台上去。开发人员自身仍然需要掌握全栈的技能,方可在无服务器计算领域游刃有余。无服务器厂商所鼓吹的“无服务器计算简化了云计算的部署和运维,尤其适合缺乏部署运维能力的中小企业”,就目前而言仍然任重道远。
- 作为客服团队,应当具备能仿真用户实际环境进行错误重现的能力,才能给到用户能真正解决问题的解决方案。在整个工单咨询的过程中,阿里云服务团队给出的产品文档库解决方案示例以及示例代码,均与本文所需的 Python 方案无关,解决办法散落在 Note.js、PHP、Go 等其它开发语言的示例当中。虽然阿里云帮助中心的产品文档像模像样地给出了囊括了 Note.js、Python、PHP、Java、C#、Go 等开发语言的入门示例,但单独看每一个开发语言仍然缺乏完整系统的用户启动参考项目。
- 【1】 知乎:三分钟了解 Serverless 是什么
- 【2】阿里云帮助中心产品文档:什么是函数计算
- 【3】阿里云帮助中心产品文档:函数计算基本概念
- 【4】阿里云帮助中心产品文档:函数计算使用流程
- 【5】阿里云帮助中心产品文档:函数类型
- 【6】阿里云帮助中心产品文档:函数计算快速入门
- 【7】阿里云帮助中心产品文档:配置 HTTP 触发器并使用 HTTP 触发
- 【8】阿里云帮助中心产品文档:实例类型及使用模式
- 【9】阿里云函数计算 gitee 文档:大代码包部署的实践案例(需登陆 gitee 账号方能查看)
- 【10】阿里云帮助中心产品文档:文件存储 NAS
- 【11】阿里云函数计算 gitee 文档:Build 命令(需登陆 gitee 账号方能查看)
- 【12】阿里云帮助中心产品文档:使用Serverless Devs管理函数资源
- 【13】阿里云帮助中心产品文档:函数实例命令行操作
- 【14】Python Documentation:15.17.2.1. Finding shared libraries



