开发函数计算的正确姿势——轻松解决大依赖部署

675 阅读11分钟

前言

首先介绍下在本文出现的几个比较重要的概念:

函数计算(Function Compute): 函数计算是一个事件驱动的服务,通过函数计算,用户无需管理服务器等运行情况,只需编写代码并上传。函数计算准备计算资源,并以弹性伸缩的方式运行用户代码,而用户只需根据实际代码运行所消耗的资源进行付费。函数计算更多信息 参考

buried_point
Fun: Fun 是一个用于支持 Serverless 应用部署的工具,能帮助您便捷地管理函数计算、API 网关、日志服务等资源。它通过一个资源配置文件(template.yml),协助您进行开发、构建、部署操作。Fun 的更多文档 参考NAS: 阿里云文件存储NAS是一个可共享访问,弹性扩展,高可靠,高性能的分布式文件系统。在函数计算的场景中,由于其有代码包的限制,可以使用 NAS 存放一些不经常变动的文件,比如数据模型、静态资源等。参考ROS: 阿里云资源编排服务(ROS)助您简化云计算资源的管理。您可以遵循ROS定义的模板规范,在模板中定义所需云计算资源的集合及资源间依赖关系。ROS自动完成所有资源的创建和配置,实现自动化部署和运维。更多文档参考

备注: 本文介绍的技巧需要 Fun 版本大于等于 3.4.0。

基本上所有的 faas 平台为了优化函数的冷启动,都会加入代码包的限制。阿里云函数计算(FC)也不例外。FC 要求压缩后的代码包大小不超过 50MB。由于用户的函数代码可能需要大量的依赖库,所以代码包很容易达到函数计算设定的阈值。

传统解决方式

基于平台硬性要求下,依然有很多途径来解决函数计算上传代码包大小受限的问题。下面列举 3 种常见的解决方案。 备注: 包括不局限于以下方案。

1. 提交工单

提交函数计算工单,由后台人员为您账号开通上传限制白名单。但提交工单依然有代码包的限制。部署时代码包上传时间长,并且也增加了函数的冷启动时间,影响函数性能。PS:通过使用预留模式可以完全去除冷启动,由于超出本文范围,这里不再阐述。

2. 利用 OSS 来实现

对代码包进行分类,除项目代码和少量依赖库可以在创建函数时上传到函数计算,用户将部分依赖库预先上传到 OSS,并在函数被触发执行时开始从 OSS 上加载依赖, 这类依赖的加载操作均可定义应用层冷启动,当加载依赖结束后,应用层冷启动才结束,函数的处理逻辑才开始执行,应用层冷启动的开销往往会导致毛刺的产生,影响函数的性能。您可以将从 OSS 加载代码包的代码逻辑放在 initializer 函数中。既可以解决函数计算对上传代码包的限制问题,又不影响函数的性能。但需要用户进行额外的操作。参考使用 initializer 接口解决函数计算上传代码包大小受限问题

3. 基于文件系统 Nas

我们通过 NAS 存放一些体积比较大且不易变动的资源。这使得即使在依赖比较大的场景下依旧适用。NAS 在帮助函数计算解决大依赖问题的同时,由于其自身的配置也比较复杂,也增加了函数计算的使用难度。如何管理 Nas 资源、上传本地资源到Nas 以及服务级别的 Nas 相关配置参考开发函数计算的正确姿势 —— 使用 Fun NAS 管理 NAS 资源

本文旨在介绍第四种方式 -- 使用 Fun 工具来解决函数计算对上传代码包的限制,使得部署步骤简单明了,不需关心额外的配置。同时也展现 Fun 工具对大依赖场景的顺滑体验,Let's go!

:已经获得 fc 支持的 runtime 有 nodejs6,nodejs8,nodejs10,python2.7,python3,java8,php7.2,dotnetcore2.1,custom。目前基于所有 fc 支持的 runtime,大依赖场景下除 php7.2 和 dotnetcore2.1 其他都支持。

环境准备

Fun 安装教程 可以直接在这里下载二进制版本的 Fun,解压后即可使用。
执行 fun --version 检查 Fun 是否安装成功。

$ fun --version
3.4.0

Fun 判定大依赖的标准是什么?

fun install 是 fun 工具的一个子命令,用于安装 pip,apt 依赖等,提供了命令行接口和 Funfile 描述文件两种形式。对于 fun install 安装的依赖,当 fun deploy 部署时会自动处理大依赖。

当 Fun 检测打包的代码压缩后超过限制(50M),会根据对应的 runtime 分离大依赖和代码。Fun 会将大依赖目录分为:系统依赖和语言依赖。系统依赖的本地路径为 .fun/root,语言依赖根据函数 runtime 得到,各个 runtime 对应的大依赖目录映射如下:

语言(runtime) 大依赖目录(directory)
nodejs6 node_modules
nodejs8 node_modules
nodejs10 node_modules
python2.7 .fun/python
python3 .fun/python
java8 .fun/build/artifacts
custom /

自定义执行环境 custom 大依赖目录为 /,可以理解为其他 runtime 大依赖目录的合集。例如:函数 runtime 为 custom,若目录下存在 node_modules.fun/python 等,Fun 在部署向导过程中会把它们都认定为大依赖,会分别对其处理。

Fun deploy 对大依赖的支持

函数计算的命令行工具 Fun 现在原生支持了这种大依赖部署,不需要任何额外操作。仅仅执行 fun deploy

$ fun deploy

整体流程如下图所示:

image.png

fun deploy 会自动完成依赖的部署。而当检测到打包的函数目录超过了平台的限制时,会进入到配置向导,帮助用户自动化地配置。即上图可以理解为:Fun 通过内置 NAS(阿里云文件存储)解决方案,可以一键帮用户创建、配置 NAS,并上传依赖到 NAS 上。而函数计算在运行时,可以自动从 NAS 读取到函数依赖。

大依赖向导

大依赖向导部分截图如下:

image.png

Fun 部署当前函数时,检测到压缩后(.zip)依赖超过了 50M,提示配置向导(后续日志省略...)。只需要输入回车或 yes 即可。最后 Fun 会自动完成配置,成功部署资源到函数计算。

体验升级

fun deploy 大依赖向导完成后,函数会部署到函数计算并对外提供服务。此时大依赖和代码通过 NAS 进行了分离,再次部署时打包本地代码目录时由于没有了大依赖,所以部署速度会非常的快。

这里推荐一篇使用 fun deploy 进行大依赖部署的实战案例,展示了 Fun 工具对大依赖场景的顺滑体验Serverless 实战 —— 快速开发一个分布式 Puppeteer 网页截图服务

Fun package 对大依赖的支持

Fun Package 是用来将代码、编译产物、静态资源等本地资源上传到 OSS 的功能。使用 fun Package 的场景,通常是,想仅仅通过一个模板文件进行部署的场景。比如,本地开发完成后,可以通过 fun package,将模板依赖的本地资源上传到 OSS,这样,无论是在其他服务器上部署,还是使用 ROS 部署时,仅仅通过一个文本格式的模板文件,就可以完成了。推荐阅读 Fun Package 功能介绍

非大依赖场景

流程如下图所示:

image.png

通过 Fun Package 可以将模板文件包含的本地资源一键上传到 OSS 上,完成资源的打包操作。

模版文件差异

将打包后的模板文件(template.packaged.yml)与原文件相比较,可以发现,差异仅仅在使用了本地资源的场景,比如:

- CodeUri: './'
+ CodeUri: 'oss://bucket/PackageDemo/function/39ce6e9109a23d313bc267b1a5211273'

大依赖场景

流程如下图所示:

image.png

当遇到打包的函数体积过大时,同样会进入大依赖向导,Fun 内置 Ros 的解决方案,帮你完成自动配置。同时 Fun 会分开大依赖和代码并分别上传到 OSS。这样做的目的下文中会有提到。

模版文件差异

大依赖场景下打包完成后生成的 template.packaged.yml 模版文件会与非大依赖场景下有所不同,除上述 CodeUri 的差异外,还会新增许多资源描述作为使用 Ros 部署时的前置条件,例如 NasCpFunc , 这里只介绍一种,其它不做赘述。

NasCpFunc:
  Type: 'Aliyun::Serverless::Function'
  Properties:
    Handler: index.cpFromOssToNasHandler
    Runtime: nodejs8
    CodeUri: 'oss://ellison-hongkong/9e610f5540e21ace83d5b742241da6aa'
    MemorySize: 3072
    Timeout: 300

NasCpFunc 为大依赖场景下 Fun 为用户内置的资源函数,可以将它理解为一个辅助函数。当用 Ros 方式部署时,将自动执行辅助函数。它用于实现从 OSS 上下载大依赖(.zip)以及解压到 Nas 的功能。这也就是为什么上述 fun pakckage 打包时,要将大依赖和代码分离开并分别上传到 OSS 的原因。

Fun Packge + Ros 部署实战

Serverless 实战——使用 Rendertron 搭建 Headless Chrome 渲染解决方案使用 Rendertron + 函数计算快速搭建一个可以直接用于生产的 Headless Chrome 渲染解决方案,以便于帮助网站更好的进行 SEO。基于文章,我们将升级文章中一键部署的体验。您可以参照上述文章中步骤,其中依赖安装项目编译等均无需额外操作。

Rendertron 项目代码依赖过大,基于 Fun 工具对大依赖项目的支持,现将其原 Fun deploy 部署改造为 Fun Packge + Ros 部署方式。Fun package 自动处理大依赖上传到 OSS,Ros 部署将大依赖从 OSS 解压到 Nas,同时模版中描述的资源自动创建成功。基于函数计算,项目的服务架构如下:

image.png

1. Fun pakcage

按照 RenderTron 文章中步骤操作,在一键部署前,执行 fun package 命令:

fun package --oss-bucket aliyun-ellison

这里的 --oss-bucket 名称为自己所拥有读写权限的 OSS 的 Bucket 名称。完整日志如下:

image.png

2. Ros 部署

ROS 通过 Transform 宏实现了将函数计算的模板语法转换为 ROS 支持的语法。这意味着对于 Fun 规范文档 里描述的语法规则,ROS 是同样支持的。同时,ROS 支持的资源 也能在 Fun 模板文件中进行声明了,比如 RAM、函数工作流 等等。

在体验上,由于 ROS 部署,要求资源必须“云化”。也就是没办法直接使用本地的代码资源。必须先通过 fun package 命令将资源上传到 oss。

可见这一步我们已经完成,不管是大依赖场景还是非大依赖场景,fun package 打包完成后,后续的部署操作,只需要完全基于这个打包后的模板文件(template.packaged.yml)即可。不再依赖本地的代码等资源,可以简化部署的难度。

最后将资源通过 ROS 的方式进行部署,推荐阅读开发函数计算的正确姿势 —— 使用 ROS 进行资源编排

fun deploy --use-ros --stack-name bucket-name

--stack-name 表示要部署的环境,可以基于该名称的不同,建立多套开发环境,比如 test、staging、prod。

3. 验证

可通过上述 RenderTron 文章中提到的方式验证,这里不做赘述。

常见问题梳理 FAQ

1. 大依赖被一个服务下的两个函数同时引用,Fun 是怎么处理的呢?

Fun 支持大依赖的场景是函数级别的,即当打包某一函数时发现超过限制才会进入向导。当两个函数处于相同 runtime 和 codeUri,Fun 会在结束第一次向导时,同时自动配置第二个函数,确保部署后,两个函数都部署成功且可用。

2. 大依赖自动配置后,如果我本地添加了新的依赖,部署时会自动将依赖更新到 Nas 吗?

不会。如果添加了新的依赖,比如 node_modules 目录添加了新的依赖库,需要在 template.yml 模版文件所在目录执行 fun nas sync,将本地 nas 资源同步到 nas 服务。如果修改了代码,只需要使用 fun deploy 重新部署即可。由于大依赖和代码通过 NAS 进行了分离,依赖通常不需要频繁变化,所以调用的频率比较低,而 fun deploy 的由于没有了大依赖,部署速度也会非常的快。

3. 为什么 java8 环境 Fun 支持大依赖的目录是 .fun/build/artifacts?

在很多场景,编译型语言从源码距离交付物其实是有一定的距离,比如 java,写完 java 代码后,还要考虑如何编译、打包的问题。Fun build 的职责就是完成从源码到交付产物的构建过程,推荐阅读 《开发函数计算的正确姿势 —— 使用 Fun Build 构建函数》
Fun build 会将编译打包后的交付产物 copy 到 .fun/build/artifacts 目录,在部署时检测到代码大小超过限制,自然会去 .fun/build/artifacts 下查找对应 serviceName/functionName 目录,并将所有的 jar 包上传到 Nas。所以 Fun 大依赖部署支持 java8 是以 Fun build 的场景为基础。未来 Fun 会集成更多的解决方案,敬请期待!

总结

Fun 通过内置 NAS(阿里云文件存储)解决方案,可以一键帮用户创建、配置 NAS,并上传依赖到 NAS 上。而函数计算在运行时,可以自动从 NAS 读取到函数依赖。同时也展现 Fun 工具对大依赖场景的顺滑体验。

如果大家在使用 Fun 的过程中遇到了一些问题,可以在 github 上提 issue,或者加入我们的钉钉群 11721331 进行反馈。