• Operator开发实例


    开发环境搭建

        1.k8s集群

        2.Golang语言环境

       3.dep工具 Operator SDK是使用的dep工具包(Go语言的依赖管理工具包)
          curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh

         

       4.安装operator-sdk

          git clone https://github.com/operator-framework/operator-sdk

         cd   operator-sdk
         git   checkout   master
         make  tidy

        

         make  install

        

     operator-sdk cli安装调试

         执行make install命令会把生成的operator-sdk二进制可执行文件存放到GOPATH/bin目录下

     operator-sdk cli安装完毕,接下来搭建项目的脚手架

         1.cd /root/gopath/src/github.com/yxh  &&   /root/gopath/bin/operator-sdk new opdemo

           

         

        2.添加API 在项目的根目录下执行  cd  /root/gopath/src/github.com/yxh/opdemo

            /root/gopath/bin/operator-sdk add api --api-version=app.example.com/v1 --kind=AppService

        3.添加控制器

           /root/gopath/bin/operator-sdk add controller --api-version=app.example.com/v1 --kind=AppService

        

        4.整个Operator项目的脚手架就已经搭建完成

           我们主要需要编写的是pkg目录下面的api定义以及对应的controller实现

    package appservice
    
    import (
        "context"
        "encoding/json"
        "reflect"
        "fmt"
        "strconv"
    
        appv1 "github.com/yxh/opdemo/pkg/apis/app/v1"
        "github.com/yxh/opdemo/pkg/resources"
        appsv1 "k8s.io/api/apps/v1"
        corev1 "k8s.io/api/core/v1"
        "k8s.io/apimachinery/pkg/api/errors"
        "k8s.io/apimachinery/pkg/runtime"
        "sigs.k8s.io/controller-runtime/pkg/client"
        "sigs.k8s.io/controller-runtime/pkg/controller"
        "sigs.k8s.io/controller-runtime/pkg/handler"
        "sigs.k8s.io/controller-runtime/pkg/manager"
        "sigs.k8s.io/controller-runtime/pkg/reconcile"
        logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
        "sigs.k8s.io/controller-runtime/pkg/source"
    )
    
    var log = logf.Log.WithName("controller_appservice")
    
    /**
    * USER ACTION REQUIRED: This is a scaffold file intended for the user to modify with their own Controller
    * business logic.  Delete these comments after modifying this file.*
     */
    
    // Add creates a new AppService Controller and adds it to the Manager. The Manager will set fields on the Controller
    // and Start it when the Manager is Started.
    func Add(mgr manager.Manager) error {
        return add(mgr, newReconciler(mgr))
    }
    
    // newReconciler returns a new reconcile.Reconciler
    func newReconciler(mgr manager.Manager) reconcile.Reconciler {
        return &ReconcileAppService{client: mgr.GetClient(), scheme: mgr.GetScheme()}
    }
    
    // add adds a new Controller to mgr with r as the reconcile.Reconciler
    func add(mgr manager.Manager, r reconcile.Reconciler) error {
        // Create a new controller
        c, err := controller.New("appservice-controller", mgr, controller.Options{Reconciler: r})
        if err != nil {
            return err
        }
    
        // Watch for changes to primary resource AppService
        err = c.Watch(&source.Kind{Type: &appv1.AppService{}}, &handler.EnqueueRequestForObject{})
        if err != nil {
            return err
        }
    
        // TODO(user): Modify this to be the types you create that are owned by the primary resource
        // Watch for changes to secondary resource Pods and requeue the owner AppService
        err = c.Watch(&source.Kind{Type: &appsv1.Deployment{}}, &handler.EnqueueRequestForOwner{
            IsController: true,
            OwnerType:    &appv1.AppService{},
        })
        if err != nil {
            return err
        }
    
        return nil
    }
    
    var _ reconcile.Reconciler = &ReconcileAppService{}
    
    // ReconcileAppService reconciles a AppService object
    type ReconcileAppService struct {
        // This client, initialized using mgr.Client() above, is a split client
        // that reads objects from the cache and writes to the apiserver
        client client.Client
        scheme *runtime.Scheme
    }
    
    // Reconcile reads that state of the cluster for a AppService object and makes changes based on the state read
    // and what is in the AppService.Spec
    // TODO(user): Modify this Reconcile function to implement your Controller logic.  This example creates
    // a Pod as an example
    // Note:
    // The Controller will requeue the Request to be processed again if the returned error is non-nil or
    // Result.Requeue is true, otherwise upon completion it will remove the work from the queue.
    func (r *ReconcileAppService) Reconcile(request reconcile.Request) (reconcile.Result, error) {
        reqLogger := log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name)
        reqLogger.Info("开始新的循环处理周期......")
        defer func(){
           reqLogger.Info("循环周期的最后执行步骤defer函数.....")
        }()
    
    
        // Fetch the AppService instance
        instance := &appv1.AppService{}
        reqLogger.Info("开始获取AppService资源.....")
        err := r.client.Get(context.TODO(), request.NamespacedName, instance)
        if err != nil {
            if errors.IsNotFound(err) {
                // Request object not found, could have been deleted after reconcile request.
                // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers.
                // Return and don't requeue
                return reconcile.Result{}, nil
            }
            // Error reading the object - requeue the request.
            reqLogger.Info("获取AppService资源异常....")
            return reconcile.Result{}, err
        }
        reqLogger.Info("结束获取AppService资源.....")
        
        if instance.DeletionTimestamp != nil {
            return reconcile.Result{}, err
        }
    
        // 如果不存在,则创建关联资源
        // 如果存在,判断是否需要更新
        //   如果需要更新,则直接更新
        //   如果不需要更新,则正常返回
        reqLogger.Info("开始获取deployment资源.....")
        deploy := &appsv1.Deployment{}
        if err := r.client.Get(context.TODO(), request.NamespacedName, deploy); err != nil && errors.IsNotFound(err) {
            // 创建关联资源
            // 1. 创建 Deploy
            deploy := resources.NewDeploy(instance)
            if err := r.client.Create(context.TODO(), deploy); err != nil {
                return reconcile.Result{}, err
            }
            // 2. 创建 Service
            service := resources.NewService(instance)
            if err := r.client.Create(context.TODO(), service); err != nil {
                return reconcile.Result{}, err
            }
            // 3. 关联 Annotations
            //把instance.Spec的json对象中的数据转换成json字符串
            data, _ := json.Marshal(instance.Spec)
    
            //reqLogger.Info(string(instance.Spec))
            //reqLogger.Info(string(data))
    
            if instance.Annotations != nil {
                instance.Annotations["spec"] = string(data)
            } else {
                instance.Annotations = map[string]string{"spec": string(data)}
            }
    
            if err := r.client.Update(context.TODO(), instance); err != nil {
                return reconcile.Result{}, nil
            }
            return reconcile.Result{}, nil
        }
        reqLogger.Info("结束获取deployment资源.....")
    
    
        oldspec := appv1.AppServiceSpec{}
        reqLogger.Info("获取AppService的spec对象")
    
        //根据kubectl describe Appservice 查看annotations.spec为空
        //这里instance.Annotations["spec"]的值为nil
        //所以这里出错是因为添加Appservice对象的时候赋值的时候有问题
    
        // if {} == (instance.Annotations["spec"]) {
        //     reqLogger.Info("instance.Annotations为空。。。")
        // } else {
        //    reqLogger.Info("instance.Annotations不为空。。。。")
        // }
        // reqLogger.Info('instance.Annotations["spec"]的类型:%T',instance.Annotations["spec"])
        fmt.Println("instance.Annotations["spec"]的类型:", reflect.TypeOf(instance.Annotations["spec"]))
        specstr := strconv.Quote(instance.Annotations["spec"])
        fmt.Println("instance.Annotations["spec"]的内容:",specstr)
    
        //将json字符串解码到相应的数据结构appv1.AppServiceSpec
        if err := json.Unmarshal([]byte(instance.Annotations["spec"]), &oldspec); err != nil {
            return reconcile.Result{}, err
        }
    
        if !reflect.DeepEqual(instance.Spec, oldspec) {
            // 更新关联资源
            newDeploy := resources.NewDeploy(instance)
            oldDeploy := &appsv1.Deployment{}
            if err := r.client.Get(context.TODO(), request.NamespacedName, oldDeploy); err != nil {
                return reconcile.Result{}, err
            }
            oldDeploy.Spec = newDeploy.Spec
            if err := r.client.Update(context.TODO(), oldDeploy); err != nil {
                return reconcile.Result{}, err
            }
    
            newService := resources.NewService(instance)
            oldService := &corev1.Service{}
    
            if err := r.client.Get(context.TODO(), request.NamespacedName, oldService); err != nil {
                return reconcile.Result{}, err
            }
    
            // oldService.Spec = newService.Spec
            // reqLogger.Info("到这里了........")
            fmt.Print(oldService.Spec)
    
            //client.Update service会出错
            //Service nginx-app-5 is invalid: spec.clusterIP: Invalid value: "": field is immutable"
            // if err := r.client.Update(context.TODO(), oldService); err != nil {
            //     return reconcile.Result{}, err
            // }
    
            //先删除原来的service
             if err := r.client.Delete(context.TODO(), oldService); err != nil {
                return reconcile.Result{}, err
            } else {
                reqLogger.Info("删除原来的service的了......")
            }
    
            //创建一个新的service
            if err := r.client.Create(context.TODO(), newService); err != nil {
                return reconcile.Result{}, err
            } else {
                reqLogger.Info("创建新的service......")
            }
            return reconcile.Result{}, nil
        }
        return reconcile.Result{}, nil
    }
    controller.go
    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.
    
    // AppServiceSpec defines the desired state of AppService
    // +k8s:openapi-gen=true
    type AppServiceSpec struct {
        // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
        // Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file
        // Add custom validation using kubebuilder tags: https://book.kubebuilder.io/beyond_basics/generating_crd.html
        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"`
    }
    
    // AppServiceStatus defines the observed state of AppService
    // +k8s:openapi-gen=true
    type AppServiceStatus struct {
        // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
        // Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file
        // Add custom validation using kubebuilder tags: https://book.kubebuilder.io/beyond_basics/generating_crd.html
        // Conditions []AppServiceConditions
        // Phase      string
        appsv1.DeploymentStatus `json:",inline"`
    }
    
    // type AppServiceConditions struct {
    //     Type    string
    //     Message string
    //     Reason  string
    //     Ready   bool
    //     // The last time this condition was updated.
    //     LastUpdateTime metav1.Time `json:"lastUpdateTime,omitempty" protobuf:"bytes,6,opt,name=lastUpdateTime"`
    //     // Last time the condition transitioned from one status to another.
    //     LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty" protobuf:"bytes,7,opt,name=lastTransitionTime"`
    // }
    
    // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
    
    // AppService is the Schema for the appservices API
    // +k8s:openapi-gen=true
    type AppService struct {
        metav1.TypeMeta   `json:",inline"`
        metav1.ObjectMeta `json:"metadata,omitempty"`
    
        Spec   AppServiceSpec   `json:"spec,omitempty"`
        Status AppServiceStatus `json:"status,omitempty"`
    }
    
    // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
    
    // AppServiceList contains a list of AppService
    type AppServiceList struct {
        metav1.TypeMeta `json:",inline"`
        metav1.ListMeta `json:"metadata,omitempty"`
        Items           []AppService `json:"items"`
    }
    
    func init() {
        SchemeBuilder.Register(&AppService{}, &AppServiceList{})
    }
    types.go

        5.创建crd

    apiVersion: apiextensions.k8s.io/v1beta1
    kind: CustomResourceDefinition
    metadata:
      name: appservices.app.example.com
    spec:
      group: app.example.com
      names:
        kind: AppService
        listKind: AppServiceList
        plural: appservices
        singular: appservice
      scope: Namespaced
      subresources:
        status: {}
      validation:
        openAPIV3Schema:
          properties:
            apiVersion:
              description: 'APIVersion defines the versioned schema of this representation
                of an object. Servers should convert recognized schemas to the latest
                internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources'
              type: string
            kind:
              description: 'Kind is a string value representing the REST resource this
                object represents. Servers may infer this from the endpoint the client
                submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds'
              type: string
            metadata:
              type: object
            spec:
              type: object
            status:
              type: object
      version: v1
      versions:
      - name: v1
        served: true
        storage: true
    crd.yaml

        6.启动operator控制器

          operator-sdk run --local

        7.创建cr自动由控制器进行调谐

    apiVersion: app.example.com/v1
    kind: AppService
    metadata:
      name: nginx-app-5
    spec:
      size: 2
      image: nginx:1.7.9
      ports:
        - port: 80
          targetPort: 80
          nodePort: 30042
    cr.yaml

       8.解决service资源不能更新的问题

      

     

  • 相关阅读:
    课堂练习02
    第五周进度条
    软件工程个人作业03
    第四周进度条
    构建之法阅读笔记02
    ---JS canvas学习笔记
    JavaScript 对象、DOM对象、jquery对象的区别、转换详解
    JQM---列车时刻查询
    JS---如何避免用户在请求时“猛击”
    HTML5 新增属性和废除属性
  • 原文地址:https://www.cnblogs.com/yxh168/p/12462681.html
Copyright © 2020-2023  润新知