欢迎访问我的GitHub

https://github.com/zq2599/blog_demos

内容:所有原创文章分类汇总及配套源码,涉及Java、Docker、Kubernetes、DevOPS等;

系列文章链接

  1. client-go实战之一:准备工作
  2. client-go实战之二:RESTClient
  3. client-go实战之三:Clientset
  4. client-go实战之四:dynamicClient
  5. client-go实战之五:DiscoveryClient

本篇概览

  • 本文是《client-go实战》系列的第四篇,前文咱们学习了Clientset客户端,发现Clientset在deployment、service这些kubernetes内置资源的时候是很方便的,每个资源都有其专属的方法,配合官方API文档和数据结构定义,开发起来比Restclient高效;
  • 但如果要处理的不是kubernetes的内置资源呢?比如CRD,Clientset的代码中可没有用户自定义的东西,显然就用不上Clientset了,此时本篇的主角dynamicClient就要登场啦!

相关知识储备

  • 在正式学习dynamicClient之前,有两个重要的知识点需要了解:Object.runtime和Unstructured,对于整个kubernetes来说它们都是非常重要的;

Object.runtime

  • 聊Object.runtime之前先要明确两个概念:资源和资源对象,关于资源大家都很熟悉了,pod、deployment这些不都是资源嘛,个人的理解是资源更像一个严格的定义,当您在kubernetes中创建了一个deployment之后,这个新建的deployment实例就是资源对象了;
  • 在kubernetes的代码世界中,资源对象对应着具体的数据结构,这些数据结构都实现了同一个接口,名为Object.runtime,源码位置是staging/src/k8s.io/apimachinery/pkg/runtime/interfaces.go,定义如下:
type Object interface {
GetObjectKind() schema.ObjectKind
DeepCopyObject() Object
}
  • DeepCopyObject方法顾名思义,就是深拷贝,也就是将内存中的对象克隆出一个新的对象;
  • 至于GetObjectKind方法的作用,相信聪明的您也猜到了:处理Object.runtime类型的变量时,只要调用其GetObjectKind方法就知道它的具体身份了(如deployment,service等);
  • 最后再次强调:资源对象都是Object.runtime的实现;

Unstructured

  • 在聊Unstructured之前,先看一个简单的JSON字符串:
{
"id": 101,
"name": "Tom"
}
  • 上述JSON的字段名称和字段值类型都是固定的,因此可以针对性编写一个数据结构来处理它:
type Person struct {
ID int
Name String
}
  • 对于上面的JSON字符串就是结构化数据(Structured Data),这个应该好理解;
  • 与结构化数据相对的就是非结构化数据了(Unstructured Data),在实际的kubernetes环境中,可能会遇到一些无法预知结构的数据,例如前面的JSON字符串中还有第三个字段,字段值的具体内容和类型在编码时并不知晓,而是在真正运行的时候才知道,那么在编码时如何处理呢?相信您会想到用interface{}来表示,实际上client-go也是这么做的,来看Unstructured数据结构的源码,路径是staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured.go:
type Unstructured struct {
// Object is a JSON compatible map with string, float, int, bool, []interface{}, or
// map[string]interface{}
// children.
Object map[string]interface{}
}
  • 显然,上述数据结构定义并不能发挥什么作用,真正重要的是关联的方法,如下图,可见client-go已经为Unstructured准备了丰富的方法,借助这些方法可以灵活的处理非结构化数据:

重要知识点:Unstructured与资源对象的相互转换

  • 另外还有一个非常重要的知识点:可以用Unstructured实例生成资源对象,也可以用资源对象生成Unstructured实例,这个神奇的能力是unstructuredConverter的FromUnstructured和ToUnstructured方法分别实现的,下面的代码片段展示了如何将Unstructured实例转为PodList实例:
// 实例化一个PodList数据结构,用于接收从unstructObj转换后的结果
podList := &apiv1.PodList{} // unstructObj
err = runtime.DefaultUnstructuredConverter.FromUnstructured(unstructObj.UnstructuredContent(), podList)
  • 您可能会好奇上述FromUnstructured方法究竟是如何实现转换的,咱们去看下此方法的内部实现,如下图所示,其实也没啥悬念了,通过反射可以得到podList的字段信息:

  • 至此,Unstructured的分析就结束了吗?没有,强烈推荐您进入上图红框2中的fromUnstructured方法去看细节,这里面是非常精彩的,以podList为例,这是个数据结构,而fromUnstructured只处理原始类型,对于数据结构会调用structFromUnstructured方法处理,在structFromUnstructured方法中

    处理数据结构的每个字段,又会调用fromUnstructured,这是相互迭代的过程,最终,不论podList中有多少数据结构的嵌套都会被处理掉,篇幅所限就不展开相信分析了,下图是一部分关键代码:

  • 小结:Unstructured转为资源对象的套路并不神秘,无非是用反射取得资源对象的字段类型,然后按照字段名去Unstructured的map中取得原始数据,再用反射设置到资源对象的字段中即可;
  • 做完了准备工作,接下来该回到本篇文章的主题了:dynamicClient客户端

关于dynamicClient

  • deployment、pod这些资源,其数据结构是明确的固定的,可以精确对应到Clientset中的数据结构和方法,但是对于CRD(用户自定义资源),Clientset客户端就无能为力了,此时需要有一种数据结构来承载资源对象的数据,也要有对应的方法来处理这些数据;
  • 此刻,前面提到的Unstructured可以登场了,没错,把Clientset不支持的资源对象交给Unstructured来承载,接下来看看dynamicClient和Unstructured的关系:
  • 先看数据结构定义,和clientset没啥区别,只有个restClient字段:
type dynamicClient struct {
client *rest.RESTClient
}
  • 这个数据结构只有一个关联方法Resource,入参为GVR,返回的是另一个数据结构dynamicResourceClient:
func (c *dynamicClient) Resource(resource schema.GroupVersionResource) NamespaceableResourceInterface {
return &dynamicResourceClient{client: c, resource: resource}
}
  • 通过上述代码可知,dynamicClient的关键是数据结构dynamicResourceClient及其关联方法,来看看这个dynamicResourceClient,如下图,果然,dynamicClient所有和资源相关的操作都是dynamicResourceClient在做(代理模式?),选了create方法细看,序列化和反序列化都交给unstructured的UnstructuredJSONScheme,与kubernetes的交互交给Restclient:

  • 小结:
  1. 与Clientset不同,dynamicClient为各种类型的资源都提供统一的操作API,资源需要包装为Unstructured数据结构;
  2. 内部使用了Restclient与kubernetes交互;
  • 对dynamicClient的介绍分析就这些吧,可以开始实战了;

需求确认

  • 本次编码实战的需求很简单:查询指定namespace下的所有pod,然后在控制台打印出来,要求用dynamicClient实现;
  • 您可能会问:pod是kubernetes的内置资源,更适合Clientset来操作,而dynamicClient更适合处理CRD不是么?---您说得没错,这里用pod是因为折腾CRD太麻烦了,定义好了还要在kubernetes上发布,于是干脆用pod来代替CRD,反正dynamicClient都能处理,咱们通过实战掌握dynamicClient的用法就行了,以后遇到各种资源都能处理之;

源码下载

名称 链接 备注
项目主页 https://github.com/zq2599/blog_demos 该项目在GitHub上的主页
git仓库地址(https) https://github.com/zq2599/blog_demos.git 该项目源码的仓库地址,https协议
git仓库地址(ssh) git@github.com:zq2599/blog_demos.git 该项目源码的仓库地址,ssh协议
  • 这个git项目中有多个文件夹,client-go相关的应用在client-go-tutorials文件夹下,如下图红框所示:

  • client-go-tutorials文件夹下有多个子文件夹,本篇对应的源码在dynamicclientdemo目录下,如下图红框所示:

编码

  • 新建文件夹dynamicclientdemo,在里面执行以下命令,新建module:
go mod init dynamicclientdemo
  • 添加k8s.io/api和k8s.io/client-go这两个依赖,注意版本要匹配kubernetes环境:
go get k8s.io/api@v0.20.0
go get k8s.io/client-go@v0.20.0
  • 新建main.go,内容如下,稍后会说一下要注意的重点:
package main

import (
"context"
"flag"
"fmt"
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/util/homedir"
"path/filepath"
) func main() { var kubeconfig *string // home是家目录,如果能取得家目录的值,就可以用来做默认值
if home:=homedir.HomeDir(); home != "" {
// 如果输入了kubeconfig参数,该参数的值就是kubeconfig文件的绝对路径,
// 如果没有输入kubeconfig参数,就用默认路径~/.kube/config
kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
} else {
// 如果取不到当前用户的家目录,就没办法设置kubeconfig的默认目录了,只能从入参中取
kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file")
} flag.Parse() // 从本机加载kubeconfig配置文件,因此第一个参数为空字符串
config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig) // kubeconfig加载失败就直接退出了
if err != nil {
panic(err.Error())
} dynamicClient, err := dynamic.NewForConfig(config) if err != nil {
panic(err.Error())
} // dynamicClient的唯一关联方法所需的入参
gvr := schema.GroupVersionResource{Version: "v1", Resource: "pods"} // 使用dynamicClient的查询列表方法,查询指定namespace下的所有pod,
// 注意此方法返回的数据结构类型是UnstructuredList
unstructObj, err := dynamicClient.
Resource(gvr).
Namespace("kube-system").
List(context.TODO(), metav1.ListOptions{Limit: 100}) if err != nil {
panic(err.Error())
} // 实例化一个PodList数据结构,用于接收从unstructObj转换后的结果
podList := &apiv1.PodList{} // 转换
err = runtime.DefaultUnstructuredConverter.FromUnstructured(unstructObj.UnstructuredContent(), podList) if err != nil {
panic(err.Error())
} // 表头
fmt.Printf("namespace\t status\t\t name\n") // 每个pod都打印namespace、status.Phase、name三个字段
for _, d := range podList.Items {
fmt.Printf("%v\t %v\t %v\n",
d.Namespace,
d.Status.Phase,
d.Name)
}
}
  • 上述代码中有三处重点需要注意:
  1. Resource方法指定了本次操作的资源类型;
  2. List方法向kubernetes发起请求;
  3. FromUnstructured将Unstructured数据结构转成PodList,其原理前面已经分析过;
  • 执行go run main.go,如下,可以从kubernetes取得数据,并且转换成PodList也正常:
zhaoqin@zhaoqindeMBP-2 dynamicclientdemo % go run main.go
namespace status name
kube-system Running coredns-7f89b7bc75-5pdwc
kube-system Running coredns-7f89b7bc75-nvbvm
kube-system Running etcd-hedy
kube-system Running kube-apiserver-hedy
kube-system Running kube-controller-manager-hedy
kube-system Running kube-flannel-ds-v84vc
kube-system Running kube-proxy-hlppx
kube-system Running kube-scheduler-hedy
  • 至此,dynamicClient的学习和实战就完成了,它是名副其实的动态客户端工具,用一套API处理所有资源,除了突破Clientset的内置资源限制,还让我们的业务代码有了更大的灵活性,希望本文能给您一些参考,辅助您写出与场景更加匹配的代码;

你不孤单,欣宸原创一路相伴

  1. Java系列
  2. Spring系列
  3. Docker系列
  4. kubernetes系列
  5. 数据库+中间件系列
  6. DevOps系列

欢迎关注公众号:程序员欣宸

微信搜索「程序员欣宸」,我是欣宸,期待与您一同畅游Java世界...

https://github.com/zq2599/blog_demos

client-go实战之四:dynamicClient的更多相关文章

  1. [原创].NET 分布式架构开发实战之四 构建从理想和实现之间的桥梁(前篇)

    原文:[原创].NET 分布式架构开发实战之四 构建从理想和实现之间的桥梁(前篇) .NET 分布式架构开发实战之四 构建从理想和实现之间的桥梁(前篇) 前言:上一篇文章讲述了一些实现DAL的理论,本 ...

  2. Flink的sink实战之四:自定义

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

  3. kubebuilder实战之四:operator需求说明和设计

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

  4. Apache CXF实战之四 构建RESTful Web Service

    Apache CXF实战之一 Hello World Web Service Apache CXF实战之二 集成Sping与Web容器 Apache CXF实战之三 传输Java对象 这篇文章介绍一下 ...

  5. Flink SQL Client综合实战

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

  6. dubbo实战之四:管理控制台dubbo-admin

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

  7. Kurento实战之四:应用开发指南

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

  8. java版gRPC实战之四:客户端流

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

  9. Hadoop实战之四~hadoop作业调度详解(2)

    这篇文章将接着上一篇wordcount的例子,抽象出最简单的过程,一探MapReduce的运算过程中,其系统调度到底是如何运作的. 情况一:数据和运算分开的情况 wordcount这个例子的是hado ...

随机推荐

  1. OOP-Klass模型 简略

    class Model { public static int a = 1; public int b; public Model(int b) { this.b = b; } } public st ...

  2. Spring最简单构建一个后台{msg:"登录成功",code:200,data:null}

    一.简介 {msg:"登录成功",code:200,data:null} 二.两种请求 如果严格msg code data也带"" @RestControlle ...

  3. OpenFaaS实战之四:模板操作(template)

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

  4. 论文笔记:(CVPR2019)Relation-Shape Convolutional Neural Network for Point Cloud Analysis

    目录 摘要 一.引言 二.相关工作 基于视图和体素的方法 点云上的深度学习 相关性学习 三.形状意识表示学习 3.1关系-形状卷积 建模 经典CNN的局限性 变换:从关系中学习 通道提升映射 3.2性 ...

  5. Calcite(一):javacc语法框架及使用

    是一个动态数据管理框架. 它包含许多组成典型数据库管理系统的部分,但省略了存储原语.它提供了行业标准的SQL解析器和验证器,具有可插入规则和成本函数的可自定义优化器,逻辑和物理代数运算符,从SQL到代 ...

  6. Flutter学习(7)——网络请求框架Dio简单使用

    原文地址: Flutter学习(7)--网络请求框架Dio简单使用 | Stars-One的杂货小窝 Flutter系列学习之前都是在个人博客发布,感兴趣可以过去看看 网络请求一般APP都是需要的,在 ...

  7. GPB重磅!浙大李兰娟院士团队修饰多组学研究揭示炎症反应新机制

    炎症 (inflammation) 是机体对致炎因子的损伤所发生的一种以防御反应为主的基本病理过程.翻译后修饰(PTMs)在调节多种炎症信号通路中起着重要作用,如磷酸化(phosphorylation ...

  8. maven将依赖打入jar包中

    1.在pom.xml中加入maven-assembly-plugin插件: <build> <plugins> <plugin> <artifactId> ...

  9. Java知识复习回顾

    1.MVC 三层架构: M:Model 数据模型层 V:View 视图层 C:Controller控制器层 目的:解耦 2.Spring Spring:轻量级的Java开源框架,容器,目的是解决企业级 ...

  10. Spring Cloud Aliaba - Ribbon

    Ribbon(有关介绍见RestTemplate末尾) Ribbon负载均衡实现策略 Ribbon负载均衡实现策略通过接口IRule进行实现,默认使用ZoneAvoidanceRule规则进行负载均衡 ...