本文编译自 Kubernetes 的官方博文,原文为 Platform9 的软件工程师 Soam Vasani 所写,讲解了一个基于 Kubernetes 的 Serverless 函数(FaaS)框架——Fission。
简单的来讲,Fission 是一个构建在 Kubernetes 之上的 FaaS/Serverless 函数框架。
注:Faas 即 Function as a Service 的缩写
Fission 允许你通过函数轻松的在 Kubernetes 上创建 HTTP 服务。它工作在源码级别函数和抽象的容器镜像之上(大多时候),同时简化了 Kubernetes 的学习曲线,让你不用深入了解 Kubernetes 就能创建出有用的服务。
通过 Fission CLI,你可以简单地创建和添加函数,还可以将这些函数关联到 HTTP 路由、Kubernetes 事件、或者其他触发器。函数在调度器触发后被执行,而且函数运行时只消耗 CPU 和内存(空闲的函数除了存储不会消耗任何资源)。Fission 当前支持多种语言,包括 NodeJS 和 Python。
基于 Fission 的特性,我们认为有必要让一个 Faas 框架运行在不同的基础设施上,包括公有云和专属云(on-premise)。但我们不清楚是否应该从无到有来构建,还是可以基于现有的编排系统。后来证明我们不应该从零开始去重复创造诸如集群管理、调度、网络管理等一系列功能。
借助强大和快速生长的社区,Kubernetes 提供了一个强大和灵活的编排系统,并有完善的 API 支持。在它上面构建意味着可以将容器编排功能留给 Kubernetes,而 Fission 就专注于 FaaS 特性。
另外,我们不想分离 FaaS 集群的原因是 FaaS 可以和其它基础设施很好的结合。比如,FaaS 可能适合做一个小的 REST API,但它需要和其它设施结合来存储状态。同时 FaaS 还有一个很好的工作机制,那就是作为事件处理者,用于处理来自存储、数据库,甚至 Kubernetes 本身的通知。Kubernetes 是一个让所有这些设施相互合作的平台。
Fission 可以用 kubectl create 命令来安装:详情可查看项目 README(https://github.com/fission/fission#get-and-run-fission-minikube-or-local-cluster) 。
如下是一个如何写 hello world HTTP 服务的例子:
$ cat > hello.py def main(context): print "Hello, world!" $ fission function create --name hello --env python --code hello.py --route /hello $ curl http:///hello Hello, world!
Fission 会关心如何将函数加载到容器当中,如何将请求路由过去等等。接下来我们将探讨更多的技术细节。
最核心的,一个 FaaS 框架必须做好:
有很多种方法可以达到以上目标,每种方法都需要权衡。框架计算是基于源码级别,还是 Docker 镜像级别,又或者是两者之间(类似buildpacks)?一个函数首次运行时间上限是多少?这些选择都会影响平台的灵活性、易用性、资源的利用率和消耗,当然还有性能。
我们的目标是想让 Fission 对新手友好。我们选择基于源码级别来计算,所以用户能避免处理容器镜像打包、推送镜像到镜像中心、管理镜像中心证书、镜像版本等问题。
然而,容器镜像还是最灵活的打包应用的方式,比如,一个纯粹的代码级别接口是不允许用户打包二进制依赖的。
所以,Fission 选择了一种混合的方法—-容器镜像包含函数所需的动态装载器。这种方法允许大多数用户纯粹从源码级别来使用 Fission,但需要时也允许他们自定义容器镜像。
在 Fission 中,这些镜像被称作“环境镜像”,包含了编程语言(比如说 NodeJS 或者 Python) 所需的运行环境,一系列通用的依赖和为函数准备动态加载器。如果这些依赖满足用户所写的函数,镜像无需重新构建,否则,依赖列表需要修改,镜像需要重新构建。
这些环境镜像只是和 Fission 的特定语言相关,它们给框架提供了一个统一的接口。这样的设计可以让 Fission 相对容易地扩展到其他语言。
Serverless 函数其中一个目标就是函数只在运行时使用 CPU/内存资源。这样优化了函数的资源消耗,但也带来了从空闲到启动的性能损耗(冷启动损耗)。
冷启动损耗在很多场景下非常重要。特别是交互式应用的场景——就像用户等待一个 web 或移动应用响应——几秒的冷启动延迟是不能接收的。
为了优化冷启动延迟,Fission 为每个环境保持着一个运行容器池。当一个函数请求进来之后, Fission 不需要部署一个新容器——它只需要选择一个已经在运行的容器,将函数拷贝到容器当中,将函数动态加载起来,并将请求路由到这个实例即可。对 NodeJS 和 Python 函数来说,这个过程的损耗差不多是 100 毫秒。
如上图所示,Fission 被设计为一系列的微服务。Controller 负责追踪函数、HTTP 路由、事件触发器和环境镜像。poolmgr 负责管理空闲的环境容器池、将函数加载到这些容器当中、以及杀死空闲的函数实例。Router 接收 HTTP 请求,并将他们路由到函数实例上,如果需要会向 poolmgr 请求新的实例。
Controller 提供 Fission API,所有其他组件通过关注 controller 来更新。Router 被暴露为 Kubernetes 的服务,可能是 LoadBalancer 类型或 NodePort 类型,依赖于 Kubernetes 集群在哪里托管。
当 router 获得请求,它会查找缓存,看是否有一个路由过去的服务。如果没有,它会查找匹配请求的函数,并且向 poolmgr 请求一个新的实例。poolmgr 有一个空闲 pods 池,它会选择一个 pod,将函数加载进去(将请求发送到 pod 的容器中),并将 pod 地址返回给 router。Router 将请求代理到这个pod。这个 pod 也会被缓存给随后的请求,如果空闲了好几分钟,那就会被杀掉。
注:目前,Fission 映射一个函数到一个容器;自动扩容到多实例还在计划当中;还计划加入重用函数 pods 用以托管多函数,特别是对于那种无需隔离的场景。
Fission 是一个不错的框架,易于构建小的 REST APIs,实现 webhooks,以及为 Slack 等服务编写聊天机器人(Chatbots)。
以一个简单的 REST API 为例,我们制作了一个小的留言板(guestbook)应用,它用函数来完成读写,结合 redis 来跟踪状态。你可以在 Fission GitHub 仓库中找到该应用。
这个应用包含两个访问终端——其中一个是 GET 终端, 它从 redis 缓存获取留言板实体,并将它们转化为 HTML 输出;另一个是 POST 终端,它向 redis 缓存中添加留言板列表想的新实体。函数所做的就这些而已——不需要管理 Dockerfile,更新 app 只需要简单地更新 Fission 函数即可。
Fission 同样支持基于 Kubernetes watches 来触发函数。举个例子,你可以构造一个函数来观察在特定 namespace 中, 匹配特定 label 的 pod。函数获取序列化对象,监听事件,并基于上下文做相应操作(增加/删除/修改)。
这些事件处理函数可以用来做简单的监控,比如,无论何时当一个新的服务被加入到集群当中,你就可以发送一个 slack 消息。还有其它更复杂的应用场景,比如写一个自定义的 controller,来监听 Kubernetes 的第三方资源。
Fission 当前还是早期的 alpha 版。还没做好生产使用的准备。我们正在寻找早期的使用者和反馈。
Fission 接下来要做什么呢?我们在努力使基于 Kubernetes 的 FaaS 更便捷,更容易使用和集成。在接下来的几个月我们会做这些支持:单元测试、Git 集成、函数监控和日志聚合。我们同样在考虑集成其它的事件源。
创建更多的语言环境也在工作当中,当前已经支持 NodeJS、Python、PHP7、.Net、Golang、Ruby。
你也可以在我们的 GitHub issues 和 projects 中查找当前的路线图。
附录:
原文链接:http://blog.kubernetes.io/2017/01/fission-serverless-functions-as-service-for-kubernetes.html
原文作者:Soam Vasani, Software Engineer, Platform9 Systems
编译: 黄庆兵,网易云系统开发工程师Fission:基于 Kubernetes 的 Serverless 函数框架