玩转 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
等等。一旦你指定了具体的运行时,那么函数动态扩容伸缩所使用的镜像就已经确定了下来。接下来就是把你的代码放入镜像创建容器中,去执行了。此时就需要 codeUri
和 handler
这些配置项了,用它们来指定,执行代码入口。
同时除了对外暴露的 handler
中的代码会被执行外,在函数实例的生命周期中,也存在着一些回调方法。就以 initialize
这个回调方法名为例,我们在函数配置中设置函数的 Initializer
回调程序为 index.initialize
,那么 exports.initialize
这个方法会在实例初始化时被执行,其他生命周期亦然。
Custom Runtime 部署
自定义运行环境部署,与预置 Runtime
部署方式,最大的特点就是可自定义语言和运行时了。
我们为啥需要这种部署方式?当然是因为预置的 Runtime
不够用了。通过这种方式,我们可以自定义运行时的语言和版本。比如使用 rust
和 nodejs2048
。而它主要的 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 部署
来自定义代码的运行时了,但是即使通过那种方式,我们也无法改变代码运行的容器环境。比如我有一段代码,只有在 Windows
的 IIS
上才能运行,怎么办? 显然 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个部署方式做不到的事情,但是它的冷启动速度也是最慢的。原因在于,容器镜像依赖的基础环境和应用很容易臃肿,这带来了额外的数据下载和解压的时间。所以这种部署方式上生产环境,往往很多措施来辅助,比如 镜像启动加速
,预留实例
和 单实例多并发
等等功能,同时自己在构建时也要做一定的优化,详见 冷启动优化最佳实践
结论
- 灵活性(从低到高)
- 单语言&普通的 CRUD ->
预置 Runtime 部署
- 自定义语言or运行版本 ->
Custom Runtime 部署
- 自定义容器环境 ->
Custom Container 部署
- 冷启动速度(从慢到快)/优化成本(从高到低)
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网关
要求的响应格式,来响应用户的请求。
一图以蔽之:
腾讯云的 事件函数
部署 web框架
,和 web函数
部署 web server
区别主要在于上图的 proxy
层,是在用户代码内,还是在 SCF
云函数环境中。
这个不同,本质上源自于 2
个云厂商实现 云函数
的方式不同。所以阿里云的 事件请求处理程序(Event Handler)
和 HTTP请求处理程序(HTTP Handler)
和腾讯云的 事件函数
还有 Web函数
不能直接进行类比。
阿里云的 Event Handler
和 HTTP Handler
更像是不同的函数种类。这个种类的不同,也体现在了函数的入参和响应方式上。
腾讯云的 事件函数
则和阿里云的 Event Handler
比较相似,而 Web函数
个人感觉其实更接近于阿里云的 Custom Runtime
的部署方式。区别主要在,阿里云要自己去下载 Runtime binary
,腾讯云则内置了一些 Runtime
。
同时相比于腾讯云,阿里云目前没有开放在线安装依赖的功能。当然这避免了用户想要自定义 安装包的源
这类的问题。同时也在一定程度上变相倡导了在容器中开发的方式。
附录
bootstrap
示例如下所示
#!/bin/bash
/code/node-v16.14.2-linux-x64/bin/node dist/main.js