Mr. Panda
Tech For Fun

[架构] Serverless 构架基础 (2)

2014 年国外 Serverless 生态迅速发展,诞生了如 Serverless Framework、Vercel 等很多优秀的产品;2017 年阿里云和腾讯云发布了国内的 Serverless 产品:函数计算和云函数。Serverless 的开发框架、WEBIDE也陆续出现。

Serverless 是一种执行模型(execution model)。在这种模型中,云服务商负责通过动态地分配资源来执行一段代码。云服务商仅仅收取执行这段代码所需要资源的费用。代码通常会被运行在一个无状态的容器内,并且可被多种事件触发(http 请求、数据库事件、监控报警、文件上传、定时任务……)。代码常常会以函数(function)的形式被上传到云服务商以供执行,因此Serverless也会被称作Functions as a Service 或者 FaaS

开发框架:如何提高应用开发调试和部署效率?

什么是 Serverless 开发框架?

Serverless 的开发框架不是传统的 Express、Spring Boot 等代码框架,而是集成 Serverless 思想、贯穿 Serverless 应用从开发到上线全流程的工具

如何设计 Serverless 的开发框架?

  • 应用管理:主要是函数的管理,可以通过配置来管理服务,通过服务来管理函数集合。
服务和函数的关系

配置示例:

服务配置文件示例
  • 应用开发:通过 cli 工具提交开发脚手架。
  • 应用调试:远程调试:将代码部署到 FaaS 平台,然后直接调用 FaaS 平台的接口执行函数,再得到函数运行日志及返回结果;本地调试:由开发框架模拟函数运行时环境,构造函数参数来执行函数。对一个 Serverless 开发框架来说,这两种调试方式都需要,也就是需要实现 serverless invoke 和 serverless local invoke 两个命令。
  • 应用部署:FaaS 平台提供了函数版本功能(默认是 LATEST 版本)。
  • 账号设置和多平台:Serverless 开发框架最好还要抹平不同 Serverless 平台的差异,让应用能够在不同 Serverless 平台中进行平滑迁移,甚至让开发者使用一个开发框架、一套开发流程就能实现多云部署。
如何设计 Serverless 开发框架

主流 Serverless 开发框架的实现

主流框架

  • Serverless Framework
  • 函数计算 Fun
  • 安装(npm i -g serverless)→账号设置(serverless config credentials --provider aws --key key --sercret sercret)→应用配置(serverless create --template aws-nodejs)→应用调试→应用部署
  • Serverless Framework 特点是功能完善、支持平台丰富;Fun 的特点是只为函数计算服务。

依赖管理:Serverless 应用怎么安装依赖?

安装依赖的难点

Serverless 应用的依赖安装很困难主要是因为它运行在 FaaS 平台上,而 FaaS 平台的运行环境由云厂商提供且预制的。

函数实例
  • 编程语言:创建函数时指定的某个具体版本的编程语言,由 FaaS 平台提供。
  • 内置模块:编程语言的一些内置模块。
  • FaaS 内置依赖:为让开发者使用更方便,FaaS 平台一般还会默认安装一些依赖。
  • 函数代码:函数实例创建时,会从存储服务中将你的代码下载下来并加载到运行环境中。

实践难点:

  • 大多编程语言的依赖通常安装在全局系统目录。
  • 安装依赖过程中可能涉及代码编译环境,不统一会导致编译产物有差异。
  • 系统依赖通常不可移植,应用运行时依赖一些系统级别的动态链接库和软件。

应该如何安装依赖

Java

  • Java 是编译型语言,部署jar包时不用关心依赖。
  • 编译环境差异可能导致编译结果一致,使用统一的构建机统一安装依赖、编译代码。
Serverless 架构中 Java 如何安装依赖

Node

  • NodeJs 依赖分为全局依赖和项目依赖,分别安装于系统目录和项目目录中,因此需要避免使用全局依赖,将全部依赖安装到项目目录 node_modules 中。
  • 对于 Node.js 来说,除了可以直接安装在 node_ moduels 中的 JS 依赖外,还会使用 C++来编写一些Node.js 扩展。cpp 源码会被编译为 .node 文件,需要注意的是,.node 文件的运行环境需要与 FaaS 平台的环境相兼容。这时可以使用统一的构建机保证构建的结果的一致性。
  • 依赖包体积太大,导致函数无法部署的问题。FaaS 平台为了保证函数的性能,一般对依赖包的体积是有限制的,如函数计算的限制为100M。然而在nodeJs 中,代码体积问题很常见,需要想方设法减少依赖体积。可以参考 Java 等编译型语言的做法,对Node.js 代码也进行编译,其实是对 js 代码的依赖进行解析,只打包需要使用的部分代码。类似的工具如 ncc。

Python

  • 给 Python 项目安装依赖比较麻烦的地方就在于,使用 pip 安装的依赖通常会散落在系统的各个文件中。
  • 方法一:使用 --install-option 参数,缺点是需要依赖的源码。
python --install-option 参数
# 将模块安装到当前目录
pip install --install-option="--install-lib=$(pwd)"
  • 方法二:--targer 参数:Targe 参数可以将依赖直接安装到当前目录,不会产生 lib/ python3.7/site- packages 子目录解构。
  • 方法三:使用 virtualenv。缺点是增加了包的体积。

Python 解析依赖的路径:

# 查看依赖解析的路径
import sys
print(sys.path);

修改代码中的依赖解析路径:

修改 Pyhthon 代码中的依赖解析路径

总结

  • 不同编程语言包管理机制不同,安装依赖的方式也不尽相同,但本质上,都是需要将依赖安装到应用项目中,并且随项目一起部署到 FaaS 平台。
  • Serverless 应用的代码依赖和系统依赖都需要安装在项目中,并和应用代码一起部署到 FaaS 平台。
  • FaaS 对代码体积大小有限制,所以最好要精简依赖体积。
  • 如果代码或依赖需要编译,则编译环境需要和 FaaS 运行环境兼容,不然编译后的产物可能无法运行。

运行时:使用自定义运行时支持自定义编程语言

FaaS 平台支持的编程语言有限,因此需要使用自定义运行时。

自定义运行时原理

运行时(runtime) 是程序运行时所依赖的环境。

FaaS 运行原理

安装依赖的本质就是要把函数运行所需要的依赖都打包上传到 FaaS 中。因此,可以将代码依赖包、系统依赖库和函数运行时共同打包发布到 FaaS 平台,并指定 FaaS 运行时所使用的运行时环境。

FaaS 平台在运行函数时会有很多参数,这些参数怎么传递给自定义运行时呢?这本质上是远程数据通信问题,需要实现一个 http 服务来传递参数。

自定义运行时原理

自定义运行时实现

TypeScript 运行时

  • 安装
npm i -S ts-node
npm i -S typescript
npm - -D @types/node
  • 创建并启动 HTTP 服务。此服务处理从触发器获取的参数信息并返回给 FaaS 平台。
  • 添加函数计算的 template.yaml 配置,设置 Runtime: custom。
  • 部署至 FaaS 平台。

Golang 运行时

go 的运行时不能直接作为依赖安装到代码中,因此需要将 go 运行时和源码打包为 docker 镜像发布到 FaaS 平台。

自定义运行时使用流程
  • 创建镜像仓库

仓库地址:registry.<地域>.aliyuncs.com/<命名空间>/<仓库名>

  • 使用 Golang 实现 HTTP 服务。
  • 构建包含 Golang 运行时及代码的镜像。编写 Dockerfile:
构建包含 Golang 运行时及代码的镜像
  • 构建并上传镜像
  • 修改函数计算的配置文件,在 template.yaml 中指定 CustomContainerConfig → image 为镜像的地址。
  • 部署至 FaaS 平台并测试。

总结

  • FaaS 平台提供了有限的编程语言及版本的支持,使用自定运行时,可以自定义编程语言进行开发。
  • 自定义运行时的原理是在函数中实现一个 HTTP 服务,FaaS 平台将触发器事件转发到你的 HTTP 服务。
  • 可以通过将运行时上传到 FaaS,在 bootstrap 中定义启动命令来实现自定义运行时。
  • 可以通过自定义容器镜像来实现任意编程语言的自定义运行时。

单元测试-Serverless 应用如何进行单元测试?

单元测试是保证代码质量和应用稳定性的重要手段。

使用 Serverlesse 的难点:

  • 分布式架构的复杂性:Serverless 架构是分布式的,组成 Serverless 应用的函数是单独运行的。这些函数集合到一起组成分布式架构,你需要对独立函数和分布式应用都进行测试。
  • 云服务难以模拟:Serverless 架构依赖很多云服务,比如各种 FaaS、BaaS 等。这些云服务很难在本地模拟。
  • 事件驱动的异步工作模式难以模拟:Serverless 架构是事件驱动的,事件驱动这种异步工作模式也很难在本地模拟。

Serverless 单元测试准则

测试金字塔理论,越底层的测试方法测试速度越快,测试成本越低,越上层的测试方法测试速度越慢,测试成本越高。

测试金字塔

单元测试是性价比最高的测试方法。

Serverless 应用依赖很多云服务,函数参数也与触发器强相关。因此,需要遵守如下的开发准则:

  • 业务逻辑和依赖的云服务分开,保持业务代码独立,使其更易于扩展和测试。
  • 对业务逻辑编写充分的单元测试,保证业务代码的正确性.
  • 对业务代码和云服务编写集成测试,保证应用的正确性。

Serverless 单元测试实践

使用 jest 针对js项目做单元测试,测试用例存放在 __test__ 目录,测试文件命名为 name.test.js。异步的数据和行为可以模拟。

单元测试的最佳实践:

  • 速度:一个测试用例的测试实践应小于 200 ms,整个应用的测试时间应小于 10 分钟。
  • 隔离外部调用:不能使用真实类、不能读写磁盘、不能有网络调用、不能读写数据库、不能依赖环境变量和系统时间等。
  • 模拟:必要时进行模拟,以确实测试不会受外部环境影响。
  • 单一职责:一个测试用例只用来验证一个行为。
  • 自描述:单元测试是代码最好的文档,也是方法最好的描述,因此单元测试要能体现代码的用途。

总结

  • Serverless 应用由于其分布式、依赖云服务、事件驱动等特性,导致编写单元测试很困难。
  • 为了方便编写单元测试,需要将业务逻辑和依赖的云服务分离开来
  • 编写单元测试时,需要考虑速度、隔离性、单一职责等因素,避免单元测试成为开发的负担。
  • 好的单元测试应该是自描述的,能对代码进行解释说明。

性能优化: 如何优化 Serverless 应用的性能?

Serverless 自动弹性伸缩的特性让应用具备了无限扩展的能力,但基于 FaaS 的实现也带来了一个很大的副作用,即冷启动。冷启动是代码在处理业务功能之前的额外开销

Serverless 的性能优化的核心就是减少冷启动

  • 深入理解冷启动
  • 如何优化 Serverless I 的性能

深入理解冷启动

深入理解冷启动,是优化 Serverless 应用性能的前提。

函数启动过程示意图

冷启动需要经过多个步骤,耗时比较长。可以通过链路工具,如阿里云的链路追踪、函数计算、AWS 的 X-Ray、Lambda等,查看函数冷启动耗时。

函数计算链路追踪
  • InvokeFunction:函数执行总时间。
  • ColdStart:是函数冷启动时间。
  • PrepareCode:是函数冷启动过程中下载代码或下载自定义镜像的时间。
  • Runtimelnitialize:是执行环境启动的时间,包括启动容器和函数运行环境。
  • Invocation:是执行函数的时间.

什么时候函数是冷启动或者热启动呢?

函数第一次执行的时候一定是冷启动,但后面的请求不一定都是热启动,这与触发函数执行的事件是串行还是并行有关。

串行请求:

100 个请求,并发 1

并发请求:

100 个请求,并发 10

团购订餐业务,可能在每天中午、晚上流量突增;促销活动,在活动开始前流量突增;社交软件,遇到重大新闻时流量突增。流量突增就意味着 FaaS 平台不得不添加更多的实例来支持更大的并发,并且新增实例时都会有冷启动,这就对用户体验有较大影响了

如何优化 Serverless 的性能

如何优化 Serverless 的性能:

  • 避免函数冷启动
  • 减小代码体积
  • 提升函数吞吐量
  • 选择合适的编程语言

避免函数冷启动

对函数进行预热
  • 预热就是指你通过定时任务,在真实请求到来之前对函数发起请求,使函数提前初始化
  • 可以对预热请求添加标记,使其与正常的请求分开,采取不同的处理方式,如不对预热请求提供计算服务。
  • 使用函数预热的代价就是需要维护预热的逻辑。
  • 是否使用预热的方案,既要考虑业务场景,也要平衡性能和成本。如果你的应用对延迟要求很高,比如秒杀业务,就可以使用预热功能。如果业务场景对延迟不是很敏感,就不需要使用预热功能。
使用预留资源

有些 FaaS 平台(比如函数计算)也提供了预留资源的功能,可以为你的函数实例持续保留,需要手动去创建释放函数运行的资源。

减小代码体积

函数冷启动的第一个步骤就是下载代码。减少代码体积,可以避免引入不必要的依赖、不要加载不需要的代码,对 SDK 进行精简、对代码进行压缩,甚至只构建需要执行的代码。

提升函数吞吐量

大多 FaaS 都支持了单实例多并发。

选择合适的编程语言

不同编程语言的冷启动时间:NodeJS < Python < Java。选择冷启动时间短的编程语言,可以大幅提升应用性能。

不同编程语言的冷启动时间
  • 解释性语言的冷启动时间低于编译型语言。
  • 函数计算中 PHP 冷启动最快,Node. Js、Python 次之,Java 最慢。
  • Java 冷启动耗时大约是 Nodejs 或 PHP 的三倍。 Node.js、Python、PHP 的冷启动耗时基本在 1s 内。
  • 随着内存增加,冷启动耗时逐渐缩短。
  • Lambda 中函数冷启动平均时间要比函数计算快 3 倍左右。

新的性能优化方案:

  • 尽量选择 Node.js、Python、PHP 等冷启动耗时短的语言编程。
  • 为函数设置合适的内存,内存越大,冷启动耗时越短,但成本也越高,所以要设置一个合适的内存。

总结

  • Serverless 应用的性能优化主要是围绕冷启动进行的。
  • Serverless 性能优化的一些实践方案:提前给函数预热使用预留资源减小代码体积,减少不必要的依赖、执行上下文重用选择冷启动耗时少的编程语言为函数设置合适的内存为函数设置并发

访问控制:如何授权访问其他云服务?

权限问题:直接使用具有 AdministratorAccess 权限的访问凭证去部署应用甚至管理云资源,这是非常不安全的。当企业规模逐渐大,企业中有不同角色的成员,为了云上资源的安全性,你就需要为不同角色配置不同权限,限制不同成员能够访问的云资源。

对于访问控制,各个云厂商都有相应的产品。如 AWS 的 IAM(Identity and Access Management),阿里云的 RAM(Resource Access Management),不同云厂商的实现细节可能有所差异,但工作原理基本一致。

访问控制的工作原理

  • 分权:使不同的成员具有不同的权限。
  • 云服务授权:使云服务能够访问特定的云资源。
  • 跨账号授权:使其他账号访问当前账号的云资源。

通过子账号、角色和权限策略来实现云上的访问控制:

通过子账号、角色和权限策略来实现云上的访问控制

子账号创建完成后,有两种使用方式:

  • 控制台访问:通过子账号登录控制台管理云资源。
  • 编程访问:在代码中使用子账号的 AK 来调用云产品的 API。

当我们资源数量越来越多时,通常会通过编程的方式来使用和管理云资源。

什么是角色?

  • 角色是一个虚拟用户,必须被某个具体用户(子账号、云服务等)扮演使用
  • 可以通过添加权限策略为角色授权
  • 同时创建角色时,需要指定角色能够被谁扮演,即角色的可信实体
角色扮演的原理

基于角色扮演的方式,你就可以实现云服务授权和跨账号授权了。

通过权限策略可以给用户或者角色授权,权限策略是通过 json 来管理的,包括系统策略、自定义策略和云服务权限。

{
  "Statement": [
    {
      "Action": [
        "oss: Get*",
        "oss: List*",
      ],
      "Effect": "Allow",
      "Resource": "*"
    }
  ],
  "Version": "1",
}

跨账号授权的案例:

跨账号授权案例

总结

云上的访问控制:

  • 云厂商主要通过主账号、角色、权限策略等方式来实现云上资源的访问控制。
  • 通过访问控制,能实现分权、云服务授权、跨账号授权等云上资源管控需求。
  • 实际工作中,对于用户访问权限要遵循最小授权原则

Jonsam

一个理科IT宅男,喜欢旅游、分享和美食,做点想做的事情,遇见想见的人。

🍒 美食 | 🌐 FE | 🕌 旅行 | 💻 加班 | ♍ 处女座

jonsam ng

jonsam ng

文章作者

海阔凭鱼跃,天高任鸟飞。

[架构] Serverless 构架基础 (2)
2014 年国外 Serverless 生态迅速发展,诞生了如 Serverless Framework、Vercel 等很多优秀的产品;2017 年阿里云和腾讯云发布了国内的 Serverless 产品:函数计算和云函数。Serverless …
扫描二维码继续阅读
2021-12-18