从HelloWorld看Knative Serving代码实现
摘要: Knative Serving以Kubernetes和Istio为基础,支持无服务器应用程序和函数的部署并提供服务。我们从部署一个HelloWorld示例入手来分析Knative Serving的代码细节。
概念先知
官方给出的这几个资源的关系图还是比较清晰的:
1.Service: 自动管理工作负载整个生命周期。负责创建route,configuration以及每个service更新的revision。通过Service可以指定路由流量使用最新的revision,还是固定的revision。
2.Route:负责映射网络端点到一个或多个revision。可以通过多种方式管理流量。包括灰度流量和重命名路由。
3.Configuration:负责保持deployment的期望状态,提供了代码和配置之间清晰的分离,并遵循应用开发的12要素。修改一次Configuration产生一个revision。
4.Revision:Revision资源是对工作负载进行的每个修改的代码和配置的时间点快照。Revision是不可变对象,可以长期保留。
看一个简单的示例
我们开始运行官方hello-world示例,看看会发生什么事情:
apiVersion: serving.knative.dev/v1alpha1
kind: Service
metadata:
name: helloworld-go
namespace: default
spec:
runLatest: // RunLatest defines a simple Service. It will automatically configure a route that keeps the latest ready revision from the supplied configuration running.
configuration:
revisionTemplate:
spec:
container:
image: registry.cn-shanghai.aliyuncs.com/larus/helloworld-go
env:
- name: TARGET
value: "Go Sample v1"
查看 knative-ingressgateway:
kubectl get svc knative-ingressgateway -n istio-system
查看服务访问:DOMAIN
kubectl get ksvc helloworld-go --output=custom-columns=NAME:.metadata.name,DOMAIN:.status.domain
这里直接使用cluster ip即可访问
curl -H "Host: helloworld-go.default.example.com" http://10.96.199.35
目前看一下服务是部署ok的。那我们看一下k8s里面创建了哪些资源:
我们可以发现通过Serving,在k8s中创建了2个service和1个deployment:
那么究竟Serving中做了哪些处理,接下来我们分析一下Serving源代码
源代码分析
Main
先看一下各个组件的控制器启动代码,这个比较好找,在/cmd/controller/main.go中。
依次启动configuration、revision、route、labeler、service和clusteringress控制器。
...
controllers := []*controller.Impl{
configuration.NewController(
opt,
configurationInformer,
revisionInformer,
),
revision.NewController(
opt,
revisionInformer,
kpaInformer,
imageInformer,
deploymentInformer,
coreServiceInformer,
endpointsInformer,
configMapInformer,
buildInformerFactory,
),
route.NewController(
opt,
routeInformer,
configurationInformer,
revisionInformer,
coreServiceInformer,
clusterIngressInformer,
),
labeler.NewRouteToConfigurationController(
opt,
routeInformer,
configurationInformer,
revisionInformer,
),
service.NewController(
opt,
serviceInformer,
configurationInformer,
routeInformer,
),
clusteringress.NewController(
opt,
clusterIngressInformer,
virtualServiceInformer,
),
}
...
Service
首先我们要从Service来看,因为我们一开始的输入就是Service资源。在/pkg/reconciler/v1alpha1/service/service.go。
比较简单,就是根据Service创建Configuration和Route资源
func (c *Reconciler) reconcile(ctx context.Context, service *v1alpha1.Service) error {
...
configName := resourcenames.Configuration(service)
config, err := c.configurationLister.Configurations(service.Namespace).Get(configName)
if errors.IsNotFound(err) {
config, err = c.createConfiguration(service)
...
routeName := resourcenames.Route(service)
route, err := c.routeLister.Routes(service.Namespace).Get(routeName)
if errors.IsNotFound(err) {
route, err = c.createRoute(service)
...
}
Route
/pkg/reconciler/v1alpha1/route/route.go
看一下Route中reconcile做了哪些处理:
1.判断是否有Ready的Revision可进行traffic
2.设置目标流量的Revision(runLatest:使用最新的版本;pinned:固定版本,不过已弃用;release:通过允许在两个修订版之间拆分流量,逐步扩大到新修订版,用于替换pinned。manual:手动模式,目前来看并未实现)
3.创建ClusterIngress:Route不直接依赖于VirtualService[https://istio.io/docs/reference/config/istio.networking.v1alpha3/#VirtualService] ,而是依赖一个中间资源ClusterIngress,它可以针对不同的网络平台进行不同的协调。目前实现是基于istio网络平台。
4.创建k8s service:这个Service主要为Istio路由提供域名访问。
func (c *Reconciler) reconcile(ctx context.Context, r *v1alpha1.Route) error {
....
// 基于是否有Ready的Revision
traffic, err := c.configureTraffic(ctx, r)
if traffic == nil || err != nil {
// Traffic targets aren't ready, no need to configure child resources.
return err
}
logger.Info("Updating targeted revisions.")
// In all cases we will add annotations to the referred targets. This is so that when they become
// routable we can know (through a listener) and attempt traffic configuration again.
if err := c.reconcileTargetRevisions(ctx, traffic, r); err != nil {
return err
}
// Update the information that makes us Addressable.
r.Status.Domain = routeDomain(ctx, r)
r.Status.DeprecatedDomainInternal = resourcenames.K8sServiceFullname(r)
r.Status.Address = &duckv1alpha1.Addressable{
Hostname: resourcenames.K8sServiceFullname(r),
}
// Add the finalizer before creating the ClusterIngress so that we can be sure it gets cleaned up.
if err := c.ensureFinalizer(r); err != nil {
return err
}
logger.Info("Creating ClusterIngress.")
desired := resources.MakeClusterIngress(r, traffic, ingressClassForRoute(ctx, r))
clusterIngress, err := c.reconcileClusterIngress(ctx, r, desired)
if err != nil {
return err
}
r.Status.PropagateClusterIngressStatus(clusterIngress.Status)
logger.Info("Creating/Updating placeholder k8s services")
if err := c.reconcilePlaceholderService(ctx, r, clusterIngress); err != nil {
return err
}
r.Status.ObservedGeneration = r.Generation
logger.Info("Route successfully synced")
return nil
}
看一下helloworld-go生成的Route资源文件:
apiVersion: serving.knative.dev/v1alpha1
kind: Route
metadata:
name: helloworld-go
namespace: default
...
spec:
generation: 1
traffic:
- configurationName: helloworld-go
percent: 100
status:
...
domain: helloworld-go.default.example.com
domainInternal: helloworld-go.default.svc.cluster.local
traffic:
- percent: 100 # 所有的流量通过这个revision
revisionName: helloworld-go-00001 # 使用helloworld-go-00001 revision
这里可以看到通过helloworld-go配置, 找到了已经ready的helloworld-go-00001(Revision)。
Configuration
/pkg/reconciler/v1alpha1/configuration/configuration.go
1.获取当前Configuration对应的Revision, 若不存在则创建。
2.为Configuration设置最新的Revision
3.根据Revision是否readiness,设置Configuration的状态LatestReadyRevisionName
func (c *Reconciler) reconcile(ctx context.Context, config *v1alpha1.Configuration) error {
...
// First, fetch the revision that should exist for the current generation.
lcr, err := c.latestCreatedRevision(config)
if errors.IsNotFound(err) {
lcr, err = c.createRevision(ctx, config)
...
revName := lcr.Name
// Second, set this to be the latest revision that we have created.
config.Status.SetLatestCreatedRevisionName(revName)
config.Status.ObservedGeneration = config.Generation
// Last, determine whether we should set LatestReadyRevisionName to our
// LatestCreatedRevision based on its readiness.
rc := lcr.Status.GetCondition(v1alpha1.RevisionConditionReady)
switch {
case rc == nil || rc.Status == corev1.ConditionUnknown:
logger.Infof("Revision %q of configuration %q is not ready", revName, config.Name)
case rc.Status == corev1.ConditionTrue:
logger.Infof("Revision %q of configuration %q is ready", revName, config.Name)
created, ready := config.Status.LatestCreatedRevisionName, config.Status.LatestReadyRevisionName
if ready == "" {
// Surface an event for the first revision becoming ready.
c.Recorder.Event(config, corev1.EventTypeNormal, "ConfigurationReady",
"Configuration becomes ready")
}
// Update the LatestReadyRevisionName and surface an event for the transition.
config.Status.SetLatestReadyRevisionName(lcr.Name)
if created != ready {
c.Recorder.Eventf(config, corev1.EventTypeNormal, "LatestReadyUpdate",
"LatestReadyRevisionName updated to %q", lcr.Name)
}
...
}
看一下helloworld-go生成的Configuration资源文件:
apiVersion: serving.knative.dev/v1alpha1
kind: Configuration
metadata:
name: helloworld-go
namespace: default
...
spec:
generation: 1
revisionTemplate:
metadata:
creationTimestamp: null
spec:
container:
env:
- name: TARGET
value: Go Sample v1
image: registry.cn-shanghai.aliyuncs.com/larus/helloworld-go
name: ""
resources: {}
timeoutSeconds: 300
status:
...
latestCreatedRevisionName: helloworld-go-00001
latestReadyRevisionName: helloworld-go-00001
observedGeneration: 1
我们可以发现LatestReadyRevisionName设置了helloworld-go-00001(Revision)。
Revision
/pkg/reconciler/v1alpha1/revision/revision.go
1.获取build进度
2.设置镜像摘要
3.创建deployment
4.创建k8s service:根据Revision构建服务访问Service
5.创建fluentd configmap
6.创建KPA
感觉这段代码写的很优雅,函数执行过程写的很清晰,值得借鉴。另外我们也可以发现,目前knative只支持deployment的工作负载
func (c *Reconciler) reconcile(ctx context.Context, rev *v1alpha1.Revision) error {
...
if err := c.reconcileBuild(ctx, rev); err != nil {
return err
}
bc := rev.Status.GetCondition(v1alpha1.RevisionConditionBuildSucceeded)
if bc == nil || bc.Status == corev1.ConditionTrue {
// There is no build, or the build completed successfully.
phases := []struct {
name string
f func(context.Context, *v1alpha1.Revision) error
}{{
name: "image digest",
f: c.reconcileDigest,
}, {
name: "user deployment",
f: c.reconcileDeployment,
}, {
name: "user k8s service",
f: c.reconcileService,
}, {
// Ensures our namespace has the configuration for the fluentd sidecar.
name: "fluentd configmap",
f: c.reconcileFluentdConfigMap,
}, {
name: "KPA",
f: c.reconcileKPA,
}}
for _, phase := range phases {
if err := phase.f(ctx, rev); err != nil {
logger.Errorf("Failed to reconcile %s: %v", phase.name, zap.Error(err))
return err
}
}
}
...
}
最后我们看一下生成的Revision资源:
apiVersion: serving.knative.dev/v1alpha1
kind: Service
metadata:
name: helloworld-go
namespace: default
...
spec:
generation: 1
runLatest:
configuration:
revisionTemplate:
spec:
container:
env:
- name: TARGET
value: Go Sample v1
image: registry.cn-shanghai.aliyuncs.com/larus/helloworld-go
timeoutSeconds: 300
status:
address:
hostname: helloworld-go.default.svc.cluster.local
...
domain: helloworld-go.default.example.com
domainInternal: helloworld-go.default.svc.cluster.local
latestCreatedRevisionName: helloworld-go-00001
latestReadyRevisionName: helloworld-go-00001
observedGeneration: 1
traffic:
- percent: 100
revisionName: helloworld-go-00001
这里我们可以看到访问域名helloworld-go.default.svc.cluster.local,以及当前revision的流量配比(100%)
这样我们分析完之后,现在打开Serving这个黑盒:
最后
这里只是基于简单的例子,分析了主要的业务流程处理代码。对于activator(如何唤醒业务容器),autoscaler(Pod如何自动缩为0)等代码实现有兴趣的同学可以一起交流。
参考
https://github.com/knative/docs/tree/master/docs/serving
本文作者:元毅
本文为云栖社区原创内容,未经允许不得转载。
从HelloWorld看Knative Serving代码实现的更多相关文章
- Knative Serving 健康检查机制分析
作者| 阿里云智能事业群技术专家牛秋霖(冬岛) 导读:从头开发一个Serverless引擎并不是一件容易的事情,今天咱们就从Knative的健康检查说起.通过健康检查这一个点来看看Serverles ...
- Knative 基本功能深入剖析:Knative Serving 的流量灰度和版本管理
作者|冬岛 阿里云技术专家 本篇主要介绍 Knative Serving 的流量灰度,通过一个 rest-api 的例子演示如何创建不同的 Revision.如何在不同的 Revision 之间按照流 ...
- Knative Serving 进阶: Knative Serving SDK 开发实践
作者 | 阿里云智能事业群技术专家 牛秋霖(冬岛) 导读:通过前面的一系列文章你已经知道如何基于 kubectl 来操作 Knative 的各种资源.但是如果想要在项目中集成 Knative 仅仅使用 ...
- 从 HelloWorld 看 Java 字节码文件结构
很多时候,我们都是从代码层面去学习如何编程,却很少去看看一个个 Java 代码背后到底是什么.今天就让我们从一个最简单的 Hello World 开始看一看 Java 的类文件结构. 在开始之前,我们 ...
- Knative 基本功能深入剖析:Knative Serving 之服务路由管理
导读:本文主要围绕 Knative Service 域名展开,介绍了 Knative Service 的路由管理.文章首先介绍了如何修改默认主域名,紧接着深入一层介绍了如何添加自定义域名以及如何根据 ...
- Knative 基本功能深入剖析:Knative Serving 自动扩缩容 Autoscaler
Knative Serving 默认情况下,提供了开箱即用的快速.基于请求的自动扩缩容功能 - Knative Pod Autoscaler(KPA).下面带你体验如何在 Knative 中玩转 Au ...
- 从零开始一起学习SLAM | 理解图优化,一步步带你看懂g2o代码
首发于公众号:计算机视觉life 旗下知识星球「从零开始学习SLAM」 这可能是最清晰讲解g2o代码框架的文章 理解图优化,一步步带你看懂g2o框架 小白:师兄师兄,最近我在看SLAM的优化算法,有种 ...
- 看图写代码---看图写代码 阅读<<Audio/Video Connectivity Solutions for Virtex-II Pro and Virtex-4 FPGAs >>
看图写代码 阅读<<Audio/Video Connectivity Solutions for Virtex-II Pro and Virtex-4 FPGAs >> 1.S ...
- 【前端模板之路】一、重构的兄弟说:我才不想看你的代码!把HTML给我交出来!
写在前面 随着前端领域的发展和社会化分工的需要,继前端攻城湿之后,又一重要岗位横空出世——重构攻城湿!所谓的重构攻城湿,他们的一大特点之一,就是精通CSS配置文件的编写...前端攻城湿跟重构攻城湿是一 ...
随机推荐
- windows 环境下搭建docker私有仓库
windows 环境下搭建docker私有仓库 1.在公用仓库中pull仓库镜像 docker pull regitry 2.启动仓库镜像 //-d意思是后台运行,-p是做端口映射,这里是将本地的50 ...
- 加载selenium2Library失败---robotframework环境搭建(site-packages下无selenium2library文件夹)
加载Selenium2library失败,检查D:\Python27\Lib\site-packages 目录下是否有Selenium2Library 目录,没有该目录,事情就尴尬了. 自己安装的版本 ...
- Vue.之.路由跳转
Vue.之.路由跳转 在进行项目开发的过程中,需要使用路由进行跳转.如下: // 不带有参数,在页面上跳转到别的页面 1. this.$router.push('/login/init'); // ...
- spark应用程序引用别的jar包
第一种方式 操作:将第三方jar文件打包到最终形成的spark应用程序jar文件中 应用场景:第三方jar文件比较小,应用的地方比较少 第二种方式 操作:使用spark-submit提交命令的参数: ...
- AutoDesk产品,Maya 2018 安装,Microsoft Visual C++ 2012 安装失败,结果 = -2147024546,安装Microsoft Visual C++ 2012 Redistributable 错误0x80070005 等等
今日老弟装Maya 2018出现问题,我帮忙解决了一下问题,过程颇为曲折,记录一下,看能否帮到有类似困惑的朋友. 我和老弟的电脑牌子一样,就现在自己电脑上装了,竟然开始和他的错误是一样的!都是Micr ...
- 2017年浙工大迎新赛热身赛 A 毕业设计选题 【结构体排序】
时间限制:C/C++ 1秒,其他语言2秒空间限制:C/C++ 65536K,其他语言131072K64bit IO Format: %lld 题目描述 又到了一年一度,大四老学长们毕业设计选题的时候, ...
- Direct2D 第4篇 渐变画刷
原文:Direct2D 第4篇 渐变画刷 #include <windows.h> #include <d2d1.h> #include <d2d1helper.h> ...
- Django框架Day3------之Models
一.Django models字段类型清单: AutoField:一个自动递增的整型字段,添加记录时它会自动增长.你通常不需要直接使用这个字段:如果你不指定主键的话,系统会自动添加一个主键字段到你的m ...
- Vue项目根据不同运行环境打包项目
前提 项目是直接通过 vue-cli脚手架 生成的: 假设在项目开发中,分为三个环境 -- · 测试环境· 预生产环境· 生产环境 每个环境的接口地址都是不同的,而 vue-cli 给出的环境只有 d ...
- 本地 vs. 云:大数据厮杀的最终幸存者会是谁?— InfoQ专访阿里云智能通用计算平台负责人关涛
摘要: 本地大数据服务是否进入消失倒计时?云平台大数据服务最终到底会趋向多云.混合云还是单一公有云?集群规模增大,上云成本将难以承受是误区还是事实?InfoQ 将就上述问题对阿里云智能通用计算平台负责 ...