kubebuilder实现一个简单的管理nginx镜像的示例

config下的yml

apiVersion: mygroup.my.domain/v1
kind: MyApp
metadata:
  labels:
    app.kubernetes.io/name: kubebuild
    app.kubernetes.io/managed-by: kustomize
  name: myapp-sample
spec:
  image: nginx:1.25    #镜像
  replicas: 3          #副本数
  port: 80             #services的端口

types

/*
Copyright 2025.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1

import (
	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.

// MyAppSpec defines the desired state of MyApp.
type MyAppSpec struct {
	// Nginx 镜像名
	Image string `json:"image,omitempty"`
	// 副本数
	Replicas *int32 `json:"replicas,omitempty"`
	// 暴露端口
	Port int32 `json:"port,omitempty"`
}

type MyAppCondition struct {
	// 类型,如 Available、Progressing、Degraded
	Type string `json:"type"`
	// 状态,True/False/Unknown
	Status string `json:"status"`
	// 原因
	Reason string `json:"reason,omitempty"`
	// 详细信息
	Message string `json:"message,omitempty"`
	// 最后变更时间
	LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty"`
}

// MyAppStatus defines the observed state of MyApp.
type MyAppStatus struct {
	// 当前阶段,如 Pending、Running、Failed
	Phase string `json:"phase,omitempty"`
	// 详细状态条件
	Conditions []MyAppCondition `json:"conditions,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{})
}

controller

/*
Copyright 2025.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package controller

import (
	"context"

	appsv1 "k8s.io/api/apps/v1"
	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"
	intstr "k8s.io/apimachinery/pkg/util/intstr"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	logf "sigs.k8s.io/controller-runtime/pkg/log"

	mygroupv1 "kubebuild/api/v1"
)

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

// +kubebuilder:rbac:groups=mygroup.my.domain,resources=myapps,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=mygroup.my.domain,resources=myapps/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=mygroup.my.domain,resources=myapps/finalizers,verbs=update

// 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.21.0/pkg/reconcile
func (r *MyAppReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	log := logf.FromContext(ctx)

	// 1. 获取 MyApp 实例
	var myapp mygroupv1.MyApp
	if err := r.Get(ctx, req.NamespacedName, &myapp); err != nil {
		if errors.IsNotFound(err) {
			// 资源已删除,无需处理
			return ctrl.Result{}, nil
		}
		return ctrl.Result{}, err
	}

	// 2. 构建期望的 Deployment
	replicas := int32(1)
	if myapp.Spec.Replicas != nil {
		replicas = *myapp.Spec.Replicas
	}
	labels := map[string]string{
		"app": myapp.Name,
	}
	deploy := &appsv1.Deployment{
		ObjectMeta: metav1.ObjectMeta{
			Name:      myapp.Name + "-nginx",
			Namespace: myapp.Namespace,
			OwnerReferences: []metav1.OwnerReference{
				*metav1.NewControllerRef(&myapp, myapp.GroupVersionKind()),
			},
		},
		Spec: appsv1.DeploymentSpec{
			Replicas: &replicas,
			Selector: &metav1.LabelSelector{
				MatchLabels: labels,
			},
			Template: corev1.PodTemplateSpec{
				ObjectMeta: metav1.ObjectMeta{
					Labels: labels,
				},
				Spec: corev1.PodSpec{
					Containers: []corev1.Container{
						{
							Name:  "nginx",
							Image: myapp.Spec.Image,
							Ports: []corev1.ContainerPort{
								{
									ContainerPort: myapp.Spec.Port,
								},
							},
						},
					},
				},
			},
		},
	}

	// 3. 检查并创建/更新 Deployment
	var found appsv1.Deployment
	err := r.Get(ctx, client.ObjectKey{Name: deploy.Name, Namespace: deploy.Namespace}, &found)
	if err != nil && errors.IsNotFound(err) {
		if err := r.Create(ctx, deploy); err != nil {
			log.Error(err, "创建 Deployment 失败")
			return ctrl.Result{}, err
		}
	} else if err == nil {
		// 若参数有变更则更新
		needUpdate := false
		if *found.Spec.Replicas != replicas ||
			found.Spec.Template.Spec.Containers[0].Image != myapp.Spec.Image ||
			len(found.Spec.Template.Spec.Containers[0].Ports) == 0 ||
			found.Spec.Template.Spec.Containers[0].Ports[0].ContainerPort != myapp.Spec.Port {
			found.Spec.Replicas = &replicas
			found.Spec.Template.Spec.Containers[0].Image = myapp.Spec.Image
			found.Spec.Template.Spec.Containers[0].Ports = []corev1.ContainerPort{
				{ContainerPort: myapp.Spec.Port},
			}
			needUpdate = true
		}
		if needUpdate {
			if err := r.Update(ctx, &found); err != nil {
				log.Error(err, "更新 Deployment 失败")
				return ctrl.Result{}, err
			}
		}
	} else {
		log.Error(err, "获取 Deployment 失败")
		return ctrl.Result{}, err
	}

	// 4. 构建期望的 Service
	svc := &corev1.Service{
		ObjectMeta: metav1.ObjectMeta{
			Name:      myapp.Name + "-nginx",
			Namespace: myapp.Namespace,
			OwnerReferences: []metav1.OwnerReference{
				*metav1.NewControllerRef(&myapp, myapp.GroupVersionKind()),
			},
		},
		Spec: corev1.ServiceSpec{
			Selector: labels,
			Ports: []corev1.ServicePort{
				{
					Port:       myapp.Spec.Port,
					TargetPort: intstrFromInt32(myapp.Spec.Port),
				},
			},
			Type: corev1.ServiceTypeClusterIP,
		},
	}

	// 5. 检查并创建/更新 Service
	var foundSvc corev1.Service
	err = r.Get(ctx, client.ObjectKey{Name: svc.Name, Namespace: svc.Namespace}, &foundSvc)
	if err != nil && errors.IsNotFound(err) {
		if err := r.Create(ctx, svc); err != nil {
			log.Error(err, "创建 Service 失败")
			return ctrl.Result{}, err
		}
	} else if err == nil {
		// 若端口有变更则更新
		needUpdate := false
		if len(foundSvc.Spec.Ports) == 0 ||
			foundSvc.Spec.Ports[0].Port != myapp.Spec.Port {
			foundSvc.Spec.Ports = []corev1.ServicePort{
				{
					Port:       myapp.Spec.Port,
					TargetPort: intstrFromInt32(myapp.Spec.Port),
				},
			}
			needUpdate = true
		}
		if needUpdate {
			if err := r.Update(ctx, &foundSvc); err != nil {
				log.Error(err, "更新 Service 失败")
				return ctrl.Result{}, err
			}
		}
	} else {
		log.Error(err, "获取 Service 失败")
		return ctrl.Result{}, err
	}

	return ctrl.Result{}, nil
}

// intstrFromInt32 用于将 int32 转为 IntOrString
func intstrFromInt32(i int32) intstr.IntOrString {
	return intstr.IntOrString{Type: intstr.Int, IntVal: i}
}

// SetupWithManager sets up the controller with the Manager.
func (r *MyAppReconciler) SetupWithManager(mgr ctrl.Manager) error {
	return ctrl.NewControllerManagedBy(mgr).
		For(&mygroupv1.MyApp{}).
		Named("myapp").
		Complete(r)
}
posted @ 2025-07-09 15:21  朝阳1  阅读(8)  评论(0)    收藏  举报