kubebuilder+code-generator开发k8s的controller

本文记录用kubebuilder和code-generator开发k8s的crd控制器。

概览

和k8s.io/code-generator类似,是一个码生成工具,用于为你的CRD生成kubernetes-style API实现。区别在于:
Kubebuilder不会生成informers、listers、clientsets,而code-generator会。
Kubebuilder会生成Controller、Admission Webhooks,而code-generator不会。
Kubebuilder会生成manifests yaml,而code-generator不会。
Kubebuilder还带有一些其他便利性设施。

安装kubebuilder

https://github.com/kubernetes-sigs/kubebuilder/releases/tag/v3.13.0
在这里下载kubebuilder对应芯片版本:
在这里插入图片描述
本身作为一个二进制可执行文件,放入到/usr/local/bin目录下即可。

使用go mod

mkdir example
go mod init gateway
kubebuilder init --domain example.com
kubebuilder edit --multigroup=true

创建API

kubebuilder create api --group app --version v1 --kind Gateway
Create Resource [y/n]
y
Create Controller [y/n]
n

在example目录下创建了,api目录,制定了group和版本以及资源类型。
在这里插入图片描述
会自动生成apis/app/v1目录,里面有{crd}_types.go和zz_generated.deepcopy.go文件,如果需要配置和修改crd字段,可以修改{crd}_types.go中的crd spec结构体,见下图:
修改{crd}_types.go之后,需用重新执行make manifests重新生成crd,此时zz_generated.deepcopy.go也会同步更新。
在这里插入图片描述

生成RBAC manifests

在api/app/v1/目录创建rbac.go文件,加入以下内容:

// +kubebuilder:rbac:groups=app.example.com,resources=gateways,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=app.example.com,resources=gateways/status,verbs=get;update;patchpackage v1

生成crd manifests

make manifests

这一步会生成config/crd/bases目录,以及该目录下的crd yaml
在这里插入图片描述

使用code-generator

先下载code-generator

go get -u -v k8s.io/code-generator/...

这里要下下载很多依赖包
在这里插入图片描述
在后面的编译过程中,需要手动下载一些依赖包到$GOPATH下
在这里插入图片描述
同时需要升级go到1.21版本,已解决如下问题:
在这里插入图片描述

准备脚本

在hack目录下新增update-codegen.sh和verify-codegen.sh
其中update-codegen.sh如下
其中:
MODULE=gateway,这里和go.mod保持一致(go mod init gateway)
API_PKG=api,目前工程的api目录,有的可能是api
OUTPUT_PKG=generated/app:输出结果的目录(generated/{group},group是之前kubebuilder create api是指定的group参数)
GROUP_VERSION=app:v1,也是跟kubebuilder create api是的group和version保持一致

#!/usr/bin/env bashset -o errexit
set -o nounset
set -o pipefail# corresponding to go mod init <module>
MODULE=gateway
# api package
APIS_PKG=api
# generated output package
OUTPUT_PKG=generated/app
# group-version such as foo:v1alpha1
GROUP_VERSION=app:v1SCRIPT_ROOT=$(dirname "${BASH_SOURCE[0]}")/..
CODEGEN_PKG=${CODEGEN_PKG:-$(cd "${SCRIPT_ROOT}"; ls -d -1 /Users/luoyi/go/src/k8s.io/code-generator 2>/dev/null || echo /Users/luoyi/go/src/k8s.io/code-generator)}# generate the code with:
# --output-base    because this script should also be able to run inside the vendor dir of
#                  k8s.io/kubernetes. The output-base is needed for the generators to output into the vendor dir
#                  instead of the $GOPATH directly. For normal projects this can be dropped.
bash "${CODEGEN_PKG}"/generate-groups.sh "client,lister,informer" \${MODULE}/${OUTPUT_PKG} ${MODULE}/${APIS_PKG} \${GROUP_VERSION} \--go-header-file "${SCRIPT_ROOT}"/hack/boilerplate.go.txt \--output-base "${SCRIPT_ROOT}"
#  --output-base "${SCRIPT_ROOT}/../../.." \

这个脚本主要就是制定了一些模块名和运行脚本的参数。本质上与进入到$GOPATH/src/k8s.io,执行如下命令一样:

./generate-groups.sh all /Users/luoyi/k8s/example/generated/app /Users/luoyi/k8s/example/api app:v1

其中,/Users/luoyi/k8s/example/generated/app指定了生成代码的路径,/Users/luoyi/k8s/example/api指定了作用路径,最后一个参数指定了group和版本。all表示client、informer和listener都生成。
在生成代码前还需要做些配置:
在{crd}_type.go上配置tag // +genclient,用于生成clienset

// +genclient
//+kubebuilder:object:root=true
//+kubebuilder:subresource:status// Gateway is the Schema for the gateways API
type Gateway struct {metav1.TypeMeta   `json:",inline"`metav1.ObjectMeta `json:"metadata,omitempty"`Spec GatewaySpec `json:"spec,omitempty"`

在apis/app/v1下新建doc.go,其中groupName要根据kubebuilder init和kubebuilder create api参数对应修改

// +groupName=app.example.com
package v1

在apis/app/v1下新建register.go,代码如下,无需修改

package v1import ("k8s.io/apimachinery/pkg/runtime/schema"
)// SchemeGroupVersion is group version used to register these objects.
var SchemeGroupVersion = GroupVersionfunc Resource(resource string) schema.GroupResource {return SchemeGroupVersion.WithResource(resource).GroupResource()
}

执行hack/update-codegen.sh可以得到clientset/lister/informer

./hack/update-codegen.sh

此时会得到example.com/gateway/generated/app目录,在目录下有clientset、liseters、informers
看到如下创建成功:

mv example.com/gateway/generated generated

在这里插入图片描述
将example.com/gateway/generated移到项目根目录即可
在这里插入图片描述

编写main.go

package mainimport ("flag""time""github.com/golang/glog""k8s.io/client-go/kubernetes""k8s.io/client-go/tools/clientcmd"// Uncomment the following line to load the gcp plugin (only required to authenticate against GKE clusters).// _ "k8s.io/client-go/plugin/pkg/client/auth/gcp"clientset "gateway/generated/app/clientset/versioned"informers "gateway/generated/app/informers/externalversions""gateway/signals"
)var (masterURL  stringkubeconfig string
)func main() {flag.Parse()// 处理信号量stopCh := signals.SetupSignalHandler()// 处理入参cfg, err := clientcmd.BuildConfigFromFlags(masterURL, kubeconfig)if err != nil {glog.Fatalf("Error building kubeconfig: %s", err.Error())}kubeClient, err := kubernetes.NewForConfig(cfg)if err != nil {glog.Fatalf("Error building kubernetes clientset: %s", err.Error())}studentClient, err := clientset.NewForConfig(cfg)if err != nil {glog.Fatalf("Error building example clientset: %s", err.Error())}studentInformerFactory := informers.NewSharedInformerFactory(studentClient, time.Second*30)//得到controllercontroller := NewController(kubeClient, studentClient,studentInformerFactory.App().V1().Gateways())//启动informergo studentInformerFactory.Start(stopCh)//controller开始处理消息if err = controller.Run(2, stopCh); err != nil {glog.Fatalf("Error running controller: %s", err.Error())}
}func init() {flag.StringVar(&kubeconfig, "kubeconfig", "", "Path to a kubeconfig. Only required if out-of-cluster.")flag.StringVar(&masterURL, "master", "", "The address of the Kubernetes API server. Overrides any value in kubeconfig. Only required if out-of-cluster.")
}

编写controller.go

package mainimport ("fmt""time""github.com/golang/glog"corev1 "k8s.io/api/core/v1""k8s.io/apimachinery/pkg/api/errors""k8s.io/apimachinery/pkg/util/runtime"utilruntime "k8s.io/apimachinery/pkg/util/runtime""k8s.io/apimachinery/pkg/util/wait""k8s.io/client-go/kubernetes""k8s.io/client-go/kubernetes/scheme"typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1""k8s.io/client-go/tools/cache""k8s.io/client-go/tools/record""k8s.io/client-go/util/workqueue"bolingcavalryv1 "gateway/api/app/v1"clientset "gateway/generated/app/clientset/versioned"studentscheme "gateway/generated/app/clientset/versioned/scheme"informers "gateway/generated/app/informers/externalversions/app/v1"listers "gateway/generated/app/listers/app/v1"
)const controllerAgentName = "student-controller"const (SuccessSynced = "Synced"MessageResourceSynced = "Student synced successfully"
)// Controller is the controller implementation for Student resources
type Controller struct {// kubeclientset is a standard kubernetes clientsetkubeclientset kubernetes.Interface// studentclientset is a clientset for our own API groupstudentclientset clientset.InterfacestudentsLister listers.GatewayListerstudentsSynced cache.InformerSyncedworkqueue workqueue.RateLimitingInterfacerecorder record.EventRecorder
}// NewController returns a new student controller
func NewController(kubeclientset kubernetes.Interface,studentclientset clientset.Interface,studentInformer informers.GatewayInformer) *Controller {utilruntime.Must(studentscheme.AddToScheme(scheme.Scheme))glog.V(4).Info("Creating event broadcaster")eventBroadcaster := record.NewBroadcaster()eventBroadcaster.StartLogging(glog.Infof)eventBroadcaster.StartRecordingToSink(&typedcorev1.EventSinkImpl{Interface: kubeclientset.CoreV1().Events("")})recorder := eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: controllerAgentName})controller := &Controller{kubeclientset:    kubeclientset,studentclientset: studentclientset,studentsLister:   studentInformer.Lister(),studentsSynced:   studentInformer.Informer().HasSynced,workqueue:        workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "Students"),recorder:         recorder,}glog.Info("Setting up event handlers")// Set up an event handler for when Student resources changestudentInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{AddFunc: controller.enqueueStudent,UpdateFunc: func(old, new interface{}) {oldStudent := old.(*bolingcavalryv1.Gateway)newStudent := new.(*bolingcavalryv1.Gateway)if oldStudent.ResourceVersion == newStudent.ResourceVersion {//版本一致,就表示没有实际更新的操作,立即返回return}controller.enqueueStudent(new)},DeleteFunc: controller.enqueueStudentForDelete,})return controller
}// 在此处开始controller的业务
func (c *Controller) Run(threadiness int, stopCh <-chan struct{}) error {defer runtime.HandleCrash()defer c.workqueue.ShutDown()glog.Info("开始controller业务,开始一次缓存数据同步")if ok := cache.WaitForCacheSync(stopCh, c.studentsSynced); !ok {return fmt.Errorf("failed to wait for caches to sync")}glog.Info("worker启动")for i := 0; i < threadiness; i++ {go wait.Until(c.runWorker, time.Second, stopCh)}glog.Info("worker已经启动")<-stopChglog.Info("worker已经结束")return nil
}func (c *Controller) runWorker() {for c.processNextWorkItem() {}
}// 取数据处理
func (c *Controller) processNextWorkItem() bool {obj, shutdown := c.workqueue.Get()if shutdown {return false}// We wrap this block in a func so we can defer c.workqueue.Done.err := func(obj interface{}) error {defer c.workqueue.Done(obj)var key stringvar ok boolif key, ok = obj.(string); !ok {c.workqueue.Forget(obj)runtime.HandleError(fmt.Errorf("expected string in workqueue but got %#v", obj))return nil}// 在syncHandler中处理业务if err := c.syncHandler(key); err != nil {return fmt.Errorf("error syncing '%s': %s", key, err.Error())}c.workqueue.Forget(obj)glog.Infof("Successfully synced '%s'", key)return nil}(obj)if err != nil {runtime.HandleError(err)return true}return true
}// 处理
func (c *Controller) syncHandler(key string) error {// Convert the namespace/name string into a distinct namespace and namenamespace, name, err := cache.SplitMetaNamespaceKey(key)if err != nil {runtime.HandleError(fmt.Errorf("invalid resource key: %s", key))return nil}// 从缓存中取对象student, err := c.studentsLister.Gateways(namespace).Get(name)if err != nil {// 如果Student对象被删除了,就会走到这里,所以应该在这里加入执行if errors.IsNotFound(err) {glog.Infof("Student对象被删除,请在这里执行实际的删除业务: %s/%s ...", namespace, name)return nil}runtime.HandleError(fmt.Errorf("failed to list student by: %s/%s", namespace, name))return err}glog.Infof("这里是student对象的期望状态: %#v ...", student)glog.Infof("实际状态是从业务层面得到的,此处应该去的实际状态,与期望状态做对比,并根据差异做出响应(新增或者删除)")c.recorder.Event(student, corev1.EventTypeNormal, SuccessSynced, MessageResourceSynced)return nil
}// 数据先放入缓存,再入队列
func (c *Controller) enqueueStudent(obj interface{}) {var key stringvar err error// 将对象放入缓存if key, err = cache.MetaNamespaceKeyFunc(obj); err != nil {runtime.HandleError(err)return}// 将key放入队列c.workqueue.AddRateLimited(key)
}// 删除操作
func (c *Controller) enqueueStudentForDelete(obj interface{}) {var key stringvar err error// 从缓存中删除指定对象key, err = cache.DeletionHandlingMetaNamespaceKeyFunc(obj)if err != nil {runtime.HandleError(err)return}//再将key放入队列c.workqueue.AddRateLimited(key)
}

信号处理

package signalsimport ("os""os/signal"
)var onlyOneSignalHandler = make(chan struct{})func SetupSignalHandler() (stopCh <-chan struct{}) {close(onlyOneSignalHandler) // panics when called twicestop := make(chan struct{})c := make(chan os.Signal, 2)signal.Notify(c, shutdownSignals...)go func() {<-cclose(stop)<-cos.Exit(1) // second signal. Exit directly.}()return stop
}
//go:build !windows
// +build !windowspackage signalsimport ("os""syscall"
)var shutdownSignals = []os.Signal{os.Interrupt, syscall.SIGTERM}

最后运行:

./gateway -kubeconfig=$HOME/.kube/config -alsologtostderr=true

kubeconfig是k8s集群的默认配置路径。
在这里插入图片描述

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

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

相关文章

宠物服务新篇章:预约小程序带来的变革

随着科技的进步和互联网的普及&#xff0c;小程序已经成为了一种非常受欢迎的应用形式。对于宠物门店来说&#xff0c;开发一个预约小程序可以大大提高客户体验和管理效率。下面是一份宠物门店预约小程序的开发指南。 浏览器搜索乔拓云&#xff0c;登录乔拓云网后台&#xff0c…

大数据仓库开发规范示例

大数据仓库开发规范示例 一、前提概要二、数仓分层原则及定义2.1 数仓分层原则2.2 数仓分层定义 三、数仓公共开发规范3.1 分层调用规范3.2 数据类型规范3.3 数据冗余规范3.4 NULL字段处理规范3.5 公共字段规范3.6 数据表处理规范3.7 事实表划分规范 四、数仓各层开发规范4.1 分…

二十四、同域名下JSESSIONID重叠导致退出

同域名下JSESSIONID重叠导致退出 近期在开发项目的时候发现,如果同域名的情况下,如果把一个单页面无登录系统嵌套进入另外一个系统,那么会出现相互退出的问题。 思考解决方案 一、清除掉嵌套的系统的JSESSIONID,意思就是嵌套系统不设置JSESSIONID 1找寻出问题接口 在无痕…

【电源专题】案例:在EN脚加个电阻就能解决电源下电输出振荡?

案例背景:在某产品上使用一颗升压芯片发现下电输出波形振荡,但此产品并不是第一个使用此升压芯片的。早先此升压芯片使用在其他产品上没有报过这个异常。 分析方法:使用DEMO板,查看标准DEMO板无异常。将异常板卡上的参数与全部换到DEMO板上发现同样存在异常。 推测原因:…

Maya参考图的导入和层的应用

参考视频&#xff1a;08.参考图的导入和层的应用_哔哩哔哩_bilibili 前视图/右视图模式下导入图形 创建图层 锁定后可以避免图片位置的移动 前视图和右视图要根据参照物对齐 与模型保持一定距离&#xff0c;同时把该参照图添加到图层中 模型可以添加到图层2中

Qt应用开发(安卓篇)——Linux下Qt15.5.2配置Android

目录 一、前言 二、Qt安装 三&#xff1a;JDK安装 四&#xff1a;安装SDK&#xff0c;NDK 五、其他事项 六、新建项目 一、前言 看网上教程&#xff0c;多数是windows环境下的&#xff0c;配置也很简单&#xff0c;想不到自己配置的时候却遇到很多问题&#xff0c;传了一…

0_项目git地址——正点原子minifly与crazyflie

1、说明&#xff1a; 在每个专栏的第一篇文章&#xff0c;笔者都会贴出项目的git地址&#xff0c;方便后来者学习和复现&#xff1b; 下面介绍两个项目的官网资料和git地址&#xff0c;最后给出两者的对比&#xff1b; 2、正点原子minifly (1)minifly官网资料下载中心&#…

【深度学习每日小知识】Training Data 训练数据

训练数据是机器学习的基本组成部分&#xff0c;在模型的开发和性能中起着至关重要的作用。它是指用于训练机器学习算法的标记或注释数据集。以下是与训练数据相关的一些关键方面和注意事项。 Quantity 数量 训练数据的数量很重要&#xff0c;因为它会影响模型的泛化能力。通常…

Flink standalone集群部署配置

文章目录 简介软件依赖部署方案二、安装1.下载并解压2.ssh免密登录3.修改配置文件3.启动集群4.访问 Web UI 简介 Flink独立模式&#xff08;Standalone&#xff09;是部署 Flink 最基本也是最简单的方式&#xff1a;所需要的所有 Flink 组件&#xff0c; 都只是操作系统上运行…

Python--装饰器

在 Python 中&#xff0c;装饰器是一种特殊类型的函数&#xff0c;它们用于修改或增强其他函数或方法的行为。装饰器本质上是一个函数&#xff0c;它接受一个函数作为参数&#xff0c;并返回一个新的函数。使用装饰器可以在不修改原函数代码的前提下&#xff0c;给函数添加新的…

Linux 网络设置与基础服务

一 配置网络设置 主机名 hostname IP地址/netmask ifconfig &#xff1b; ip a 路由&#xff1a;默认网关 route -n DNS服务器 cat /etc/resolv.conf 网络连接状态 ss netstat 域名解析 ns…

索引不是银弹

数据库索引&#xff1a;不是银弹 使用环境索引分类创建索引的代价最佳实践不是所有针对索引列的查询都能使用索引加速查询 索引只能匹配列的前缀条件涉及函数操作的无法使用索引联合索引只能匹配左边的列 总结 数据库索引是优化性能的良药&#xff0c;但却不是银弹&#xff01…

10-skywalking告警

https://github.com/apache/skywalking/blob/master/docs/en/setup/backend/backend-alarm.md 5.1&#xff1a;告警指标 ~$ vim /apps/apache-skywalking-apm-bin/config/oal/core.oal service_resp_time # 服务的响应时间 service_sla # 服务http请求成功率SLV&#xff0c;比…

09-Python服务链路追踪案例

skyWalking Python agent requires SkyWalking 8.0 and Python 3.7 # 将django包导入 ~$ cd /apps ~$ tar xf django-test.tgz ~$ cd django-test# 安装模块 ~$ apt install python3-pip ~$ pip3 install -r requirements.txt# 创建django项目mysite ~$ django-admin startpro…

创建一个简单鸿蒙app项目

文章目录 前言TypeScript 基础类型创建一个鸿蒙app总结 一、前言 鸿蒙系统上的开发已经是趋势了&#xff0c;必须紧跟时代的潮流。先简单了解下鸿蒙系统中&#xff0c;我们开发一个app需要用到的语言&#xff0c;那么就是TypeScript。这篇文章主要讲的就是一些基础的语法。最…

算法回忆录——排序

文章目录 1. 插入排序2. 选择排序3. 冒泡排序4. 希尔排序5. 归并排序6. 快速排序7. 堆排序8. 计数排序9. 桶排序10. 基数排序 1. 插入排序 分为两个序列&#xff0c;前面一个序列是排好序的&#xff0c;后面一个序列是未排好的。未排好的序列的第一个元素&#xff08;a&#x…

腾讯云TDSQL TCA/TCP/TCE 认证考试有什么区别呢?

腾讯云认证等级&#xff1a;专项认证考试&云方向认证考试 一、专项认证考试 数据库交付运维-腾讯云TDSQL认证考试一共分为三个等级&#xff1a; 初级TCA、高级工程师TCP、专家级TCE 1、TDSQL TCA培训(MySQL版/PostgreSQL版)考试安排 TCA考试是纯理论题&#xff0c;总分是…

大模型推理优化实践:KV cache 复用与投机采样

作者&#xff1a;米基 一、背景 RTP-LLM 是阿里巴巴大模型预测团队开发的大模型推理加速引擎&#xff0c;作为一个高性能的大模型推理解决方案&#xff0c;它已被广泛应用于阿里内部。该引擎与当前广泛使用的多种主流模型兼容&#xff0c;并通过采用高性能的 CUDA 算子来实现了…

出租车费 C语言xdoj697

问题描述 某城市普通出租车计费标准如下&#xff1a; 起步里程为 3 公里&#xff0c;起步费 10 元&#xff1b; 超起步里程后 10 公里内&#xff0c;每公里 2 元&#xff1b; 超过 10 公里以上的部分&#xff0c;每公里加收 50%的回空补贴费&#xff1b; 营运过程中&#xff0c…

大数据技术之Hudi

第1章 Hudi概述 1.1 Hudi简介 Apache Hudi&#xff08;Hadoop Upserts Delete and Incremental&#xff09;是下一代流数据湖平台。Apache Hudi将核心仓库和数据库功能直接引入数据湖。Hudi提供了表、事务、高效的upserts/delete、高级索引、流摄取服务、数据集群/压缩优化和…