开发环境搭建
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 }
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{}) }
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
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
8.解决service资源不能更新的问题