查看原文
其他

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

牛秋霖(冬岛) 几米宋 2022-09-06

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

  • https://github.com/knative/docs/blob/master/eventing/README.md

  • https://thenewstack.io/knative-enables-portable-serverless-platforms-on-kubernetes-for-any-cloud/

本文不对基本概念做介绍,本文主要是基于 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

  1. apiVersion: eventing.knative.dev/v1alpha1

  2. kind: Channel

  3. metadata:

  4.  name: testchannel

  5. spec:

  6.  provisioner:

  7.    apiVersion: eventing.knative.dev/v1alpha1

  8.    kind: ClusterChannelProvisioner

  9.    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 资源

  1. NAME                                       AGE

  2. channel.eventing.knative.dev/testchannel   7d


  3. NAME                                    TYPE           CLUSTER-IP       EXTERNAL-IP                                           PORT(S)           AGE

  4. 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

  1. ... ...

  2. status:

  3.  address:

  4.    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 事件源的定义如下:

  1. apiVersion: sources.eventing.knative.dev/v1alpha1

  2. kind: KubernetesEventSource

  3. metadata:

  4.  name: testevents

  5. spec:

  6.  sink:

  7.    apiVersion: eventing.knative.dev/v1alpha1

  8.    kind: Channel

  9.    name: testchannel

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

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

  1. apiVersion: extensions/v1beta1

  2. kind: Deployment

  3. metadata:

  4.  name: testevents-n5t2w-qzhbv

  5. ... ...

  6. spec:

  7.  ... ...

  8.    spec:

  9.      containers:

  10.      - args:

  11.        - --namespace=default

  12.        - --sink=http://testchannel-channel-9j22r.default.svc.cluster.local/

  13.        env:

  14.        - name: SINK

  15.          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 的关联,从而达到消费事件的目的


参考文档

  • Knative Install on a Kubernetes Cluster

  • https://github.com/knative/eventing/blob/master/docs/spec/overview.md

  • https://github.com/knative/eventing/blob/master/docs/spec/spec.md

  • https://github.com/knative/eventing/blob/master/docs/spec/interfaces.md

点击阅读原文跳转到 Web 浏览可以查看文中链接。

相关阅读推荐

Knative:重新定义Serverless

Google 开源的 Serverless 平台 knative 简介

Service Mesh的未来将与Knative和Apahce Whisk等技术和谐共存——采访RedHat的Istio产品经理

基于Kubernetes和Istio的Serverless框架Knative解析之Autoscaler评估Kubernetes中的Serverless框架

Serverless vs Container——开发人员向左,DevOps向右

加入 ServiceMesher 社区

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存