函数计算|如何使用层解决依赖包问题?

本文涉及的产品
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
容器镜像服务 ACR,镜像仓库100个 不限时长
应用实时监控服务-可观测链路OpenTelemetry版,每月50GB免费额度
简介: 本文首先介绍了自定义层的特点和困境,然后介绍了近期发布的公共层功能,详细陈述了基于官方公共层实现的两个示例程序,最后探讨了层的最佳实践是什么,希望通过本文能让读者更好的理解层的概念及其应用场景。

作者:落泥


在使用阿里云函数计算平台时,如果您曾经遇到过以下问题,本文应该会对您有所帮助:
  1. 第三方依赖包太大,每次更新代码都非常耗时,甚至会出现超过代码包限制的情况,我该怎么办?
  2. 安装第三方依赖包后,可以在本地运行成功,上传到阿里云函数计算平台上就会报错,这是什么情况?
  3. 有很多常用的依赖包,很多用户应该都会用到,阿里云函数计算官方不能直接内置到运行时环境中么?
  4. 我在多个函数中有相同的依赖包,我该如何管理这些相同的依赖包?

提供了集中管理,跨多个功能且能够共享代码和数据的方法。


2021 年 1 月,阿里云函数计算发布了 “自定义层”功能,让用户可以自定义层,并支持跨函数共享。2022 年 8 月,阿里云函数计算发布“公共层”功能,提供了官方公共层,供用户直接使用,进一步提升了用户体验。


接下来先介绍“自定义层”的功能和作用。


自定义层


在层功能发布之前,必须将代码与代码的依赖项一起打包和部署,这些依赖项在不同函数中可能是相同的,很多情况下这些依赖项的大小,远远大于代码的大小。


在层功能发布后,我们可以将代码的依赖项,或者多个函数中共享的部分打包成 Zip 压缩文件,并作为函数计算的自定义层发布,不同函数都可以使用该自定义层。阿里云函数计算会在调用时将层与函数代码一起加载。可参考文末文档-创建自定义层[1]、在函数中配置自定义层[2]


为什么使用自定义层?


使用自定义层有以下优势:


  • 跨函数复用代码
     

将多个函数中的通用代码或数据提取出来,打包成 Zip 包,做成自定义层,供不同函数引用,避免了在多个地方维护通用的代码或数据。


与此同时,也实现了依赖项和业务逻辑的分离,用户可以专注于核心的业务逻辑。


  • 使代码包更小
     

函数的代码包越来越大时,部署速度也会越来越慢,导致函数的维护和测试愈加困难。


此外函数代码包大小也有限制,比如阿里云函数计算的代码包限制为 500MB (2022 年 9 月),层是突破该限制的方法之一。层也有大小限制,目前单个层的代码包大小限制为 500MB,单个函数最多可配置5个层,总大小不能超过 2GB。


  • 加速代码部署,简化函数管理
     

函数代码包越小,代码包的部署就越快。尤其是一些大型依赖项时,核心功能代码可能只有几兆字节,但依赖项可能有几百兆。比如 Puppeteer 依赖包超过 100MB,阿里云的 DataX 依赖包超过 800MB。


一般来讲,这些依赖项很少修改,因此将他们打包成层后,可以避免在核心代码修改时频繁修改这些大型依赖项。对这些依赖项也可以拆分成多个层,每次修改一个功能时,只需要更新其中一个层。比如我们实现了自定义运行时 Python3.10 以及该运行时兼容的科学计算库 SciPy,可以将自定义运行时和依赖包拆成两个层,当需要更新依赖包时,只需要更新依赖包的层,而自定义运行时的层保持不变。


自定义层的困境


  • 制作层有一定门槛
     

层的 Zip 包有一定的格式规范,用户需要按照该规范进行制作。以 Python 的 requests 库为例,依赖打包后的文件结构为:


my-layer-code.zip
└── python
    └── requests


为什么有这种要求呢?这个涉及到不同运行时在搜索第三方依赖包的实现逻辑,以 Python 为例,Python 运行时会在 sys.path 路径下搜索依赖包,上面的 Zip 包会解压到函数实例的 /opt 目录下,解压后 requests 这个包就放到了 /opt/python 目录下。


然后,函数计算平台会将一些特定的目录放到运行时语言的依赖搜索路径上,比如 Python 运行时就会将 /opt/python 放到 sys.path 中,这样,代码中就可以直接引用 requests 库了。其他运行时的使用方法可参考文末文档-创建自定义层


当然,你也可以不按照这个格式规范来制作层,此时就需要在代码中添加对应的搜索路径了,具体方法可参考文末文档-如何在 Custom Runtime 中引用层中的依赖?[3]


需要在指定操作系统和处理器架构下制作层。有一些依赖是与操作系统和处理器架构有依赖关系的,比如 Python 的科学计算库 NumPy,假如你在 M1 芯片的 MacOS 下安装,其版本为:


numpy-1.23.3-cp39-cp39-macosx_11_0_arm64


可看到兼容的操作系统为 mac os, 处理器架构为 arm64。但在函数计算平台的实例环境为 Linux x86_64,操作系统目前使用的发行版为 Debian 9,因此在 M1 Mac 下安装的 NumPy 库不能在阿里云函数计算平台使用。


我们推荐在 Debian 9 系统下进行安装,但用户本地可能没有该环境,您可以使用在线构建依赖库或者使用函数计算官方运行时镜像来构建,此处不再赘述。


层需要包含新增的共享动态库。有些依赖库需要安装额外的共享动态库,在构建层的 Zip 包时也需要包含这些共享动态库。例如 Nodejs 的依赖库 Puppeteer,需要额外安装二十多个共享动态库(如 libxss1,libnspr4 等),这些依赖库都要打包到层 Zip 包中。如何成功的安装 Puppeteer 库并不是简单的事情。


共享动态库推荐放到 Zip 包的 lib 目录下,函数计算平台会将/opt/lib 目录添加到 LD_LIBRARY_PATH(仅限于内置运行时)。


  • 无法跨账号共享
     

自定义层默认只能在同账号同地域的不同函数之间共享,无法进行跨账号共享。因此,用户 A 创建的自定义层无法给用户 B 使用,这不仅给用户带来了重复的工作量,也不利于宿主机上相同层的复用。


公共层


由于自定义层的这些痛点,阿里云函数计算在 2022 年 8 月发布了公共层功能。实现层跨账号共享,并提供了一些官方公共层[6]供用户直接使用,方便用户快速开发示例原型。阿里云函数计算平台主要提供了三类官方公共层:


  • 自定义运行时(如 Python 3.10、Nodejs17、PHP 8.1、Java17、.NET 6 等)
  • 常用依赖库 (如 PyTorch、Scipy、Puppeteer 等)
  • 阿里云 SDK (如 Aliyun DataX ) 


详情可参考文末官方文档-在函数中配置官方公共层[4],目前官方公共层仍在持续补充,如果您有需要的运行时或者依赖库想通过官方公共层的方式使用,可通过钉钉答疑群(钉钉群号:11721331)与我们联系,也可以直接在 Github[5]提交 issue。


如何公开自定义层?

目前,层公开功能在内测中,如有需求欢迎通过钉钉答疑群(钉钉群号:11721331)联系我们。


同时,我们也非常欢迎大家贡献公共层到仓库,我们很快会在该仓库提供公共层贡献的方法和示例。


示例展示


官方公共层的最新版本和使用说明可参考 Github。下面我们介绍一些使用官方公共层的典型示例。


示例一、基于 Nodejs16 + Puppeteer 实现网页截图示例程序


Puppeteer 是一个 Nodejs 库,它提供了高级的 API 并通过 DevTools 协议来控制 Chrome(或 Chromium)。通俗来说就是一个 headless chrome 浏览器,可以使用它完成很多自动化的事情,比如:


  • 生成网页截图或者 PDF
  • 做表单的自动提交、UI 的自动化测试、模拟键盘输入等
  • more... 


本示例使用 Puppeteer 完成一个网页截图示例程序。


首先,我们使用内置运行时 Nodejs16 创建一个函数 start-puppeteer,其中请求处理程序类型选择“处理 HTTP 请求”。


1.png


然后,在高级配置中将内存规格设置为 1GB,示例程序的内存使用大概在 550MB 左右。


创建成功后,在控制台上打开 index.js 文件,将下面的代码拷贝并覆盖该文件,点击部署按钮。


const fs = require('fs');
const puppeteer = require('puppeteer');
function autoScroll(page) {
  return page.evaluate(() => {
      return new Promise((resolve, reject) => {
          var totalHeight = 0;
          var distance = 100;
          var timer = setInterval(() => {
              var scrollHeight = document.body.scrollHeight;
              window.scrollBy(0, distance);
              totalHeight += distance;
              if (totalHeight >= scrollHeight) {
                  clearInterval(timer);
                  resolve();
              }
          }, 100);
      })
  });
}
module.exports.handler = function (request, response, context) {
  console.log('Node version is: ' + process.version);
  (async () => {
    const browser = await puppeteer.launch({
      headless: true,
      args: [
        '--disable-gpu',
        '--disable-dev-shm-usage',
        '--disable-setuid-sandbox',
        '--no-first-run',
        '--no-zygote',
        '--no-sandbox'
      ]
    });
    let url = request.queries['url'];
    if (!url) {
      url = 'https://www.serverless-devs.com';
    }
    if (!url.startsWith('https://') && !url.startsWith('http://')) {
      url = 'http://' + url;
    }
    const page = await browser.newPage();
    await page.emulateTimezone('Asia/Shanghai');
    await page.goto(url, {
      'waitUntil': 'networkidle2'
    });
    await page.setViewport({
      width: 1200,
      height: 800
    });
    await autoScroll(page)
    let path = '/tmp/example';
    let contentType = 'image/png';
    await page.screenshot({ path: path, fullPage: true, type: 'png' });
    await browser.close();
    response.setStatusCode(200);
    response.setHeader('content-type', contentType);
    response.send(fs.readFileSync(path))
  })().catch(err => {
    response.setStatusCode(500);
    response.setHeader('content-type', 'text/plain');
    response.send(err.message);
  });
};


简要介绍一下上述代码的核心逻辑,首先代码会解析 query 参数获取需要截图的 url 地址(如果解析失败则默认使用 Serverless Devs 官网主页),然后使用 Puppeteer 对该网页进行截图,并保存到运行实例的 /tmp/example 文件中,然后将该文件作为 HTTP 请求的返回体直接返回。


然后,我们需要配置 Puppeteer 公共层,在函数配置中找到,点击编辑,选择添加官方公共层


2.png


选择官方公共层 Puppeteer17x,目前最新的层版本为 1。


3.png


参考官方公共层 Nodejs-Puppeteer17x README[6]添加环境变量,对于版本 1,需要添加 LD_LIBRARY_PATH=/opt/lib/x86_64-linux-gnu:/opt/lib 环境变量。


4.png


最后,使用触发器管理中的测试地址进行测试验证。


5.png


测试结果如下所示,已成功将 Serverless Devs 官方进行截图。


6.png


示例二、基于公共层快速实现 .NET 6 自定义运行时


首先,通过控制台创建 .NET 6 自定义运行时。在最上层选择 “使用自定义运行时创建”,选择“处理 HTTP 请求”,选择 .NET 6 运行时,其他配置使用默认值。


7.png


创建成功后,可以通过 WebIDE 看到示例代码 Program.cs


8.png


示例代码中需要注意四个部分:


  • 该示例监听了 0.0.0.0 的 9000 端口,Custom Runtime 启动的服务一定要监听 0.0.0.0:CAPort 或*:CAPort 端口,不能监听 127.0.0.1 或 localhost。详情参考文档 Custom Runtime>基本原理[7]
  • 添加路由 /,直接返回字符串 "Hello World!"
  • 添加路由/invoke,该路由为使用事件请求处理程序的路径,可参考文档 Custom Runtime >事件请求处理程序(Event Handler)[8]
  • 添加路由 /initialize,该路由为函数初始化回调程序对应的路径,该方法会在示例初始化时执行一次,可参考文档 Custom Runtime >函数实例生命周期回调[9]  


首先,我们直接使用触发器管理页面中的测试地址进行测试,此时不添加任何 PATH 信息,结果如下图所示:


9.png


然后,我们测试添加 /invoke 路径进行测试,因为该路由方法为 POST,我们直接使用 curl -XPOST 测试:


10.png


同样,我们用这种方法测试一下/initialize


注意:此处只是做测试,初始化回调函数不需要主动调用,函数计算平台会在实例启动后自动调用该回调方法(不要忘记在配置里启用 initializer 回调程序)


11.png


最后,我们再做一个小测试,在触发器管理页面将 HTTP 触发器删除,删除后该函数类型会转换成事件请求处理程序,在函数配置中,将 Initializer 回调程序启用


12.png


在控制台上测试该函数,结果如下图所示:


13.png


点击实时日志按钮,可以看到在该请求执行前,已经执行了 Initialize 回调方法。


14.png


层的最佳实践是什么?


前文介绍了什么是自定义层,为什么使用自定义层,什么是公共层,并介绍了两个官方公共层的示例。但我们对层的使用仍然还有一些疑惑,比如什么场景下推荐使用层?层与代码包有什么区别?有没有与层相似的功能?与这些相似功能相比,层的优缺点是什么?接下来尝试回答一下这些问题。


什么场景下推荐使用层?


目前,使用层的场景主要有两类,一类是自定义运行时,另一类是各种语言的依赖库。强烈推荐通过层来构建并使用自定义运行时,但对于各类语言的依赖库,可以参考下面这些建议:


  • 推荐优先使用官方公共层
  • 非编译型语言的依赖库推荐使用层来管理,对编译行语言需要根据实际情况进行判断(比如,对自定义运行时,如果使用 JAR 包的方式运行 Java 程序,则无法引入层中的依赖,可参考文档-如何在 Custom Runtime中引用层中的依赖?[3]
  • 如果依赖库较大,并且没有超过层的限制大小,推荐使用层
  • 如果依赖库需要额外安装共享动态库,推荐使用层(如果构建比较复杂,可联系函数计算团队制作)
  • 如果在多个函数、多个账号之间有共享代码或数据的需求,推荐使用层 


层与代码包有什么区别?


直观上看,层就是把原来代码包的一部分内容拆分出来,再重新建一个代码包而已,那为什么又建立一个层的概念呢?这里的主要区别是层与代码包的设计理念不同


  • 层有更简洁的版本管理方案

层的版本是从 1 开始自动递增的,目前一个层最大支持 100 个可用版本(不包括已删除的版本);而对代码包来讲没有版本的概念,只有在服务层面上有版本概念,相对层的版本会更加复杂。


  • 层版本是只读的,不可变的

一个层的版本在创建后内容是无法改变的(权限除外),如果想修改层的内容,只能发布一个新的版本。层版本的只读特性能够避免层的改动对函数的影响。


  • 层的共享能力

层可以跨函数、跨账号进行共享,而代码包不支持。


  • 层版本的软删除策略

层版本删除后,不会影响已经配置改层版本的函数的正常运行。因为在层版本删除,阿里云函数计算平台并不会直接将层版本的代码删掉,而是先进行一次软删除操作,避免新的函数使用已删除的层版本,当该层版本没有函数引用时,才会彻底删除该层版本。


阿里云函数计算中有没有与层相似的功能?与相似功能相比,层的优缺点是什么?


在阿里云函数计算平台中,与层类似的功能是服务配置中的“挂载 NAS 文件系统”和“挂载 OSS 对象存储”功能,层与挂载 NAS/OSS 在功能和应用场景上有一些明显的差异:

15.png

简单总结一下,如果代码或数据的大小超过层的限制,则推荐使用挂载 NAS/OSS 的方式;如果代码或数据会经常改动,或者有运行中修改数据的需求,也推荐使用挂载 NAS/OSS 的方式。


结语


在阿里云函数计算中,层的定位是一种不可变的基础设施,通过层版本的只读特性保证层的一致性和可靠性。本文首先介绍了自定义层的特点和困境,然后介绍了近期发布的公共层功能,详细陈述了基于官方公共层实现的两个示例程序,最后探讨了层的最佳实践是什么,希望通过本文能让读者更好的理解层的概念及其应用场景。


层的功能仍在持续完善中,接下来我们会在一下几个方向进行重点优化:


  • 完善官方公共层体验,补充更多的常用依赖库或自定义运行时作为官方公共层,并提供完善的应用示例。
  • 提供公共层贡献的方法和示例,促进公共层的开源共建。 


如果对层的使用有任何的疑惑或者建议,欢迎搜索(群号:11721331)进入阿里云函数计算钉钉群联系我们。


延申阅读


[1] 创建自定义层 :

https://help.aliyun.com/document_detail/193057.html


[2] 在函数中配置自定义层:

https://help.aliyun.com/document_detail/193058.html


[3] 如何在Custom Runtime中引用层中的依赖?https://help.aliyun.com/document_detail/71142.html


[4] 在函数中配置官方公共层:

https://help.aliyun.com/document_detail/451191.html


[5] 阿里云函数计算 公共层github:

github.com/awesome-fc/awesome-layers


[6] 官方公共层-Nodejs-Puppeteer17x README

https://github.com/awesome-fc/awesome-layers/tree/main/docs/Nodejs-Puppeteer17x


[7] Custom Runtime 基本原理

https://help.aliyun.com/document_detail/425055.html#section-ffl-tm3-txg


[8] Custom Runtime 事件请求处理程序

https://help.aliyun.com/document_detail/191342.html


[9] Custom Runtime 函数实例生命周期回调

https://help.aliyun.com/document_detail/425056.html


点击此处,了解函数计算 FC!

相关实践学习
【AI破次元壁合照】少年白马醉春风,函数计算一键部署AI绘画平台
本次实验基于阿里云函数计算产品能力开发AI绘画平台,可让您实现“破次元壁”与角色合照,为角色换背景效果,用AI绘图技术绘出属于自己的少年江湖。
从 0 入门函数计算
在函数计算的架构中,开发者只需要编写业务代码,并监控业务运行情况就可以了。这将开发者从繁重的运维工作中解放出来,将精力投入到更有意义的开发任务上。
相关文章
|
机器学习/深度学习 算法 API
Kaggle
Kaggle 是一个在线数据科学竞赛平台,旨在为数据科学家和机器学习工程师提供一个学习和实践的社区。在 Kaggle 上,用户可以参加各种数据科学竞赛,通过解决实际问题来提高自己的技能。Kaggle 提供了丰富的数据集和工具,支持多种编程语言,如 Python、R 和 Julia 等。
838 3
|
Ubuntu 网络协议 Linux
如何在无公网IP环境使用Windows远程桌面Ubuntu
如何在无公网IP环境使用Windows远程桌面Ubuntu
509 0
|
5月前
|
JSON 监控 算法
淘宝 + 京东关键字搜索比价 API 接口详解
本项目整合淘宝和京东商品搜索与比价API,支持跨平台价格对比、商品匹配、价格走势分析等功能。提供消费者最优价格选择,辅助商家市场调研与定价策略,附完整Python实现及应用解析。
|
4月前
|
存储 安全 数据处理
阿里云OSS如何支持大规模数据迁移和传输?
阿里云OSS凭借全球基础设施、无限扩展、高持久性、成本优化及安全防护等优势,成为企业大规模数据迁移与传输的首选。其支持智能分层存储、高速传输及多场景数据处理,提供端到端解决方案,助力企业高效构建全球化数据管道,实现数据价值最大化。
|
11月前
|
CDN
阿里云CDN收费标准,不同计费模式价格表(基础服务费和增值服务费用整理)
阿里云CDN的计费包括基础费用和增值费用。基础费用有三种计费方式:按流量、带宽峰值和月结95带宽峰值,默认按流量计费。增值服务如HTTPS、QUIC、WAF和实时日志等,使用才收费。详细价格和规则请参考阿里云官网。
1236 118
|
前端开发 Java API
Apache Seata(incubating) 首个版本重磅发布!
2.1.0 是 Seata 进入 Apache 基金会的第一个 Release Version。此次发布将 io.seata 包名更改为 org.apache.seata。除了按原有的 Roadmap 技术演进外,2.1.0 进行了大量兼容性工作,实现了 API、数据和协议的兼容。用户无需修改原有的 API 和配置,即可实现到 Apache 版本的平滑升级。
391 103
Apache Seata(incubating) 首个版本重磅发布!
|
存储 安全 API
利用环境变量管理配置:最佳实践与技巧
本文介绍了如何利用环境变量管理应用程序配置,涵盖安全性、灵活性和简化部署等方面的优势。详细探讨了最佳实践,包括避免敏感信息泄露、使用`.env`文件、环境特定配置、环境变量注入与验证,以及使用第三方服务。同时分享了一些实用技巧,如分层管理、环境变量加密和版本控制。旨在帮助开发者更高效、安全地管理应用配置。
|
编译器 C语言 C++
配置C++的学习环境
【10月更文挑战第18天】如果想要学习C++语言,那就需要配置必要的环境和相关的软件,才可以帮助自己更好的掌握语法知识。 一、本地环境设置 如果您想要设置 C++ 语言环境,您需要确保电脑上有以下两款可用的软件,文本编辑器和 C++ 编译器。 二、文本编辑器 通过编辑器创建的文件通常称为源文件,源文件包含程序源代码。 C++ 程序的源文件通常使用扩展名 .cpp、.cp 或 .c。 在开始编程之前,请确保您有一个文本编辑器,且有足够的经验来编写一个计算机程序,然后把它保存在一个文件中,编译并执行它。 Visual Studio Code:虽然它是一个通用的文本编辑器,但它有很多插
497 6
|
数据管理 API 调度
阿里云百炼平台知识检索应用评测:搭建之旅与一点建议
阿里云百炼平台成为企业智能化转型的重要工具之一。
|
并行计算 监控 网络协议
西门子PLC常用的通讯接口和通讯协议有哪些?RS232、RS485、PPI、MPI、Modbus、Profibus、Uss的特点
西门子PLC常用的通讯接口和通讯协议有哪些?RS232、RS485、PPI、MPI、Modbus、Profibus、Uss的特点
西门子PLC常用的通讯接口和通讯协议有哪些?RS232、RS485、PPI、MPI、Modbus、Profibus、Uss的特点

热门文章

最新文章

相关产品

  • 函数计算