Kubernetes源码之旅:从kubectl到API Server
概述:
Kubernetes项目目前依然延续着之前爆炸式的扩张。急需能够理解Kubernetes原理并且贡献代码的软件开发者。学习Kubernetes源码并不容易。Kubernetes是使用相对年轻的Go语言编写,并且拥有大量的源代码。在这个系列的多篇文章里,我将为大家深入分析Kubernetes的关键源码,以及介绍那些帮助我理解源码的技术。我的目标是提供一系列的文章,让对于Kubernetes还较为陌生的开发者能够快速学习Kubernetes源码。
在第一篇文章里,我会分析从运行一个简单的kubectl命令到向API Server发送REST调用的源码执行过程。在开始深入Kubernetes之前,我建议你先阅读一下Julia Evans对Kubernetes架构的高级概述分析的文章。
Kubectl命令的基本运行
Kubernetes里的命令行接口叫做kubectl。它用来控制Kubernetes集群。阅读这部分源码实现是一个好的开始。我们要追踪的命令是kubectl create -f
——它会从文件创建K8s资源。我们要创建的资源是使用了Nginx基础镜像的单副本Pod。下面是它的yaml描述:
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:
现在我们知道该如何执行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指向一个函数实际执行这条命令。
在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成员。
Kubernetes源码之旅:从kubectl到API Server的更多相关文章
- kubernetes源码解析---- apiserver路由构建解析(1)
kubernetes源码解析---- apiserver路由构建解析(1) apiserver作为k8s集群的唯一入口,内部主要实现了两个功能,一个是请求的路由和处理,简单说就是监听一个端口,把接收到 ...
- kubernetes源码阅读及编译
kubernetes源码阅读 工欲善其事,必先利其器.在阅读kubernetes源码时,我也先后使用过多个IDE,最终还是停留在IDEA上. 我惯用的是pycharm(IDEA的python IDE版 ...
- Kubernetes 学习(九)Kubernetes 源码阅读之正式篇------核心组件之 Scheduler
0. 前言 继续上一篇博客阅读 Kubernetes 源码,参照<k8s 源码阅读>首先学习 Kubernetes 的一些核心组件,首先是 kube-scheduler 本文严重参考原文: ...
- 如何顺利完成Kubernetes源码编译?
为什么要编译源码 ? Kubernetes是一个非常棒的容器集群管理平台.通常情况下,我们并不需要修改K8S代码即可直接使用.但如果,我们在环境中发现了某个问题/缺陷,或按照特定业务需求需要修改K8S ...
- kubernetes源码解析---- apiserver路由构建解析(2)
kubernetes源码解析---- apiserver路由构建解析(2) 上文主要对go-restful这个包进行了简单的介绍,下面我们通过阅读代码来理解apiserver路由的详细构建过程. (k ...
- [源码]随机获取虾米音乐song_id API文件
[源码]随机获取虾米音乐song_id API文件 January 11, 2015 注意:此API请放置于国内主机使用,如香港.北京等等,否则会提示:虾米音乐在您所处的国家或地区暂时无法使用 < ...
- Kubernetes 学习(八)Kubernetes 源码阅读之初级篇------源码及依赖下载
0. 前言 阅读了一段时间 Golang 开源代码,准备正式阅读 Kubernetes 项目代码(工作机 Golang 版本为 Go 1.12) 参照 <k8s 源码阅读> 选择 1.13 ...
- kubernetes源码学习-环境配置篇
下载源码 根据kubernetes github 方式可以 mkdir -p $GOPATH/src/k8s.io cd $GOPATH/src/k8s.io git clone https://gi ...
- kubernetes 源码安装部署 1.12
一. 前期准备 参考文档 https://jimmysong.io/kubernetes-handbook/practice/create-tls-and-secret-key.html 1. 安装g ...
随机推荐
- Java日期类:Date和Calendar的使用
总是使用这两个类,总是需要百度.还不如一次全部整理完. 一.介绍: Date 类 Date 表示特定的瞬间,精确到毫秒. 在 JDK 1.1 之前,类 Date 有两个其他的函数.它允许把日期解释为年 ...
- Java实现 Hessian接口测试
Hessian接口测试简单单例 Hessian是基于Binary –RPC(二进制远程过程调用协议)进行通讯. Hessian就是把Java对象转变成字节序列,然后通过Http传输到目标服务器上(主机 ...
- 史上最易懂的大数据 OTO
史上最易懂的大数据 OTO http://network.51cto.com/art/201503/467068.htm 终于有人把O2O.C2C.B2B.B2C的区别讲透了 http://tech. ...
- Java日志记录工具SLF4J介绍
SLF4J是什么 SLF4J是一个包装类,典型的facade模式的工具,对用户呈现统一的操作方式,兼容各种主流的日志记录框架,典型的有log4j/jdk logging/nop/simple/jaka ...
- List、Map、Set三个接口,存取元素时,各有什么特点?
List与Set都是单列元素的集合,它们有一个功共同的父接口Collection. Set里面不允许有重复的元素, 存元素:add方法有一个boolean的返回值,当集合中没有某个元素,此时add方法 ...
- position学习终结者(二)
版权声明:本文为博主原创文章.未经博主同意不得转载. https://blog.csdn.net/wangshuxuncom/article/details/30982863 在博客& ...
- Q35+uefi or bios+legacy // PCI | PCIE
1:首先统一可扩展固件接口(UEFI)是一种规范定义操作系统和平台固件之间的软件接口. UEFI旨在替代基本输入/输出系统(BIOS)固件接口.(legacy) 硬件平台厂商越来越多地采用UEFI管理 ...
- LeetCode:数组中的第K个最大元素【215】
LeetCode:数组中的第K个最大元素[215] 题目描述 在未排序的数组中找到第 k 个最大的元素.请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素. 示例 1: ...
- LeetCode:N叉树的层次遍历【429】
LeetCode:N叉树的层次遍历[429] 题目描述 给定一个 N 叉树,返回其节点值的层序遍历. (即从左到右,逐层遍历). 例如,给定一个 3叉树 : 返回其层序遍历: [ [1], [3,2, ...
- c#基础-自动内存管理
1.自动垃圾回收是什么? 在非托管环境下程序员要自已管理内存,由疏忽的原因,通常会犯两种错误,请求内存后在不使用时忘记释放,或使用已经释放了的内存.但在托管环境下,程序员不用担心这两个问题,C ...