解读 kubernetes client-go 官方 examples - Part Ⅰ
目录
转发请注明出处: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 资源也是一样的:
- 通过 kubeconfig 信息,构造 Config 实例。该实例记录了集群证书、 API Server 地址等信息;
- 根据 Config 实例携带的信息,创建 http 客户端;
- 向 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 变量,然后在内部依次调用如下函数:
func NewNonInteractiveDeferredLoadingClientConfig(loader ClientConfigLoader, overrides *ConfigOverrides) ClientConfig
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 client-go 官方 examples - Part Ⅰ的更多相关文章
- 【爬坑系列】之解读kubernetes的认证原理&实践
对于访问kube-apiserver模块的请求来说,如果是使用http协议,则会顺利进入模块内部得到自己想要的:但是如果是用的是https,则能否进入模块内部获得想要的资源,他会首先要进行https自 ...
- 配置kubernetes.client的参数遇到的坑
配置kubernetes.client遇到的一些坑: 一,job-name不能重名,如果job-name已经有了,再创建job,则会发生冲突cliflict 这样将会报以下错误:Reason : Co ...
- Kubernetes tutorial - K8S 官方入门教程 中文翻译
官方教程,共 6 个小节.每一小节的第一部分是知识讲解,第二部分是在线测试环境的入口. kubectl 的命令手册 原文地址 1 创建集群 1.1 使用 Minikube 创建集群 Kubernete ...
- Kubernetes tutorial - K8S 官方入门教程
tutorials 教程 kubectl 的命令手册 1 Creating a Cluster 1.1 Using Minikube to Create a Cluster Kubernetes Cl ...
- Prometheus监控学习笔记之解读prometheus监控kubernetes的配置文件
0x00 概述 Prometheus 是一个开源和社区驱动的监控&报警&时序数据库的项目.来源于谷歌BorgMon项目.现在最常见的Kubernetes容器管理系统中,通常会搭配Pro ...
- Kubernetes官方java客户端之三:外部应用
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...
- Kubernetes官方java客户端之四:内部应用
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...
- Kubernetes官方java客户端之五:proto基本操作
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...
- Kubernetes官方java客户端之六:OpenAPI基本操作
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...
随机推荐
- BZOJ_3173_[Tjoi2013]最长上升子序列_splay
BZOJ_3173_[Tjoi2013]最长上升子序列_splay Description 给定一个序列,初始为空.现在我们将1到N的数字插入到序列中,每次将一个数字插入到一个特定的位置.每插入一个数 ...
- python 备份文件脚本
使用python备份服务器的文件 #coding=utf- import os import os.path def copyFiles(sourceDir, targetDir): for file ...
- Spring py登陆模块(包含 记录登陆时间,记录ip,增加积分)
嘛基于最近的复习准备写个关于spring登陆模块的小程序 虽然小但是五脏俱全呐 话不多说让我来介绍一下今天的登陆程序. 这些是 基于Spring JDBC 的持久层实现 基于Spring 声明事物的业 ...
- hystrix基本配置项(2)
①配置HystrixCommand HystxixCommand支持如下的配置: GroupKey:该命令属于哪一个组,可以帮助我们更好的组织命令. CommandKey:该命令的名称 ThreadP ...
- 漏洞经验分享丨Java审计之XXE(下)
上篇内容我们介绍了XXE的基础概念和审计函数的相关内容,今天我们将继续分享Blind XXE与OOB-XXE的知识点以及XXE防御方法,希望对大家的学习有所帮助! 上期回顾 ◀漏洞经验分享丨Java ...
- [AST实战]从零开始写一个wepy转VUE的工具
为什么需要 wepy 转 VUE "转转二手"是我司用 wepy 开发的功能与 APP 相似度非常高的小程序,实现了大量的功能性页面,而新业务 H5 项目在开发过程中有时也经常需要 ...
- SpringBoot进阶教程(二十六)整合Redis之共享Session
集群现在越来越常见,当我们项目搭建了集群,就会产生session共享问题.因为session是保存在服务器上面的.那么解决这一问题,大致有三个方案,1.通过nginx的负载均衡其中一种ip绑定来实现( ...
- Android 个人手机通讯录开发
一.Android 个人手机通讯录开发 数据存储:SQLite 数据库 开发工具:Android Studio 二.Phone Module 简介 1. 界面展示 2. ...
- 2019-01-29 VS Code创建自定义Python代码片段
续前文[日常]Beyond的歌里最多是"唏嘘"吗? - Python分词+词频最后的想法, 发现VS Code支持用户自定义代码片段: Creating your own snip ...
- SQL Server统计信息偏差影响表联结方式案例浅析
我们知道数据库中的统计信息的准确性是非常重要的.它会影响执行计划.一直想写一篇关于统计信息影响执行计划的相关博客,但是都卡在如何构造一个合适的例子上,所以一直拖着没有写.巧合,最近在生产环境中遇到 ...