目录
一、前言
二、什么是ServerLess?
三、ServerLess技术选型
四、ServerLess基础服务搭建
Mac安装示例:
Windows安装说明:
五、生成ServerLess应用
六、ServerLess部署
验证并访问函数应用
七、ServerLess进阶演示
八、ServerLess最后总结
一、前言
通常我们在做ServerLess的时候会想到用各种云的Faas服务,比如腾讯云,AWS,阿里云等等。但我们很少去研究自己怎么搭建一个ServerLess服务。本篇文章重点会讲解如何自己在服务器上搭建ServerLess服务,并如何使用它。
二、什么是ServerLess?
Serverless,又叫无服务器。Serverless 强调的是一种架构思想和服务模型,让开发者无需关心基础设施(服务器等),而是专注到应用程序业务逻辑上。Serverless 也是下一代计算引擎。
Serverless 与 FaaS(函数即服务)通常被视为可以互换的术语,但这并不准确。Serverless 是一种抽象层次更高的架构模式,而**“FaaS + BaaS”只是 Serverless 这种架构模式的一种实现**。
其中,FaaS 是一种特定类型的服务,例如 AWS Lambda,Google Cloud Functions,Azure Functions,阿里云函数计算和腾讯云云函数等等;而 BaaS(后端即服务)可以理解为其他类型的托管服务,例如数据库服务,对象存储服务和日志服务等等。
ServerLess具有以下特征:
-
免运维:不需要管理服务器主机或者服务器进程。
-
弹性伸缩:根据负载进行自动规模伸缩与自动配置。伸缩范围零到无穷大。
-
按需付费:根据使用情况决定实际成本。
-
高可用:具备隐含的高可用性。
三、ServerLess技术选型
两种方案,一种基于现有的Faas云服务,这样不用关心搭建和维护,只需要购买就行了(这种方式我就不讲了,大家可以直接去看腾讯云官网文档)。另一种是自己搭建SeverLess服务端。在这里我们不讲第一种方式,我们讲第二种方式。
搭建服务有很多开源的方案,首先最火的是的OpenFaas库,它是基于kubernetes服务的,环境搭建比较复杂。出于快速上手的目的,选用一块轻量级的SeverLess服务的库。这里我们选用fnproject。它入手简单,只需要有docker环境,即可运行。可以实现快速部署。
fnproject 是一个原生容器无服务器项目,它几乎支持任何编程语言,并且几乎可以在任何地方运行。Fn 是用 Go 语言编写的,因此性能较好且十分轻量。Fnproject 支持 AWS Lambda 风格(AWS Lambda是亚马逊提供的serverless服务),因此你可以轻松导入你的 Lambda 函数并通过 Fnproject 启动它。
四、ServerLess基础服务搭建
首先我们需要安装FnProject,它只支持Linux / Mac 两种系统, 对于Window用户,我建议安装一个ubuntu的操作系统。基于WSL2的话,还是比较简单的。
FnProject的服务端和客户端都是在一个程序中,所以我们只需要安装一个fn-cli就可以了。下面我讲一下详细的安装步骤。
Mac安装示例:
使用Homebrew安装 (这里有个坑,大家要换国内的镜像源,不然会很慢,清华镜像)
brew update && brew install fn
或者使用FnProject的脚本安装
curl -LSs https://raw.githubusercontent.com/fnproject/cli/master/install | sh
Windows安装说明:
首先升级你的系统支持WSL2协议, 安装Ubuntu系统,然后用上面的脚本安装即可。
最后启动ServerLess服务
fn start
#默认会走8080端口
fn start -p 9080
#可以指定一个端口启动
#这里注意,如果修改端口后,还需要修改环境变量
export FN_API_URL=http://127.0.0.1:9080
五、生成ServerLess应用
fnproject 提供一下五种语言的支持,分别都有对应的教程,在这里我们主要讲的是nodejs语言,其他语言大家可以自行去看。
-
Go
-
Java
-
Node.js
-
Python
-
Ruby
fn init --runtime node nodeTest #初始化node项目的结构
然后我们来看一下nodeTest的目录结构
理解func.yaml文件作用
schema_version: 20180708
name: nodetest
version: 0.0.1
runtime: node
build_image: fnproject/node:14-dev
run_image: fnproject/node:14
entrypoint: node func.js
-
schema_version 服务的唯一标识 ,它决定哪些字段是可以用的
-
name 服务的名称,也是所在的目录名称
-
Version 当前的函数的版本
-
runtime 运行的语言信息
-
build_image 打包的docker镜像
-
run_image 运行的docker镜像
-
entrypoint 函数是由docker启动的,这个是最后docker执行的命令,node func.js,代表启动这个node服务
func.js是我们的代码文件,比较简单,输出hello world,内容如下
const fdk=require('@fnproject/fdk');
fdk.handle(function(input){
let name = 'World';
if (input.name) {
name = input.name;
}
console.log('\nInside Node Hello World function')
return {'message': 'Hello ' + name}
})
六、ServerLess部署
首先我们需要create app appname 一个node应用,对于现有的函数应用进行管理的app,也相当于给你的函数应用一个命名空间。这个app name 也是部署需要用到的。在同一个app下的函数,可以一起部署。
#在当前函数代码目录下执行
fn create app nodeApp
创建完后,我们就可以部署我们的函数应用了。 这里fnproject为我们提供deploy命令
fn --verbose deploy --app nodeApp --local
这里说明一些参数
- --verbose 这能将控制台中命令执行的细节和过程打印出来。
- --app 指定app name
- --local 如果你的ServerLess服务在本地机器的这里需要指定,如果在远程机器上,这里就不需要它
执行过程中,fnproject会自动构建docker镜像,并自动执行安装npm install , 执行完后,我们会看到上面的结果。
验证并访问函数应用
我们如何访问这个Severless服务呢?FnProject提供了两种调用方式。
第一种是采用CLI命令行的方式去调用
fn invoke nodeApp nodetest #invoke是fn提供给我们可以直接调用funcApp的命令
{"message":"Hello World"} #这个是控制台的打印输出
上面的函数例子,我们发现有一个input参数, 那么我们怎么将参数传递给func app呢?
这里我们可以通过shell的管道命令传递过去。
echo -n '{"name":"Felix"}' | fn invoke nodeApp nodetest --content-type application/json
{"message":"Hello Felix"} #这个是控制台的打印输出
第二种就是采用API接口方式去调用。
首先我们需要获取函数的Api地址,通过下面命令可以获取到Api地址。
fn inspect function nodeApp nodetest
{
"annotations": {
"fnproject.io/fn/invokeEndpoint": "http://localhost:8080/invoke/01FG6BBGV9NG8G00GZJ0000002"
},
"app_id": "01FG681T38NG8G00GZJ0000001",
"created_at": "2021-09-22T08:53:31.113Z",
"id": "01FG6BBGV9NG8G00GZJ0000002",
"idle_timeout": 30,
"image": "nodetest:0.0.2",
"memory": 128,
"name": "nodetest",
"timeout": 30,
"updated_at": "2021-09-22T08:53:31.113Z"
}
http://localhost:8080/invoke/01FG6BBGV9NG8G00GZJ0000002 这个地址就是我们nodetest 服务的请求地址。我们可以通过Postman调用,可以在代码里面通过ajax调用。
这里我用curl去演示一下。我就一步到了,用POST请求直接传递数据调用接口
curl -X "POST" -H "Content-Type: application/json" -d '{"name":"Felix"}' http://localhost:8080/invoke/01FG6BBGV9NG8G00GZJ0000002
{"message":"Hello Felix"} #这个是控制台的打印输出
到这里,我们关于一些基本的能力都讲完了,那我们讲一点高级的应用。比如如何访问数据库,自定义DockerFile文件,如何debug等。
七、ServerLess进阶演示
首先我们先来看一看,如何访问Mysql, 首先我们先用docker装一下Mysql
#先拉取镜像
docker pull mysql:5.7
#启动mysql镜像
docker run -p 3306:3306 --name mysql -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7
环境变量处理,对于Mysql连接的话,我们需要考虑对db_host单独配置。避免写死在代码中。
FnProject提供了丰富的环境变量,供我们使用
-
DB_HOST_URL
数据库的地址链接. -
DB_USER
数据库的用户名. -
DB_PASSWORD
数据库的密码.
#配置数据库基本信息
fn cf a nodeApp DB_HOST_URL 172.29.149.191 #这里需要指定容器宿主Ip
fn cf a nodeApp DB_HOST_PORT 3306
fn cf a nodeApp DB_NAME db_test
fn cf a nodeApp DB_USER root
fn cf a nodeApp DB_PASSWORD 123456
#通过此命令可以查看环境变量
fn ls cf a nodeApp
目前是一个空的数据库,我们需要创建简单数据库和一张表结构
简单起见,我们通过命令行创建数据库和表吧。方便后面的演示。
#进入mysql容器内
docker exec -it mysql bash
#CLI登录mysql
mysql -uroot -p123456
#创建数据库
create database db_test;
use db_test;
#创建表
CREATE TABLE IF NOT EXISTS `article`(
`article_id` INT UNSIGNED AUTO_INCREMENT,
`article_title` VARCHAR(100) NOT NULL,
`article_author` VARCHAR(40) NOT NULL,
PRIMARY KEY ( `article_id` )
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
#插入一条测试数据
INSERT INTO article (article_title,article_author) VALUES("Attention is all you need","Bengio")
然后给我们的nodeTest 函数应用赋能。 安装连接mysql的npm包.
传统mysql连接拼接sql语句,我一直不提倡用这个方式。 我选一个支持sql builder的npm工具。
这里我用一款国外比较流行的库knex(我就不介绍这款工具的使用了,大家自行学习)
然后下面我们修改func.js的代码
const fdk=require('@fnproject/fdk');
fdk.handle(async function(input,ctx){
let name = 'World';
if (input.name) {
name = input.name;
}
const knex = require('knex')({
client: 'mysql',
connection: {
host : ctx.config['DB_HOST_URL'], //获取数据库配置环境变量
port : ctx.config['DB_HOST_PORT'],
user : ctx.config['DB_USER'],
password : ctx.config['DB_PASSWORD'],
database : ctx.config['DB_NAME']
}
});
// 根据传入的 name查询数据表
const rows = await knex('article').where('article_title','like',`%${name}%`)
return rows
})
#下面我们将代码重新部署一下
fn --verbose deploy --app nodeApp --local
# 然后执行接口
curl -X "POST" -H "Content-Type: application/json" -d '{"name":"you"}' http://localhost:8080/invoke/01FG6BBGV9NG8G00GZJ0000002
# 下面直接获得接口返回的JSON
[{"article_id":1,"article_title":"Attention is all you need","article_author":"Bengio"}]
到此我们完成对数据库的访问。 举一反三同样,我们同样也可以访问redis,mongodb等外部服务。 但是这里有一个问题,我们缺少连接池,每次都需要重新创建一个新连接,这种方式不太高效。但对一些小的应用,用完就关闭,也可以满足了。 但做好也有办法可以解决,一个是将连接操作放外面。或者对这种连接操作,再单独做函数应用,类似微服务化的方式。在这里我就不展开讲了。 这里只是让大家通过自己动手搭建,来逐步理解ServerLess是怎么工作的。
八、ServerLess最后总结
在此,我只是简单的动手搭建了一个最基本的ServerLess服务,真正用于生产,还需要考虑更多。服务注册发现、健康检查、配置、容错,监控,流量,接入Kubernetes等 都是需要考虑的事情。 但ServerLess也有一定的弊端,开发、调试有点不太方便,你需要自己写单元测试提前在本地运行好。不然就只能看控制台日志。本地做的话,也需要注意,把业务拆细一点。代码太长也是不利于调试的。 对前端来说,可以用ServerLess快速实现一些想法,不依赖服务端的介入。还是很不错的。