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;patch

package 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 bash

set -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:v1

SCRIPT_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 v1

import (
    "k8s.io/apimachinery/pkg/runtime/schema"
)

// SchemeGroupVersion is group version used to register these objects.
var SchemeGroupVersion = GroupVersion

func 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 main

import (
	"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  string
	kubeconfig 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)

	//得到controller
	controller := NewController(kubeClient, studentClient,
		studentInformerFactory.App().V1().Gateways())

	//启动informer
	go 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 main

import (
	"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 clientset
	kubeclientset kubernetes.Interface
	// studentclientset is a clientset for our own API group
	studentclientset clientset.Interface

	studentsLister listers.GatewayLister
	studentsSynced cache.InformerSynced

	workqueue workqueue.RateLimitingInterface

	recorder 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 change
	studentInformer.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已经启动")
	<-stopCh
	glog.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 string
		var ok bool

		if 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 name
	namespace, 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 string
	var 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 string
	var err error
	// 从缓存中删除指定对象
	key, err = cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
	if err != nil {
		runtime.HandleError(err)
		return
	}
	//再将key放入队列
	c.workqueue.AddRateLimited(key)
}

信号处理

package signals

import (
	"os"
	"os/signal"
)

var onlyOneSignalHandler = make(chan struct{})

func SetupSignalHandler() (stopCh <-chan struct{}) {
	close(onlyOneSignalHandler) // panics when called twice

	stop := make(chan struct{})
	c := make(chan os.Signal, 2)
	signal.Notify(c, shutdownSignals...)
	go func() {
		<-c
		close(stop)
		<-c
		os.Exit(1) // second signal. Exit directly.
	}()

	return stop
}
//go:build !windows
// +build !windows

package signals

import (
	"os"
	"syscall"
)

var shutdownSignals = []os.Signal{os.Interrupt, syscall.SIGTERM}

最后运行:

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【工具栏】jclasslib 插件的安装和使用

1. 安装 2.使用 安装之后 在 view 的 ToolWindows 里也有一个这样的窗口 jclasslib 的主要作用是查看字节码的相关信息 package com.test;public class Test {public static void main(String[] args) {Integer a 1;int b a 2;} }例如我写了一段这样的代码&#xff0c;然后去…

语义分割发展现状

语义分割是对图像中的每一个像素进行分类&#xff0c;目前广泛应用于医学图像与无人驾驶等。从这几年的论文来看&#xff0c;这一领域主要分为有监督语义分割、无监督语义分割、视频语义分割等。 语意分割究竟有什么用呢&#xff1f;似乎看起来没有目标检测/跟踪等应用范围广。…

P1379 八数码难题

题目描述 在 33 的棋盘上&#xff0c;摆有八个棋子&#xff0c;每个棋子上标有 1 至 8 的某一数字。棋盘中留有一个空格&#xff0c;空格用 0 来表示。空格周围的棋子可以移到空格中。要求解的问题是&#xff1a;给出一种初始布局&#xff08;初始状态&#xff09;和目标布局&…

Linux centos stream9 parted

在Linux中&#xff0c;常用的磁盘管理工具包括 fdisk、parted、gdisk 等。它们可以用于创建、删除、调整分区、查看分区表等操作。 传统的MBR分区表(即主引导记录)大家都很熟悉&#xff0c;是过去我们使用windows时常见的。所支持的最大卷2T&#xff0c;且对分区有限制&#x…

SRM供应商招标采购管理系统(源码)

软件相关资料获取&#xff1a;点我获取 一、SRM供应商在线采购 SRM供应商在线采购是指企业通过互联网平台&#xff0c;实现对供应商的在线招募、选择、关系管理等一系列活动。这种采购方式具有高效、透明、便于管理的特点&#xff0c;能够帮助企业降低采购成本&#xff0c;提…

Vue中v-if与v-show区别详解

✨ 专栏介绍 在当今Web开发领域中&#xff0c;构建交互性强、可复用且易于维护的用户界面是至关重要的。而Vue.js作为一款现代化且流行的JavaScript框架&#xff0c;正是为了满足这些需求而诞生。它采用了MVVM架构模式&#xff0c;并通过数据驱动和组件化的方式&#xff0c;使…

Nightingale 夜莺监控系统 - 告警篇(3)

Author&#xff1a;rab 官方文档&#xff1a;https://flashcat.cloud/docs/content/flashcat-monitor/nightingale-v6/usage/alert/alert-rule/ 目录 前言一、配置1.1 创建钉钉机器人1.2 n9e 创建通知用户1.3 n9e 创建团队&#xff08;组&#xff09;1.4 将通知用户添加团队1.…

C++核心编程——文件操作

本专栏记录C学习过程包括C基础以及数据结构和算法&#xff0c;其中第一部分计划时间一个月&#xff0c;主要跟着黑马视频教程&#xff0c;学习路线如下&#xff0c;不定时更新&#xff0c;欢迎关注。 当前章节处于&#xff1a; ---------第1阶段-C基础入门 ---------第2阶段实战…

Realm Management Extension领域管理扩展(下)

四、颗粒保护检查 本节描述了RME引入的颗粒保护检查。颗粒保护检查使得能够在不同的物理地址空间之间动态分配内存区域。 本节将向您介绍以下功能: 颗粒保护表的结构用于颗粒保护检查的故障报告区域在物理地址空间之间的过渡正如在物理地址一节中所述,RME提供了四个物理地址…

gitee完整使用教程,创建项目并上传

目录 一 什么是gitee 二 安装Git 三 登录gitee&#xff0c;生成密钥 四 配置SSH密钥 五 创建项目 六 克隆仓库到本地 七 关联本地工程到远程仓库 八 添加文件 九 异常处理 十 删除仓储 十一 git常用命令 一 什么是gitee gitee是开源中国推出的基于git的代码托管服务…

.Net Core项目在linux部署实战 1.sdk下载 2.环境变量配置 3.运行

1)下载.net core sdk https://download.visualstudio.microsoft.com/download/pr/01292c7c-a1ec-4957-90fc-3f6a2a1e5edc/025e84c4d9bd4aeb003d4f07b42e9159/dotnet-sdk-6.0.418-linux-x64.tar.gz 2)配置下环境变量 step1: // 解压到指定目录 mkdir -p $HOME/dotnet &…

缓解大语言模型(LLM)幻觉的可行方法探究(课程综述)

缓解大语言模型&#xff08;LLM&#xff09;幻觉的可行方法探究 转载请标明出处&#xff0c;&#x1f232;抄袭 摘要&#xff1a;2022年11月OpenAI推出能够进行多场景对话的大语言模型ChatGPT&#xff0c;ChatGPT凭借大规模的训练参数、海量的训练数据及强化学习人类反馈在语…

亚马逊新店成长手册:从起步到壮大,每一步都有策略(测评)

在亚马逊的浩瀚海洋中&#xff0c;每天都有无数商家乘风破浪&#xff0c;争先恐后地开设自己的新店铺。如何在波涛汹涌的市场中独树一帜&#xff0c;成功地将产品送达顾客手中&#xff1f;接下来将为你揭晓这个秘密。首先&#xff0c;要确定产品方向。这需要深入了解你的目标受…

高级分布式系统-第7讲 分布式系统的时钟同步

顺序的分类 在分布式系统中&#xff0c; 顺序关系主要分为以下三类&#xff1a;时间顺序&#xff1a; 事件在时间轴上发生的先后关系。 无限时刻集组成有向时间轴&#xff0c; 时间顺序是通过时刻的顺序体现的。 因果顺序&#xff1a; 如果事件e1是事件e2发生的原因&#xf…

抖店怎么做的?开店带货流程+运营基础问题解答,感兴趣可收藏!

我是王路飞。 抖店具体是怎么做的呢&#xff1f; 像有些人在抖音既没有粉丝基础、也没有拍短视频和开直播的能力&#xff0c;适合做抖店吗&#xff1f; 先说明&#xff0c;抖店可以0粉丝开通&#xff0c;店铺运营和出单也跟这些没关系&#xff0c;所以你们可以放心去做。 这…

Web前端-移动web开发_流式布局

文章目录 移动web开发流式布局1.0 移动端基础1.1浏览器现状1.2 手机屏幕的现状1.3常见移动端屏幕尺寸1.4移动端调试方法 2.0 视口2.1 布局视口 layout viewport2.2视觉视口 visual viewport2.3理想视口 ideal viewport&#xff08;苹果&#xff09;2.4meta标签 3.0 物理像素(手…

Linux高性能服务器编程——学习笔记①

第一章、tcp/ip协议族 一、tcp/ip协议族1.1 主要的协议1.1.1 数据链路层1.1.2 网络层1.1.3 传输层1.1.4 应用层 1.2 封装1.3 分用1.4 测试网络1.5 ARP协议工作原理1.5.1 以太网ARP请求/应答报文详解1.5.2 ARP高速缓存的查看和修改1.5.3 使用tcpdump观察ARP通信过程 1.6 DNS工作…

LTD259次升级 | 新增发票管理 • 官网与名片线索管理多维度 • 公海线索客户轨迹更周全

1、 商城会员中心新增申请发票&#xff1b; 2、 商城管理新增发票审核与开票管理功能&#xff1b; 3、 官网客户、名片客户、线索公海新增筛选、跟进、分配功能&#xff1b; 4、 其他已知问题修复与优化&#xff1b; 01 商城 在本次升级中&#xff0c;我们为商城新增了发票管理…

无需任何三方库,在 Next.js 项目在线预览 PDF 文件

前言&#xff1a; 之前在使用Vue和其它框架的时候&#xff0c;预览 PDF 都是使用的 PDFObject 这个库&#xff0c;步骤是&#xff1a;下载依赖&#xff0c;然后手动封装一个 PDF 预览组件&#xff0c;这个组件接收本地或在线的pdf地址&#xff0c;然后在页面中使用组件的车时候…