绝对的说法都是错误的。

FaaS & Kubernetes & ?

扫了下 k8s 的基本概念和各种组件,很大很“美好”,是个吃经验的东西,想着通过实践的方式来学习 k8s,正好 Serverless 概念非常火,现在大概的表现形式就是 FaaS?这个东西很玄乎,并没有很明确清晰的定义,做个没用的 “FaaS” 框架就是本文的目标。文中关于 Serverless/FaaS 的很多东西都是道听途说然后自行脑补出来的,对 k8s 的使用大概也不符合最佳实践。

思路

FaaS

大概就是微服务框架的套路,只不过将业务逻辑完全抽离了,通过配置文件的手段声明各种功能需求,比如函数间的依赖是通过声明的方式,然后框架来做通信交互甚至是治理相关的事情,最后打包镜像的时候将框架和函数整合在一起编译。但是这样有个问题,每种语言都需要搞这么一套框架,大概可以通过某种 sidecar 的形式,类似 ServiceMesh,或者把其它的语言函数翻译成一种通用的语言形式。

通过事件流驱动函数执行,交互变成了消息总线的方式,流程上和 RPC 的方式有很大的差异,框架这一层或许能有一点简化。

比如使用 Kafka 作为消息系统,每个 Function 有两个 Topic:Input 和 Output,对外暴露的 Gateway 负责将事件塞进 Kafka InputTopic,然后从 OutputTopic 消费处理结果并返回给用户,框架消费 InputTopic 将输入传给函数,将输出塞进 OutputTopic。这种方式看起来并不适合简单的 WebService 场景,比如 Gateway 需要生成一个唯一的 ID 或者使用其它方式将 Input/Output Message 匹配起来,这样还需要每个 Gateway 和 Function 实例独享一个 Topic 或者 Partition,不然对应的 Output 可能被其它实例消费或者需要做大量的过滤工作做一些无用的消费。

然后使用 Kafka 这类消息系统可能需要使用某种方式将其与具体的函数解耦,比如如果使用 Kafka 的 Consumer Group 机制,那么一个 Partition 不能同时被 Consumer Group 内的多个 Consumer 消费,Consumer 和函数绑定在一起会导致的函数的处理能力受限,增多 Partition 数量会触发 Kafka 的 rebalance 和数据迁移等,带来潜在的问题和瓶颈,不使用 Consumer Group 就需要自己管理调度对 Partition 的消费非要这么玩也没啥好说的。Apache Bookkeeper 或者基于其之上的 Apache Pulsar 存储的模型是要优于 Kafka 的,或许是个更好的选择。

k8s

看到有实现通过添加一个叫 Trigger Controller 的组件消费 Kafka 并转发输入给对应的 Function,Controller 这个东西感觉玩法一般都是一主多备来保证 HA,但不作为一种可以横向扩容以提高处理能力的组件而存在?具体怎么用的没有细看,当然事件驱动这种方式不是针对 Web Service 的。

纯粹为了学习 k8s,套路简单点,k8s 的使用姿势如下:

  • Function 通过 CRD 来表示。
  • 自定义的 Controller 用来追踪管理 Function 资源的创建销毁等
    • 提供 HA,避免单点故障以及多主带来的状态冲突,通过 k8s 提供的锁和选举 API(based on etcd)
    • 需要手动编译打包 Function 的镜像,runtime 包括一个简单的 HTTP Server,只是把函数注入进去了..
    • 监听到新的 Function 资源的创建后:
      • 创建 Service 做服务发现和负载均衡相关的工作(Ingress 啥的就先不管了..)
      • 创建一个包含 Function 的 Deployment
      • 创建 HorizontalPodAutoscaler
    • 删除的话干掉对应的资源即可,使用 ownerReferences 能简化这个步骤

然后 k8s 不支持缩容到零,仔细想想貌似也不太现实..

实现

值得一提的是如果资源给少了,minikube 可能会无限失败哦,这里只做参考:

minikube start --cpus=2 --memory=2048 --disk-size=2g \
  --image-repository=registry.cn-hangzhou.aliyuncs.com/google_containers

实现的具体过程就不展开了,见 https://github.com/damnever/useless

k8s 的 API 比较多给人的感觉比较乱,Golang 虽然简单但是确实差了点什么,代码长度没有限制加上只有 if/else 这种基本的流程控制方式还特么要八8个空格缩进(大概其它的语言也有这样那样的问题,纯粹的只是为了喷而喷哈哈).. 话又说回来,虽然只是简单的体验了下,但是总体来说很不错,基本上所有的东西都被定义为资源,都能被 CRUD,作为一个平台套路还是比较让人认可的,很多内置/基本/核心的组件和功能都是基于核心的框架流程实现的。

后话

Serverless/FaaS 初衷是很好的,主要目的和微服务框架、ServiceMesh 差不多就是需要把脏活累活收的更干净,然后充分利用云计算的各种优势,大概要解决的问题是如何优雅的去做脏活累活,甚至是能实现自举自治那也就没程序员啥事了..

如果实现一个生产环境可用的 “FaaS” 框架产品,还有很多问题需要解决,包括但不限于:

  • 函数以什么形式执行,如果不是 Fire-and-Forget 会不会依赖进程内的全局共享状态?!
  • 多语言、依赖包管理、环境变量、日志、指标等等一些基础的功能支持
  • 配置管理
  • CI/CD
  • Web IDE 或者其它类似的工具也是很有必要的
  • 对函数输入/输出进行裁剪转换
  • 全功能的 gateway 支持比如 HTTP CORS 等等,甚至是其它协议
  • 自动扩缩容
  • 最小化额外的资源开销
    • 降低容器、语言工具启动初始化以及销毁带来的资源消耗
      • 比如常见的使用本地缓存或者 P2P 的方式来快速拉取函数对应的容器
      • 除了函数运行之外的其它开销都需要尽可能避免,因为函数的粒度已经很小了
    • 诸多类似 CGI 程序模型的问题
      • 比如对于数据库等外部服务的依赖如何优雅的解决?
      • 如果是直接在函数里即时的访问,会有很大的开销,比如无法重用连接
      • 或者通过另一种常驻的 Worker/Service 来做数据库的操作,函数的输出 Pub 进消息总线,Worker/Service Sub 对应的数据库操作?诶.. 不也要通过某种方式通信么..
    • 或许能用各种用户态软件、高端硬件的方式来解决一些问题
  • 缓存功能:维度,一致性要求
  • 通过 Namespace/Group 的方式组织函数集
  • 很多 workflow 引擎的功能
    • 定时调度甚至自定义调度引擎
    • 带有依赖关系和优先级关系的函数集
    • 生命周期和状态机管理等
  • 各种主动被动的 Hook 或者其它方式方便的将状态导出和外部组件交互
  • 函数维度比微服务更小?肯定会更乱,在有更好的规范和流程之前肯定会趟更多坑,就更离不开治理
  • ..

仔细想下来 Serverless 应该看做是一种愿景或者理想的状态,这里说的 “FaaS” 也不能硬套在不合适的应用场景上。