文档章节

解读 kubernetes client-go 官方 examples

o
 osc_gu9d45li
发布于 2019/04/22 23:53
字数 2788
阅读 9
收藏 0

精选30+云产品,助力企业轻松上云!>>>

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

[toc]

1. 介绍

最近,因为开发 Kubernetes 应用,接触了 client-go 库。client-go 作为官方维护的 go 语言实现的 API 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 API 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 文件后生成的对象
type Config struct {
	// Legacy field from pkg/api/types.go TypeMeta.
	// TODO(jlowdermilk): remove this after eliminating downstream dependencies.
	// +optional
	Kind string `json:"kind,omitempty"`
	// Legacy field from pkg/api/types.go TypeMeta.
	// TODO(jlowdermilk): remove this after eliminating downstream dependencies.
	// +optional
	APIVersion string `json:"apiVersion,omitempty"`
	// Preferences holds general information to be use for cli interactions
	Preferences Preferences `json:"preferences"`
	// Clusters is a map of referencable names to cluster configs
	Clusters map[string]*Cluster `json:"clusters"`
	// AuthInfos is a map of referencable names to user configs
	AuthInfos map[string]*AuthInfo `json:"users"`
	// Contexts is a map of referencable names to context configs
	Contexts map[string]*Context `json:"contexts"`
	// CurrentContext is the name of the context that you would like to use by default
	CurrentContext string `json:"current-context"`
	// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
	// +optional
	Extensions map[string]runtime.Object `json:"extensions,omitempty"`
}
  • tools/clientcmd#ClientConfig:负责用 api#Config 真正创建 rest#Config。处理、解析 kubeconfig 中的认证信息,有了它才能创建 rest#Config,所以命名叫 ClientConfig
  • rest#Config:用于创建 http 客户端
type Config struct {
	// Host must be a host string, a host:port pair, or a URL to the base of the apiserver.
	// If a URL is given then the (optional) Path of that URL represents a prefix that must
	// be appended to all request URIs used to access the apiserver. This allows a frontend
	// proxy to easily relocate all of the apiserver endpoints.
	Host string
	// APIPath is a sub-path that points to an API root.
	APIPath string

	// ContentConfig contains settings that affect how objects are transformed when
	// sent to the server.
	ContentConfig

	// Server requires Basic authentication
	Username string
	Password string

	// Server requires Bearer authentication. This client will not attempt to use
	// refresh tokens for an OAuth2 flow.
	// TODO: demonstrate an OAuth2 compatible client.
	BearerToken string

	// Path to a file containing a BearerToken.
	// If set, the contents are periodically read.
	// The last successfully read value takes precedence over BearerToken.
	BearerTokenFile string

	// Impersonate is the configuration that RESTClient will use for impersonation.
	Impersonate ImpersonationConfig

	// Server requires plugin-specified authentication.
	AuthProvider *clientcmdapi.AuthProviderConfig

	// Callback to persist config for AuthProvider.
	AuthConfigPersister AuthProviderConfigPersister

	// Exec-based authentication provider.
	ExecProvider *clientcmdapi.ExecConfig

	// TLSClientConfig contains settings to enable transport layer security
	TLSClientConfig

	// UserAgent is an optional field that specifies the caller of this request.
	UserAgent string

	// Transport may be used for custom HTTP behavior. This attribute may not
	// be specified with the TLS client certificate options. Use WrapTransport
	// to provide additional per-server middleware behavior.
	Transport http.RoundTripper
	// WrapTransport will be invoked for custom HTTP behavior after the underlying
	// transport is initialized (either the transport created from TLSClientConfig,
	// Transport, or http.DefaultTransport). The config may layer other RoundTrippers
	// on top of the returned RoundTripper.
	//
	// A future release will change this field to an array. Use config.Wrap()
	// instead of setting this value directly.
	WrapTransport transport.WrapperFunc

	// QPS indicates the maximum QPS to the master from this client.
	// If it's zero, the created RESTClient will use DefaultQPS: 5
	QPS float32

	// Maximum burst for throttle.
	// If it's zero, the created RESTClient will use DefaultBurst: 10.
	Burst int

	// Rate limiter for limiting connections to the master from this client. If present overwrites QPS/Burst
	RateLimiter flowcontrol.RateLimiter

	// The maximum length of time to wait before giving up on a server request. A value of zero means no timeout.
	Timeout time.Duration

	// Dial specifies the dial function for creating unencrypted TCP connections.
	Dial func(ctx context.Context, network, address string) (net.Conn, error)

	// Version forces a specific version to be used (if registered)
	// Do we need this?
	// Version string
}

对于 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的用法

o
粉丝 0
博文 500
码字总数 0
作品 0
私信 提问
加载中
请先登录后再评论。
开箱即用的 Java Kubernetes Operator 运行时

本篇分享的内容难度为“初学者/Beginner”级别,以下是阅读本文前推荐您了解的背景知识: Java 语言编程基础; 了解过 Kubernetes 平台上的 Operator/Controller 工作机制; 也可以同步参考 ...

SOFAStack
01/16
43
0
Prometheus监控学习笔记之解读prometheus监控kubernetes的配置文件

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

osc_cenl5m72
2019/02/25
4
0
Kubernetes 的 Client Libraries 的使用

说明 kubernetes 估计会成为 linux 一样的存在,client-go 是它的 go sdk,client-go/examples/ 给出了一些用例,但是数量比较少。 api Resource 的定义不在client-go中,而是在一个名为 ap...

osc_qg4dad59
2019/08/14
30
0
spring-cloud-kubernetes官方demo运行实战

关于spring-cloud-kubernetes spring-cloud-kubernetes是springcloud官方推出的开源项目,用于将Spring Cloud和Spring Boot应用运行在kubernetes环境,并且提供了通用的接口来调用kubernete...

程序员欣宸
2019/09/16
38
0
K8S之traefik高级特性

Traefik Traefik是一个用Golang开发的轻量级的Http反向代理和负载均衡器。由于可以自动配置和刷新backend节点,目前可以被绝大部分容器平台支持,例如Kubernetes,Swarm,Rancher等。由于tra...

osc_q010126d
2019/08/01
10
0

没有更多内容

加载失败,请刷新页面

加载更多

Linux系统检查用户账户到期时间

如果你在 Linux 上启用了密码策略。密码必须在到期前进行更改,并且登录到系统时会收到通知。如果你很少使用自己的帐户,那么可能由于密码过期而被锁定。在许多情况下,这可能会在无需密码登...

老孟的Linux私房菜
13分钟前
9
0
关于南京哪里有开餐饮费发票?

关于南京哪里有开餐饮费发票?聚焦餐饮行业,谈话〖18 7一電一7 5 3 8一徴一3331〗研究院昨发布数据显示,今年上半年,全国餐饮行业招聘需求增长46.18%,平均月薪6387元.随着餐饮行业的快速...

点击fojewio
46分钟前
7
0
android studio 4.0 打开DDMS

1、先找到AndroidStudio配置的SDK路径; 2、在SDK的/tools/路径下有个monitor.bat 的批处理文件; 3、鼠标连续点击两下monitor.bat这个批处理文件,在屏幕上会打开一个类似CMD的命令行中输入...

chenhongjiang
48分钟前
10
0
如何在Android中使用SharedPreferences来存储,获取和编辑值

问题: Closed . 已关闭 。 This question needs to be more focused. 这个问题需要更加集中。 It is not currently accepting answers. 它当前不接受答案。 Learn more . 了解更多 。 Want...

fyin1314
58分钟前
6
0
【JDK1.8】LinkedList源码分析

LinkedList的特性 LinkedList内部使用双向链表作为存储结构,LinkedList可以理解为链表的扩展对象,封装了常用的和非常用的操作链表的方法。以及在通过索引获取元素时的简单优化,通常Linke...

XuePeng77
今天
36
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部