转发请注明出处:https://www.cnblogs.com/guangze/p/10753929.html,知乎、博客园同步更新。

1. 介绍

最近,因为需要对 Kubernetes 进行二次开发,接触了 client-go 库。client-go 作为官方维护的 go 语言实现的 client 库,提供了大量的高质量代码帮助开发者编写自己的客户端程序,来访问、操作 Kubernetes 集群。 在学习过程中我发现,除了官方的几个 examples 和 README 外,介绍 client-go 的文章较少。因此,这里有必要总结一下我的学习体会,分享出来。

访问 Kubernetes 集群的方式有多种(见 Access Clusters Using the Kubernetes API ),但本质上都要通过调用 Kubernetes REST API 实现对集群的访问和操作。比如,使用最多 kubernetes 命令行工具 kubectl,即是通过调用 Kubernetes REST API 完成的。当执行 kubectl get pods -n test 命令时, kubectl 向 Kubernetes API Server 完成认证、并发送 GET 请求:

GET /api/v1/namespaces/test/pods
---
200 OK
Content-Type: application/json
{
  "kind": "PodList",
  "apiVersion": "v1",
  "metadata": {"resourceVersion":"10245"},
  "items": [...]
}

那么如何编写自己的 http 客户端程序呢? 这就需要 Kubernetes 提供的 Golang client 库。

本文通过解读 Kubernetes client-go 官方例子之一 Create, Update & Delete Deployment ,详细介绍 client-go 原理和使用方法。该例子实现了创建、更新、查询、删除 deployment 资源。

2. 运行测试

2.1 测试环境

  • Ubuntu 18.04.2
  • Minikube 1.0.0
  • golang 1.12.4
  • k8s.io/client-go v11.0.0
  • GoLand IDE

下载 Minikube release 地址:https://github.com/kubernetes/minikube/releases

下载 k8s.io/client-go 源码:https://github.com/kubernetes/client-go

client-go 源码下载后,使用 go mod vendor 下载依赖库,或直接从github上下载依赖的其他库(如果没有设置外网代理的话)。

2.2 运行结果

因为我自己开了 VPN 连接到远程的 Kubernetes 集群内网,并复制 .kube/config 到了本地,所以可以直接在 GoLand 上编译运行,就能看到如下输出:

Creating deployment...
Created deployment "demo-deployment".
-> Press Return key to continue.

Updating deployment...
Updated deployment...
-> Press Return key to continue.

Listing deployments in namespace "default":
 * demo-deployment (1 replicas)
 * intended-quail-fluentbit-operator (1 replicas)
 * test (1 replicas)
-> Press Return key to continue.

Deleting deployment...
Deleted deployment.

Process finished with exit code 0

在运行过程中,你也可以通过 kubectl 命令观察创建的 deployment 变化。可以看到,这个 example 分别完成了四个操作:

  • 在 default namespace 下创建了一个叫 demo-deployment 的 deployment
  • 更新该 deployment 的副本数量、修改容器镜像版本到 nginx:1.13
  • 列出 default namespace 下的所有 deployment
  • 删除创建的 demo-deployment

3. 原理解析

完成 deployment 资源的增删改查,大体可以分为以下几个步骤。这个流程对访问其他 Kubernete 资源也是一样的:

  1. 通过 kubeconfig 信息,构造 Config 实例。该实例记录了集群证书、 API Server 地址等信息;
  2. 根据 Config 实例携带的信息,创建 http 客户端;
  3. 向 apiserver 发送请求,创建 Kubernetes 资源等

我用 go-callvis 制作了 example 中的函数调用图,以供参考:

3.1 获取 kubeconfig 信息,并构造 rest#Config 实例

Note: 我用 <package>#<func, struct> 表示某包下的函数、结构体

在访问 Kubernetes 集群时,少不了身份认证。使用 kubeconfig 配置文件是其中一种主要的认证方式。kubeconfig 文件描述了集群(cluster)、用户(user)和上下文(context)信息。默认的 kubeconfig 文件位于 $HOME/.kube/config 下。可以通过 cat $HOME/.kube/config, 或者 kubectl config view 查看:

apiVersion: v1
kind: Config
clusters:
- cluster:
    certificate-authority-data: DATA+OMITTED
    server: https://192.168.0.8:6443
  name: cluster.local
contexts:
- context:
    cluster: cluster.local
    user: kubernetes-admin
  name: kubernetes-admin@cluster.local
users:
- name: kubernetes-admin
  user:
    client-certificate-data: REDACTED
    client-key-data: REDACTED
current-context: kubernetes-admin@cluster.local
preferences: {}

我的测试环境 kubeconfig 配置显示,集群 API Server 地址位于 192.168.0.8:6443,集群开启 TLS,certificate-authority-data 指定公钥。客户端用户名为 kubernetes-admin,证书为 client-certificate-data,通过私钥 client-key-data 访问集群。上下文参数将集群和用户关联了起来。关于 kubeconfig 的更多介绍可以参考 kubernetes中kubeconfig的用法

源码中,kubeconfig 变量记录了 kubeconfig 文件路径。通过 BuildConfigFromFlags 函数返回了一个 rest#Config 结构体实例。该实例记录了 kubeconfig 文件解析、处理后的信息。

var kubeconfig *string
if home := homedir.HomeDir(); home != "" {
  kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
} else {
  kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file")
}
flag.Parse()

config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
if err != nil {
  panic(err)
}

BuildConfigFromFlags 函数是如何实例化 rest#Config 结构体的呢?

首先,BuildConfigFromFlags 函数接受一个 kubeconfigPath 变量,然后在内部依次调用如下函数:

  1. func NewNonInteractiveDeferredLoadingClientConfig(loader ClientConfigLoader, overrides *ConfigOverrides) ClientConfig
  2. func (config *DeferredLoadingClientConfig) ClientConfig() (*restclient.Config, error)
func BuildConfigFromFlags(masterUrl, kubeconfigPath string) (*restclient.Config, error) {
  if kubeconfigPath == "" && masterUrl == "" {
    ...
  }
  return NewNonInteractiveDeferredLoadingClientConfig(
    &ClientConfigLoadingRules{ExplicitPath: kubeconfigPath},
    &ConfigOverrides{ClusterInfo: clientcmdapi.Cluster{Server: masterUrl}}).ClientConfig()
}

我们来看看这两个链式调用的函数都做了哪些工作:

3.1.1 tools/clientcmd#NewNonInteractiveDeferredLoadingClientConfig

func NewNonInteractiveDeferredLoadingClientConfig(loader ClientConfigLoader, overrides *ConfigOverrides) ClientConfig {
  return &DeferredLoadingClientConfig{loader: loader, overrides: overrides, icc: &inClusterClientConfig{overrides: overrides}}
}

返回值:

  • 返回一个 tools/clientcmd#DirectClientConfig 类型的实例。

DeferredLoadingClientConfig 结构体是 ClientConfig 接口的一种实现。主要工作是确保装载的 rest#Config 实例使用最新 kubeconfig 数据(对于配置了多个集群的,export KUBECONFIG=cluster1-config:cluster2-config,需要执行 merge)。虽然本例子中还感受不到 Deferred Loading 体现在何处。源码注释中有这样一段话:

It is used in cases where the loading rules may change after you've instantiated them and you want to be sure that the most recent rules are used. This is useful in cases where you bind flags to loading rule parameters before the parse happens and you want your calling code to be ignorant of how the values are being mutated to avoid passing extraneous information down a call stack

参数列表:

  • loader ClientConfigLoader:

    我的测试环境是通过单一的路径 $HOME/.kube/config 获取 kubeconfig。但 kubeconfig 可能由不只一个配置文件 merge 而成,loader 确保在最终创建 rest#Config 实例时,使用的是最新的 kubeconfig。loader 的 ExplicitPath 字段记录指定的 kubeconfig 文件路径,Precedence 字符串数组记录要 merge 的 kubeconfig 信息。这也是为什么返回值叫 Deferred Loading ClientConfig

    loader 接受一个 ClientConfigLoader 接口实现,比如:&ClientConfigLoadingRules{ExplicitPath: kubeconfigPath}(这里是地址类型,因为是 *ClientConfigLoadingRules 实现了 ClientConfigLoader 接口,而不是 ClientConfigLoadingRules)。

  • overrides *ConfigOverrides:

    overtrides 保存用于强制覆盖 rest#Config 实例的信息。本例中没有用到。

3.1.2 (*DeferredLoadingClientConfig).ClientConfig()

上一个函数返回了 ClientConfig 接口实例。这里调用 ClientConfig 接口定义的 ClientConfig() 方法。ClientConfig() 工作是解析、处理 kubeconfig 文件里的认证信息,并返回一个完整的 rest#Config 实例。

// 错误处理省略
func (config *DeferredLoadingClientConfig) ClientConfig() (*restclient.Config, error) {
  mergedClientConfig, err := config.createClientConfig()
  ...

  // load the configuration and return on non-empty errors and if the
  // content differs from the default config
  mergedConfig, err := mergedClientConfig.ClientConfig()
  ...

  // check for in-cluster configuration and use it
  if config.icc.Possible() {
    klog.V(4).Infof("Using in-cluster configuration")
    return config.icc.ClientConfig()
  }

  // return the result of the merged client config
  return mergedConfig, err
}

这个函数主要有两个重要部分:

1.mergedClientConfig, err := config.createClientConfig()

内部执行遍历 kubeconfig files (如果有多个), 对每个 kubeconfig 执行 LoadFromFile 返回 tools/clientcmd/api#Config 实例。api#Config 顾名思义 api 包下的 Config,是把 kubeconfig (eg. $HOME/.kube/config) 序列化为一个 API 资源对象。

现在,我们看到了几种结构体或接口命名相似,不要混淆了:

  • api#Config:序列化 kubeconfig 文件后生成的对象
  • tools/clientcmd#ClientConfig:负责用 api#Config 真正创建 rest#Config。处理、解析 kubeconfig 中的认证信息,有了它才能创建 rest#Config,所以命名叫 ClientConfig
  • rest#Config:用于创建 http 客户端

对于 merge 后的 api#Config,调用 NewNonInteractiveClientConfig 创建一个 ClientConfig 接口的实现。

2.mergedConfig, err := mergedClientConfig.ClientConfig()

真正创建 rest#Config 的地方。在这里解析、处理 kubeconfig 中的认证信息。

3.2 创建 ClientSet

// NewForConfig creates a new Clientset for the given config.
config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
if err != nil {
  panic(err)
}

ClientSet 是一个重要的对象。它就是负责访问集群 apiserver 的客户端。那为什么叫 ClientSet 呢? 说明 Client 不止一个。比如 deployment 的 extensions/v1beta1、apps/v1beta、最新的 apps/v1 有多种版本(API Group),每种都有一个 Client 用于创建该版本的 deployment

// Clientset contains the clients for groups. Each group has exactly one
// version included in a Clientset.
type Clientset struct {
  ...
  appsV1                       *appsv1.AppsV1Client
  appsV1beta1                  *appsv1beta1.AppsV1beta1Client
  appsV1beta2                  *appsv1beta2.AppsV1beta2Client
  ...
  extensionsV1beta1            *extensionsv1beta1.ExtensionsV1beta1Client
}

3.3 创建一个 default 命名空间下的 apps/v1#deployment 资源

3.3.1 创建 deploymentsClient

创建 apps/v1 版本的 deployment,首先获得该版本的 client。

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

3.3.2 构造一个 apps/v1#deployment 实例

deployment := &appsv1.Deployment{
  ObjectMeta: metav1.ObjectMeta{
    Name: "demo-deployment",  // 指定 deployment 名字
  },
  Spec: appsv1.DeploymentSpec{
    Replicas: int32Ptr(2), // 指定副本数
    Selector: &metav1.LabelSelector{  // 指定标签
      MatchLabels: map[string]string{
        "app": "demo",
      },
    },
    Template: apiv1.PodTemplateSpec{ // 容器模板
      ObjectMeta: metav1.ObjectMeta{
        Labels: map[string]string{
          "app": "demo",
        },
      },
      Spec: apiv1.PodSpec{
        ...
      },
    },
  },
}

3.3.3 向 apiserver 发送 POST 创建 deployment

有兴趣的朋友可以进一步看源码这里是如何实现 http client 的。

result, err := deploymentsClient.Create(deployment)

---

// Create takes the representation of a deployment and creates it.  Returns the server's representation of the deployment, and an error, if there is any.
func (c *deployments) Create(deployment *v1.Deployment) (result *v1.Deployment, err error) {
  result = &v1.Deployment{}
  err = c.client.Post().
    Namespace(c.ns).
    Resource("deployments").
    Body(deployment).
    Do().
    Into(result)
  return
}

至此,一个 deployment 就创建完成了。删、改、查操作也是一样。

4. 总结

要彻底搞清楚 client-go,一方面要多查看 K8s 的 API 文档,另一方建议用 GoLand 单步调试,搞清楚每一步的含义。

5. 参考资料

Access Clusters Using the Kubernetes API

Kubernetes API Concepts

kubernetes中kubeconfig的用法

解读 kubernetes client-go 官方 examples - Part Ⅰ的更多相关文章

  1. 【爬坑系列】之解读kubernetes的认证原理&实践

    对于访问kube-apiserver模块的请求来说,如果是使用http协议,则会顺利进入模块内部得到自己想要的:但是如果是用的是https,则能否进入模块内部获得想要的资源,他会首先要进行https自 ...

  2. 配置kubernetes.client的参数遇到的坑

    配置kubernetes.client遇到的一些坑: 一,job-name不能重名,如果job-name已经有了,再创建job,则会发生冲突cliflict 这样将会报以下错误:Reason : Co ...

  3. Kubernetes tutorial - K8S 官方入门教程 中文翻译

    官方教程,共 6 个小节.每一小节的第一部分是知识讲解,第二部分是在线测试环境的入口. kubectl 的命令手册 原文地址 1 创建集群 1.1 使用 Minikube 创建集群 Kubernete ...

  4. Kubernetes tutorial - K8S 官方入门教程

    tutorials 教程 kubectl 的命令手册 1 Creating a Cluster 1.1 Using Minikube to Create a Cluster Kubernetes Cl ...

  5. Prometheus监控学习笔记之解读prometheus监控kubernetes的配置文件

    0x00 概述 Prometheus 是一个开源和社区驱动的监控&报警&时序数据库的项目.来源于谷歌BorgMon项目.现在最常见的Kubernetes容器管理系统中,通常会搭配Pro ...

  6. Kubernetes官方java客户端之三:外部应用

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  7. Kubernetes官方java客户端之四:内部应用

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  8. Kubernetes官方java客户端之五:proto基本操作

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  9. Kubernetes官方java客户端之六:OpenAPI基本操作

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

随机推荐

  1. 【最小生成树】UVA1494Qin Shi Huang's National Road System秦始皇修路

    Description During the Warring States Period of ancient China(476 BC to 221 BC), there were seven ki ...

  2. 【英国毕业原版】-《伯明翰城市大学毕业证书》BCU一模一样原件

    ☞伯明翰城市大学毕业证书[微/Q:865121257◆WeChat:CC6669834]UC毕业证书/联系人Alice[查看点击百度快照查看][留信网学历认证&博士&硕士&海归 ...

  3. 【爆料】-《阿伯丁大学毕业证书》AU一模一样原件

    ☞阿伯丁大学毕业证书[微/Q:865121257◆WeChat:CC6669834]UC毕业证书/联系人Alice[查看点击百度快照查看][留信网学历认证&博士&硕士&海归&a ...

  4. oracle服务的一些问题,先发2个,以后慢慢添加~~

    OracleOraDb11g_home1TNSLister服务启动后停止 解决办法: 1. 修改文件C:\app\zhuwei\product\11.1.0\db_1\NETWORK\ADMIN\li ...

  5. css3 深入理解flex布局

    一.简要介绍 css3最喜欢的新属性之一便是flex布局属性,用六个字概括便是简单.方便.快速. flex( flexible box:弹性布局盒模型),是2009年w3c提出的一种可以简洁.快速弹性 ...

  6. 深度学习之Batch Normalization

    在机器学习领域中,有一个重要的假设:独立同分布假设,也就是假设训练数据和测试数据是满足相同分布的,否则在训练集上学习到的模型在测试集上的表现会比较差.而在深层神经网络的训练中,当中间神经层的前一层参数 ...

  7. java游戏开发杂谈 - 实现游戏主菜单

    经常玩游戏的同学,大家都知道,游戏都会有个主菜单,里面有多个菜单选项:开始游戏.游戏设置.关于游戏.退出游戏等等,这个菜单是怎么实现的呢. 有一定桌面软件开发基础的同学可能会想到,用JButton组件 ...

  8. 【Java】广州三本秋招经历

    前言 只有光头才能变强 离上次发文章已经快两个月时间了,最近一直忙着秋招的事.今天是2018年10月22日,对于互联网行业来说,秋招就基本结束了.我这边的流程也走完了(不再笔试/面试了),所以来写写我 ...

  9. 来聊一聊不low的Linux命令——find、grep、awk、sed

    前几天面试,被一位面试官嫌弃了"你的Linux命令有点low".被嫌弃也挺正常的,因为我的简历写的我自己都有点看不下去:了解Linux常用命令,如ls,tail -f等命令,基本满 ...

  10. C# 数组比较--取得两个集合的交集,差集,并集的方法

    方法关键字: 交集:Intersect 差集:Except 并集:Union 使用代码: , , , , }; , , , , }; var 交集 = arr1.Intersect(arr2).ToL ...