写点什么

Knative 全链路流量机制探索与揭秘

  • 2020-04-27
  • 本文字数:5982 字

    阅读完需:约 20 分钟

Knative全链路流量机制探索与揭秘

自动扩缩容是 Serverless 的核心特征,更好、更快的冷启动是所有 Serverless 平台的极致追求,本文基于网易杭州研究院云计算团队的探索,针对热门 Serverless 平台 Knative,解析其与自动扩容密切相关的流量实现机制,希望能够帮助从业者更好地理解 Knative autoscale 功能。

引子——从自动扩缩容说起

服务接收到流量请求后,从 0 自动扩容为 N,以及没有流量时自动缩容为 0,是 Serverless 平台最核心的一个特征。


可以说,自动扩缩容机制是那顶皇冠,戴上之后才能被称之为 Serverless。


当然了解 Kubernetes 的人会有疑问,HPA 不就是用来干自动扩缩容的事儿的吗?难道我用了 HPA 就可以摇身一变成为 Serverless 了。


这里有一点关键的区别在于,Serverless 语义下的自动扩缩容是可以让服务从 0 到 N 的,但是 HPA 不能。HPA 的机制是检测服务 Pod 的 metrics 数据(例如 CPU 等)然后把 Deployment 扩容,但当你把 Deployment 副本数置为 0 时,流量进不来,metrics 数据永远为 0,此时 HPA 也无能为力。


所以 HPA 只能让服务从 1 到 N,而从 0 到 1 的这个过程,需要额外的机制帮助 hold 住请求流量,扩容服务,再转发流量到服务,这就是我们常说的冷启动


可以说,冷启动是 Serverless 皇冠上的那颗明珠,如何实现更好、更快的冷启动,是所有 Serverless 平台极致追求的目标。


Knative 作为目前被社区和各大厂商如此重视和受关注的 Serverless 平台,当然也在不遗余力的优化自动扩缩容和冷启动功能。


不过,本文并不打算直接介绍 Knative 自动扩缩容机制,而是先探究一下 Knative 中的流量实现机制,流量机制和自动扩容密切相关,只有了解其中的奥秘,才能更好地理解 Knative autoscale 功能。


由于 Knative 其实包括 Building(Tekton)、Serving 和 Eventing,这里只专注于 Serving 部分。


另外需要提前说明的是,Knative 并不强依赖 Istio,Serverless 网关的实际选择除了集成 Istio,还支持 Gloo、Ambassador 等。同时,即使使用了 Istio,也可以选择是否使用 envoy sidecar 注入。本文默认使用 Istio 和注入 sidecar 的部署方式。

简单但是有点过时的老版流量机制

整体架构回顾

先回顾一下 Knative 官方的一个简单的原理示意图如下所示。用户创建一个 Knative Service(ksvc)后,Knative 会自动创建 Route(route)、Configuration(cfg)资源,然后 cfg 会创建对应的 Revision(rev)版本。rev 实际上又会创建 Deployment 提供服务,流量最终会根据 route 的配置,导入到相应的 rev 中。



这是简单的 CRD 视角,实际上 Knative 的内部 CRD 会多一些层次结构,相对更复杂一点。下文会详细描述。

冷启动时的流量转发

从冷启动和自动扩缩容的实现角度,可以参考一下下图 。从图中可以大概看到,有一个 Route 充当网关的角色,当服务副本数为 0 时,自动将请求转发到 Activator 组件,Activator 会保持请求,同时 Autoscaler 组件会负责将副本数扩容,之后 Activator 再将请求导入到实际的 Pod,并且在副本数不为 0 时,Route 会直接将流量负载均衡到 Pod,不再走 Activator 组件。这也是 Knative 实现冷启动的一个基本思路。



在集成使用 Istio 部署时,Route 默认采用的是 Istio Ingress Gateway 实现,大概在 Knative 0.6 版本之前,我们可以发现,Route 的流量转发本质上是由 Istio virtualservice(vs)控制。副本数为 0 时,vs 如下所示,其中 destination 指向的是 Activator 组件。此时 Activator 会帮助转发冷启动时的请求。


apiVersion: networking.istio.io/v1alpha3kind: VirtualServicemetadata:  name: route-f8c50d56-3f47-11e9-9a9a-08002715c9e6spec:  gateways:  - knative-ingress-gateway  - mesh  hosts:  - helloworld-go.default.example.com  - helloworld-go.default.svc.cluster.local  http:  - appendHeaders:    route:    - destination:        host: Activator-Service.knative-serving.svc.cluster.local        port:          number: 80      weight: 100
复制代码


当服务副本数不为 0 之后,vs 变为如下所示,将 Ingress Gateway 的流量直接转发到服务 Pod 上。


apiVersion: networking.istio.io/v1alpha3kind: VirtualServicemetadata:  name: route-f8c50d56-3f47-11e9-9a9a-08002715c9e6spec: hosts:  - helloworld-go.default.example.com  - helloworld-go.default.svc.cluster.local  http:  - match:    route:    - destination:        host: helloworld-go-2xxcn-Service.default.svc.cluster.local        port:          number: 80      weight: 100
复制代码


我们可以很明显的看出,Knative 就是通过修改 vs 的 destination host 来实现冷启动中的流量保持和转发。


相信目前你在网上能找到资料,也基本上停留在该阶段。不过,由于 Knative 的快速迭代,这里的一些实现细节分析已经过时。


下面以 0.9 版本为例,我们仔细探究一下现有的实现方式,和关于 Knative 流量的真正秘密。

复杂但是更优异的新版流量机制

鉴于官方文档并没有最新的具体实现机制介绍,我们创建一个简单的 hello-go ksvc,并以此进行分析。ksvc 如下所示:


apiVersion: serving.knative.dev/v1alpha1kind: Servicemetadata:  name: hello-go  namespace: faasspec:  template:    spec:      containers:      - image: harbor-yx-jd-dev.yx.netease.com/library/helloworld-go:v0.1        env:        - name: TARGET          value: "Go Sample v1"
复制代码

virtualservice 的变化

笔者的环境可简单的认为是一个标准的 Istio 部署,Serverless 网关为 Istio Ingress Gateway,所以创建完 ksvc 后,为了验证服务是否可以正常运行,需要发送 http 请求至网关。Gateway 资源已经在部署 Knative 的时候创建,这里我们只需要关心 vs。在服务副本数为 0 的时候,Knative 控制器创建的 vs 关键配置如下:


spec:  gateways:  - knative-serving/cluster-local-gateway  - knative-serving/knative-ingress-gateway  hosts:  - hello-go.faas  - hello-go.faas.example.com  - hello-go.faas.svc  - hello-go.faas.svc.cluster.local  - f81497077928a654cf9422088e7522d5.probe.invalid  http:  - match:    - authority:        regex: ^hello-go\.faas\.example\.com(?::\d{1,5})?$      gateways:      - knative-serving/knative-ingress-gateway    - authority:        regex: ^hello-go\.faas(\.svc(\.cluster\.local)?)?(?::\d{1,5})?$      gateways:      - knative-serving/cluster-local-gateway    retries:      attempts: 3      perTryTimeout: 10m0s    route:    - destination:        host: hello-go-fpmln.faas.svc.cluster.local        port:          number: 80
复制代码


vs 指定了已经创建好的 gw,同时 destination 指向的是一个 Service 域名。这个 Service 就是 Knative 默认自动创建的 hello-go 服务的 Service。


细心的我们又发现 vs 的 ownerReferences 指向了一个 Knative 的 CRD ingress.networking.internal.knative.dev:


  ownerReferences:  - apiVersion: networking.internal.knative.dev/v1alpha1    blockOwnerDeletion: true    controller: true    kind: Ingress    name: hello-go    uid: 4a27a69e-5b9c-11ea-ae53-fa163ec7c05f
复制代码


根据名字可以看到这是一个 Knative 内部使用的 CRD,该 CRD 的内容其实和 vs 比较类似,同时 ingress.networking.internal.knative.dev 的 ownerReferences 指向了我们熟悉的 route,总结下来就是:


route -> kingress(ingress.networking.internal.knative.dev) -> vs
复制代码


在网关这一层涉及到的 CRD 资源就是如上这些。这里 kingress 的意义在于增加一层抽象,如果我们使用的是 Gloo 等其他网关,则会将 kingress 转换成相应的网关资源配置。最新的版本中,负责 kingress 到 Istio vs 的控制器部分代码已经独立出一个项目,可见如今的 Knative 对 Istio 已经不是强依赖。


现在,我们已经了解到 Serverless 网关是由 Knative 控制器最终生成的 vs 生效到 Istio Ingress Gateway 上,为了验证我们刚才部署的服务是否可以正常的运行,简单的用 curl 命令试验一下。


和所有的网关或者负载均衡器一样,对于 7 层 http 访问,我们需要在 Header 里加域名 Host,用于流量转发到具体的服务。在上面的 vs 中已经可以看到对外域名和内部 Service 域名均已经配置。所以,只需要:


curl -v -H'Host:hello-go.faas.example.com'  <IngressIP>:<Port> 
复制代码


其中,IngressIP 即网关实例对外暴露的 IP。


对于冷启动来说,目前的 Knative 需要等十几秒,即会收到请求。根据之前老版本的经验,这个时候 vs 会被更新,destination 指向 hello-go 的 Service。


不过,现在我们实际发现,vs 没有任何变化,仍然指向了服务的 Service。对比老版本中服务副本数为 0 时,其实 vs 的 destination 指向的是 Activator 组件的。但现在,不管服务副本数如何变化,vs 一直不变。


蹊跷只能从 destination 的 Service 域名入手。

revision service 探索

创建 ksvc 后,Knative 会帮我们自动创建 Service 如下所示。


$ kubectl -n faas get svcNAME                     TYPE           CLUSTER-IP     EXTERNAL-IP                                            PORT(S)      hello-go                 ExternalName   <none>         cluster-local-gateway.istio-system.svc.cluster.local   <none>           hello-go-fpmln           ClusterIP      10.178.4.126   <none>                                                 80/TCP             hello-go-fpmln-m9mmg     ClusterIP      10.178.5.65    <none>                                                 80/TCP,8022/TCP  hello-go-fpmln-metrics   ClusterIP      10.178.4.237   <none>                                                 9090/TCP,9091/TCP
复制代码


hello-go Service 是一个 ExternalName Service,作用是将 hello-go 的 Service 域名增加一个 dns CNAME 别名记录,指向网关的 Service 域名。


根据 Service 的 annotation 我们可以发现,Knative 对 hello-go-fpmln、hello-go-fpmln-m9mmg 、hello-go-fpmln-metrics 这三个 Service 的定位分别为 public Service、private Service 和 metric Service(最新版本已经将 private 和 metrics Service 合并)。


private Service 和 metric Service 其实不难理解。问题的关键就在这里的 public Service,仔细研究 hello-go-fpmln Service,我们可以发现这是一个没有 labelSelector 的 Service,它的 Endpoint 不是 kubernetes 自动创建的,需要额外生成。


在服务副本数为 0 时,查看一下 Service 对应的 Endpoint,如下所示:


$ kubectl -n faas get epNAME                     ENDPOINTS                               AGEhello-go-fpmln           172.31.16.81:8012                       hello-go-fpmln-m9mmg     172.31.16.121:8012,172.31.16.121:8022   hello-go-fpmln-metrics   172.31.16.121:9090,172.31.16.121:9091   
复制代码


其中,public Service 的 Endpoint IP 是 Knative Activator 的 Pod IP,实际发现 Activator 的副本数越多这里也会相应的增加。并且由上面的分析可以看到,vs 的 destination 指向的就是 public Service。


输入几次 curl 命令模拟一下 http 请求,虽然副本数从 0 开始增加到 1 了,但是这里的 Endpoint 却没有变化,仍然为 Activator Pod IP。


接着使用 hey 来压测一下:


./hey_linux_amd64 -n 1000000 -c 300  -m GET -host helloworld-go.faas.example.com http://<IngressIP>:80
复制代码


发现 Endpoint 变化了,通过对比服务的 Pod IP,已经变成了新启动的服务 Pod IP,不再是 Activator Pod 的 IP。


$ kubectl -n faas get epNAME                     ENDPOINTS                         helloworld-go-mpk25      172.31.16.121:8012hello-go-fpmln-m9mmg     172.31.16.121:8012,172.31.16.121:8022   hello-go-fpmln-metrics   172.31.16.121:9090,172.31.16.121:9091   
复制代码


原来,现在新版本的冷启动流量转发机制已经不再是通过修改 vs 来改变网关的流量转发配置了,而是直接更新服务的 public Service 后端 Endpoint,从而实现将流量从 Activator 转发到实际的服务 Pod 上。


通过将流量的转发功能内聚到 Service/Endpoint 层,一方面减小了网关的配置更新压力,一方面 Knative 可以在对接各种不同的网关时的实现时更加解耦,网关层不再需要关心冷启动时的流量转发机制。

流量路径

再深入从上述的三个 Service 入手研究,它们的 ownerReference 是 serverlessservice.networking.internal.knative.dev(sks),而 sks 的 ownerReference 是 podautoscaler.autoscaling.internal.knative.dev(kpa)。


在压测过程中同样发现,sks 会在冷启动过后,会从 Proxy 模式变为 Serve 模式:


$ kubectl -n faas get sksNAME             MODE    SERVICENAME      PRIVATESERVICENAME     READY   REASONhello-go-fpmln   Proxy   hello-go-fpmln   hello-go-fpmln-m9mmg   True
复制代码


$ kubectl -n faas get sksNAME             MODE    SERVICENAME      PRIVATESERVICENAME     READY   REASONhello-go-fpmln   Serve   hello-go-fpmln   hello-go-fpmln-m9mmg   True
复制代码


这也意味着,当流量从 Activator 导入的时候,sks 为 Proxy 模式,服务真正启动起来后会变成 Serve 模式,网关流量直接流向服务 Pod。


从名称上也可以看到,sks 和 kpa 均为 Knative 内部 CRD,实际上也是由于 Knative 设计上可以支持自定义的扩缩容方式和支持 Kubernetes HPA 有关,实现更高一层的抽象。


现在为止,我们可以梳理 Knative 的绝大部分 CRD 的关系如下图所示:



一个更复杂的实际实现架构图如下所示。



简单来说,服务副本数为 0 时,流量路径为:


网关-> public Service -> Activator
复制代码


经过冷启动后,副本数为 N 时,流量路径为:


网关-> public Service -> Pod
复制代码


当然流量到 Pod 后,实际内部还有 Envoy sidecar 流量拦截,Queue-Proxy sidecar 反向代理,才再到用户的 User Container。这里的机制背后实现我们会有另外一篇文章再单独细聊。

总结

Knative 本身的实现可谓云原生领域里的一个集大成者,融合 Kubernetes、Service Mesh、Serverless 让 Knative 充满了魅力,但同时也导致了它的复杂性。


网络流量的稳定保障是 Serverless 服务真正生产可用性的关键因素,Knative 也还在高速的更新迭代中,相信 Knative 会在未来对网络方面的性能和稳定性投入更多的优化。


作者简介:


傅轶,网易杭州研究院云计算技术部高级研发工程师,目前负责网易轻舟容器云和微服务平台研发,致力于网易容器技术及其生态体系建设,对 Kubernetes、Serverless 有深入研究,具有丰富的云原生分布式架构设计开发经验与项目实践。


2020-04-27 11:564666

评论

发布
暂无评论
发现更多内容

使用前端技术实现静态图片局部流动效果

dragonir

CSS JavaScript html 前端 SVG

为什么Spring Boot项目加上就可以更新版本?

冉然学Java

程序员 源码分析 springboot Java 分布式 Java core

DeepLink在转转的实践

转转技术团队

ios android 客户端

高性能创作本,日常修图剪辑选华硕无畏Pro15 2022完全足矣!

科技热闻

阿里云 EMAS Serverless 升级发布

移动研发平台EMAS

小程序云开发 阿里云 Serverless 开发者 云开发

开源一夏 | 阿里云物联网平台之极速体验

六月的雨在InfoQ

阿里云 开源 物联网 8月月更

ffplay视频播放原理分析

百度Geek说

音视频

手摸手带你完成智慧路灯构建及避坑【华为云至简致远】

神奇视野

Python 科技

彻底搞懂云桌面配置及实践踩坑【华为云至简致远】

神奇视野

Python 科技

破解数字化转型困局,企业分析协同场景案例解析

ModelWhale

数据分析 数字化转型 构建模型 成功案例 协同软件

使用华为HECS云服务器打造Telegraf+Influxdb+Grafana 监控系统【华为云至简致远】

科技云未来

Grafana Influxdb 系统管理 开源监控系统 提高效率

阿里大佬力荐的这份“Spring全家桶”太强了,在轻松中学习掌握

Java工程师

Java spring spring-boot

利用java实现视频人像分割及视频背景替换

夏夜许游

Java 图像分割 视频人像分割 背景替换

兆骑科创高层次人才引进平台,创新创业赛事活动路演

兆骑科创凤阁

ModelWhale 云端运行 WRF 中尺度数值气象模式,随时随地即开即用的一体化工作流

ModelWhale

数据科学 气象 全流程一体化 WRF 大气科学

交大医学院临床研究中心如何将 ModelWhale 应用于临床医生教学、研究丨数据科学 x 临床医学

ModelWhale

人才培养 数据科学 低代码平台 教学 临床医学

Jupyter Notebook 交互式编程 & 低代码拖拽式编程 | 数据科学生态下的理想平台

ModelWhale

云原生 Jupyter Notebook 数据科学 低代码开发 协作平台

2022最新发布超全的Java面试八股文,整整1700页,太全了

Java工程师

Java 面试 八股文

【实战】Next.js + 云函数开发一个面试刷题网站

狂奔滴小马

Serverless React

R7 6800H+RTX3050+120Hz 2.8K OLED屏,无畏Pro15 2022开启预售

科技热闻

NFT盲盒挖矿DAO智能合约dapp系统开发详情

开发微hkkf5566

利用华为云ECS服务器搭建安防视频监控平台【华为云至简致远】

科技云未来

nginx securecrt RTMP SSH工具

生物统计师与临床医生协同研究使用的低代码洞察平台丨数据科学 x 临床医学

ModelWhale

团队协作 Jupyter Notebook 数据科学 低代码开发 临床医学

一次做数据报表的踩坑经历,让我领略了数据同步增量和全量的区别

百思不得小赵

数据同步 增量同步 全量同步 签约计划第三季 8月月更

技术干货|如何将 Pulsar 数据快速且无缝接入 Apache Doris

SelectDB

数据库 Doris pulsar 数据导入 kafaka

云硬盘EVS详解以及如何用与避坑【华为云至简致远】

神奇视野

Python 后端 云服务 科技

Mysql 生成排序序号

六月的雨在InfoQ

8月月更

兆骑科创创业大赛,双创服务平台,线上直播路演

兆骑科创凤阁

快速定位线上慢SQL问题,掌握这几个性能排查工具可助你一臂之力

IT学习日记

MySQL性能优化 数据库优化 MySQL 数据库 签约计划第三季 explain关键字

开源一夏 | 打工人的第25天-曾经的考研人

Amazing_eve

#开源

大数据程序员培训学习多长时间可以找工作

小谷哥

Knative全链路流量机制探索与揭秘_服务革新_傅轶_InfoQ精选文章