欢迎访问我的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

关于Clientset

  • 本篇是《client-go实战》系列的第三篇,前文学习了最基础的客户端Restclient,尽管咱们实战的需求很简单(获取指定namespace下所有pod的信息),但还是写了不少代码,如下图,各种设置太麻烦,例如api的path、Group、Version、返回的数据结构、编解码工具等:

  • 如果业务代码中,需要操作kubernetes资源的代码都写成上图的样子,相信您是难以忍受的,应该会做一些封装以简化代码,不过client-go已经给出了简化版客户端,就省去了咱们自己动手的麻烦,也就是本文的主题:Clientset

本篇概览

  • 本篇目标是学习如何使用Clientset,因此毫无难度,咱们先来个源码速读,快速搞清楚Clientset到底是啥,然后确认需求,最后快速编码和验证就完事儿了;

源码速度

  • 之所以是速读而非精度,是因为Clientset内容简单容易理解,快速掌握其原理即可用于实战;
  • Clientset源码阅读的切入点就是其名字中的set,这是个集合,里面有很多东西,看一下Clientset数据结构的源码(限于篇幅只展示了一部分):
type Clientset struct {
*discovery.DiscoveryClient
admissionregistrationV1 *admissionregistrationv1.AdmissionregistrationV1Client
admissionregistrationV1beta1 *admissionregistrationv1beta1.AdmissionregistrationV1beta1Client
internalV1alpha1 *internalv1alpha1.InternalV1alpha1Client
appsV1 *appsv1.AppsV1Client
appsV1beta1 *appsv1beta1.AppsV1beta1Client
appsV1beta2 *appsv1beta2.AppsV1beta2Client
authenticationV1 *authenticationv1.AuthenticationV1Client
...
  • 如果您还没有理解上述源码的含义,再请看下图,左边是kubernetes的Group、Version信息,右边依旧是Clientset数据结构的源码,通过箭头可见:kubernetes的Group和Version的每个组合,都对应Clientset数据结构的一个字段:

  • Clientset果然名副其实,是所有Group和Version组合对象的集合,不过Group和Version组合对象到底是啥呢?以appsV1字段为例,去看看其类型appsv1.AppsV1Client,如下图,AppsV1Client只有一字段,就是咱们熟悉的restClient,所以RESTClient是Clientset的基础,这话没毛病,另外注意红框2中的Deployments方法,返回的是DeploymentInterface接口实现:

  • 顺藤摸瓜去看DeploymentInterface,打开deployment.go文件后真相大白,接口定义的和实现一目了然:

  • 挑一个接口实现的代码看看,就选新建deployment的方法吧,如下,和我们使用RESTClient编码差不多:
func (c *deployments) Create(ctx context.Context, deployment *v1.Deployment, opts metav1.CreateOptions) (result *v1.Deployment, err error) {
result = &v1.Deployment{}
err = c.client.Post().
Namespace(c.ns).
Resource("deployments").
VersionedParams(&opts, scheme.ParameterCodec).
Body(deployment).
Do(ctx).
Into(result)
return
}
  • 至此,您对Clientset应该心里有数了:其实就是把我们使用RESTClient操作资源的代码按照Group和Version分类再封装而已,这不像技术活,更像体力活---所以,这种体力活是谁做的呢?如下图红框所示,源码中已经注明这些代码是工具client-gen自动生成的:

  • 至此,Clientset的源码速度就算完成了,咱们已经知道了Clientset的内幕,接下来开始尝试使用它;

需求确认

  • 本次编码实战的需求如下:
  • 写一段代码,检查用户输入的operate参数,该参数默认是create,也可以接受clean;
  • 如果operate参数等于create,就执行以下操作:
  1. 新建名为test-clientset的namespace
  2. 新建一个deployment,namespace为test-clientset,镜像用tomcat,副本数为2
  3. 新建一个service,namespace为test-clientset,类型是NodePort
  • 如果operate参数等于clean,就删除create操作中创建的service、deployment、namespace等资源:
  • 以上需求使用Clientset客户端实现,完成后咱们用浏览器访问来验证tomcat是否正常;

源码下载

名称 链接 备注
项目主页 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文件夹下有多个子文件夹,本篇对应的源码在clientsetdemo目录下,如下图红框所示:

编码

  • 新建文件夹restclientdemo,在里面执行以下命令,新建module:
go mod init clientsetdemo
  • 添加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"
appsv1 "k8s.io/api/apps/v1"
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/util/homedir"
"k8s.io/utils/pointer"
"path/filepath"
) const (
NAMESPACE = "test-clientset"
DEPLOYMENT_NAME = "client-test-deployment"
SERVICE_NAME = "client-test-service"
) 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")
} // 获取用户输入的操作类型,默认是create,还可以输入clean,用于清理所有资源
operate := flag.String("operate", "create", "operate type : create or clean") flag.Parse() // 从本机加载kubeconfig配置文件,因此第一个参数为空字符串
config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig) // kubeconfig加载失败就直接退出了
if err != nil {
panic(err.Error())
} // 实例化clientset对象
clientset, err := kubernetes.NewForConfig(config) if err!= nil {
panic(err.Error())
} fmt.Printf("operation is %v\n", *operate) // 如果要执行清理操作
if "clean"==*operate {
clean(clientset)
} else {
// 创建namespace
createNamespace(clientset) // 创建deployment
createDeployment(clientset) // 创建service
createService(clientset)
}
} // 清理本次实战创建的所有资源
func clean(clientset *kubernetes.Clientset) {
emptyDeleteOptions := metav1.DeleteOptions{} // 删除service
if err := clientset.CoreV1().Services(NAMESPACE).Delete(context.TODO(), SERVICE_NAME, emptyDeleteOptions) ; err != nil {
panic(err.Error())
} // 删除deployment
if err := clientset.AppsV1().Deployments(NAMESPACE).Delete(context.TODO(), DEPLOYMENT_NAME, emptyDeleteOptions) ; err != nil {
panic(err.Error())
} // 删除namespace
if err := clientset.CoreV1().Namespaces().Delete(context.TODO(), NAMESPACE, emptyDeleteOptions) ; err != nil {
panic(err.Error())
}
} // 新建namespace
func createNamespace(clientset *kubernetes.Clientset) {
namespaceClient := clientset.CoreV1().Namespaces() namespace := &apiv1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: NAMESPACE,
},
} result, err := namespaceClient.Create(context.TODO(), namespace, metav1.CreateOptions{}) if err!=nil {
panic(err.Error())
} fmt.Printf("Create namespace %s \n", result.GetName())
} // 新建service
func createService(clientset *kubernetes.Clientset) {
// 得到service的客户端
serviceClient := clientset.CoreV1().Services(NAMESPACE) // 实例化一个数据结构
service := &apiv1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: SERVICE_NAME,
},
Spec: apiv1.ServiceSpec{
Ports: []apiv1.ServicePort{{
Name: "http",
Port: 8080,
NodePort: 30080,
},
},
Selector: map[string]string{
"app" : "tomcat",
},
Type: apiv1.ServiceTypeNodePort,
},
} result, err := serviceClient.Create(context.TODO(), service, metav1.CreateOptions{}) if err!=nil {
panic(err.Error())
} fmt.Printf("Create service %s \n", result.GetName())
} // 新建deployment
func createDeployment(clientset *kubernetes.Clientset) {
// 得到deployment的客户端
deploymentClient := clientset.
AppsV1().
Deployments(NAMESPACE) // 实例化一个数据结构
deployment := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: DEPLOYMENT_NAME,
},
Spec: appsv1.DeploymentSpec{
Replicas: pointer.Int32Ptr(2),
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"app" : "tomcat",
},
}, Template: apiv1.PodTemplateSpec{
ObjectMeta:metav1.ObjectMeta{
Labels: map[string]string{
"app" : "tomcat",
},
},
Spec: apiv1.PodSpec{
Containers: []apiv1.Container{
{
Name: "tomcat",
Image: "tomcat:8.0.18-jre8",
ImagePullPolicy: "IfNotPresent",
Ports: []apiv1.ContainerPort{
{
Name: "http",
Protocol: apiv1.ProtocolSCTP,
ContainerPort: 8080,
},
},
},
},
},
},
},
} result, err := deploymentClient.Create(context.TODO(), deployment, metav1.CreateOptions{}) if err!=nil {
panic(err.Error())
} fmt.Printf("Create deployment %s \n", result.GetName())
}

数据结构初始化的烦恼

  • 看过或者上述代码后您可能在烦恼:创建资源时,数据结构的字段太多太复杂根本记不住,对应的代码不好写,这里分享一个我的做法,如下图,我在开发的时候一共有两个窗口,左侧是官方的yaml示例,右侧用了GoLand的分屏功能,分屏的左侧是我写代码的窗口,右侧是数据结构定义,此时内容不会搞错,数据结构也能对应上,写起来就舒服多了:

验证

  • 代码写完后,执行go run main.go,即可创建namespace、deployment、service等资源;

  • 查看kubernetes上资源是否成功创建:

[root@hedy ~]# kubectl get pods -n test-clientset
NAME READY STATUS RESTARTS AGE
client-test-deployment-7677cc9669-kd7l7 1/1 Running 0 178m
client-test-deployment-7677cc9669-kt5rv 1/1 Running 0 178m
[root@hedy ~]# kubectl get service -n test-clientset
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
client-test-service NodePort 10.109.189.151 <none> 8080:30080/TCP 178m
  • 浏览器访问kubernetes服务器的30080端口,可见熟悉的tomcat首页:

  • 执行命令go run main.go -operate clean即可删除刚才创建的所有资源;

  • 至此Clientset的学习和实战就结束了,总得来说这是个大部分都是自动生成代码的客户端,逻辑简单容易理解,多用几次熟练后,就能随心所欲的操控kubernetes的资源了;

关于容器和镜像的环境

如果您不想自己搭建kubernetes环境,推荐使用腾讯云容器服务TKE:无需自建,即可在腾讯云上使用稳定, 安全,高效,灵活扩展的 Kubernetes 容器平台;

如果您希望自己的镜像可以通过外网上传和下载,推荐腾讯云容器镜像服务TCR:像数据加密存储,大镜像多节点快速分发,跨地域镜像同步

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

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

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

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

https://github.com/zq2599/blog_demos

client-go实战之三:Clientset的更多相关文章

  1. kubebuilder实战之三:基础知识速览

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

  2. [Java聊天室server]实战之三 接收循环

    前言 学习不论什么一个稍有难度的技术,要对其有充分理性的分析,之后果断做出决定---->也就是人们常说的"多谋善断":本系列尽管涉及的是socket相关的知识.但学习之前,更 ...

  3. [原创].NET 分布式架构开发实战之三 数据访问深入一点的思考

    原文:[原创].NET 分布式架构开发实战之三 数据访问深入一点的思考 .NET 分布式架构开发实战之三 数据访问深入一点的思考 前言:首先,感谢园子里的朋友对文章的支持,感谢大家,希望本系列的文章能 ...

  4. Flink的sink实战之三:cassandra3

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

  5. spring4.1.8扩展实战之三:广播与监听

    提到广播与监听,我们常常会想到RabbitMQ.Kafka等消息中间件,这些常用于分布式系统中多个应用之间,有时候应用自身内部也有广播和监听的需求(例如某个核心数据发生变化后,有些业务模块希望立即被感 ...

  6. Flink SQL Client综合实战

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

  7. java版gRPC实战之三:服务端流

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

  8. Spring Cloud Gateway实战之三:动态路由

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

  9. Hadoop实战之三~ Hello World

    本文介绍的是在Ubuntu下安装用三台PC安装完成Hadoop集群并运行好第一个Hello World的过程,软硬件信息如下: Ubuntu:12.04 LTS Master: 1.5G RAM,奔腾 ...

随机推荐

  1. CSS 格式 设置标签间距 和 input slot

    作者:张艳涛 日期:2020-07-29 CSS设置俩个标签的间距 及 Input Slots <div> <div class="m-b-20 ovf-hd"& ...

  2. SQL SERVER获取表在哪些存储过程中使用过

    1.获取某张表在哪些存储过程中使用到 select distinct object_name(id) from syscomments where id in (select object_id fr ...

  3. ASPX页面传参中文乱码处理

    前端 function() { var msg='这是一段中文参数'; window.location.href="New.aspx?name="+escape(msg); } 后 ...

  4. Python中比较运算符连用的语法规则

    在Python中,比较运用符<.>.<=.>=.== .!=可以连用,但语法规则和其它编程语言不一样 以 == 为例,具体语法规则是: a == b == c == d 等价于 ...

  5. 第1篇-关于JVM运行时,开篇说的简单些

    开讲Java运行时,这一篇讲一些简单的内容.我们写的主类中的main()方法是如何被Java虚拟机调用到的?在Java类中的一些方法会被由C/C++编写的HotSpot虚拟机的C/C++函数调用,不过 ...

  6. 痞子衡嵌入式:恩智浦i.MX RT1xxx系列MCU启动那些事(11.B)- FlexSPI NOR连接方式大全(RT1160/1170)

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是恩智浦i.MXRT1160/1170两款MCU的FlexSPI NOR启动的连接方式. 这个 i.MXRT FlexSPI NOR 启动 ...

  7. Java代码搭建Dubbo+ZooKeeper 的示例

    .personSunflowerP { background: rgba(51, 153, 0, 0.66); border-bottom: 1px solid rgba(0, 102, 0, 1); ...

  8. Linux从头学07:中断那么重要,它的本质到底是什么?

    作 者:道哥,10+年的嵌入式开发老兵. 公众号:[IOT物联网小镇],专注于:C/C++.Linux操作系统.应用程序设计.物联网.单片机和嵌入式开发等领域. 公众号回复[书籍],获取 Linux. ...

  9. Abp vNext 基础篇丨分层架构

    介绍 本章节对 ABP 框架进行一个简单的介绍,摘自ABP官方,后面会在使用过程中对各个知识点进行细致的讲解. 领域驱动设计 领域驱动设计(简称:DDD)是一种针对复杂需求的软件开发方法.将软件实现与 ...

  10. SQL注入:基本查询原理

    SQL注入概述 sql注入不需要精通sql的各种命令,只需要了解几个命令并会使用即可. SQL注入:一种针对于数据库的攻击 现在的web网站都需要数据库的支持. SQL部分重要内容: 库:databa ...