Kubernetes源码之旅:从kubectl到API Server

概述:

Kubernetes项目目前依然延续着以前爆炸式的扩张。急需可以理解Kubernetes原理而且贡献代码的软件开发者。学习Kubernetes源码并不容易。Kubernetes是使用相对年轻的Go语言编写,而且拥有大量的源代码。在这个系列的多篇文章里,我将为你们深刻分析Kubernetes的关键源码,以及介绍那些帮助我理解源码的技术。个人目标是提供一系列的文章,让对于Kubernetes还较为陌生的开发者可以快速学习Kubernetes源码nginx

在第一篇文章里,我会分析从运行一个简单的kubectl命令到向API Server发送REST调用的源码执行过程。在开始深刻Kubernetes以前,我建议你先阅读一下Julia Evans对Kubernetes架构的高级概述分析的文章。git

Kubectl命令的基本运行

Kubernetes里的命令行接口叫作kubectl。它用来控制Kubernetes集群。阅读这部分源码实现是一个好的开始。咱们要追踪的命令是kubectl create -f——它会从文件建立K8s资源。咱们要建立的资源是使用了Nginx基础镜像的单副本Pod。下面是它的yaml描述:github

apiVersion: v1 kind: ReplicationController metadata:  name: nginx spec:  replicas: 1  selector:    app: nginx  template:    metadata:      name: nginx      labels:        app: nginx    spec:      containers:      - name: nginx        image: nginx        ports:        - containerPort: 80

在一个Kubernetes 开发环境中咱们能够用下面的方式调用kubectl:api

如今咱们知道该如何执行kubectl命令,下面来看看在Kubernetes源码的哪里能找到它的实现吧。浏览器

在源码中寻找kubectl的实现

实现kubectl命令的源码能够在 https://github.com/kubernetes/kubernetes/tree/master/pkg/kubectl/cmd目录找到。在这个目录里,名为kubectl对应命令的go文件就是实现的地方。例如,kubectl create命令的起点在create.go。下图展现了这个目录和示例go文件的多种多样实现:架构

Kubernetes ❤️ Cobra命令框架

Kubernetes命令使用Cobra命令框架实现。Cobra提供了不少构建命令行接口的特性。基本的Cobra功能说明能够在 https://blog.gopheracademy.com/advent-2014/introducing-cobra/ 找到。如图所示,很容易就能够定位哪一个文件实现了哪一个命令行选项。并且Cobra结构使得命令的使用说明、命令描述与运行的代码相邻。图中所示的代码能够在 https://github.com/kubernetes/kubernetes/blob/fd9a91e0b57face905c4225b8a6633b2ea9c832d/pkg/kubectl/cmd/create.go#L62-#76 找到。这种结构它的好处在于你能够阅读并找到全部Kubernetes kubectl命令的描述,而且快速跳转到这些命令的代码实现。图中62~76行的字符串Use、Short、Long和Example都包含了描述命令的信息,和Run指向一个函数实际执行这条命令。app

在74行调用的RunCreate函数是kubectl create命令的主要实现。这个函数的实现能够在 https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/create.go 文件找到。下图列出了RunCreate函数。在132行,我添加了一句fmt.Println来确保这段代码如我所料被调用了。在后面的编译运行Kubernetes的部分我会展现当为kubectl源码添加了一些用于调试的单独语句等时,怎样加速Kubernetes代码的从新编译过程。框架

Builders 和 Visitors

下面的133~140行是resource.NewBuilder的代码。一些Go和Kubernetes的新手可能以为特别惧怕。这段代码值得深刻解释一下。从高处看,这段代码所作的事情是将命令行接收到的参数转化为一个资源的列。它也负责建立一个能够用来迭代访问全部资源的Visitor结构。这个命令比较复杂,由于它使用了Builder模式的变种,使用独立的函数作各自的数据初始化工做。函数Schema、ContinueOnError、NamespaceParam、DefaultNamespace、FilenameParam、SelectorParam和Flatten都引入了一个指向Builder结构的指针,执行一些对它的修改,而且将这个结构体返回给调用链中的下一个方法来执行这些修改。全部的这些方法能够在这里找到 https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/resource/builder.go,但我在下面列出了一些你能够理解它如何运行的代码:函数

func (b *Builder) Schema(schema validation.Schema) *Builder {    b.schema = schema    return b } func (b *Builder) ContinueOnError() *Builder {    b.continueOnError = true    return b } func (b *Builder) Flatten() *Builder {    b.flatten = true    return b }

一旦全部的初始化都完成,resource.NewBuilder函数会调用Do函数。这个Do函数很关键,它会返回一个Result对象,而且将执行对资源的建立。Do函数还会建立一个Visitor对象,能够用来遍历全部关联到resource.NewBuilder执行过程的资源。Do函数的实现展现以下:工具

就像816行所展现的,建立了一个新的DecoratedVisitor,并做为Builder Do函数返回的Result的一部分。这个DecoratedVisitor有一个Visit函数将会调用传给它的Visitor函数。它的实如今 https://github.com/kubernetes/kubernetes/blob/6b52d8f1383d3a4a769b403a04f812c99ed98815/pkg/kubectl/resource/visitor.go#L306,以下:

这个Result对象由Do函数返回,拥有用来调用DecoratedVisitor Visit的函数Visit。这为咱们找到了从create.go的RunCreate函数到实际最终调用的匿名函数,以及包含了API Server进行调用的createAndRefresh函数。这个在create.go的150行实现的Result Visit函数展现以下:

如今咱们明白了Visit函数和DecoratedVisitor类如何把这一切链接起来。能够看到150行的inline visitor函数在165行有一个createAndRefresh函数:

这个createAndRefresh函数调用了NewHelper函数,在 https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/resource/helper.go,而且返回了一个新的Helper对象:

这里的代码返回了一个新的Helper对象,十分显而易见

func NewHelper(client RESTClient, mapping *meta.RESTMapping) *Helper {    return &Helper{        Resource:        mapping.Resource,        RESTClient:      client,        Versioner:       mapping.MetadataAccessor,        NamespaceScoped: mapping.Scope.Name() == meta.RESTScopeNameNamespace,    } }

在217行createAndRefresh里Helper的建立和调用它的Create函数,咱们最终能够看到Create函数调用了一个createResource函数。在119行的Helper Create函数里,以下所示是这个Helper createResource函数,以及实际向API Server发送的用来建立yaml文件描述的资源的REST调用。

编译和运行Kubernetes

如今咱们回顾了代码,是时候了解如何编译和运行这些代码了。在上面的许多代码示例中你均可以发现fmt.Println()调用。全部这些我添加的用来调试的语句,你也能够将它们加入源代码。为了编译这段代码,咱们将使用一个特殊的选项,以告知Kubernetes构建过程只编译kubectl这部分代码。这样能够极大地加快Kubernetes的编译速度。为作这个优化的make命令为:

make WAHT='cmd/kubectl'

而且指出了如何从命令行运行这个指令

一旦咱们从新编译了包含前面添加的print语句的这部分kubectl代码,就能够用下面的命令启动咱们的Kubernetes开发环境:

PATH=$PATH KUBERNETES_PROVIDER=local hack/local-up-cluster.sh

下面的图片说明了在命令行运行这条命令:

在另外一个终端窗口里咱们来继续执行kubectl命令,而后观察它的fmt.Printlns的输出。咱们使用下面的命令:

cluster/kubectl.sh create -f ~/nginx_kube_example/nginx_pod.yaml

下图展现了咱们的调试输出应该有的样子:

代码学习工具

我知道你可能会想:Brad,你虽然在Kube和Go都是新手,但你能够快速搞定这一切。你必定是个天才!然而,我有不少的Twitter粉丝,都会积极地拿出证据来驳斥这句话。借助于别人的帮助,我发现了几个能够真正有助于提高你阅读Kubernetes源码能力的工具和技术。在这部分里,我会介绍我最喜欢的技术:Chrome Sourcegraph Plugin,正确地格式化打印语句,使用go panic来得到所须要的stack trace,以及Github Blame来进行时空旅行。

Chrome Sourcegraph 插件

这是Morgan Bauer向我介绍了阅读Kubernetes 源码最酷炫的工具之一。Chrome Sourcegraph plugin提供了多种高级IDE特性,让在浏览Github仓库时理解Kubernetes Go代码变得很是容易。这里是它的使用例子。当我首先开始阅读Kubernetes 源码时,咱们发现下面的代码片断很是难以分段和理解。它有数不清的函数,快要淹没我了。

当在装有Sourcegraph扩展插件的Chrome浏览器里看向这段代码时,你能够把鼠标移过每一个函数,很快就获得了这个函数的描述,它接受了什么参数,返回了什么结果。这帮助你节省了无比巨大的时间,你能够避免在代码里抓取对应的函数定义,来了解它的功能。下面的图是一个示例:

Chrome Sourcegraph扩展还有一个高级视图,提供深刻被调用函数代码的功能。这是很是有用的机制:

惟一的问题是有时候Chrome Sourcegraph插件会卡住,而且不能弹出代码细节。个人经验是只要轻点页面刷新就能够修复。

打印语句从不过期

我在这篇文章中屡次加入了打印语句,来帮助咱们肯定代码是否按照预期执行。这个%#v格式选项展现了提供了最典型的调试信息。不要忘了你可能须要添加“fmt”包:

fmt.Prinln("\n createAndRefresh Info = %#v", info)

有疑问?PANIC!

我有一段时间很是难以理解Create.go里createAndRefresh函数是如何被调用的。最后,我决定抛出一个异常来强行获得stack trace并打印到屏幕上。下面的代码展现了我是怎么添加这句Panic的。这帮助我最终决定了是哪一种Visitor实际被用来调用createAndRefresh函数。

func createAndRefresh(info *resource.Info) error {    fmt.Println("\n createAndRefresh Info = %#v", info)    panic("Want Stack Trace")    obj, err := resource.NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, info.Object)    if err != nil {        return err    }    info.Refresh(obj, true)    return nil }

查看过去的源码

有时你看到一些代码,而后本身开始思考:这些人在提交代码的时候是怎么想的。感天谢地,Github浏览器接口提供了一个blame选项做为用户接口,下面展现了这个接口:

当咱们按下blame按钮,你会获得一份关于每一行代码的commit的列表。这让你能够穿越时空,看到某一特定行在添加的时候开发者试着完成的是什么。下面的图展现了blame选项的使用,左手边列出了全部的commits:

总结

本文中咱们试验了Kubernetes关于运行一个简单的kubectl命令的多个关键代码,而且阅读到它向API Server实际发送REST调用的代码。咱们也描述了如何在Kubernetes开发环境中编译和运行命令。咱们最后介绍了几个有用的工具和技巧。在下篇文章里,咱们将会试验Kubernetes代码中另外一段重要的代码。同时,但愿这篇文章可以给你带来学习Kubernetes源码的勇气:千里之行始于足下。

原文做者:Dr. Brad Topol,IBM杰出工程师,专一于开源技术和开发推广,同时他也是Kubernetes的贡献者和Kubernetes Conformance Workgroup成员。

相关文章
相关标签/搜索