k8s之Kubebuilder安装与初步体验

Kubebuilder安装步骤

一、下载可执行文件

  1. 首先,访问github上的Kubebuilder发行版本页面:https://github.com/kubernetes-sigs/kubebuilder/releases
  2. 例如,若要安装3.14.0版本,点击进入对应的版本链接:https://github.com/kubernetes-sigs/kubebuilder/releases/tag/v3.14.0
  3. 在该页面最下方的“Assets”区域中,找到与你所使用的操作系统(OS)和架构(Arch)相匹配的安装包,然后点击进行下载。比如,若你使用的是基于amd架构的Linux系统,就下载相应的安装包。

二、上传或直接下载到Linux系统

  1. 若已在本地下载了安装包,需将其上传到你的Linux系统中。
  2. 若你的Linux系统网络连接正常,也可以直接在Linux系统中执行以下命令进行下载:
wget https://github.com/kubernetes-sigs/kubebuilder/releases/download/v3.14.0/kubebuilder_linux_amd64

三、安装操作

  1. 下载完成后,执行以下命令对文件进行重命名:
mv kubebuilder_linux_amd64 kubebuilder
  1. 为文件赋予可执行权限,并将其移动到系统的可执行文件目录中:
chmod +x kubebuilder && mv kubebuilder /usr/local/bin/

四、验证安装结果

在终端中执行以下命令,查看Kubebuilder的版本信息,以验证安装是否成功:

kubebuilder version

若安装成功,你将看到类似以下的输出信息:

Version: main.version{KubeBuilderVersion:"3.14.0", KubernetesVendor:"1.27.1", GitCommit:"11053630918ac421cb6eb6f0a3225e2a2ad49535", BuildDate:"2024-01-30T09:29:27Z", GoOs:"linux", GoArch:"amd64"}

开发一个Kubernetes Operator的步骤

定义自定义资源

使用Kubernetes的自定义资源定义语言(CRD),定义一个自定义资源来描述您的应用程序。
除了必备的TypeMeta、ObjectMeta,CRD中还应包含 资源规范Spec 和 资源状态Status 字段。

创建Operator

编写一个自定义的控制器(Controller),用于监控和处理您定义的CR。
当用户创建或修改CR时,Controller 能够 获取到用户修改后的CR,并根据CR中Metadata、Spec等字段的变化,执行必要的操作,处理完后还要将CR的状态信息写入Status字段中,供用户查看。

部署Operator

部署Operator包括两部分:安装CRD,部署Controller

  1. 安装CRD:将CRD应用到集群中,使kubernetes集群能够识别到该资源
  2. 部署Controller:Controller 通常会在control plane之外运行,就像运行任何容器化的应用程序一样。例如,将controller打包成镜像,以 Deployment 运行。

创建和管理自定义资源实例

  1. 使用 kubectl 或 编写yaml 创建CR的实例
  2. 使用 kubectl 或 其他kubernetes客户端工具 管理CR

项目初始化

go env -w GOPROXY=https://mirrors.aliyun.com/goproxy/,direct
kubebuilder init --domain github.com --repo github.com/new

–domain github.com我们的项目的域名,就是后续 CRD Group 的 Domain
比如后续创建一个名为 Application 的 CRD,加上 domain,完整名称就是myjob.github.com。
–repo github.com/new是仓库地址,也就是go module名称

kevin@Kevin:~/kube/test$ tree
.
├── Dockerfile
├── Makefile
├── PROJECT
├── README.md
├── cmd
│   └── main.go
├── config
│   ├── default
│   │   ├── kustomization.yaml
│   │   ├── manager_auth_proxy_patch.yaml
│   │   └── manager_config_patch.yaml
│   ├── manager
│   │   ├── kustomization.yaml
│   │   └── manager.yaml
│   ├── prometheus
│   │   ├── kustomization.yaml
│   │   └── monitor.yaml
│   └── rbac
│       ├── auth_proxy_client_clusterrole.yaml
│       ├── auth_proxy_role.yaml
│       ├── auth_proxy_role_binding.yaml
│       ├── auth_proxy_service.yaml
│       ├── kustomization.yaml
│       ├── leader_election_role.yaml
│       ├── leader_election_role_binding.yaml
│       ├── role.yaml
│       ├── role_binding.yaml
│       └── service_account.yaml
├── go.mod
├── go.sum
├── hack
│   └── boilerplate.go.txt
└── test
    ├── e2e
    │   ├── e2e_suite_test.go
    │   └── e2e_test.go
    └── utils
        └── utils.go

10 directories, 28 files

Create API

创建 API 对象时就会用到前面提到的 GVK 了,这里我们创建一个名为 Application 的 CRD 对象,具体如下:

kubebuilder create api --group myjob --version v1beta1 --kind MyJob

–namespaced=true指定 CRD 的 scope,默认是 namespaced,如果是集群范围的 CRD 在创建时这里需要设置为 false。

.
├── Dockerfile
├── Makefile
├── PROJECT
├── README.md
├── api
│   └── v1beta1
│       ├── groupversion_info.go
│       ├── myjob_types.go
│       └── zz_generated.deepcopy.go
├── bin
│   └── controller-gen-v0.14.0
├── cmd
│   └── main.go
├── config
│   ├── crd
│   │   ├── kustomization.yaml
│   │   └── kustomizeconfig.yaml
│   ├── default
│   │   ├── kustomization.yaml
│   │   ├── manager_auth_proxy_patch.yaml
│   │   └── manager_config_patch.yaml
│   ├── manager
│   │   ├── kustomization.yaml
│   │   └── manager.yaml
│   ├── prometheus
│   │   ├── kustomization.yaml
│   │   └── monitor.yaml
│   ├── rbac
│   │   ├── auth_proxy_client_clusterrole.yaml
│   │   ├── auth_proxy_role.yaml
│   │   ├── auth_proxy_role_binding.yaml
│   │   ├── auth_proxy_service.yaml
│   │   ├── kustomization.yaml
│   │   ├── leader_election_role.yaml
│   │   ├── leader_election_role_binding.yaml
│   │   ├── myjob_editor_role.yaml
│   │   ├── myjob_viewer_role.yaml
│   │   ├── role.yaml
│   │   ├── role_binding.yaml
│   │   └── service_account.yaml
│   └── samples
│       ├── kustomization.yaml
│       └── myjob_v1beta1_myjob.yaml
├── go.mod
├── go.sum
├── hack
│   └── boilerplate.go.txt
├── internal
│   └── controller
│       ├── myjob_controller.go
│       ├── myjob_controller_test.go
│       └── suite_test.go
└── test
    ├── e2e
    │   ├── e2e_suite_test.go
    │   └── e2e_test.go
    └── utils
        └── utils.go

具体如下:

  1. 新增了 /api/v1 目录
  2. 新增 /bin 目录
  3. config 目录下新增 /config/crd 和 /config/samples
  4. 新增 /internal/controllers 目录
    API 目录下就是我们的 CRD 对象, /internal/controllers 目录下则是 Controller 了,接下来我们要做的就是完善 CRD 并实现 Controller。

修改 CRD types文件

package v1beta1

import (
    v1 "k8s.io/api/core/v1"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const (
    // myjob 刚创建的时候默认状态
    MyJobPending = "pending"
    // myjob 管理的 pod 创建后对应的状态
    MyJobRunning = "running"
    // myjob 管理的 pod 执行完成后对应的状态
    MyJobCompleted = "completed"
)

type MyJobSpec struct {
    Template v1.PodTemplateSpec `json:"template" protobuf:"bytes,6,opt,name=template"`
}

type MyJobStatus struct {
    Phase string `json:"phase,omitempty"`
}

type MyJob struct {
    metav1.TypeMeta   `json:",inline"`
    metav1.ObjectMeta `json:"metadata,omitempty"`

    Spec   MyJobSpec   `json:"spec,omitempty"`
    Status MyJobStatus `json:"status,omitempty"`
}

type MyJobList struct {
    metav1.TypeMeta `json:",inline"`
    metav1.ListMeta `json:"metadata,omitempty"`
    Items           []MyJob `json:"items"`
}

func (j *MyJobStatus) SetDefault(job *MyJob) bool {
    changed := false

    if job.Status.Phase == "" {
        job.Status.Phase = MyJobPending
        changed = true
    }

    return changed
}

func (j *MyJob) StatusSetDefault() bool {
    return j.Status.SetDefault(j)
}

func init() {
    SchemeBuilder.Register(&MyJob{}, &MyJobList{})
}

但是还没有为该资源生成对应的CRD yaml文件。执行 make manifests 命令就可以生成

kevin@Kevin:~/kube/new$ make manifests
/home/kevin/kube/new/bin/controller-gen-v0.14.0 rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases

通过运行 make manifests,可以确保生成的 Kubernetes 资源清单文件,与代码中定义的资源规范保持同步。所以以后但凡更改了 api/v1/guestbook_types.go,就一定要执行一下 make manifests

修改 CRD Controller 文件

package controller

import (
	"context"

	"github.com/go-logr/logr"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
	"sigs.k8s.io/controller-runtime/pkg/handler"
	"sigs.k8s.io/controller-runtime/pkg/source"

	myjobv1beta1 "github.com/sky-big/myjob-operator/api/v1beta1"
)

// MyJobReconciler reconciles a MyJob object
type MyJobReconciler struct {
	client.Client
	Log    logr.Logger
	Scheme *runtime.Scheme
}

//+kubebuilder:rbac:groups=myjob.github.com,resources=myjobs,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=myjob.github.com,resources=myjobs/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=myjob.github.com,resources=myjobs/finalizers,verbs=update

func (r *MyJobReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	logger := r.Log.WithValues("myjob", req.NamespacedName)

	// 初始化一个指向 MyJob 对象的指针,用于后续存储从 Kubernetes API 获取的 MyJob 实例
	j := &myjobv1beta1.MyJob{}
	// r.Get(ctx, req.NamespacedName, j) 方法是根据 req.NamespacedName 中明确指定的命名空间(namespace)和资源名称(name)来精准定位并获取唯一的一个资源对象
	// 如果获取过程中出现错误,使用 client.IgnoreNotFound 忽略 "对象未找到" 错误
	// 并返回一个空的 ctrl.Result 和错误信息(如果不是 "对象未找到" 错误)
	if err := r.Get(ctx, req.NamespacedName, j); err != nil {
		return ctrl.Result{}, client.IgnoreNotFound(err)
	}

	// 在创建 MyJob 对象时用到的
	// 调用 MyJob 对象的 StatusSetDefault 方法,设置其状态的默认值
	// 如果状态发生了改变,该方法会返回 true
	if j.StatusSetDefault() {
		// 只有status值为空的时候,会进入这里
		// 如果状态发生了改变,使用客户端更新 MyJob 对象的状态
		if err := r.Status().Update(ctx, j); err != nil {
			// 如果更新状态时出现错误,返回一个空的 ctrl.Result 和错误信息
			return ctrl.Result{}, err
		}
		// 如果状态更新成功,返回一个带有 Requeue 标志为 true 的 ctrl.Result
		// 表示需要重新将该请求加入处理队列,以便再次检查状态
		return ctrl.Result{Requeue: true}, nil
	}

	// 初始化一个指向 Pod 对象的指针,用于后续存储从 Kubernetes API 获取的 Pod 实例
	p := &corev1.Pod{}
	// 使用客户端从 Kubernetes API 中获取指定命名空间和名称的 Pod 对象
	err := r.Get(ctx, req.NamespacedName, p)
	// 如果获取 Pod 对象时没有出现错误,说明 Pod 已经存在
	if err == nil {
		// MyJob 的状态还是 Pending,但是对应的 Pod 已经创建,则将 MyJob 的状态置为 Running
		if !isPodCompleted(p) && myjobv1beta1.MyJobRunning != j.Status.Phase {
			j.Status.Phase = myjobv1beta1.MyJobRunning
			// 使用客户端更新 MyJob 对象的状态
			if err := r.Status().Update(ctx, j); err != nil {
				return ctrl.Result{}, err
			}
			logger.Info("myjob phase changed", "Phase", myjobv1beta1.MyJobRunning)
		}
		// MyJob 对应的 Pod 已经执行完毕(即成功或失败),则将 MyJob 的状态置为 Completed
		if isPodCompleted(p) && myjobv1beta1.MyJobRunning == j.Status.Phase {
			j.Status.Phase = myjobv1beta1.MyJobCompleted
			// 使用客户端更新 MyJob 对象的状态
			if err := r.Status().Update(ctx, j); err != nil {
				return ctrl.Result{}, err
			}
			logger.Info("myjob phase changed", "Phase", myjobv1beta1.MyJobCompleted)
		}
		// 如果获取 Pod 对象时出现错误,并且错误类型是 "对象未找到"
	} else if err != nil && errors.IsNotFound(err) {
		// 调用 makePodByMyJob 函数,根据 MyJob 对象的信息创建一个新的 Pod 对象
		pod := makePodByMyJob(j)
		// 设置 Pod 对象的所有者引用为当前的 MyJob 对象
		// 这样可以确保 Pod 对象与 MyJob 对象关联起来,当 MyJob 对象被删除时,Pod 对象也会被自动删除
		if err := controllerutil.SetControllerReference(j, pod, r.Scheme); err != nil {
			return ctrl.Result{}, err
		}
		// 使用客户端在 Kubernetes 集群中创建新的 Pod 对象
		if err := r.Create(ctx, pod); err != nil && !errors.IsAlreadyExists(err) {
			return ctrl.Result{}, err
		}
		logger.Info("myjob create pod success")
	} else {
		return ctrl.Result{}, err
	}

	// 如果所有操作都成功完成,返回一个空的 ctrl.Result 和 nil 错误信息
	return ctrl.Result{}, nil
}

func (r *MyJobReconciler) SetupWithManager(mgr ctrl.Manager) error {
	c := ctrl.NewControllerManagedBy(mgr)

	// 监视拥有者是 MyJob 类型的 Pod,同时将 Pod 的拥有者 MyJob 扔进处理队列中,对 MyJob 进行调和
	c.Watches(&source.Kind{Type: &corev1.Pod{}}, &handler.EnqueueRequestForOwner{
		IsController: true,
		OwnerType:    &myjobv1beta1.MyJob{},
	})
	return c.For(&myjobv1beta1.MyJob{}).Complete(r)
}

func isPodCompleted(pod *corev1.Pod) bool {
	if corev1.PodSucceeded == pod.Status.Phase ||
		corev1.PodFailed == pod.Status.Phase ||
		pod.DeletionTimestamp != nil {
		return true
	}
	return false
}

func makePodByMyJob(j *myjobv1beta1.MyJob) *corev1.Pod {
	return &corev1.Pod{
		ObjectMeta: metav1.ObjectMeta{
			Name:      j.Name,
			Namespace: j.Namespace,
		},
		Spec: *j.Spec.Template.Spec.DeepCopy(),
	}
}

// 返回所有的CR
func listMyJobsInNamespace(r client.Client, namespace string) (*myjobv1beta1.MyJobList, error) {
	ctx := context.Background()
	myJobList := &myjobv1beta1.MyJobList{}
	listOptions := client.ListOptions{
		Namespace: namespace,
	}
	err := r.List(ctx, myJobList, &listOptions)
	if err != nil {
		return nil, err
	}
	return myJobList, nil
}

后续操作步骤

重新生成资源清单

Kind 的 types 结构文件编写好,并且 Controller 开发完毕后,再次执行以下命令重新生成资源清单:

make manifests

通过运行此命令,可以确保生成的 Kubernetes 资源清单文件与代码中定义的资源规范保持同步。以后但凡更改了 api/v1beta1/myjob_types.go 文件,就一定要执行一下 make manifests

安装 CRD 到 Kubernetes 集群

将生成的 CRD 安装到当前的 Kubernetes 集群中,执行以下命令:

make install

此命令会将自定义资源定义(CRD)应用到集群中,使得 Kubernetes 集群能够识别该自定义资源。

前台运行 Controller

如果想要快速查看编写的 Controller 效果,验证自己的代码,可以先在前台运行 Controller,执行以下命令:

make run

执行该命令后,Controller 会在前台启动并开始监控和处理定义的自定义资源(CR)。当有 CR 被创建、修改或删除时,Controller 会根据其逻辑进行相应的操作,并更新 CR 的状态信息。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值