Knative Eventing in-memory-channel实现原理解析

2019年2月22日 | 作者 牛秋霖 | 2000字 | 阅读大约需要4分钟
归档于 knative | 标签 #knative #serverless

关于 Knative eventing 的基本概念可以参考:

本文不对基本概念做介绍,本文主要是基于 Kubernetes Event Source example 为例分析 in-memory-channel 的实现原理。

在运行 Kubernetes Event Source example 之前要保证已经安装了 in-memory-channel , 下面先从 in-memory-channel controller 开始介绍 channel 的工作机制。

in-memory-channel controller

in-memory-channel 安装好以后就会自动创建一个 controller 和 in-memory-channel-dispatcher。 dispatcher 启动 http 服务接受 event,并根据 event 所属 channel 自动寻找相关的 subscription 然后把事件发送出去。因为这是一个基于内存实现的 channel 所以仅仅是转发一下事件不能用于生产环境,在生产环境可以使用 gcppubsub、kafka 以及 natss 等存储介质。

in-memory-channel controller 监听 channel 资源如果发现有 channel 的 provisioner 是自己就开始做 EventResource 到 channel 的 feed(目前是基于 istio 的 virtualService 实现的)

通过上面的介绍我们发现 channel 其实是一个虚拟的概念作用仅仅是提供一个分组功能,有点儿像是 kubernetes 中的 service 的概念,channel 的作用是:

  • 提供一种事件从事件源路由到 consumer 的介质
  • 绑定事件源和 channel (sink)
  • 把 consumer 绑定到 channel(subscription)
  • 把接收到的消息 dispatch 到相应的 subscription
  • channel 是可以有自己的后端存储的,自己的后端存储可以是任何消息中间件,in-memory 只保存在内存中
  • 目前 channel 接收和 dispatch eventing 都是基于 http 协议的(CloudEvent 在设计上是支持很多协议的但是目前 knative 只实现了 http 协议)

in-memory channel controller 发现如果有 channel 的 provisioner 是 ClusterChannelProvisioner 会做如下三件事情:

  • 创建一个 service,这个 service 就是 event Resource 向自己发送事件时使用的 service,并且 service 会写入到 channel 的 Status.Address.Hostname 字段。channel 这是一个地址可达的资源

  • 创建一个 Istio 的 VirtualService,作用是把 Channel 的 service 转换到 in-memory channel dispatcher service 从而达到 channel 和 provisioner 绑定 的效果

  • 配置 Channel 的 subscriptions

不过配置 subscriptions 的动作不是在接收到 channel cr 的时候触发的。in-memory-channel controller 会 watch subscriptions 资源,当有 subscriptions 创建或者修改的时候就把相关的 subscription 绑定到 channel 上( subscribable.subscribers)

创建一个 testchannel

创建 channel

apiVersion: eventing.knative.dev/v1alpha1
kind: Channel
metadata:
  name: testchannel
spec:
  provisioner:
    apiVersion: eventing.knative.dev/v1alpha1
    kind: ClusterChannelProvisioner
    name: in-memory-channel

channel 定义中的 provisioner 是 channel 的实现实体(如果把 channel 比喻成 service/endpoint 的话那么 provisioner 就相当于是 service 对应的 Pod)。 channel 本身其实只是一个概念、一个定义。具体的实现都是 provisioner 来做的。channel 使用哪个 provisioner 就相当于是使用哪一种实现(是不是有一种 StorageClass 的感觉?)。目前 knative 支持的实现有:

  • in-memory 一般只在测试时使用
  • gcppubsub
  • kafka
  • natss

channel 创建提交以后会创建出现下面这样的一个 channel 资源,和一个kubernetes serivce 资源

NAME                                       AGE
channel.eventing.knative.dev/testchannel   7d

NAME                                    TYPE           CLUSTER-IP       EXTERNAL-IP                                           PORT(S)           AGE
service/testchannel-channel-9j22r       ClusterIP      10.102.124.54    <none>                                                80/TCP            7d3h

这个 service/testchannel-channel-9j22r service 并没有对应到真实的 pod, 因为 channel 只是一个概念具体的实现是需要对应的 provisioner 来实现的,并且 channel 的 status 里面会记录到当前 channel 的 hostname

... ...
status:
  address:
    hostname: testchannel-channel-9j22r.default.svc.cluster.local

我们以 in-memory-channel provisioner controller 为例说明,在 in-memory-channel 的实现中是通过 istio 的一个 VirtualService 实现的,把到这个 service 的访问直接跳转到 in-memory-channel-dispatcher.knative-eventing.svc.cluster.local 这个 service。而 in-memory-channel-dispatcher.knative-eventing.svc.cluster.local 这个 service 就是 in-memory-channel controller 实现创建的。in-memory-channel controller 通过 istio 的 VirtualService 实现了事件源和channel的绑定

创建 k8s 事件源

k8s 事件源的定义如下:

apiVersion: sources.eventing.knative.dev/v1alpha1
kind: KubernetesEventSource
metadata:
  name: testevents
spec:
  sink:
    apiVersion: eventing.knative.dev/v1alpha1
    kind: Channel
    name: testchannel

事件源通过 spec.sink 定义事件需要发送到哪个 channel 上去。

K8S 事件源会生成相应的 Deployment 用于收集 kubernetes 事件并发送到相应的 channel,pod 的 –sink= 启动参数就是到目标 channel 的 url。–sink 这个参数的 url 就是根据 KubernetesEventSource crd 的 spec.sink 信息获取到目标 channel 然后取的其 status.address.hostname 字段

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: testevents-n5t2w-qzhbv
... ...
spec:
  ... ...
    spec:
      containers:
      - args:
        - --namespace=default
        - --sink=http://testchannel-channel-9j22r.default.svc.cluster.local/
        env:
        - name: SINK
          value: http://testchannel-channel-9j22r.default.svc.cluster.local/

当 testevents-n5t2w-qzhbv pod 发送事件到 http://testchannel-channel-9j22r.default.svc.cluster.local/ 时请求会被 sidecar 中的 istio-proxy 转发到 http://in-memory-channel-dispatcher.knative-eventing.svc.cluster.local/ (Knative 就是通过 istio 的 virtualService 实现的 eventSource 到 channel 的绑定)从而达到事件转发到 in-memory-channel 的能力。接下来 in-memory-channel 的 dispatcher 把接收到的时候转发给响应的 subscription就完成了实践的整个生命周期的流转。

总结

我们来汇总一下 Knative eventing 涉及到的几个关键概念及其之间的关系。

  • event producers
  • eventSource
  • channel
  • provisioners
  • subscription
  • consumers

下面我们以 Kubernetes Event Source example 来说明每一个概念对应的角色:

  • event producers:

Kubernetes Event Source example 是演示如何把 kubernetes 中的事件发送到 channel 并通过 subscription 最终触发 message-dumper 函数执行的例子。这其中 kubernetes 集群就是事件的最初生产者,所以 kubernetes 集群就是 event producers

  • eventSource

eventSource 不是 event producers , eventSource 是把 event producers 生成的事件 接入 到 Knative 体系中,其实是一个和外部系统的适配层

  • channel

是一个逻辑概念,主要是对事件本身、事件的存储以及事件的 subscription 向下传递路径做一个分组归类,每一个 channel 都有一个 status.address.hostname 字段,这个字段确定了如何把事件发送到这个 channel

  • provisioners

provisioners 是 channel 的存储介质, 可以使用 gcpsubsub、kafka 以及 natss 等产品支持。provisioners 是 channel crd 在创建的时候指定的。这个设计和 kubernetes 的 StorageClass 是一脉相承的。因为 eventSource 是通过 channel 的 status.address.hostname 向 channel post 事件的,所以 in-memory-channel 这个 provisioners 通过 istio virtualService 的方式 在 eventSource 的 sidecar 中劫持了 发向 channel 的事件,直接转发给了 provisioners ,从而实现了 provisioners 和 channel 的动态绑定的功能

  • subscription

subscription 是一个独立的 crd,一个 channel 可以对应多个 subscription,当 provisioners watch 到一个新的 subscription 是就建立自己管理的 channel 和 subscription 的绑定关系(在 channel 的 spec 中增加到 subscription 的引用列表)。当有事件发送到 channel 时 provisioners 就自动把事件转发给相关的 subscription

  • consumers

消费事件的角色,和 channel 一样,consumers 也必须是一个地址可达的资源,并通过 status.address.hostname 字段指明如何访问到此 consumer。consumers 是通过 subscription 建立和channel 的关联,从而达到消费事件的目的

参考文档