serverless-devs
是一个开源的 Serverless
应用全生命周期管理工具。
笔者作为一名 Serverless
应用开发者,对国内的阿里云 FC,腾讯云 SCF 都有一定的了解。接下来我将在此文中介绍:如何使用这个工具,来把同一个应用,通过不同的方式,部署到阿里云函数计算中。
文章中使用的示例项目为一个 nestjs
应用,运行环境为 nodejs
,源代码见附录。这三种部署方式,由于部署目标平台都为阿里云,统一使用 阿里云函数计算(FC)组件 。
这个部署方式是最简单直接的,假如你只是想搭建一个简单的 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
这个方法会在实例初始化时被执行,其他生命周期亦然。
自定义运行环境部署,与预置 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 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 个部署方式做不到的事情,但是它的冷启动速度也是最慢的。原因在于,容器镜像依赖的基础环境和应用很容易臃肿,这带来了额外的数据下载和解压的时间。所以这种部署方式上生产环境,往往很多措施来辅助,比如 镜像启动加速
,预留实例
和 单实例多并发
等等功能,同时自己在构建时也要做一定的优化,详见 冷启动优化最佳实践
预置 Runtime 部署
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