摘要: 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代码实现的更多相关文章

  1. Knative Serving 健康检查机制分析

    作者|  阿里云智能事业群技术专家牛秋霖(冬岛) 导读:从头开发一个Serverless引擎并不是一件容易的事情,今天咱们就从Knative的健康检查说起.通过健康检查这一个点来看看Serverles ...

  2. Knative 基本功能深入剖析:Knative Serving 的流量灰度和版本管理

    作者|冬岛 阿里云技术专家 本篇主要介绍 Knative Serving 的流量灰度,通过一个 rest-api 的例子演示如何创建不同的 Revision.如何在不同的 Revision 之间按照流 ...

  3. Knative Serving 进阶: Knative Serving SDK 开发实践

    作者 | 阿里云智能事业群技术专家 牛秋霖(冬岛) 导读:通过前面的一系列文章你已经知道如何基于 kubectl 来操作 Knative 的各种资源.但是如果想要在项目中集成 Knative 仅仅使用 ...

  4. 从 HelloWorld 看 Java 字节码文件结构

    很多时候,我们都是从代码层面去学习如何编程,却很少去看看一个个 Java 代码背后到底是什么.今天就让我们从一个最简单的 Hello World 开始看一看 Java 的类文件结构. 在开始之前,我们 ...

  5. Knative 基本功能深入剖析:Knative Serving 之服务路由管理

    导读:本文主要围绕 Knative Service 域名展开,介绍了 Knative Service 的路由管理.文章首先介绍了如何修改默认主域名,紧接着深入一层介绍了如何添加自定义域名以及如何根据 ...

  6. Knative 基本功能深入剖析:Knative Serving 自动扩缩容 Autoscaler

    Knative Serving 默认情况下,提供了开箱即用的快速.基于请求的自动扩缩容功能 - Knative Pod Autoscaler(KPA).下面带你体验如何在 Knative 中玩转 Au ...

  7. 从零开始一起学习SLAM | 理解图优化,一步步带你看懂g2o代码

    首发于公众号:计算机视觉life 旗下知识星球「从零开始学习SLAM」 这可能是最清晰讲解g2o代码框架的文章 理解图优化,一步步带你看懂g2o框架 小白:师兄师兄,最近我在看SLAM的优化算法,有种 ...

  8. 看图写代码---看图写代码 阅读<<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 ...

  9. 【前端模板之路】一、重构的兄弟说:我才不想看你的代码!把HTML给我交出来!

    写在前面 随着前端领域的发展和社会化分工的需要,继前端攻城湿之后,又一重要岗位横空出世——重构攻城湿!所谓的重构攻城湿,他们的一大特点之一,就是精通CSS配置文件的编写...前端攻城湿跟重构攻城湿是一 ...

随机推荐

  1. JS文字翻滚效果

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN""http://www.w3.org/TR/xhtm ...

  2. angular4 动态创建组件 vs 动态创建模板

    实现  模拟场景:页面上"帮助"按钮的点击触发帮助文档的弹出框,且每个页面的帮助文档不一样 因此弹出框里的帮助文档是一个动态模板而不是动态组件 以下comp均代表Type类型的动态 ...

  3. Comparator进行List集合排序

    对数据库中查询到的结果进行排序,一般开发中,实体类是没有实现Comparable接口的,所以不能实现compareTo()方法进行排序, 只能用Comparator去进行排序,只需要在带排序的集合中加 ...

  4. 关系数据库(ch.2)

    2.1.1 关系 域 笛卡儿积 关系 candiate key 如果一组属性值可以唯一的标识一个元祖,但是他的子集不行,那么这是一个候选码 关系可以由三种类型 基本关系 查询关系 视图 为关系附加如下 ...

  5. 洛谷P1979 [NOIP2013提高组Day2T3]华容道

    P1979 华容道 题目描述 [问题描述] 小 B 最近迷上了华容道,可是他总是要花很长的时间才能完成一次.于是,他想到用编程来完成华容道:给定一种局面, 华容道是否根本就无法完成,如果能完成, 最少 ...

  6. nodeJs基础方法

    Node.js 是一个基于Chrome javascript 运行时建立的一个平台, 用来方便地搭建快速的 易于扩展的网络应用 Node.js 借助事件驱动, 非阻塞I/O 模型变得轻量和高效, 非常 ...

  7. 函数的length属性

       函数的length 属性指明函数的形参个数.   length 是函数对象的一个属性值,指该函数有多少个必须要传入的参数,即形参的个数.形参的数量不包括剩余参数个数,仅包括第一个具有默认值之前的 ...

  8. webpack学习之——Entry Points(入口起点)

    1.Entry property(entry属性) 1.1 Single Entry (Shorthand) Syntax(单个入口语法) 用法:entry: string | Array<st ...

  9. Android中使用ormlite实现持久化--HelloOrmLite

    Android中内置了sqlite,但是常用的开发语言java是面向对象的,而数据库是关系型的,二者之间的转化每次都很麻烦(主要是我对sql语言不熟悉).而Java Web开发中有很多orm框架,但是 ...

  10. 2017年2月27日Unicorn, US (148) and China (69), followed by the U.K. (10), India (9), Israel (5) and Germany (5).

    Revisiting The Unicorn Club Get to know the newest crowd of billion dollar startups In 2013, when Ai ...