Kubebuilder安装步骤
一、下载可执行文件
- 首先,访问github上的Kubebuilder发行版本页面:https://github.com/kubernetes-sigs/kubebuilder/releases 。
- 例如,若要安装3.14.0版本,点击进入对应的版本链接:https://github.com/kubernetes-sigs/kubebuilder/releases/tag/v3.14.0 。
- 在该页面最下方的“Assets”区域中,找到与你所使用的操作系统(OS)和架构(Arch)相匹配的安装包,然后点击进行下载。比如,若你使用的是基于amd架构的Linux系统,就下载相应的安装包。
二、上传或直接下载到Linux系统
- 若已在本地下载了安装包,需将其上传到你的Linux系统中。
- 若你的Linux系统网络连接正常,也可以直接在Linux系统中执行以下命令进行下载:
wget https://github.com/kubernetes-sigs/kubebuilder/releases/download/v3.14.0/kubebuilder_linux_amd64
三、安装操作
- 下载完成后,执行以下命令对文件进行重命名:
mv kubebuilder_linux_amd64 kubebuilder
- 为文件赋予可执行权限,并将其移动到系统的可执行文件目录中:
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
- 安装CRD:将CRD应用到集群中,使kubernetes集群能够识别到该资源
- 部署Controller:Controller 通常会在control plane之外运行,就像运行任何容器化的应用程序一样。例如,将controller打包成镜像,以 Deployment 运行。
创建和管理自定义资源实例
- 使用 kubectl 或 编写yaml 创建CR的实例
- 使用 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
具体如下:
- 新增了 /api/v1 目录
- 新增 /bin 目录
- config 目录下新增 /config/crd 和 /config/samples
- 新增 /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 的状态信息。