WEBKT

Kubebuilder快速上手:手把手教你创建Kubernetes Operator

149 0 0 0

Kubebuilder快速上手:手把手教你创建Kubernetes Operator

Kubernetes Operator 是一种扩展 Kubernetes API 的方式,它允许你像管理内置资源一样管理应用程序。Kubebuilder 是一个用于构建 Kubernetes Operator 的 SDK,它提供了一套工具和库,可以帮助你快速地创建、测试和部署 Operator。本文将带你一步步使用 Kubebuilder 创建一个简单的 Operator,并实现对 CRD 资源的管理自动化。

1. 准备工作

在开始之前,你需要确保满足以下条件:

  • 安装 Go:Kubebuilder 是用 Go 编写的,因此你需要安装 Go 语言环境。
  • 安装 Docker:你需要 Docker 来构建 Operator 的镜像。
  • 安装 Kubectl:你需要 Kubectl 来与 Kubernetes 集群交互。
  • 安装 Kubebuilder:按照 Kubebuilder 官方文档进行安装:https://kubebuilder.io/docs/installation
  • 连接到 Kubernetes 集群:确保你的 Kubectl 配置正确,并且可以连接到 Kubernetes 集群。

2. 创建项目

首先,使用 Kubebuilder 创建一个新的项目:

mkdir my-operator
cd my-operator
kubebuilder init --domain example.com --owner "Your Name"
  • --domain example.com:指定你的域名,用于生成 API 组名。请替换为你自己的域名。
  • --owner "Your Name":指定 Operator 的所有者。

3. 定义 CRD

接下来,我们需要定义一个 CRD(Custom Resource Definition),它描述了我们想要管理的自定义资源。使用 Kubebuilder 创建一个 API:

kubebuilder create api --group apps --version v1alpha1 --kind MyApp
  • --group apps:指定 API 组名。
  • --version v1alpha1:指定 API 版本。
  • --kind MyApp:指定自定义资源的 Kind(类型)。

执行上述命令后,Kubebuilder 会生成以下文件:

  • api/v1alpha1/myapp_types.go:定义 MyApp 资源的结构体。
  • controllers/myapp_controller.go:定义 MyApp 资源的控制器逻辑。

4. 修改 CRD 定义

打开 api/v1alpha1/myapp_types.go 文件,修改 MyApp 资源的结构体,添加你需要的字段。例如,我们可以添加一个 Spec 字段来描述 MyApp 的规格,以及一个 Status 字段来描述 MyApp 的状态:

package v1alpha1

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

// MyAppSpec defines the desired state of MyApp
type MyAppSpec struct {
    // Replicas is the desired number of replicas
    Replicas *int32 `json:"replicas,omitempty"`
    // Image is the image to use for the deployment
    Image string `json:"image,omitempty"`
}

// MyAppStatus defines the observed state of MyApp
type MyAppStatus struct {
    // ReadyReplicas is the number of ready replicas
    ReadyReplicas int32 `json:"readyReplicas,omitempty"`
}

//+kubebuilder:object:root=true
//+kubebuilder:subresource:status

// MyApp is the Schema for the myapps API
type MyApp struct {
    metav1.TypeMeta   `json:",inline"`
    metav1.ObjectMeta `json:"metadata,omitempty"`

    Spec   MyAppSpec   `json:"spec,omitempty"`
    Status MyAppStatus `json:"status,omitempty"`
}

//+kubebuilder:object:root=true

// MyAppList contains a list of MyApp
type MyAppList struct {
    metav1.TypeMeta `json:",inline"`
    metav1.ListMeta `json:"metadata,omitempty"`
    Items           []MyApp `json:"items"`
}

func init() {
    SchemeBuilder.Register(&MyApp{}, &MyAppList{})
}

注意:

  • //+kubebuilder:object:root=true:这个标记告诉 Kubebuilder 这个类型是一个 CRD。
  • //+kubebuilder:subresource:status:这个标记告诉 Kubebuilder 这个类型有一个状态子资源,用于存储资源的状态信息。
  • json:"replicas,omitempty"omitempty 选项表示如果该字段为空,则在序列化为 JSON 时忽略该字段。

修改完 CRD 定义后,需要运行以下命令来更新 CRD:

make manifests

5. 实现控制器逻辑

打开 controllers/myapp_controller.go 文件,实现控制器逻辑。控制器负责监听 MyApp 资源的创建、更新和删除事件,并根据事件执行相应的操作。例如,我们可以创建一个 Deployment 来运行 MyApp:

package controllers

import (
    "context"


    appsv1 "k8s.io/api/apps/v1"
    corev1 "k8s.io/api/core/v1"
    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/log"

    appsv1alpha1 "example.com/my-operator/api/v1alpha1"
)

// MyAppReconciler reconciles a MyApp object
type MyAppReconciler struct {
    client.Client
    Scheme *runtime.Scheme
}

//+kubebuilder:rbac:groups=apps.example.com,resources=myapps,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=apps.example.com,resources=myapps/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=apps.example.com,resources=myapps/finalizers,verbs=update
//+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch

// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
// TODO(user): Modify the Reconcile function to compare the state specified by
// the MyApp object against the actual cluster state, and then
// perform operations to make the cluster state reflect the state specified by
// the user.
//
// For more details, check Reconcile and its Result here:
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.14.1/pkg/reconcile
func (r *MyAppReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    log := log.FromContext(ctx)

    // TODO(user): your logic here
    var myApp appsv1alpha1.MyApp
    err := r.Get(ctx, req.NamespacedName, &myApp)
    if err != nil {
        log.Error(err, "unable to fetch MyApp")
        // we'll ignore not-found errors, since they can't be fixed by an immediate
        // requeue (we'll need to wait for a new notification), and we can get them
        // on deleted requests.
        return ctrl.Result{}, client.IgnoreNotFound(err)
    }

    // Check if the deployment already exists, if not create a new one
    var deployment appsv1.Deployment
    err = r.Get(ctx, client.ObjectKey{Namespace: myApp.Namespace, Name: myApp.Name}, &deployment)
    if err != nil && client.IgnoreNotFound(err) == nil {
        // Define a new deployment
        deploy := r.deploymentForMyApp(&myApp)
        log.Info("Creating a new Deployment", "Deployment.Namespace", deploy.Namespace, "Deployment.Name", deploy.Name)
        err = r.Create(ctx, deploy)
        if err != nil {
            log.Error(err, "Failed to create new Deployment", "Deployment.Namespace", deploy.Namespace, "Deployment.Name", deploy.Name)
            return ctrl.Result{}, err
        }

        // Deployment created successfully - return and requeue
        return ctrl.Result{Requeue: true}, nil
    } else if err != nil {
        log.Error(err, "Failed to get Deployment")
        return ctrl.Result{}, err
    }

    // Ensure the deployment size is the same as the spec
    replicas := myApp.Spec.Replicas
    if *deployment.Spec.Replicas != *replicas {
        log.Info("Updating Deployment", "Deployment.Namespace", deployment.Namespace, "Deployment.Name", deployment.Name)
        deployment.Spec.Replicas = replicas
        err = r.Update(ctx, &deployment)
        if err != nil {
            log.Error(err, "Failed to update Deployment", "Deployment.Namespace", deployment.Namespace, "Deployment.Name", deployment.Name)
            return ctrl.Result{}, err
        }
        // Spec updated - return and requeue
        return ctrl.Result{Requeue: true}, nil
    }

    // Update the MyApp status with the pod names
    // List the pods for this myapp's deployment
    podList := &corev1.PodList{}
    lbls := map[string]string{
        "app": myApp.Name,
    }
    listOpts := []client.ListOption{
        client.InNamespace(myApp.Namespace),
        client.MatchingLabels(lbls),
    }
    if err = r.List(ctx, podList, listOpts...); err != nil {
        log.Error(err, "Failed to list pods", "MyApp.Namespace", myApp.Namespace, "MyApp.Name", myApp.Name)
        return ctrl.Result{}, err
    }
    podNames := getPodNames(podList.Items)

    // Update status.Nodes if needed
    if !reflect.DeepEqual(podNames, myApp.Status.Nodes) {
        myApp.Status.Nodes = podNames
        err = r.Status().Update(ctx, &myApp)
        if err != nil {
            log.Error(err, "Failed to update MyApp status", "MyApp.Namespace", myApp.Namespace, "MyApp.Name", myApp.Name)
            return ctrl.Result{}, err
        }
    }

    return ctrl.Result{}, nil
}

// deploymentForMyApp returns a myapp Deployment object
func (r *MyAppReconciler) deploymentForMyApp(myApp *appsv1alpha1.MyApp) *appsv1.Deployment {
    ls := map[string]string{
        "app": myApp.Name,
        "controller": myApp.Name,
    }

    replicas := myApp.Spec.Replicas

    dep := &appsv1.Deployment{
        ObjectMeta: metav1.ObjectMeta{
            Name:      myApp.Name,
            Namespace: myApp.Namespace,
            Labels:    ls,
        },
        Spec: appsv1.DeploymentSpec{
            Replicas: replicas,
            Selector: &metav1.LabelSelector{
                MatchLabels: ls,
            },
            Template: corev1.PodTemplateSpec{
                ObjectMeta: metav1.ObjectMeta{
                    Labels: ls,
                },
                Spec: corev1.PodSpec{
                    Containers: []corev1.Container{{
                        Image: myApp.Spec.Image,
                        Name:  "myapp",
                        Ports: []corev1.ContainerPort{{ContainerPort: 8080, Name: "http"}}}},
                },
            },
        },
    }
    // Set MyApp instance as the owner and controller
    ctrl.SetControllerReference(myApp, dep, r.Scheme)
    return dep
}

// getPodNames returns the pod names of the array of pods passed in
func getPodNames(pods []corev1.Pod) []string {
    var podNames []string
    for _, pod := range pods {
        podNames = append(podNames, pod.Name)
    }
    return podNames
}

// SetupWithManager sets up the controller with the Manager.
func (r *MyAppReconciler) SetupWithManager(mgr ctrl.Manager) error {
    return ctrl.NewControllerManagedBy(mgr).For(&appsv1alpha1.MyApp{}).Owns(&appsv1.Deployment{}).Complete(r)
}

注意:

  • //+kubebuilder:rbac:…:这些标记用于声明 Operator 需要的权限。确保你声明了所有需要的权限,否则 Operator 将无法正常工作。
  • Reconcile 函数是控制器的主循环。它负责监听资源事件,并执行相应的操作。
  • ctrl.SetControllerReference 函数用于设置 Deployment 的 OwnerReference。这可以确保当 MyApp 资源被删除时,Deployment 也会被自动删除。

6. 部署 Operator

在部署 Operator 之前,需要先构建 Operator 的镜像:

make docker-build docker-push IMG="your-docker-repo/my-operator:latest"
  • your-docker-repo/my-operator:latest:替换为你自己的 Docker 镜像仓库地址和标签。

然后,运行以下命令来部署 Operator:

make deploy IMG="your-docker-repo/my-operator:latest"

7. 创建 MyApp 资源

创建一个 YAML 文件,例如 config/samples/apps_v1alpha1_myapp.yaml,来定义一个 MyApp 资源:

apiVersion: apps.example.com/v1alpha1
kind: MyApp
metadata:
  name: myapp-sample
spec:
  replicas: 3
  image: nginx:latest

然后,使用 Kubectl 创建 MyApp 资源:

kubectl apply -f config/samples/apps_v1alpha1_myapp.yaml

8. 验证 Operator

使用 Kubectl 验证 Operator 是否正常工作:

kubectl get myapps
kubectl get deployments
kubectl get pods

你应该能够看到 MyApp 资源、Deployment 和 Pod 都被成功创建。

9. 总结

本文介绍了如何使用 Kubebuilder 快速创建一个 Kubernetes Operator,并实现对 CRD 资源的管理自动化。通过本文的学习,你应该能够掌握 Kubebuilder 的基本用法,并能够使用 Kubebuilder 创建自己的 Operator。

进一步学习:

希望本文能够帮助你快速入门 Kubernetes Operator 开发!

Operator小学生 Kubernetes OperatorKubebuilderCRD

评论点评