“功能”包——为无服务器应用程序打包设定标准

转载自原文发表在CNCF博客上
函数里有什么
创建无服务器应用程序是一个多步骤的过程。此过程中的关键步骤之一是将希望部署到所选的FaaS(功能即服务)平台中的无服务器功能打包。
在可以部署函数之前,它需要两种类型的依赖:直接函数依赖和运行时依赖。让我们来看看这两种类型。
直接函数依赖-这些对象是函数过程本身的一部分,包括:
- 该函数的源代码或二进制
- 函数使用的第三方二进制文件和库
- 函数直接需要的应用程序数据文件
运行时函数依赖项-这是与你的函数的运行时方面相关的数据。它不是函数直接需要的,而是配置或引用函数将在其中运行的外部环境。例如:
- 事件消息结构和路由设置
- 环境变量
- 运行时二进制文件,如操作系统级库
- 外部服务,如数据库等。
要运行函数服务,需要将所有依赖项(直接依赖和运行时依赖)打包并上传到无服务器平台。然而,今天还没有包装功能的通用标准。无服务器包格式是特定于供应商的,并且高度依赖于将要运行函数的环境类型。这意味着,在大多数情况下,您的无服务器应用程序被锁定到单个提供者,即使您的函数代码本身抽象了特定于提供者的细节。

本文探讨了为开放和可扩展的“功能包”创建标准的可能性,该标准支持跨不同的FaaS供应商部署无服务器功能二进制文件以及一些额外的元数据。
函数运行时环境
在查看当前常见的运行时FaaS提供程序时,我们可以看到运行函数的两种主流方法:容器函数和自定义函数运行时。让我们仔细看看每一种类型。
容器函数运行时
该方法使用基于容器的运行时,如Kubernetes,是本地FaaS解决方案的常用运行时方法。顾名思义,它通常向一个或另一个级别的用户公开容器运行时语义。下面是它的工作原理。
容器入口点与事件层一起用作函数,以调用具有正确参数集的特定容器。创建函数码头工人建造或任何其他映像生成器来创建OCI容器映像作为最终包格式。
基于无服务器容器的运行时示例包括:谷歌KnativeBitnami这样的Kubeless, IguazioNuclio和Apache的OpenWhisk.
容器函数的主要问题是,开发函数通常需要了解运行时环境;开发人员需要将他们的功能编织到容器中。有时这个过程被框架隐藏了,但是对于开发人员来说,为了更好地控制操作系统级的服务和镜像结构,编写Dockerfile仍然是很常见的。这导致了函数与运行时环境的高度耦合。
自定义函数运行时
自定义函数运行时通常由云提供商提供。它们提供了一个“干净”的模型,通过简单地用您最喜欢的编程语言编写处理程序回调来创建函数。与基于容器的函数相比,运行时细节完全留给云提供商(即使在后台运行时基于容器的情况下也是如此)。
无服务器自定义函数运行时的示例如下:AWSλ,Azure的功能和谷歌云函数.
自定义运行时环境使用以语言为中心的函数模型。软件语言已经有了健康的打包实践(比如Java jar、npm包或Go模块)。但是,对于它们的等效功能,还不存在通用的打包格式。这就造成了云计算供应商的锁定。已经做出了一些努力来定义一个通用的部署模型,例如AWS无服务器应用模型(SAM)和开源的无服务器框架),但是这些模型假设已经存在自定义二进制包,或者它们可能包含将其构建到每个云提供商标准的过程。
需要一个功能包
为了能够在生产环境中使用函数,我们需要使用对它们的稳定引用,以支持部署、升级和回滚到特定功能版本时的可重复性。这可以通过一个包含函数及其所有直接依赖项的不可变的、版本化的、密封的包来实现。
容器映像可以满足这些要求,因为它们提供了通用的包格式。2022年世界杯预选赛赛程表然而,由于将函数与容器运行时的细节紧密耦合,会“污染”函数。自定义运行时也展示了与它们的函数包的耦合。虽然它们提供了简洁的函数,但它们使用专有的包格式,将函数依赖项与运行时依赖项混合在一起。
我们需要的是一个清晰的分离:一个干净的函数和它的依赖关系放在一个本地的“函数包”中,与运行时特定依赖关系的外部定义分开。这种分离将允许我们获得一个功能,并在不同的无服务器平台上重用它,只需要添加外部配置。

让我们重新审视一下构建和运行基于容器的函数所需的步骤。我们可以把这个过程分为四个步骤:
- 包装:与(直接+运行时)函数依赖项和入口点定义一起构建容器映像。
- 坚持:将图像推到a容器注册表所以我们对它有一个稳定的参考。
- 安装:将映像拉到运行时并根据特定于运行时的依赖项对其进行配置。
- 运行:在定义的入口点接受函数事件。
这个过程对于容器运行时非常有效。我们可以试着把它表述成一个更广义的观点:
- 包装:构建一个包含其直接依赖项(或对它们的引用)和入口点定义的函数包.
- 坚持:将包上传到包注册表.
- 安装:将包(及其直接依赖项)下载到运行时并配置特定于运行时的依赖项.
- 运行:在定义的入口点接受函数事件。
创建函数包
这个通用过程可以应用于许多无服务器提供商!开发人员只需要担心创建一个干净的函数包并保持它的持久性。运行时配置可以在安装时提供,并且可以是特定于供应商的。
一个函数包将包含:
- 函数文件-源代码或二进制格式
- 直接依赖-库和数据文件
- 入口点定义
开发人员的体验很简单,并且以编程为中心,因此我们可以创建映射到特定语言类型的标准“配置文件”。例如:
| Java配置文件 | Golang概要 | 码头工人档案 | |
| 函数文件 | JAR文件 | Go模块源文件 | 构建文件引用到由入口点运行的通用文件 |
| 直接依赖 | 为jar生成的pom文件,包含依赖项和数据文件的完整列表 | go.modfile containing the dependencies + data files | 构建文件引用基础镜像+其他服务文件和数据 |
| 入口点定义 | 类引用 | “main”包引用 | 构建文件对映像入口点的引用 |
依赖关系呢?
如果运行时可以在安装之前可靠地提供二进制依赖项,则函数包不必物理嵌入二进制依赖项。相反,只能声明对依赖项的引用。例如,在POM文件中声明的外部JAR坐标,或者在go.mod文件。这些依赖关系将在安装期间由运行时拉取——类似于容器映像的拉取方式从docker注册表按函数运行时。
通过使用稳定的引用,我们保证了可重复性和重用性。我们还创建了更轻的包,允许通过靠近运行时的注册表依赖项更快更经济地安装功能,而不必每次都重新加载它们。

总结
为公共运行时语言创建概要文件允许在不同的无服务器FaaS提供商之间轻松移动功能,仅在安装阶段添加与运行时配置和事件相关的特定于供应商的信息。对于应用程序开发人员来说,这意味着他们可以避免供应商锁定,专注于编写应用程序的业务逻辑,而不必熟悉操作方面。通过创建可共享的Function Packages作为CNCF无服务器标准,可以更快地实现这一目标。
