【k8s】kubernetes编写自己的operator(operator-sdk:v1.xxx)

关于operator-sdk怎么安装,请参考官方文档:https://sdk.operatorframework.io/docs/installation/install-operator-sdk/

1. 创建operator新项目

1)初始化项目,注意,一定要初始化go mod,否则operator-sdk无法识别项目依赖,且以下命令均在空文件夹hello-world-operator下执行:
在这里插入图片描述
2)创建api和控制器,创建HelloWorldManager的自定义资源类型:
在这里插入图片描述
以上命令执行成功后,可观察到项目目录中创建出了api/v1和controllers文件夹,后面我们的开发主要基于这两个文件夹中的go文件:
在这里插入图片描述

2. 定义crd数据结构

1)在xxx_types中定义HelloWorldManager的数据结构,需要什么定义什么即可:
在这里插入图片描述
代码如下:

package v1

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

// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!
// NOTE: json tags are required.  Any new fields you add must have json tags for the fields to be serialized.

// HelloWorldManagerSpec defines the desired state of HelloWorldManager
type HelloWorldManagerSpec struct {
    
    
	// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
	// Important: Run "make" to regenerate code after modifying this file

	Size      *int32                      `json:"size"`
	Image     string                      `json:"image"`
	Resources corev1.ResourceRequirements `json:"resources,omitempty"`
	Envs      []corev1.EnvVar             `json:"envs,omitempty"`
	Ports     []corev1.ServicePort        `json:"ports,omitempty"`
}

// HelloWorldManagerStatus defines the observed state of HelloWorldManager
type HelloWorldManagerStatus struct {
    
    
	// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
	// Important: Run "make" to regenerate code after modifying this file
	appsv1.DeploymentStatus `json:",inline"`
}

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

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

	Spec   HelloWorldManagerSpec   `json:"spec,omitempty"`
	Status HelloWorldManagerStatus `json:"status,omitempty"`
}

// +kubebuilder:object:root=true

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

func init() {
    
    
	SchemeBuilder.Register(&HelloWorldManager{
    
    }, &HelloWorldManagerList{
    
    })
}

2)定义完成后,执行make generate,该命令为更新api/v1/xxxdeepcopy.go文件,sdk会自动帮助我们生成实现了runtime.Object接口的代码。
在这里插入图片描述
3)执行make manifests生成crd清单,该清单用于在k8s集群中注册我们的自定义资源类型:
在这里插入图片描述

3. 编写controller,完成自己的业务逻辑

我们所有的业务逻辑在controllers下的xxx_controller.go下的Reconcile方法完成即可,该方法可以监听资源状态的变化。
在这里插入图片描述
下面代码的主要逻辑为判断资源是否存在,不存在则创建,存在则更新为最新的(并没有真正更新,而是输出了一段话,伙伴们可以填充自己的逻辑):

package controllers

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/go-logr/logr"
	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	v1 "k8s.io/api/rbac/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"reflect"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"

	csdnv1 "csdn.net/api/v1"
)

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

// +kubebuilder:rbac:groups=csdn.csdn.net,resources=helloworldmanagers,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=csdn.csdn.net,resources=helloworldmanagers/status,verbs=get;update;patch

func (r *HelloWorldManagerReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
    
    
	_ = context.Background()
	_ = r.Log.WithValues("helloworldmanager", req.NamespacedName)

	// your logic here
	helloWorldManagerInstance := &csdnv1.HelloWorldManager{
    
    }
	err := r.Client.Get(context.TODO(), req.NamespacedName, helloWorldManagerInstance)
	r.Log.WithValues("k8s client error:", err)
	if err != nil {
    
    
		if errors.IsNotFound(err) {
    
    
			return ctrl.Result{
    
    }, nil
		}
		return ctrl.Result{
    
    }, err
	}

	if helloWorldManagerInstance.DeletionTimestamp != nil {
    
    
		return ctrl.Result{
    
    }, err
	}

	deployment := &appsv1.Deployment{
    
    }
	if err := r.Client.Get(context.TODO(), req.NamespacedName, deployment); err != nil && errors.IsNotFound(err) {
    
    
		// 创建关联资源
		// 1. 创建 Deploy
		deploy := NewDeploy(helloWorldManagerInstance)
		if err := r.Client.Create(context.TODO(), deploy); err != nil {
    
    
			return ctrl.Result{
    
    }, err
		}
		// 2. 创建 Service
		service := NewService(helloWorldManagerInstance)
		if err := r.Client.Create(context.TODO(), service); err != nil {
    
    
			return ctrl.Result{
    
    }, err
		}
		// 3. 关联 Annotations
		data, _ := json.Marshal(helloWorldManagerInstance.Spec)
		if helloWorldManagerInstance.Annotations != nil {
    
    
			helloWorldManagerInstance.Annotations["spec"] = string(data)
		} else {
    
    
			helloWorldManagerInstance.Annotations = map[string]string{
    
    "spec": string(data)}
		}

		if err := r.Client.Update(context.TODO(), helloWorldManagerInstance); err != nil {
    
    
			return ctrl.Result{
    
    }, nil
		}
		return ctrl.Result{
    
    }, nil
	}

	oldspec := &csdnv1.HelloWorldManagerSpec{
    
    }
	fmt.Printf("-----%v----\n", helloWorldManagerInstance.Annotations["spec"])
	if err := json.Unmarshal([]byte(helloWorldManagerInstance.Annotations["spec"]), oldspec); err != nil {
    
    
		return ctrl.Result{
    
    }, err
	}
	fmt.Printf("-----副本数量:%v----\n", *helloWorldManagerInstance.Spec.Size)
	if !reflect.DeepEqual(helloWorldManagerInstance.Spec, oldspec) {
    
    
		// 更新关联资源,update deployment和service,TODO
		fmt.Printf("-----%v----\n", "更新资源")

		return ctrl.Result{
    
    }, nil
	}
	return ctrl.Result{
    
    }, nil
}

func (r *HelloWorldManagerReconciler) SetupWithManager(mgr ctrl.Manager) error {
    
    
	return ctrl.NewControllerManagedBy(mgr).
		For(&csdnv1.HelloWorldManager{
    
    }).
		Complete(r)
}

func NewDeploy(app *csdnv1.HelloWorldManager) *appsv1.Deployment {
    
    
	labels := map[string]string{
    
    "app": app.Name}
	selector := &metav1.LabelSelector{
    
    MatchLabels: labels}
	return &appsv1.Deployment{
    
    
		TypeMeta: metav1.TypeMeta{
    
    
			APIVersion: "apps/v1",
			Kind:       "Deployment",
		},
		ObjectMeta: metav1.ObjectMeta{
    
    
			Name:      app.Name,
			Namespace: app.Namespace,

			OwnerReferences: []metav1.OwnerReference{
    
    
				*metav1.NewControllerRef(app, schema.GroupVersionKind{
    
    
					Group:   v1.SchemeGroupVersion.Group,
					Version: v1.SchemeGroupVersion.Version,
					Kind:    "HelloWorldManager",
				}),
			},
		},
		Spec: appsv1.DeploymentSpec{
    
    
			Replicas: app.Spec.Size,
			Template: corev1.PodTemplateSpec{
    
    
				ObjectMeta: metav1.ObjectMeta{
    
    
					Labels: labels,
				},
				Spec: corev1.PodSpec{
    
    
					Containers: newContainers(app),
				},
			},
			Selector: selector,
		},
	}
}

func newContainers(app *csdnv1.HelloWorldManager) []corev1.Container {
    
    
	containerPorts := []corev1.ContainerPort{
    
    }
	for _, svcPort := range app.Spec.Ports {
    
    
		cport := corev1.ContainerPort{
    
    }
		cport.ContainerPort = svcPort.TargetPort.IntVal
		containerPorts = append(containerPorts, cport)
	}
	return []corev1.Container{
    
    
		{
    
    
			Name:            app.Name,
			Image:           app.Spec.Image,
			Resources:       app.Spec.Resources,
			Ports:           containerPorts,
			ImagePullPolicy: corev1.PullIfNotPresent,
			Env:             app.Spec.Envs,
		},
	}
}

func NewService(app *csdnv1.HelloWorldManager) *corev1.Service {
    
    
	return &corev1.Service{
    
    
		TypeMeta: metav1.TypeMeta{
    
    
			Kind:       "Service",
			APIVersion: "v1",
		},
		ObjectMeta: metav1.ObjectMeta{
    
    
			Name:      app.Name,
			Namespace: app.Namespace,
			OwnerReferences: []metav1.OwnerReference{
    
    
				*metav1.NewControllerRef(app, schema.GroupVersionKind{
    
    
					Group:   v1.SchemeGroupVersion.Group,
					Version: v1.SchemeGroupVersion.Version,
					Kind:    "HelloWorldManager",
				}),
			},
		},
		Spec: corev1.ServiceSpec{
    
    
			Type:  corev1.ServiceTypeNodePort,
			Ports: app.Spec.Ports,
			Selector: map[string]string{
    
    
				"app": app.Name,
			},
		},
	}
}

4. 本地调试

1)注册自定义资源,在2.3中我们生成了资源清单,该资源清单位于config/crd/bases/xxx.yaml,我们只需在k8s中创建该资源即可:
在这里插入图片描述
2)在编辑器中运行我们的operator:
在这里插入图片描述
3)接下来我们创建自定义资源并观察operator是否完成了我们的业务逻辑,编写自定义资源yaml,符合上面我们自己定义的数据结构即可,如下示例:
在这里插入图片描述
创建并查看自定义资源,发现operator成功帮我们将服务自动跑了起来:
在这里插入图片描述
同时我们也看到operator成功监听到了本次资源创建操作:
在这里插入图片描述
浏览器访问node公网ip+端口验证:
在这里插入图片描述
我们也可以更改下自定义资源的副本数量,查看operator是否监听到资源的变化:
在这里插入图片描述
operator成功监听到了资源的变化:
在这里插入图片描述

5. 部署

1)编译并推送镜像:
在这里插入图片描述
在这里插入图片描述
也可以使用operator官方已经写好的脚本命令,其实质上也是调用了docker:

make docker-build docker-push IMG=<some-registry>/<project-name>:<tag>

查看镜像推送成功:
在这里插入图片描述
2)为k8s安装crd资源
在这里插入图片描述
3)部署operator
在这里插入图片描述
4)创建crd资源
首先确认crd是否已经注册:
在这里插入图片描述
创建crd资源:
在这里插入图片描述
5)验证
发现我们的operator已经成功为我们创建了资源:
在这里插入图片描述
在这里插入图片描述
也可访问node公网ip+端口访问服务进行验证。

6. 删除operator及crd资源

1)删除自定义资源
在这里插入图片描述
2)卸载operator
在这里插入图片描述
可视化操作或执行make uninstall即可。

参考文献:

  1. https://sdk.operatorframework.io/docs/building-operators/golang/tutorial/
  2. https://www.jianshu.com/p/628aac3e6758

猜你喜欢

转载自blog.csdn.net/m0_38075425/article/details/108498545
今日推荐