在今年4月份的DockerCon压轴的 Moby's Cool Hack Session上,Alex Ellis给大家展现了一个名为Function as a Service (FaaS)项目。FaaS基于Docker Swarm集群上实现了一个极简的Serverless框架,支持将任意Unix进程作为函数实现来对外提供服务。
FaaS 架构
在 FaaS 原型系统中
- 任何进程都可以转化成为一个函数,并利用Docker镜像进行打包和交付
- 利用 Docker Swarm 集群的资源调度和routing mesh的负载均衡能力简洁地实现了函数的调度能力。其中每个函数对应一个Docker集群中的服务
- 基于 Prometheus 实现函数调用监控和自动伸缩
其设计架构非常简单,其中
- API Gateway 负责接受服务调用,路由请求到后端函数实现,并采集服务调用的指标发送给 Prometheus。 Prometheus 则会根据一段时间内服务调用的次数,回调API Gateway 来动态伸缩服务容器实例数量。
- Function Watchdog 将HTTP请求转发为进程调用,并将请求数据通过 STDIN 传递给进程,而将进程的 STDOUT 作为 HTTP 响应的结果返回给调用者。将函数进程和Function Watchdog打包成一个容器镜像进行部署。其调用流程如下:
FaaS 在本地部署非常简单
首先你需要准备一个本地的Docker Swarm集群,如果没有,可以安装最新Docker Engine并执行下面命令:
docker swarm init
执行如下命令来部署FaaS
git clone https://github.com/alexellis/faas
cd faas
./deploy_stack.sh
在部署完成之后,我们可以通过如下命令检查FaaS的状态
$ docker stack services func
ID NAME MODE REPLICAS IMAGE
1a8b2tb19ulk func_gateway replicated 1/1 functions/gateway:0.5.6
4jdexem6kppg func_webhookstash replicated 1/1 functions/webhookstash:latest
9ju4er5jur9l func_wordcount replicated 1/1 functions/alpine:health
e190suippx7i func_markdown replicated 1/1 alexellis2/faas-markdownrender:latest
l70j4c7kf99t func_alertmanager replicated 1/1 functions/alertmanager:latest
mgujgoa2u8f3 func_decodebase64 replicated 1/1 functions/alpine:health
o44asbnhqbda func_hubstats replicated 1/1 alexellis2/faas-dockerhubstats:latest
q8rx49ow3may func_echoit replicated 1/1 functions/alpine:health
t1ao5psnsj0s func_base64 replicated 1/1 functions/alpine:health
vj5z7rpdlo48 func_prometheus replicated 1/1 functions/prometheus:latest
xmwzd4z7l4dv func_nodeinfo replicated 1/1 functions/nodeinfo:latest
随后通过浏览器来访问 http://127.0.0.1:8080/ui FaaS
整个流程非常简单,就像夏日的清风,让人感到自然愉悦。
在阿里云上测试FaaS
由于FaaS是基于Docker Swarm mode集群进行部署的,你首先需要在阿里云容器服务创建一个Swarm mode集群
然后利用如下模板来部署应用
version: "3"
services:
# Core API services are pinned, HA is provided for functions.
gateway:
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
ports:
- 8080:8080
labels:
aliyun.routing.port_8080: faas
image: functions/gateway:0.5.6
networks:
- functions
environment:
dnsrr: "true" # Temporarily use dnsrr in place of VIP while issue persists on PWD
deploy:
placement:
constraints: [node.role == manager]
prometheus:
image: functions/prometheus:latest # autobuild from Dockerfile in repo.
command: "-config.file=/etc/prometheus/prometheus.yml -storage.local.path=/prometheus -storage.local.memory-chunks=10000 --alertmanager.url=http://alertmanager:9093"
ports:
- 9090:9090
depends_on:
- gateway
- alertmanager
labels:
aliyun.routing.port_9090: prometheus
environment:
no_proxy: "gateway"
networks:
- functions
deploy:
placement:
constraints: [node.role == manager]
alertmanager:
image: functions/alertmanager:latest # autobuild from Dockerfile in repo.
environment:
no_proxy: "gateway"
command:
- '-config.file=/alertmanager.yml'
networks:
- functions
ports:
- 9093:9093
deploy:
placement:
constraints: [node.role == manager]
# Sample functions go here.
# Service label of "function" allows functions to show up in UI on http://gateway:8080/
webhookstash:
image: functions/webhookstash:latest
labels:
function: "true"
depends_on:
- gateway
networks:
- functions
environment:
no_proxy: "gateway"
https_proxy: $https_proxy
# Pass a username as an argument to find how many images user has pushed to Docker Hub.
hubstats:
image: alexellis2/faas-dockerhubstats:latest
labels:
function: "true"
depends_on:
- gateway
networks:
- functions
environment:
no_proxy: "gateway"
https_proxy: $https_proxy
# Node.js gives OS info about the node (Host)
nodeinfo:
image: functions/nodeinfo:latest
labels:
function: "true"
depends_on:
- gateway
networks:
- functions
environment:
no_proxy: "gateway"
https_proxy: $https_proxy
# Uses `cat` to echo back response, fastest function to execute.
echoit:
image: functions/alpine:health
labels:
function: "true"
depends_on:
- gateway
networks:
- functions
environment:
fprocess: "cat"
no_proxy: "gateway"
https_proxy: $https_proxy
# Counts words in request with `wc` utility
wordcount:
image: functions/alpine:health
labels:
function: "true"
com.faas.max_replicas: "10"
depends_on:
- gateway
networks:
- functions
environment:
fprocess: "wc"
no_proxy: "gateway"
https_proxy: $https_proxy
# Calculates base64 representation of request body.
base64:
image: functions/alpine:health
labels:
function: "true"
depends_on:
- gateway
networks:
- functions
environment:
fprocess: "base64"
no_proxy: "gateway"
https_proxy: $https_proxy
# Decodes base64 representation of request body.
decodebase64:
image: functions/alpine:health
labels:
function: "true"
depends_on:
- gateway
networks:
- functions
environment:
fprocess: "base64 -d"
no_proxy: "gateway"
https_proxy: $https_proxy
# Converts body in (markdown format) -> (html)
markdown:
image: alexellis2/faas-markdownrender:latest
labels:
function: "true"
depends_on:
- gateway
networks:
- functions
environment:
no_proxy: "gateway"
https_proxy: $https_proxy
networks:
functions:
driver: overlay
和本地部署相比只是增加了两个 label,定义了API Gatway和Prometheus的路由
- "aliyun.routing.port_8080: faas" : API Gatway的虚拟域名
- "aliyun.routing.port_9090: prometheus" : prometheus服务的虚拟域名
然后基于上面模板创建应用,
注:这里的应用名为 “faas_default”,部署完成之后所有函数服务和访问的名空间都基于这个名称。
部署完成之后,我们可以看见相应的服务列表
选择路由列表标签,我们可以看到之前定义的路由地址已经出现在列表中
可以点击连接上面连接访问FaaS的API Gateway和Prometheus服务界面
下面我们来进行一个测试来验证服务的伸缩性,首先,我们参照文档将Prometheus的URL修改为
http://<prometheus-endpoint>/graph?g0.range_input=15m&g0.expr=gateway_service_count&g0.tab=0&g1.range_input=15m&g1.expr=rate(gateway_function_invocation_total%5B20s%5D)&g1.tab=0&g2.range_input=15m&g2.expr=gateway_functions_seconds_sum+%2F+gateway_functions_seconds_count&g2.tab=0
注:其中URL的prometheus-endpoint需要替换为上文中端点地址
然后在本地运行如下命令
while [ true ] ; do curl -X POST http://<faas-endpoint>/function/faas-default_echoit -d 'Hello, Function as a Service'; done
注:命令中路径需要替换faas-endpoint,如果服务名称与faas-default_echoit不同,也请自行调整。
在Prometheus界面可以看到服务调用量的变化
在容器服务的应用服务列表界面,可以看到,faas-default_echoit的容器实例从1个扩容到20个。
当结束测试之后,服务实例也会缩容到一个
如果对创建自己的函数感兴趣,可以参考 Alex 的博客,本文不再赘述
https://blog.alexellis.io/build-and-deploy-with-faas/
总结
FaaS基于Docker Swarm集群技术,实现了一个极简的Serverless框架,支持将任意Unix进程作为函数来对外提供服务。FaaS目前还是一个原型系统,如果大家需要完备的Function as a Service能力,体验无服务器运维,还是建议采用阿里云 FunctionCompute服务。
FaaS框架展示了一些有趣的可能性
- 将现有程序封装成为函数,可以作为服务方便地集成到应用业务逻辑中。将函数计算和现有微服务架构应用有机结合在一起。
- 基于Docker和Swarm集群技术,部署运维非常简单。可以在任何地方部署,甚至是ARM设备上。下图来自RICHARD GEE,演示了在树莓派集群上运行FaaS。