云原生框架- KNative入门与原理

Serverless

Serverless通常被直译成“无服务器”,无服务器计算是可以让用户在不考虑服务器的情况下构建并运行应用程序。使用无服务器计算,应用程序仍在服务器上运行,但所有服务器管理工作均由Serverless平台负责。如机器申请、代码发布、机器宕机、实例扩缩容、机房容灾、按量付费等都由平台帮助自动完成,业务开发只需考虑业务逻辑的实现即可。

适用场景

  • 无状态、短期运行、对冷启动延迟不敏感的工作负载
  • 低频或有零星请求,但具有较大不可预测扩容变化需求的工作负载
  • 业务需求变化迅速,要求快速开发实现的场景

KNative简介

iOS元生开发框架_iOS元生开发框架

Knative 是谷歌牵头的 Serverless 架构方案,旨在提供一套简单易用的 serverless 开源方案,把Serverless 标准化和平台化。

主要功能包括:

  • 自动完成代码到容器的的应用构建部署,支持多版本发布(Reversion)
  • 基于流量百分比的蓝绿发布(基于Istio)
  • 根据请求自动扩缩容,支持缩容到零(AutoScaler)
  • 基于 CloudEvents 的事件驱动 ,把应用(或者函数)和特定的事件进行绑定:当事件发生时,自动触发应用(或者函数)
  • 丰富的可扩展的事件源

Knative 建立在 kubernetes 和 istio 平台之上,使用 kubernetes 提供的容器管理能力(deployment、replicaset、和 pods等),以及 istio 提供的网络管理功能(Ingress、LB、VirtualService、DestinationRule等)。

Knative 将重点放在两个关键组件上:为其提供流量Serving(服务),以及确保应用程序能够轻松地生产和消费Event(事件)。

Build(构建):

构建系统,把用户定义的函数和应用 build 成容器镜像

Serving(服务)

根据负载情况自动伸缩,包括在没有负载时能够缩减到零。

允许创建多个修订版本(revision)的应用流量策略,从而能够通过 URL 轻松路由到目标应用程序。

Event(事件)

使得生产和消费事件变得更容易。KNative 能够抽象出事件源,并允许操作人员使用自定义的消息传递层(Broker)。

Serving

组件介绍

iOS元生开发框架_knative_02

包括了四个主要资源:Service、Route、Configuration 和 Revision:

  1. Service: 自动管理工作负载整个生命周期。负责创建 route,configuration。通过 Service可以指定路由流量使用最新的revision,或者历史版本的 revision
  2. Route:负责映射网络端点到一个或多个revision。可以通过多种方式管理流量。包括灰度流量和重命名路由
  3. Configuration:负责保持工作负载的期望状态,包含了代码与配置相关的信息,修改一次 Configuration 产生一个 revision ,在 Configuration 中为部署定义所需的状态,最小化 Configuration 至少包括一个配置名称和一个要部署容器镜像的引用。
  4. Revision:Revision 资源是对工作负载进行的每个修改的代码和配置的快照。Revision 是不可变对象,可以长期保留

其他资源:

  1. Knative Ingress: 外部请求首先到达Ingress Gateway,根据Kingress的配置做转发,默认采用istio-ingressgateway作为其基础网络服务, 采用istio的VirtualService配置来实现服务路由管理,流量百分比。

iOS元生开发框架_数据_03

Knative 的 Pod 是由两个 Container 组成的:Queue-Proxy 和业务容器 User-Container。

  1. Queue-proxy: Queue-proxy 是每个业务 pod 中都存在的 Sidecar,每个发到业务pod的请求都会先经过 queue-proxy queue-proxy 的其主要作用是 收集和限制 业务应用的并发量,比如当一个 revision 设定了并发量为 5 ,那么 queue-proxy 会保证每次到达业务容器的请求数不会大于 5. 如果多于 5 个请求到达,queue-proxy 会将请求暂存在本地队列中。 暴露几个端口表示如下: • 8012, queue-proxy 代理的http端口,流量的入口都会到 8012 • 8013, http2 端口,用于grpc流量的转发 • 8022, queue-proxy 管理端口,如健康检查 • 9090, queue-proxy的监控端口,暴露指标供 autoscaler 采集,用于KPA扩缩容**
  2. iOS元生开发框架_运维_04

  3. • 9091, prometheus 应用监控指标(请求数,响应时长等)** • USER_PORT, 是用户配置的容器端口,即业务实际暴露的服务端口
  4. Autoscaler: Knative 的扩缩容实现,通过request指标来决定是否扩缩容实例,指标来源有两个: 通过获取每个 pod queue-proxy 中的指标,Activator 通过Websocket 主动上报。
  5. Activator : 流量的负载和缓存,是Knative能缩容到 0 的关键,实例为0 时(冷启动),流量会先转发到 Activator,由 Activator 通过 websocket主动触发 Autoscaler 扩缩容。Activator 只在 冷启动阶段是 proxy 模式,当实例足够时,autoscaler会更新 Ingress 指向 revision对应的pod,将请求导向真正的后端

Knative是如何做伸缩容的?

首先要解决的问题是根据什么指标判断伸缩容?cpu、内存、请求数?

knative使用的是并发请求数。

Knative的伸缩是核心是依赖修改deployment的replica数据来实现的。

如何采集请求数?

  • 启动revision的pod时,也会启动一个autoscaler(一个knative revision只启动一个autoscaler),autoscaler自己本身也会scale到0,用于接收请求数统计和处理伸缩容。
  • 业务pod中,会注入queue-proxy sidecar,用于接收请求,在这里会统计并发数,每秒向Autoscaler 汇报,接收到的请求会转发给业务container。

计算需要pod的个数?

Autoscaler接收到并发统计的时候,会根据算法计算需要的pod个数。

算法中有两种模式,分别是panic和stable模式,一个是短时间,一个是长时间,为了解决短时间内请求突增的场景,需要快速扩容。

如果服务中并发数设置了 10,这时候如果加载了 50 个并发请求的服务,Autoscaler 就会创建了 5 个 POD (50 个并发请求/10=POD)。

再举个例子

一个 Revision 每秒收到 350 个请求并且每次请求大约需要处理 0.5 秒。使用默认设置 (每 Pod 100 个并发请求)。

350 * .5 = 175 //一秒钟的并发就是175 
175 / 100 = 1.75 
ceil(1.75) = 2 pods
Stable Mode(稳定模式)

在稳定模式下,Autoscaler 根据每个pod期望的并发来调整Deployment的副本个数。根据每个pod在60秒窗口内的平均并发来计算,而不是根据现有副本个数计算,因为pod的数量增加和pod变为可服务和提供指标数据有一定时间间隔。

Panic Mode (恐慌模式)

Panic时间窗口默认是6秒,如果在6秒内达到2倍期望的并发,则转换到恐慌模式下。在恐慌模式下,Autoscaler根据这6秒的时间窗口计算,这样更能及时的响应突发的流量请求。每2秒调整Deployment的副本数达到想要的pod个数(或者最大10倍当前pod的数量),为了避免pod数量频繁变动,在恐慌模式下只能增加,不会减少。60秒后会恢复回稳定模式,进入文稳定模式后就会产生缩容。

KPA&HPA比较k8s

也有 hpa 进行扩缩容,但是 Knative 的 kpa 和 k8s 的 hpa 有很大的不同:

Knative KPA

k8s HPA

指标类型

可以根据 请求量扩速容

只能根据 cpu memory 等指标扩缩容(或自定义指标)

01启动

可以缩容到0和冷启动

只能缩容到1(如果缩容到0,就没有实例了,流量进不来,metrics数据永远为0,此时HPA也无能为力)

指标获取方式

Knative 指标获取有两种方式,Activator 和queue-proxy, activator的metrics 是通过websocket主动push 给Autoscaler的,反应更迅速

k8s 只能是通过prometheus 轮询获取。

反应速度

Knative 默认会 计算 60 秒窗口内的平均并发数, 也会计算 6 秒的恐慌窗口,6s内达到目标并发的 2 倍,则会进入恐慌模式。在恐慌模式下,Autoscaler 在更短、更敏感的紧急窗口上工作

而且 HPA 本身设计比较保守,有一个稳定期(默认5min)默认在5min内没有重新扩缩容的情况下,才会触发扩缩容。当大流量突发过来时,如果正处在5min内的HPA稳定期,这个时候根据HPA的策略,会导致无法扩容。

从0启动扩容 整个流程

iOS元生开发框架_数据_05

Eventing

基于事件驱动是 Serveless 的核心功能之一,通过事件驱动服务,满足了用户按需付费(Pay-as-you-go)的需求

组件介绍

kubectl get deployment -n knative-eventing -- 查看组件

Broker & Trigger

从 v0.5 开始,Knative Eventing 定义 Broker 和 Trigger 对象,从而能方便的对事件进行过滤。

  • Trigger 描述基于事件属性的过滤器。同时可以根据需要创建多个 Trigger

iOS元生开发框架_运维_06

Event Source

向Broker发出Kubernetes的事件资源。

Broker

事件集代理层中心,接收Source Event 并转发到一个或者多个匹配的Trigger中

Trigger

Triggers定义了每个事件消费者能接受到的事件。Brokers使用triggers将事件发送给正确的消费者。每个trigger能够指定一个过滤器,该过滤器能够基于Cloud Event context attribute选择相关的事件。

Sink

实际接收事件的消费者,目的端,例如Knative中的Service

消息系统

每个 Broker 下面对应一个消息的系统,来承载对事件的整个流转。目前社区支持的消息系统包括 Kafka、NATS、Rocket MQ、Rabbit MQ 等。

Channel

在使用Knative的Broker之前,必须安装一个通道提供者,例如InMemoryChannel(开发、测试)、Kafka或者NATS等

数据流程图

![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IvBzHImH-1670253225977)(https://odocs.myoas.com/uploader/f/9TLoUFNnGc92O9UJ.png?accessToken=eyJhbGciOiJIUzI1NiIsImtpZCI6ImRlZmF1bHQiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJhY2Nlc3NfcmVzb3VyY2UiLCJleHAiOjE2MzgyNTQxMTQsImciOiIxbHE3TXcxMUU1Q25Wb0FlIiwiaWF0IjoxNjM4MjU0MDg0LCJ1c2VySWQiOjg2NDl9.w0PpgXH0GKrX1oA4DijDrClaV564AT0mRMdrII9mgE4)]()

Broker & Trigger 模型的中 Event 传递的基本方式,其中事件传递的格式是标准的 CLoudEvent 的格式。简要介绍下图中的流程:

  1. 事件由事件源 Source 产生, Knative 支持多种事件源,如 GitHub、Heartbeats、k8s、ContainerSource等
  2. 图中 事件源产生 type 为 foo 的事件,发送到 Broker,其中 有三个 Trigger 绑定了 Broker,两个 Trigger 的 filter 是 type:foo ,也就是会关注 type=foo 的事件,然后发送给对应的消费者。
  3. Service1和 Service3 只是单纯的消费,并不回复事件
  4. Service2 收到事件后回复 type=bar 的事件,事件重新传递到 Broker上,此时只有一个 Trigger 过滤了 type:bar 事件被传送到 Service3 消费。
  5. 其中消费者是否回复事件是可选的。
底层实现原理

iOS元生开发框架_运维_07

备注:控制平面为实线,数据平面为虚线,实线的方框为 Knative 组件,虚线的方框为 k8s CR 资源

Knative Eventing 事件传递过程中依赖消息通道 Channel,为了便于持久化,生产环境使用可持久化的消息通过(NatsStreaming ) 来做事件的消息通道,如果是开发调试,则直接用 InMemoryChannel 即可(只会保存在内存中,不会持久化)

系统数据平面流程

EventSource------> Broker------>Trigger------->SubScriber

  1. EventSource------> BrokerBroker 可以手动生成,或者通过给 namespace 打 label eventing.knative.dev/injection:true ,让sugar-controller 会自动生成对应的 Broker 实例。 EventSource 一般通过 SinkURI 环境变量将 Broker 的 地址传入 比如,Broker 的地址为 http://broker-ingress.knative-eventing.svc.cluster.local/default/default,该地址为broker-ingress 的地址
# kubectl get broker NAME
URL     AGE   READY   REASON 
default   http://broker-ingress.knative-eventing.svc.cluster.local/default/default   46h   True
  1. Broker------>Triggerbroker-ingress 得到 Broker 的信息(name namespace)之后, 可以通过Broker-Controller得到对应的Channel 的 status 信息, 如下得到 channelAddress 的地址(http://default-kne-trigger-kn-channel.default.svc.cluster.local)channelAddress 的地址是个svc 的地址,通过 externalName 指向 natss-ch-dispatcher
apiVersion: eventing.knative.dev/v1 
kind: Broker 
metadata:  
	name: default  
	namespace: default 
status:  
    address: 
    	--broker 地址    
		url: http://broker-ingress.knative-eventing.svc.cluster.local/default/default  
	annotations:    
        knative.dev/channelAPIVersion: messaging.knative.dev/v1beta1    
        -- channelAddress 地址,会被Broker-Controller更新    
        knative.dev/channelAddress: http://default-kne-trigger-kn-channel.default.svc.cluster.local    
        knative.dev/channelKind: NatssChannel    
        knative.dev/channelName: default-kne-trigger

natss-ch-dispatcher 负责将 消息(主题为channel.Name + "." + channel.Namespace) 发布到 Natss-Streaming 组件

  1. Trigger------->SubScriber
  • natss-ch-dispatcher 不仅负责发布,还负责订阅消息,natss-ch-dispatcher 订阅natssChannel , 获取 natssChannel的 subscriber的地址 subscriberUri,通过 subscriberUri发送消息给 broker-filter ,跟 broker-ingress 一样,subscriberUri 的地址是broker-filter 的 地址,通过请求 path 区分哪个 trigger ,请求 path :/triggers/<trigger namespace>/<trigger name>/<trigger UID>apiVersion:
apiVersion: messaging.knative.dev/v1beta1
kind: NatssChannel
metadata:
  name: default-kne-trigger
  namespace: default
spec:
--subscribers集合
  subscribers:
  - generation: 1
    replyUri: http://broker-ingress.knative-eventing.svc.cluster.local/default/default
--subscriberUri2
    subscriberUri: http://broker-filter.knative-eventing.svc.cluster.local/triggers/default/trigger2/c0f3f1bb-9a25-4fb2-b803-fc5cd74e57da
    uid: 9b4c991a-8912-4333-aa5a-caf053e5ee9c
  - generation: 1
    replyUri: http://broker-ingress.knative-eventing.svc.cluster.local/default/default
 --subscriberUri3
    subscriberUri: http://broker-filter.knative-eventing.svc.cluster.local/triggers/default/trigger3/28e7ba73-d514-4aea-a0de-7946fc21e7cc
    uid: 56d00260-e69f-4d7a-bd05-c81479774a95
  - generation: 1
    replyUri: http://broker-ingress.knative-eventing.svc.cluster.local/default/default
 --subscriberUri1
    subscriberUri: http://broker-filter.knative-eventing.svc.cluster.local/triggers/default/trigger1/3f6e7a8e-f7d1-4c8f-a25a-1970b6ab73e3
    uid: 963fd671-097f-489a-a857-c1803ad3fb19
status:
  address:
    url: http://default-kne-trigger-kn-channel.default.svc.cluster.local
  • broker-filter 获取到 Trigger 的信息后, 通过 根据 Trigger 的filter 将消息过滤,再决定是否将消息发给 对应的 subscriber, subscriber可以从 Triggerstatus 的 subcriberUri 获取到,对于subscriber Reply 的消息,broker-filter 发送到 replyUri 地址上,http://broker-ingress.knative-eventing.svc.cluster.local/default/default ,也就是发送给 Broker (实际是 broker-ingress)
apiVersion: eventing.knative.dev/v1
kind: Trigger
metadata:
  name: trigger1
  namespace: default
spec:
  broker: default
  filter:
    attributes:
      type: dev.knative.sources.ping
  subscriber:
    ref:
      apiVersion: serving.knative.dev/v1
      kind: Service
      name: service1
      namespace: default
status:
  subscriberUri: http://service1.default.svc.cluster.local

系统控制平面流程

iOS元生开发框架_iOS元生开发框架_08

  1. 详细流程
  • 1.1 mt-broker-controller watch Broker 的创建
  • 1.2 mt-broker-controller 根据 Broker 的配置,创建对应的 Channel,此处为 NatssChannel
  • 1.3 Natss-ch-controller watch NatssChannel 的创建,更新 Natss-Streaming 的服务端状态到NatssChannel 的 status
  • 1.4 Natss-ch-controller创建 svc,externalname指向 natss-ch-dispatcher
  • 1.5 mt-broker-controllerwatch NatssChannel 的status
  • 1.6 mt-broker-controller 更新 Broker的status,其中包含: Broker 的address(broker-ingress 的地址),channel 的 address,供 broker-ingress 使用
  • 2.1 mt-broker-controllerwatch Trigger
  • 2.2 mt-broker-controller根据 Trigger(含 subscriber 的信息)创建subscription,其中包含 subsciber(broker-filter) 的地址和 Broker 的信息
apiVersion: messaging.knative.dev/v1
kind: Subscription
metadata:
  name: default-trigger1-3f6e7a8e-f7d1-4c8f-a25a-1970b6ab73e3
  namespace: default
spec:
  --生成系统目前对应的Channel
  channel:
    apiVersion: messaging.knative.dev/v1beta1
    kind: NatssChannel
    name: default-kne-trigger
  reply:
    ref:
      apiVersion: eventing.knative.dev/v1
      kind: Broker
      name: default
      namespace: default
-- 生成Trigger对应的subscriber
  subscriber:
    uri: http://broker-filter.knative-eventing.svc.cluster.local/triggers/default/trigger1/3f6e7a8e-f7d1-4c8f-a25a-1970b6ab73e3
  • 2.3 eventing-controller watch subscription ,解析 subcriber和 replyUri的地址(broker-ingress 的地址)
  • 2.4 eventing-controller 根据 已更新的subcription 列表,然后再去更新 NatssChannel 里的subsriberUrl 和 replyUrl
  • 2.5 mt-broker-controller 根据 subscription 和 broker 的状态 更新 Trigger 的状态

控制面和数据面的逻辑结合的地方在于:

broker-ingress 和 broker-filter :

  1. broker-ingress watch Broker ,从 Broker 的 status 中获取 channel 的地址(natss-ch-dispatch 的 svc 的地址)
  2. broker-filter watch Trigger ,根据 trigger 中subscriber 地址,将event filter 后决定是否发送到 对应的 target

用户配置Yaml示例

ping Event Source

apiVersion: sources.knative.dev/v1beta2
kind: PingSource
metadata:
  name: ping-source
spec:
  schedule: "*/2 * * * *"
  contentType: "application/json"
  data: '{"message": "Hello world!"}'
  sink:
    ref:
      apiVersion: eventing.knative.dev/v1
      kind: Broker
      name: default
--- SinkURI

Broker

default-broker.yaml

apiVersion: eventing.knative.dev/v1
kind: Broker
metadata:
  name: default
  namespace: default

Trigger

apiVersion: eventing.knative.dev/v1
kind: Trigger
metadata:
  name: trigger1
spec:
  filter:
-- 过滤属性
    attributes:
      type: foo
  subscriber:
    ref:
      apiVersion: serving.knative.dev/v1
      kind: Service
      name: service1
--trigger2.yaml     
apiVersion: eventing.knative.dev/v1
kind: Trigger
metadata:
  name: trigger2
spec:
  filter:
  -- 过滤属性
    attributes:
      type: foo
  subscriber:
    ref:
      apiVersion: serving.knative.dev/v1
      kind: Service
      name: service2
--trigger3.yaml     
apiVersion: eventing.knative.dev/v1
kind: Trigger
metadata:
  name: trigger3
spec:
  filter:
-- 过滤属性
    attributes:
      type: bar
  subscriber:
    ref:
      apiVersion: serving.knative.dev/v1
      kind: Service
      name: service3

Service

apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: service1
spec:
  template:
    spec:
      containers:
        - image: docker.io/zqb/event-display:v1
--service2
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: service2
spec:
  template:
    spec:
      containers:
        - image: docker.io/zqb/event-display:v1
-- service3
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: service3
spec:
  template:
    spec:
      containers:
        - image: docker.io/zqb/event-display-with-reply:v1

Install

Install the Knative Serving component

To install the Knative Serving component:

Install the required custom resources by running the command:

kubectl apply -f https://github.com/knative/serving/releases/download/v0.24.0/serving-crds.yaml

Install the core components of Knative Serving by running the command:

kubectl apply -f https://github.com/knative/serving/releases/download/v0.24.0/serving-core.ya

Install a networking layer

The following commands install Istio and enable its Knative integration.

Install a properly configured Istio by following the Advanced Istio installation instructions or by running the command:

kubectl apply -l knative.dev/crd-install=true -f https://github.com/knative/net-istio/releases/download/v0.24.0/istio.yaml
kubectl apply -f https://github.com/knative/net-istio/releases/download/v0.24.0/istio.yaml

Install the Knative Istio controller by running the command:

kubectl apply -f https://github.com/knative/net-istio/releases/download/v0.24.0/net-istio.yaml

Fetch the External IP address or CNAME by running the command:

kubectl --namespace istio-system get service istio-ingressgateway\

Install the Eventing component

To install the Eventing component:

Install the required custom resource definitions (CRDs):

kubectl apply -f https://github.com/knative/eventing/releases/download/v0.24.0/eventing-crds.yaml

Install the core components of Eventing:

kubectl apply -f https://github.com/knative/eventing/releases/download/v0.24.0/eventing-core.yaml

  1. For information about the YAML files in Knative Eventing, see Description Tables for YAML Files.kubectl apply --filename https://github.com/knative/eventing/releases/download/v0.24.0/eventing.yaml
  2. Verify the installation Monitor the Knative components until all of the components show a STATUS of Running or Completed:kubectl get pods --namespace knative-eventing
Optional: Install a default channel (messaging) layer

kubectl apply --filename https://github.com/knative/eventing/releases/download/v0.24.0/in-memory-channel.yaml

文档地址

官方文档:https://knative.dev/

Github:https://github.com/knative

Knative Service Docshttps://github.com/knative/serving/tree/main/docs

Knative Eventing Docshttps://github.com/knative/eventing/tree/main/docs