k8s-client-go源码剖析(一)

2023-10-27

简介:云原生社区活动---Kubernetes源码剖析第一期

 

有幸参与云原生社区举办的Kubernetes源码剖析活动,活动主要以书籍《Kubernetes源码剖析》为主要思路进行展开,提出在看书过程中遇到的问题,和社区成员一起讨论,最后会将结果总结到云原生社区的知识星球或Github。

 

第一期活动主要以书本第五章<Client-go编程式交互>为主题进行学习,计划共三周半。

 

计划如下:

 

  1. client-go客户端学习

  2. Infoermer机制学习

  3. WorkQueue学习

  4. 整体结构回顾、逻辑回顾、优秀代码回顾

 

 

 

学习总得有个重要的优先级,我个人的优先级是这样的,仅供参考:

 

  1. Informer机制原理

  2. WorkerQueue原理

  3. 几种Client-go客户端的使用、优劣

 

学习环境相关:

 

  1. Kubernetes 1.14版本

  2. 对应版本的client-go

 

本文主题

 

本文是第一周,课题有两个:

 

  • Client-go源码结构

  • 几种Client客户端对象学习

 

 

 

Client-go源码目录结构

 

Plain Text

1

[root@normal11 k8s-client-go]# tree . -L 1

2

.

3

├── CHANGELOG.md

4

├── code-of-conduct.md

5

├── CONTRIBUTING.md

6

├── discovery

7

├── dynamic

8

├── examples

9

├── Godeps

10

├── go.mod

11

├── go.sum

12

├── informers

13

├── INSTALL.md

14

├── kubernetes

15

├── kubernetes_test

16

├── LICENSE

17

├── listers

18

├── metadata

19

├── OWNERS

20

├── pkg

21

├── plugin

22

├── README.md

23

├── rest

24

├── restmapper

25

├── scale

26

├── SECURITY_CONTACTS

27

├── testing

28

├── third_party

29

├── tools

30

├── transport

31

└── util

32

33

 

 

 

client-go代码库已经集成到了Kubernetes源码中,所以书本中展示的内容是在Kubernetes源码中源码结构,而这里展示的是Client-go代码库中原始的内容,所以多了一些源码之外的内容,例如README、example、go.mod等。下面讲一下各个目录的作用,内容引自书本:

 

 

 

 

编辑

删除

 

 

 

 

 

 

几种Client-go客户端

 

下图是一个简单的总结,其中ClientSet、DynamicClient、DiscoveryClient都是基于RESTClient封装的。

 

 

RESTClient

 

最基础的客户端,对HTTP Request进行了封装,实现了RESTFul风格的API。

 

案例代码:

 

Plain Text

 

1

package main

2

3

import (

4

  "fmt"

5

6

  corev1 "k8s.io/api/core/v1"

7

  metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

8

  "k8s.io/client-go/kubernetes/scheme"

9

  "k8s.io/client-go/rest"

10

  "k8s.io/client-go/tools/clientcmd"

11

)

12

13

func main() {

14

  config, err := clientcmd.BuildConfigFromFlags("", "/root/.kube/config")

15

  if err != nil {

16

    panic(err.Error())

17

  }

18

19

  config.APIPath = "api"

20

  config.GroupVersion = &corev1.SchemeGroupVersion

21

  config.NegotiatedSerializer = scheme.Codecs

22

23

  restClient, err := rest.RESTClientFor(config)

24

  if err != nil {

25

    panic(err.Error())

26

  }

27

28

  result := &corev1.NodeList{}

29

  err = restClient.Get().Namespace("").Resource("nodes").VersionedParams(&metav1.ListOptions{Limit: 100}, scheme.ParameterCodec).Do().Into(result)

30

  if err != nil {

31

    panic(err)

32

  }

33

34

  for _, d := range result.Items {

35

    fmt.Printf("Node Name %v \n", d.Name)

36

  }

37

}

38

39

 

预期运行结果将会打印K8S集群中的node

 

 

 

ClientSet

 

对RESTClient进行了对象分类方式的封装,可以实例化特定资源的客户端,

 

以Resource和Version的方式暴露。例如实例化一个只操作appsv1版本的Deploy客户端,

 

ClientSet可以认为是一系列资源的集合客户端。缺点是不能直接访问CRD。

 

通过client-gen代码生成器生成带有CRD资源的ClientSet后可以访问CRD资源。(未测试)

 

 

 

案例代码:

 

Plain Text

 

1

package main

2

3

import (

4

  apiv1 "k8s.io/api/core/v1"

5

  metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

6

  "k8s.io/client-go/kubernetes"

7

  "k8s.io/client-go/tools/clientcmd"

8

)

9

10

func main() {

11

  config, err := clientcmd.BuildConfigFromFlags("", "/root/.kube/config")

12

  if err != nil {

13

    panic(err)

14

  }

15

  clientset, err := kubernetes.NewForConfig(config)

16

  if err != nil {

17

    panic(err)

18

  }

19

20

  podClient := clientset.CoreV1().Pods(apiv1.NamespaceDefault)

21

22

  list, err := podClient.List(metav1.ListOptions{Limit: 500})

23

  if err != nil {

24

    panic(err)

25

  }

26

  for _, d := range list.Items {

27

    if d.Name == "" {

28

    }

29

    // fmt.Printf("NAME:%v \t NAME:%v \t STATUS: %+v\n ", d.Namespace, d.Name, d.Status)

30

  }

31

32

  //请求namespace为default下的deploy

33

  deploymentClient := clientset.AppsV1().Deployments(apiv1.NamespaceDefault)

34

  deployList, err2 := deploymentClient.List(metav1.ListOptions{Limit: 500})

35

  if err2 != nil {

36

    panic(err2)

37

  }

38

  for _, d := range deployList.Items {

39

    if d.Name == "" {

40

41

    }

42

    // fmt.Printf("NAME:%v \t NAME:%v \t STATUS: %+v\n ", d.Namespace, d.Name, d.Status)

43

  }

44

45

  // 请求ds资源 todo  有兴趣可以尝试下

46

  // clientset.AppsV1().DaemonSets()

47

48

}

49

50

 

 

 

代码中分别打印了获取到K8S集群中的500个Pod和500个deploy,目前打印语句是注释了,如果要看效果需要先删掉注释。

 

案例代码中还留了一个小内容,请求获取daemonset资源,感兴趣的可以试一试。

 

DynamicClient

 

这是一种动态客户端,对K8S任意资源进行操作,包括CRD。

 

请求返回的结果是map[string]interface{}

 

 

 

代码案例:

 

Plain Text

 

1

package main

2

3

import (

4

  "fmt"

5

6

  apiv1 "k8s.io/api/core/v1"

7

  corev1 "k8s.io/api/core/v1"

8

  metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

9

  "k8s.io/apimachinery/pkg/runtime"

10

  "k8s.io/apimachinery/pkg/runtime/schema"

11

  "k8s.io/client-go/dynamic"

12

  "k8s.io/client-go/tools/clientcmd"

13

)

14

15

func main() {

16

  config, err := clientcmd.BuildConfigFromFlags("", "/root/.kube/config")

17

  if err != nil {

18

    panic(err)

19

  }

20

21

  dymaicClient, err := dynamic.NewForConfig(config)

22

  checkErr(err)

23

  //map[string]interface{}

24

25

         //TODO 获取CRD资源 这里是获取了TIDB的CRD资源

26

  // gvr := schema.GroupVersionResource{Version: "v1alpha1", Resource: "tidbclusters", Group: "pingcap.com"}

27

  // unstructObj, err := dymaicClient.Resource(gvr).Namespace("tidb-cluster").List(metav1.ListOptions{Limit: 500})

28

  // checkErr(err)

29

  // fmt.Println(unstructObj)

30

31

  gvr := schema.GroupVersionResource{Version: "v1", Resource: "pods"}

32

  unstructObj, err := dymaicClient.Resource(gvr).Namespace(apiv1.NamespaceDefault).List(metav1.ListOptions{Limit: 500})

33

  checkErr(err)

34

  // fmt.Println(unstructObj)

35

  podList := &corev1.PodList{}

36

  err = runtime.DefaultUnstructuredConverter.FromUnstructured(unstructObj.UnstructuredContent(), podList)

37

  checkErr(err)

38

  for _, d := range podList.Items {

39

    fmt.Printf("NAME:%v \t NAME:%v \t STATUS: %+v\n ", d.Namespace, d.Name, d.Status)

40

  }

41

42

}

43

44

func checkErr(err error) {

45

  if err != nil {

46

    panic(err)

47

  }

48

}

49

50

 

 

 

这个案例是打印了namespace为default下的500个pod,同样的,在案例中也有一个todo,获取CRD资源,感兴趣的可以尝试一下。如果K8S集群中没有TIDB的资源可以自行换成自己想要的CRD资源。

 

代码中已经有获取v1alpha1版本的tidbclusters资源。如果你不知道CRD相关的信息,可以按照下面的步骤来找出对应的信息:

 

  1. 通过kubectl api-resources 获取到资源的Group和Resource

  2. 通过kubectl api-versions 找到对应Group的版本

 

这样 资源的GVR(Group、Version、Resource)都有了

 

 

 

DiscoveryClient

 

这是一种发现客户端,在前面的客户端中需要知道资源的Resource和Version才能找到你想要的,

 

这些信息太多很难全部记住,这个客户端用于获取资源组、版本等信息。

 

前面用到的api-resources和api-versions都是通过discoveryClient客户端实现的,源码在Kubernetes源码库中 pkg/kubectl/cmd/apiresources/apiresources.gopkg/kubectl/cmd/apiresources/apiversions.go

 

Plain Text

 

1

// RunAPIResources does the work

2

func (o *APIResourceOptions) RunAPIResources(cmd *cobra.Command, f cmdutil.Factory) error {

3

  w := printers.GetNewTabWriter(o.Out)

4

  defer w.Flush()

5

6

  //拿到一个DiscoveryClient客户端

7

  discoveryclient, err := f.ToDiscoveryClient()

8

  if err != nil {

9

    return err

10

  }

11

12

13

 

 

 

Plain Text

 

1

// RunAPIVersions does the work

2

func (o *APIVersionsOptions) RunAPIVersions() error {

3

  // Always request fresh data from the server

4

  o.discoveryClient.Invalidate()

5

6

  //通过discoveryClient获取group相关信息

7

  groupList, err := o.discoveryClient.ServerGroups()

8

  if err != nil {

9

    return fmt.Errorf("couldn't get available api versions from server: %v", err)

10

  }

 

 

 

案例代码:

 

获取集群中的GVR

 

Plain Text

 

1

package main

2

3

import (

4

  "fmt"

5

  "k8s.io/apimachinery/pkg/runtime/schema"

6

  "k8s.io/client-go/discovery"

7

  "k8s.io/client-go/tools/clientcmd"

8

)

9

10

func main()  {

11

  config, err := clientcmd.BuildConfigFromFlags("","/root/.kube/config")

12

  if err != nil {

13

    panic(err.Error())

14

  }

15

16

  discoveryClient, err := discovery.NewDiscoveryClientForConfig(config)

17

  if err != nil {

18

    panic(err.Error())

19

  }

20

21

  _, APIResourceList, err := discoveryClient.ServerGroupsAndResources()

22

  if err != nil {

23

    panic(err.Error())

24

  }

25

  for _, list := range APIResourceList {

26

    gv, err := schema.ParseGroupVersion(list.GroupVersion)

27

    if err != nil {

28

      panic(err.Error())

29

    }

30

    for _, resource := range list.APIResources {

31

      fmt.Printf("name: %v, group: %v, version %v\n", resource.Name, gv.Group, gv.Version)

32

    }

33

  }

34

}

 

 

 

预期效果:打印集群中的GVR

 

Plain Text

 

1

[root@normal11 discoveryclient]# go run main.go 

2

name: bindings, group: , version v1

3

name: componentstatuses, group: , version v1

4

name: configmaps, group: , version v1

5

name: endpoints, group: , version v1

6

...

 

 

 

DiscoveryClient在请求到数据之后会缓存到本地,默认存储位置是~/.kube/cache和~/.kube/http-cache,默认是每10分钟会和API Server同步一次。

 

总结

 

第一周主要是了解下各种客户端的使用以及不同,有时间的可以再进行一些拓展试验,研究对象可以选择一些主流的框架或官方示例,例如:

 

  1. Sample-Controller 中如何使用client-go的

  2. Kubebuilder中如何使用client-go的

  3. Operator-sdk中如何使用client-go的

 

 

 

延伸阅读:

 

  1. [活动] Kubernetes 源码研习社 第一期活动

  2. 如何高效阅读 Kubernetes 源码?

 

始发于 四颗咖啡豆,转载请声明出处.
关注公粽号->[四颗咖啡豆] 获取最新内容

四颗咖啡豆

四颗咖啡豆

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

k8s-client-go源码剖析(一) 的相关文章

  • 从 []byte 到 char*

    我想包装一个 C 函数 它需要一个char 指向非空字节缓冲区 的第一个元素 我正在尝试使用 CGo 将其包装在 Go 函数中 以便我可以将其传递给 byte 但我不知道如何进行转换 C 函数签名的简化版本是 void foo char c
  • 检查值是否实现接口的说明

    我读过 Effective Go 和其他类似这样的问答 golang接口合规性编译类型检查 https stackoverflow com questions 17994519 golang interface compliance com
  • Go中如何自定义http.Client或http.Transport超时重试?

    我想实现一个自定义http Transport对于标准http Client 如果客户端超时 它将自动重试 附 由于某种原因 习俗http Transport is a 一定有 我已经查过了hashcorp go retryablehttp
  • Google Kubernetes Engine 中的存储 ReadWriteMany

    有没有一种方法能够提供 ReadWriteMany 存储而无需实现存储集群 我能够使用 gcsfuse 提供存储 但速度非常慢 我需要接近 GlusterFS 速度的东西 我目前正在使用 GlusterFS 另一种选择 Google Clo
  • 如何为某些节点分配命名空间?

    有什么办法可以配置吗nodeSelector在命名空间级别 我只想在此命名空间的某些节点上运行工作负载 为了达到这个目的 你可以使用PodNodeSelector准入控制器 首先 您需要在您的kubernetes apiserver Edi
  • 如何在运行“go test”时排除或跳过特定目录[重复]

    这个问题在这里已经有答案了 go test go list grep v vendor coverprofile testCoverage txt 我正在使用上述命令来测试文件 但有 1 个名为 Store 的文件夹我想从测试中排除 怎样才
  • GOPATH值设置

    我用go1 3 1 windows amd64 msi安装go 安装后GOROOT是默认设置 我发现 D Programs Go bin 在 PATH 中 然后我创建一个 GOPATH 环境变量 使用 go get 命令时 出现错误 软件包
  • 我怎么知道我的所有 goroutine 确实正在使用 golang 的同步包等待一个条件

    我有一个应用程序 我正在创建多个 goroutine 来同时执行某个任务 所有工作协程都会等待条件 事件发生 一旦事件被触发 它们就会开始执行 创建完所有goroutines后 主线程在发送广播信号之前应该知道所有goroutines确实处
  • k8s书签解决什么问题?

    我正在尝试做什么 我正在尝试进行部署并监视 k8s 事件 直到部署准备好使用k8s节点API 手表 https github com kubernetes client javascript blob master examples typ
  • 使用 HTTPS GRC 从 AWS Codecommit 获取私有存储库

    我正在尝试导入位于 AWS codecommit 中的模块 为了克隆存储库 我使用 HTTPS GRC Git 远程代码提交 方法 该方法使用 Google Suite 凭证来访问 AWS 控制台 我用来克隆存储库的命令是 git clon
  • 如何在 kubernetes 中将秘密标记为可选?

    来自文档 除非将秘密标记为可选 否则必须先创建秘密 然后再将其作为环境变量在 pod 中使用 引用不存在的 Secret 将阻止 pod 启动 如何将秘密标记为可选 您正在寻找的是 name ENV NAME valueFrom secre
  • go中有memset的类似物吗?

    在 C 中 我可以使用某些值初始化数组memset https msdn microsoft com en us library aa246471 28v vs 60 29 aspx const int MAX 1000000 int is
  • 如何允许 Kubernetes 作业访问主机上的文件

    我已经彻底阅读了 Kubernetes 文档 但在与主机文件系统上的文件与 K8 作业启动的 pod 内运行的应用程序进行交互时仍然遇到问题 即使是最简单的实用程序也会发生这种情况 因此我提供了 yaml 配置的精简示例 此处引用的本地文件
  • K8s服务无法ping通

    我在 minikube 集群中有一个 k8s 服务 部署 名称amq in default命名空间 D20181472 argo k8s gms kubectl get svc all namespaces NAMESPACE NAME T
  • Kubernetes / kubectl - “必须指定容器名称”,但看起来确实如此?

    我正在调试 kubectl 的日志输出 其中指出 Error from server BadRequest a container name must be specified for pod postgres operator 49202
  • 有队列实现吗?

    任何人都可以建议使用 Go 容器来实现简单快速的 FIF 队列 Go 有 3 种不同的容器 heap list and vector 哪一种更适合实现队列 事实上 如果您想要的是一个基本且易于使用的 fifo 队列 slice 可以满足您所
  • 如何在 Kubernetes 中按特定顺序配置 Pod 初始化?

    我想知道如何按特定顺序开始部署 我知道initContainers但这对我不起作用 我有一个巨大的平台 有大约 20 个部署和 5 个有状态集 每个都有自己的服务 环境变量 卷 水平自动缩放器等 所以不可能 或者我不知道如何 在另一个中定义
  • 将 time.Time 转换为字符串

    我正在尝试将数据库中的一些值添加到 string在围棋中 其中一些是时间戳 我收到错误 无法在数组元素中使用 U Created date 类型 time Time 作为类型字符串 我可以转换吗time Time to string typ
  • 为什么 json.Unmarshal 返回映射而不是预期的结构?

    看看这个游乐场 http play golang org p dWku6SPqj5 http play golang org p dWku6SPqj5 基本上 我正在工作的图书馆收到了interface 作为参数 然后需要json Unma
  • 直接从一个通道发送到另一个通道

    当从一个通道直接发送到另一个通道时 我偶然发现了令人惊讶的行为 package main import fmt func main my chan make chan string chan of chans make chan chan

随机推荐