高阶k8s二次开发教程 -- 通过阅读Istio源码习得

本篇文章全网几乎找不到,在做深层次的k8s二次开发时非常管用。那就是使用Client-go去访问自定义CRD资源。

我们先使用kubebuilder生成一个CRD,论生成CRD这些,还是kubebuilder更加方便。

创建CRD

apiVersion: "apiextensions.k8s.io/v1beta1"
kind: "CustomResourceDefinition"
metadata:name: "projects.example.sealyun.com"
spec:group: "example.sealyun.com"version: "v1alpha1"scope: "Namespaced"names:plural: "projects"singular: "project"kind: "Project"validation:openAPIV3Schema:required: ["spec"]properties:spec:required: ["replicas"]properties:replicas:type: "integer"minimum: 1

创建Golang客户端

package v1alpha1import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"type ProjectSpec struct {Replicas int `json:"replicas"`
}type Project struct {metav1.TypeMeta   `json:",inline"`metav1.ObjectMeta `json:"metadata,omitempty"`Spec ProjectSpec `json:"spec"`
}type ProjectList struct {metav1.TypeMeta `json:",inline"`metav1.ListMeta `json:"metadata,omitempty"`Items []Project `json:"items"`
}

定义DeepCopy方法

Kubernetes API(在本例中为Project和ProjectList)提供的每种类型都需要实现该k8s.io/apimachinery/pkg/runtime.Object接口。该接口定义了两种方法GetObjectKind()和DeepCopyObject()。第一种方法已经由嵌入式metav1.TypeMeta结构提供; 第二个你必须自己实现。

如果你不实现DeepCopy方法,那么资源是无法注册进入后续的资源列表中的。

该DeepCopyObject方法旨在生成对象的深层副本。由于这涉及许多样板代码,因此很多工具通常会自动生成这些方法。为了本文的目的,我们将手动完成。继续向deepcopy.go同一个包添加第二个文件:

package v1alpha1import "k8s.io/apimachinery/pkg/runtime"// DeepCopyInto copies all properties of this object into another object of the
// same type that is provided as a pointer.
func (in *Project) DeepCopyInto(out *Project) {out.TypeMeta = in.TypeMetaout.ObjectMeta = in.ObjectMetaout.Spec = ProjectSpec{Replicas: in.Spec.Replicas,}
}// DeepCopyObject returns a generically typed copy of an object
func (in *Project) DeepCopyObject() runtime.Object {out := Project{}in.DeepCopyInto(&out)return &out
}// DeepCopyObject returns a generically typed copy of an object
func (in *ProjectList) DeepCopyObject() runtime.Object {out := ProjectList{}out.TypeMeta = in.TypeMetaout.ListMeta = in.ListMetaif in.Items != nil {out.Items = make([]Project, len(in.Items))for i := range in.Items {in.Items[i].DeepCopyInto(&out.Items[i])}}return &out
}

注册类型

需要让客户端知道新类型。允许客户端在与API服务器通信的时候自动处理新类型,为此,register.go在包中添加一个新文件。

注意这里仅仅是指客户端而已,k8s服务器在apply crd资源的时候就自动会帮我们去注册。

package v1alpha1import (metav1 "k8s.io/apimachinery/pkg/apis/meta/v1""k8s.io/apimachinery/pkg/runtime""k8s.io/apimachinery/pkg/runtime/schema"
)const GroupName = "example.sealyun.com"
const GroupVersion = "v1alpha1"var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: GroupVersion}var (SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)AddToScheme   = SchemeBuilder.AddToScheme
)func addKnownTypes(scheme *runtime.Scheme) error {scheme.AddKnownTypes(SchemeGroupVersion,&Project{},&ProjectList{},)metav1.AddToGroupVersion(scheme, SchemeGroupVersion)return nil
}

此代码实际上并没有做任何事情(除了创建新runtime.SchemeBuilder实例)。重要的部分是AddToScheme函数,它是runtime.SchemeBuilder中创建的类型。一旦Kubernetes客户端初始化为注册您的类型,您可以稍后从客户端代码的任何部分调用此函数。

构建HTTP客户端

在定义类型并添加方法以在全局方案构建器中注册它们之后,您现在可以创建能够加载自定义资源的HTTP客户端。

package mainimport ("flag""log""time""k8s.io/apimachinery/pkg/runtime/schema""k8s.io/apimachinery/pkg/runtime/serializer""github.com/martin-helmich/kubernetes-crd-example/api/types/v1alpha1""k8s.io/client-go/kubernetes/scheme""k8s.io/client-go/rest""k8s.io/client-go/tools/clientcmd"
)var kubeconfig stringfunc init() {flag.StringVar(&kubeconfig, "kubeconfig", "", "path to Kubernetes config file")flag.Parse()
}func main() {var config *rest.Configvar err errorif kubeconfig == "" {log.Printf("using in-cluster configuration")config, err = rest.InClusterConfig()} else {log.Printf("using configuration from '%s'", kubeconfig)config, err = clientcmd.BuildConfigFromFlags("", kubeconfig)}if err != nil {panic(err)}v1alpha1.AddToScheme(scheme.Scheme)crdConfig := *configcrdConfig.ContentConfig.GroupVersion = &schema.GroupVersion{Group: v1alpha1.GroupName, Version: v1alpha1.GroupVersion}crdConfig.APIPath = "/apis"crdConfig.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: scheme.Codecs}crdConfig.UserAgent = rest.DefaultKubernetesUserAgent()exampleRestClient, err := rest.UnversionedRESTClientFor(&crdConfig)if err != nil {panic(err)}
}

您现在可以使用第exampleRestClient中创建的内容来查询example.sealyun.com/v1alpha1API组中的所有自定义资源。示例可能如下所示:

result := v1alpha1.ProjectList{}
err := exampleRestClient.Get().Resource("projects").Do().Into(&result)

没错,刚才注册的逻辑的唯一目的就是让这个地方的Into能够认识的了ProjectList方法。

为了以更加类型安全的方式使用您的API,通常最好将这些操作包装在您自己的客户端集中。为此,创建一个新的子包clientset/v1alpha1。首先,实现一个定义API组类型的接口,并将配置设置从您的main方法移动到该clientset的构造函数中(NewForConfig在下面的示例中):

package v1alpha1import ("github.com/martin-helmich/kubernetes-crd-example/api/types/v1alpha1""k8s.io/apimachinery/pkg/runtime/schema""k8s.io/apimachinery/pkg/runtime/serializer""k8s.io/client-go/kubernetes/scheme""k8s.io/client-go/rest"
)type ExampleV1Alpha1Interface interface {Projects(namespace string) ProjectInterface
}type ExampleV1Alpha1Client struct {restClient rest.Interface
}func NewForConfig(c *rest.Config) (*ExampleV1Alpha1Client, error) {config := *cconfig.ContentConfig.GroupVersion = &schema.GroupVersion{Group: v1alpha1.GroupName, Version: v1alpha1.GroupVersion}config.APIPath = "/apis"config.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: scheme.Codecs}config.UserAgent = rest.DefaultKubernetesUserAgent()client, err := rest.RESTClientFor(&config)if err != nil {return nil, err}return &ExampleV1Alpha1Client{restClient: client}, nil
}func (c *ExampleV1Alpha1Client) Projects(namespace string) ProjectInterface {return &projectClient{restClient: c.restClient,ns: namespace,}
}

以上是对client的封装

接下来,您需要实现一个特定的Project客户端集来访问自定义资源(请注意,上面的示例已经使用了我们仍需要提供的ProjectInterface和projectClient类型)。projects.go在同一个包中创建第二个文件:

package v1alpha1import ("github.com/martin-helmich/kubernetes-crd-example/api/types/v1alpha1"metav1 "k8s.io/apimachinery/pkg/apis/meta/v1""k8s.io/apimachinery/pkg/watch""k8s.io/client-go/kubernetes/scheme""k8s.io/client-go/rest"
)type ProjectInterface interface {List(opts metav1.ListOptions) (*v1alpha1.ProjectList, error)Get(name string, options metav1.GetOptions) (*v1alpha1.Project, error)Create(*v1alpha1.Project) (*v1alpha1.Project, error)Watch(opts metav1.ListOptions) (watch.Interface, error)// ...
}type projectClient struct {restClient rest.Interfacens         string
}func (c *projectClient) List(opts metav1.ListOptions) (*v1alpha1.ProjectList, error) {result := v1alpha1.ProjectList{}err := c.restClient.Get().Namespace(c.ns).Resource("projects").VersionedParams(&opts, scheme.ParameterCodec).Do().Into(&result)return &result, err
}func (c *projectClient) Get(name string, opts metav1.GetOptions) (*v1alpha1.Project, error) {result := v1alpha1.Project{}err := c.restClient.Get().Namespace(c.ns).Resource("projects").Name(name).VersionedParams(&opts, scheme.ParameterCodec).Do().Into(&result)return &result, err
}func (c *projectClient) Create(project *v1alpha1.Project) (*v1alpha1.Project, error) {result := v1alpha1.Project{}err := c.restClient.Post().Namespace(c.ns).Resource("projects").Body(project).Do().Into(&result)return &result, err
}func (c *projectClient) Watch(opts metav1.ListOptions) (watch.Interface, error) {opts.Watch = truereturn c.restClient.Get().Namespace(c.ns).Resource("projects").VersionedParams(&opts, scheme.ParameterCodec).Watch()
}

上面的Delete和Update方法和这个差不多,没有什么区别。

创建informer

构建Kubernetes operator时,通常希望能够对新创建或更新的事件进行监听。理论上,可以定期调用该List()方法并检查是否添加了新资源。

大多数情况通过使用初始List()初始加载资源的所有相关实例,然后使用Watch()订阅相关事件进行处理。然后,使用从informer接收的初始对象列表和更新来构建本地缓存,该缓存允许快速访问任何自定义资源,而无需每次都访问API服务器。

这种模式非常普遍,以至于client-go库为此提供了一个cache包:来自包的Informerk8s.io/client-go/tools/cache。您可以为自定义资源构建新的Informer,如下所示:

package mainimport ("time""github.com/martin-helmich/kubernetes-crd-example/api/types/v1alpha1"client_v1alpha1 "github.com/martin-helmich/kubernetes-crd-example/clientset/v1alpha1"metav1 "k8s.io/apimachinery/pkg/apis/meta/v1""k8s.io/apimachinery/pkg/runtime""k8s.io/apimachinery/pkg/util/wait""k8s.io/apimachinery/pkg/watch""k8s.io/client-go/tools/cache"
)func WatchResources(clientSet client_v1alpha1.ExampleV1Alpha1Interface) cache.Store {projectStore, projectController := cache.NewInformer(&cache.ListWatch{ListFunc: func(lo metav1.ListOptions) (result runtime.Object, err error) {return clientSet.Projects("some-namespace").List(lo)},WatchFunc: func(lo metav1.ListOptions) (watch.Interface, error) {return clientSet.Projects("some-namespace").Watch(lo)},},&v1alpha1.Project{},1*time.Minute,cache.ResourceEventHandlerFuncs{},)go projectController.Run(wait.NeverStop)return projectStore
}

该NewInformer方法返回两个对象:第二个返回值,控制器控制List()和Watch()调用并填充第一个返回值,Store缓存监听到的一些信息。您现在可以使用store轻松访问CRD,列出所有CRD或通过名称访问它们。store函数返回interface{}类型,因此您必须将它们强制转换回CRD类型:

store := WatchResource(clientSet)
project := store.GetByKey("some-namespace/some-project").(*v1alpha1.Project)

如此很多情况下就不需要再去调用apiserver了,给apiserver减轻压力.

总结

虽然现在很多工具给我们写CRD controller带来了极大的便捷,但是对于client-go这些基本的使用还是非常必要的,而官方client-go的开发文档和事例真的是少之又少,基本仅包含非常基本的操作。

还有一个dynamic client的方式也可以用来访问自定义CRD,但是文中的方式会更优雅更清晰更适合工程化。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/11263.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

了解Unity编辑器 之组件篇Effects(十一)

一、Halo:是一个可用于游戏对象的特效组件,它可以在对象周围添加一个光晕效果 Color属性: 用于设置Halo的颜色。你可以通过选择颜色面板中的颜色来指定光晕的外观。选择适当的颜色可以使光晕与游戏场景中的其他元素相匹配或突出显示。 Size属性: 用于设…

[SQL挖掘机] - 视图介绍

介绍: 视图(View)是数据库中的一种虚拟表格,它是基于一个或多个实际表格(或其他视图)的查询结果集合。与实际表格不同,视图不包含实际存储的数据,而是根据定义在其之上的查询语句来动态生成数据…

LeetCode|backtracking|review:40. 131. 93. 47. 332. | 37. Sudoku Solver

复习: 40. Combination Sum II [1,1,2,3]中,答案里有[1,1,2], 但是不能有两个[1,2,3] 131. Palindrome Partitioning 每个for都是在给定的start之后找一个palindrome。当start 93. Restore IP Addresses forloop每次loop都是在给定的start的后三个数…

javascript 模板引擎

使用场景 在实际开发中,一般都是使用动态请求数据来更新页面,服务器端通常返回json格式的数据,正常操作是我们手动的去拼装HTML,但麻烦且容易出错,因此出现了一些用模版生成HTML的的框架叫js模板引擎如:jq…

Sugar BI : AI 问答,即问即答

AI 探索功能提供给所有用户自由探索和分析数据模型的能力。在 AI 探索页中,有授权的用户可以通过 AI 问答和字段拖拽两种方式对数据模型进行探索。 下面,我们将为大家详细指导如何使用 AI 探索 新建 AI 探索页 空间管理员可以在报表管理中新建「AI 探索…

Docker 容器基础操作

Docker容器基础操作 容器(container)是Docker镜像的运行实例,类似于可执行文件与进程的关系,Docker是容器引擎,相当于系统平台。 容器的生命周期 容器的基础操作(以 tomcat8.0 为例) # 拉取tomcat8.0镜像 [root@tudou tudou]# docker pull tomcat:8.0 8.0: Pulling f…

紫光FPGA试用--软件篇

目录 一 软件安装启动 二 如何打开IP核?查看/修改现有IP核参数? 三 如何定义引脚? 四 如何下载code进入FPGA? 1. 下载到FPGA芯片内: 2.下载到外部FLASH中 五 如何进入在线调试模式,调试步骤 操作步骤&#xff…

智慧园区楼宇合集:数字孪生管控系统

智慧园区是指将物联网、大数据、人工智能等技术应用于传统建筑和基础设施,以实现对园区的全面监控、管理和服务的一种建筑形态。通过将园区内设备、设施和系统联网,实现数据的传输、共享和响应,提高园区的管理效率和运营效益,为居…

学习笔记|大模型优质Prompt开发与应用课(二)|第五节:只需3步,优质Prompt秒变应用软件

原作者:依依│百度飞桨产品经理 一乔│飞桨开发者技术专家 分享内容 01:大模型应用简介 02:LLM应用开发范式 03: Al Studio大模型社区 04:AI对话类应用开发技巧 大模型技术爆发,各类应用产品涌现 文心产业级知识增强大模型 工作中的“超级助手”—…

兵兵数码:网络机顶盒哪个好?2023最新网络机顶盒排名

网络机顶盒让电视机重生,解决卡顿、资源少、广告多等问题,我们每年都会进行网络机顶盒测评,今年已经测评过17款,通过多角度对比筛选了五款表现最佳的产品整理成网络机顶盒排名,近期想买网络机顶盒不知道网络机顶盒哪个…

Java lamda对List<JSONObject>里多个动态属性字段进行动态的降序或者升序

最近做到一个需求&#xff0c;需要把业务侧返回的数据&#xff08;格式为List<JSONObject>&#xff09;,然后根据前端传来的排序字段、以及升降序属性来排序并返回给前端。要对List<JSONObject>中的多个属性字段进行动态的升序或降序排序&#xff0c;我们可以根据需…

7.28黄金还会继续下跌吗?收官多空如何布局

近期有哪些消息面影响黄金走势&#xff1f;黄金多空该如何研判&#xff1f; ​黄金消息面解析&#xff1a; 周五&#xff08;7月28日&#xff09;亚洲时段&#xff0c;现货黄金震荡微涨&#xff0c;目前交投于1952.94附近&#xff0c;隔夜金价大跌后&#xff0c;吸引了一些逢低…

【深入了解pytorch】PyTorch循环神经网络(RNN)

【深入了解pytorch】PyTorch循环神经网络(RNN) PyTorch循环神经网络(RNN):概念、工作原理与常见变体循环神经网络概念和工作原理RNN的结构RNN的工作原理LSTM(长短期记忆网络)LSTM的结构LSTM的工作原理GRU(门控循环单元)GRU的结构GRU的工作原理在PyTorch中实现RNN、LST…

HTML 速查列表

HTML 速查列表 HTML 速查列表. 你可以打印它&#xff0c;以备日常使用。 HTML 基本文档 <!DOCTYPE html> <html> <head> <title>文档标题</title> </head> <body> 可见文本... </body> </html> 基本标签&#xff08;Ba…

使用python将PDF转word

实现功能&#xff0c;将程序所在当前路径下的所有PDF文件转化为word import os from pdf2docx import Converter# 获取当前路径 current_path os.getcwd()# 遍历当前路径下的所有文件和文件夹 for file_name in os.listdir(current_path):# 检查文件是否为 PDF 文件if file_n…

[SQL挖掘机] - 全连接: full join

介绍: 在sql中&#xff0c;join是将多个表中的数据按照一定条件进行关联的操作。全连接&#xff08;full join&#xff09;是一种连接类型&#xff0c;它会返回所有满足连接条件的行&#xff0c;同时还包括那些在左表和右表中没有匹配行的数据。 在进行全连接时&#xff0c;会…

Kotlin Multiplatform 使用测试单元

编写常见的测试代码 现在您有了一个基于字符串的 API&#xff0c;可以通过它进行基本的测试。 1、在公共的测试模块中创建一个 org.jetbrains.base64 包 2、在新包下面创建 Base64Test.kt文件 3、在文件中添加代码 package org.jetbrains.base64import com.example.myapplicat…

医学案例|配对wilcoxon符号秩检验

一、案例介绍 某单位想要研究某保健品对小鼠是否具有抗疲劳作用&#xff0c;将同种属的小鼠按性别与年龄相同、体重相近配成对子&#xff0c;共14对&#xff0c;并将每对中的两只小鼠随机分配到两个不同的保健食品剂量组&#xff0c;测量小鼠负重5&#xff05;体重时的游泳时间…

git 合并非关联分支

面对的场景&#xff1a;现在有三个仓库&#xff0c;一个是本地的仓库1&#xff0c;第二个是和仓库1关联的在github上的仓库2&#xff0c;第三个是把仓库1拷贝到一个无网络环境中持续开发一段时间的仓库3. 分析 基本想法是把仓库3作为仓库1的远程仓库&#xff0c;然后在仓库1上…

uiautomatorViewer无法获取Android8.0手机屏幕截图的解决方案

问题描述&#xff1a; 做APP UI自动化的时候&#xff0c;会碰到用uiautomatorViewer在Android 8.0及以上版本的手机上&#xff0c;无法获取到手机屏幕截图&#xff0c;无法获取元素定位信息的问题&#xff0c;会有以下的报 在低版本的Android手机上&#xff0c;则没有这个问题…