本文是开源 serverless 产品原理剖析系列文章的第二篇,关于 serverless 背景知识的介绍可参考文章开源 serverless 产品原理剖析(一) - Kubeless,这里再也不赘述。node
Fission 是由私有云服务提供商 Platform9 领导开源的 serverless 产品,它借助 kubernetes 灵活强大的编排能力完成容器的管理调度工做,而将重心投入到 FaaS 功能的开发上,其发展目标是成为 AWS lambda 的开源替代品。从 CNCF 视角,fission 属于 serverless 平台型产品。python
Fission 包含 Function、Environment 、Trigger 三个核心概念,其关系以下图所示:git
Fission 包含 Controller、Router、Executor 三个关键组件:github
本章节将从如下几个方面介绍 fission 的基本原理:golang
本文所作的调研基于kubeless 0.12.0
和k8s 1.13
。docker
CNCF 对函数生命周期的定义以下图所示,它描绘了函数构建、部署、运行的通常流程。apache
要理解 fission,首先须要了解它是如何管理函数生命周期的。Fission 的函数执行器是其控制函数生命周期的关键组件。Fission 包含 PoolManager 和 NewDeploy 两类执行器,下面分别对二者进行介绍。设计模式
Poolmgr 使用了池化技术,它经过为每一个 environment 维持了必定数量的通用 pod 并在函数被触发时将 pod 特化,大大下降了函数的冷启动的时间。同时,poolmgr 会自动清理一段时间内未被访问的函数,减小闲置成本。该执行器的原理以下图所示。api
此时,函数的生命周期以下:缓存
使用 fission CLI 向 controller 发送请求,建立函数运行时须要的特定语言环境。例如,如下命令将建立一个 python 运行环境。
fission environment create --name python --image fission/python-env
使用 fission CLI 向 controller 发送建立函数的请求。此时,controller 只是将函数源码等信息持久化存储,并未真正构建好可执行函数。例如,如下命令将建立一个名为 hello 的函数,该函数选用已经建立好的 python 运行环境,源码来自 hello.py,执行器为 poolmgr。
fission function create --name hello --env python --code hello.py --executortype poolmgr
Poolmgr 很好地平衡了函数的冷启动时间和闲置成本,但没法让函数根据度量指标自动伸缩。NewDeploy 执行器实现了函数 pod 的自动伸缩和负载均衡,该执行器的原理以下图所示。
此时,函数的生命周期以下:
使用 fission CLI 向 controller 发送建立函数的请求。例如,如下命令将建立一个名为 hello 的函数,该函数选用已经建立好的 python 运行环境,源码来自 hello.py,执行器为 newdeploy,目标副本数在 1 到 3 之间,目标 cpu 使用率是 50%。
fission fn create --name hello --env python --code hello.py --executortype newdeploy --minscale 1 --maxscale 3 --targetcpu 50
Newdeploy 监听到了函数的 ADD 事件后,会根据 minscale 的取值判断是否当即为该函数建立相关资源。
实际使用过程当中,用户须要从延迟和闲置成本两个角度考虑选择何种类型的执行器。不一样执行器的特色以下表所示。
执行器类型 | 最小副本数 | 延迟 | 闲置成本 |
---|---|---|---|
Newdeploy | 0 | 高 | 很是低 - pods 一段时间未被访问会被自动清理掉。 |
Newdeploy | >0 | 低 | 中等 - 每一个函数始终会有必定数量的 pod 在运行。 |
Poolmgr | 0 | 低 | 低 - 通用池中的 pod 会一直运行。 |
Fission 将函数执行器的概念暴露给了用户,增长了产品的使用成本。实际上能够将 poolmgr 和 newdeploy 技术相结合,经过建立 deployment 将特化后的 pod 管理起来,这样能够很天然地利用 HPA 来实现对函数的自动伸缩。
在介绍函数执行器时屡次提到了 pod 特化,它是 fission 将环境容器变成函数容器的奥秘。Pod 特化的本质是经过向容器发送特化请求让其加载用户函数,其原理以下图所示。
一个函数 pod 由下面两种容器组成:
具体步骤以下:
前面的章节介绍了 fission 函数的构建、加载和执行的逻辑,本章节主要介绍如何基于各类事件源触发 fission 函数的执行。CNCF 将函数的触发方式分红了以下图所示的几种类别,关于它们的详细介绍可参考连接 Function Invocation Types。
对于 fission 函数,最简单的触发方式是使用 fission CLI,另外还支持经过各类触发器。下表展现了 fission 函数目前支持的触发方式以及它们所属的类别。
触发方式 | 类别 |
---|---|
fission CLI | Synchronous Req/Rep |
HTTP Trigger | Synchronous Req/Rep |
Time Trigger | Job (Master/Worker) |
Message Queue Trigger 1. nats-streaming 2. azure-storage-queue 3. kafka |
Async Message Queue |
Kubernetes Watch Trigger | Async Message Queue |
下图展现了 fission 函数部分触发方式的原理:
全部发往 fission 函数的请求都会由 router 转发,fission 经过为 router 建立 NodePort 或 LoadBalancer 类型的 service 让其可以被外界访问。
除了直接访问 router,还能够利用 K8s ingress 机制实现 http trigger。如下命令将为函数 hello 建立一个 http trigger,并指定访问路径为/echo
。
fission httptrigger create --url /echo --method GET --function hello --createingress --host example.com
该命令会建立以下 ingress 对象,能够参考 createIngress 深刻了解 ingress 的建立逻辑。
apiVersion: extensions/v1beta1 kind: Ingress metadata: # 该 Ingress 的名称 name: xxx ... spec: rules: - host: example.com http: paths: - backend: # 指向 router service serviceName: router servicePort: 80 # 访问路径 path: /echo
Ingress 只是用于描述路由规则,要让规则生效、实现请求转发,集群中须要有一个正在运行的 ingress controller。想要深刻了解 ingress 原理可参考系列文章第一篇中的 HTTP trigger 章节。
若是但愿按期触发函数执行,须要为函数建立 time trigger。Fission 使用 deployment 部署了组件 timer,该组件负责管理用户建立的 timer trigger。Timer 每隔一段时间会同步一次 time trigger 列表,并经过 golang 中被普遍使用的 cron 库 robfig/cron 按期触发和各 timer trigger 相关联函数的执行。
如下命令将为函数 hello 建立一个名为halfhourly
的 time trigger,该触发器每半小时会触发函数 hello 执行一次。这里使用了标准的 cron 语法定义执行计划。
fission tt create --name halfhourly --function hello --cron "*/30 * * * *" trigger 'halfhourly' created
为了支持异步触发,fission 容许用户建立消息队列触发器。目前可供选择的消息队列有 nats-streaming、azure-storage-queue、kafka,下面以 kafka 为例描述消息队列触发器的使用方法和实现原理。
如下命令将为函数 hello 建立一个基于 kafka 的消息队列触发器hellomsg
。该触发器订阅了主题 input 中的消息,每当有消息到达它便会触发函数执行。若是函数执行成功,会将结果写入主题 output 中,不然将结果写入主题 error 中。
fission mqt create --name hellomsg --function hello --mqtype kafka --topic input --resptopic output --errortopic error
Fission 使用 deployment 部署了组件 mqtrigger-kafka,该组件负责管理用户建立的 kafka trigger。它每隔一段时间会同步一次 kafka trigger 列表,并为每一个 trigger 建立 1 个用于执行触发逻辑的 go routine,触发逻辑以下:
Message/Record Streams
触发方式的支持,该方式要求消息被顺序处理;K8s 经过 Horizontal Pod Autoscaler 实现 pod 的自动水平伸缩。对于 fission,只有经过 newdeploy 方式建立的函数才能利用 HPA 实现自动伸缩。
如下命令将建立一个名为 hello 的函数,运行该函数的 pod 会关联一个 HPA,该 HPA 会将 pod 数量控制在 1 到 6 之间,并经过增长或减小 pod 个数使得全部 pod 的平均 cpu 使用率维持在 50%。
fission fn create --name hello --env python --code hello.py --executortype newdeploy --minmemory 64 --maxmemory 128 --minscale 1 --maxscale 6 --targetcpu 50
Fission 使用的是autoscaling/v1
版本的 HPA API,该命令将要建立的 HPA 以下:
apiVersion: autoscaling/v1 kind: HorizontalPodAutoscaler metadata: labels: executorInstanceId: xxx executorType: newdeploy functionName: hello ... # 该 HPA 名称 name: hello-${executorInstanceId} # 该 HPA 所在命名空间 namespace: fission-function ... spec: # 容许的最大副本数 maxReplicas: 6 # 容许的最小副本数 minReplicas: 1 # 该 HPA 关联的目标 scaleTargetRef: apiVersion: extensions/v1beta1 kind: Deployment name: hello-${executorInstanceId} # 目标 CPU 使用率 targetCPUUtilizationPercentage: 50
想了解 HPA 的原理可参考系列文章第一篇中的自动伸缩章节,那里详细介绍了 K8s 如何获取和使用度量数据以及目前采用的自动伸缩策略。
为了能更好地洞察函数的运行状况,每每须要对函数产生的日志进行采集、处理和分析。Fission 日志处理的原理以下图所示。
日志处理流程以下:
/var/log/
和/var/lib/docker/containers
挂载进来,方便直接采集。fission function logs --name hello
能够查看到函数 hello 产生的日志。目前,fission 只作到了函很多天志的集中化存储,可以提供的查询分析功能很是有限。另外,influxdb 更适合存储监控指标类数据,没法知足日志处理与分析的多样性需求。
函数是运行在容器里的,所以函很多天志处理本质上可归结为容器日志处理。针对容器日志,阿里云日志服务团队提供了成熟完备的解决方案,欲知详情可参考文章面向容器日志的技术实践。
在介绍完 fission 的基本原理后,不妨从如下几个方面将其和第一篇介绍的 kubeless 做一个简单对比。