此页面需要javascript支持,请在浏览器中启用javascript

玩转 serverless devs 的三种部署方式

serverless-devs
deploy
cli
共2454个字,阅读时间 12 分钟
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://icebreaker.top/articles/2022/5/8-serverless-devs-deploy

Image

玩转 serverless devs 的三种部署方式

serverless-devs 是一个开源的 Serverless 应用全生命周期管理工具。

笔者作为一名 Serverless 应用开发者,对国内的阿里云 FC,腾讯云 SCF 都有一定的了解。接下来我将在此文中介绍:如何使用这个工具,来把同一个应用,通过不同的方式,部署到阿里云函数计算中。

文章中使用的示例项目为一个 nestjs 应用,运行环境为 nodejs,源代码见附录。这三种部署方式,由于部署目标平台都为阿里云,统一使用 阿里云函数计算(FC)组件

预置 Runtime 部署

这个部署方式是最简单直接的,假如你只是想搭建一个简单的 web service 或者处理做一些 batch job,不需要依赖一些额外的系统库或者软件,往往使用这个方式部署就够了。主要的 yml 配置也很简单:

function:
  runtime: nodejs14  # 运行环境
  handler: index.handler # 函数入口

这种方式最大的特点,就是 runtime 需要从预置的枚举值中配置一个具体值,比如 nodejs12,nodejs14,python2.7,python3,java8,java11,php7.2,dotnetcore2.1 等等。一旦你指定了具体的运行时,那么函数动态扩容伸缩所使用的镜像就已经确定了下来。接下来就是把你的代码放入镜像创建容器中,去执行了。此时就需要 codeUrihandler 这些配置项了,用它们来指定,执行代码入口。

同时除了对外暴露的 handler 中的代码会被执行外,在函数实例的生命周期中,也存在着一些回调方法。就以 initialize 这个回调方法名为例,我们在函数配置中设置函数的 Initializer 回调程序为 index.initialize ,那么 exports.initialize 这个方法会在实例初始化时被执行,其他生命周期亦然。

Custom Runtime 部署

自定义运行环境部署,与预置 Runtime 部署方式,最大的特点就是可自定义语言和运行时了。

我们为啥需要这种部署方式?当然是因为预置Runtime 不够用了。通过这种方式,我们可以自定义运行时的语言和版本。比如使用 rustnodejs2048。而它主要的 yml 配置也很简单:

function:
  runtime: custom  # 运行环境(从预置的枚举值中选一个)
  caPort: 9000
  customRuntimeConfig: # 不用这个就用 bootstrap 文件,示例见附录
    command:
      - /code/node-v16.15.0-linux-x64/bin/node
    args:
      - 'dist/main.js'

其中 customRuntimeConfig 中声明了启动命令和参数,直接执行便可。

需要注意的是,这种部署方式,需要你上传运行环境的解析器/运行时,再和你的代码文件,一起打包部署到函数计算。这往往很大,比如我下载的 node-v16.15.0-linux-x64 解压后足足有 100M,所以可以找一种方式来复用运行时的包来加快你的部署速度。

Custom Runtime 部署这种方式,要求你的代码是一个 HTTP Server 并监听指定的 caPort 端口。而且你的函数实例生命周期回调也是由 HTTP Server 中指定路由来完成的,比如 /initialize, /pre-freeze,/pre-stop 这类。

总的来说,它比起 预置 Runtime 部署 有了更多的可操作性,相比来说它的速度也差一些,毕竟代码包的体积变大了,每次都要下载解压,这个速度肯定是慢一点的。所以灵活的代价无非就是性能差一点,我们在选择部署方式的时候也要根据情况,斟酌损益。

Custom Container 部署

刚刚我们已经通过 Custom Runtime 部署 来自定义代码的运行时了,但是即使通过那种方式,我们也无法改变代码运行的容器环境。比如我有一段代码,只有在 WindowsIIS 上才能运行,怎么办?显然 Custom Runtime 部署 固定的容器环境,是不满足我们的需求的。

这时候我们就需要在本地,构建我们自己的容器镜像,并把它推送到 阿里云的镜像仓库 里去。所以启用 Custom Container 部署 ,最重要的先决条件是什么?

安装 docker 并 开通阿里云容器镜像服务

它对应的 serverless-devs 也非常简单:

function:
  caPort: 9000
  runtime: custom-container
  customContainerConfig:
    image: registry.cn-hangzhou.aliyuncs.com/som-custom-container/nest-app
actions: 
  pre-deploy: # 在部署前执行,在你的本地构建镜像,所以需要你已经安装好了 docker
    - component: fc build --use-docker --dockerfile ./code/Dockerfile

customContainerConfig#image 就是你的镜像仓库的地址 (我示例中用的公网地址,最佳实践为函数计算同地域的 VPC 镜像地址),复制粘贴即可。

fc build --use-docker --dockerfile ./code/Dockerfile 这个命令,你可以理解成一堆 docker 构建发布的命令脚本。

Dockerfile 就是构建镜像的核心了,在这里我们可以任意的配置我们的系统环境。比如我们要转化操作图集变成pdf文件,则预先安装好 ghostscript

我们要在 nodejs runtime 中构建类似 Canvas 实现,额外安装 build-essential libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev.....

这里给出一个 Dockerfile 示例参考:

FROM node:18-alpine

RUN mkdir -p /usr/src/bot
WORKDIR /usr/src/bot

COPY package.json yarn.lock /usr/src/bot/

# 注册 alpinelinux 镜像地址,防止下载过慢
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories \
  && apk add --no-cache \
  build-base \
  g++ \
  cairo-dev \
  jpeg-dev \
  pango-dev \
  giflib-dev  \
  && apk add --update  --repository http://dl-3.alpinelinux.org/alpine/edge/testing \
  libmount \
  ttf-dejavu \
  ttf-droid \
  ttf-freefont \
  ttf-liberation \
  fontconfig \
  && yarn --prod

COPY ./src /usr/src/bot/src

EXPOSE 9000

ENTRYPOINT ["yarn" ,"start"]

这种部署方式最灵活,能做到很多上述 2 个部署方式做不到的事情,但是它的冷启动速度也是最慢的。原因在于,容器镜像依赖的基础环境和应用很容易臃肿,这带来了额外的数据下载和解压的时间。所以这种部署方式上生产环境,往往很多措施来辅助,比如 镜像启动加速预留实例单实例多并发 等等功能,同时自己在构建时也要做一定的优化,详见 冷启动优化最佳实践

结论

  1. 灵活性 (从低到高)
  • 单语言 & 普通的 CRUD -> 预置 Runtime 部署
  • 自定义语言 or 运行版本 -> Custom Runtime 部署
  • 自定义容器环境 -> Custom Container 部署
  1. 冷启动速度 (从慢到快)/ 优化成本 (从高到低)
  • Custom Container
  • Custom Runtime
  • 预置 Runtime 部署

扩展阅读 (友商对比)

前面主要讲了,利用 serverless devs 的部署的三种方式。

现在,让我们先回到 预置 Nodejs Runtime 部署 这种方式,在部署时,开发者们应该都注意过。我们在传统 web 框架部署到 FC 时,需要安装一个额外包: @serverless-devs/fc-http 来包裹我们的框架实例。这个包是干啥用的呢?

@serverless-devs/fc-http 本质上是一个 阿里云 FC 兼容传统 web 框架的适配层,和友商的 tencent-serverless-http 一样,它们都源自于 serverless-http

不过同样是 proxy,阿里云和腾讯云的实现方式有所不同。

阿里云的 @serverless-devs/fc-http 负责做一些 FC 的http函数 上下文 和传统 web 框架上下文相互转化的适配。

腾讯云的 tencent-serverless-http 本质上是一个 SCF事件函数腾讯云的API网关 的适配层。

它负责把用户请求API网关后,传给云函数 event,转化为函数内部包裹的 web框架(express,koa...) 能够处理的 http上下文 (req,res,ctx...),经过中间件的处理后,再把响应值转化为API网关要求的响应格式,来响应用户的请求。

一图以蔽之:

Image

腾讯云的 事件函数 部署 web框架,和 web函数 部署 web server 区别主要在于上图的 proxy 层,是在用户代码内,还是在 SCF 云函数环境中。

这个不同,本质上源自于 2 个云厂商实现 云函数 的方式不同。所以阿里云的 事件请求处理程序(Event Handler)HTTP请求处理程序(HTTP Handler) 和腾讯云的 事件函数 还有 Web函数 不能直接进行类比。

阿里云的 Event HandlerHTTP Handler 更像是不同的函数种类。这个种类的不同,也体现在了函数的入参和响应方式上。

腾讯云的 事件函数 则和阿里云的 Event Handler 比较相似,而 Web函数 个人感觉其实更接近于阿里云的 Custom Runtime 的部署方式。区别主要在,阿里云要自己去下载 Runtime binary,腾讯云则内置了一些 Runtime

同时相比于腾讯云,阿里云目前没有开放在线安装依赖的功能。当然这避免了用户想要自定义 安装包的源 这类的问题。同时也在一定程度上变相倡导了在容器中开发的方式。

附录

官网

@serverless-devs/fc-http

源码

bootstrap 示例如下所示

#!/bin/bash
/code/node-v16.14.2-linux-x64/bin/node dist/main.js