• 介绍
    • 准备开发环境
    • 创建项目
    • 添加业务逻辑
    • 安装调试
    • 打镜像部署

    一、介绍

    • Operator介绍

    Operator可以看成是CRDController的组合,通过CRD及其控制器扩展Kubernetes API的资源。

    业务逻辑在Controller里,Controller代码结构大同小异,每次手动从头写费时费力,于是产生了以下两个控制器代码生成工具。

    生成控制器Controller代码的工具:Operator Franmeworkkubebuilder

    Opertor Framework是一个快速开发Operator的工具包,该框架主要包括两个部分:
    Operator SDK: 无需了解复杂的 Kubernetes API 特性,即可让你根据你自己的专业知识构建一个 Operator 应用。
    Operator Lifecycle Manager(OLM): 帮助你安装、更新和管理跨集群的运行中的所有 Operator(以及他们的相关服务) 。

    • 当前示例项目背景

      1. <br />我们平时创建一个简单的应用的时候,一般是编写deployment yaml,然后再创建一个service,通过标签进行关 联,最后通过Ingress或者type=NodePort暴露服务,略显麻烦。

    设想一下,有一种CRD,指定镜像、服务名、端口、环境变量等,就能自动创建对应的Deployment和service,是不是方便很多?

    二、准备开发环境

    环境 版本 节点
    k8s集群 1.18.0 虚拟机A、B、C (centos7.5)
    docker 17.03+ 虚拟机A (centos7.5)
    go 1.16+ 虚拟机A (centos7.5)
    kubectl 1.16.0+ 虚拟机A (centos7.5)
    operator-sdk 1.12.0+ 虚拟机A (centos7.5)
    Goland 2020+ 本地windows

    这里只演示operator-sdk在linux(centos7)的安装。其他平台请参考:https://sdk.operatorframework.io/docs/installation/

    1. wget https://github.com/operator-framework/operator-sdk/releases/download/v1.17.0/operator-sdk_linux_amd64
    2. mv operator-sdk_linux_amd64 /usr/local/bin/operator-sdk
    3. chmod +x /usr/local/bin/operator-sdk
    4. operator-sdk version

    image.png

    三、创建项目

    1、预设CRD资源清单

    这里创建一个名为AppService的CRD资源对象,其资源清单如下:

    apiVersion: app.ydzs.io/v1beta1
      kind: DeployService 
    metadata: 
      name: DS-app 
    spec: 
      size: 2 
      image: nginx:1.7.9 
      ports: 
      - port: 80 
        targetPort: 80 
        nodePort: 30002
    

    2、创建项目

    $ operator-sdk                                      # 查看operator-sdk命令使用帮助
    $ mkdir -p test1 && cd test1        # 创建项目目录
    $ export GO111MODULE=on  # 使用gomodules包管理工具
    $ export GOPROXY="https://goproxy.cn" 
    # 使用包代理,加速
    # 使用 sdk 创建一个名为 test1 的 operator 项目,如果在 GOPATH 之外需要指定 repo 参数
    $ go mod init github.com/fuyu89/test1/v2
    # 使用下面的命令初始化项目
    $ operator-sdk init --domain ydzs.io --license apache2 --owner "fuyu89"
    

    项目结构:
    Operator开发入门 -- 简单示例 - 图2

    3、添加API

    operator-sdk create api --group app --version v1beta1 --kind DeployService --resource=true --controller=true
    

    Operator开发入门 -- 简单示例 - 图3

    四、添加业务逻辑

    1、main.go中添加日志模块初始化

    if err = (&controllers.DeployServiceReconciler{
        Client: mgr.GetClient(),
        Log:    ctrl.Log.WithName("controllers").WithName("DeployService"), // 日志模块初始化
        Scheme: mgr.GetScheme(),
    }).SetupWithManager(mgr); err != nil {
        setupLog.Error(err, "unable to create controller", "controller", "DeployService")
        os.Exit(1)
    }
    //+kubebuilder:scaffold:builder
    
    if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
        setupLog.Error(err, "unable to set up health check")
        os.Exit(1)
        }
    

    2、修改api/v1beta1/deployservice_type.go

    1)添加模导入块

    Operator开发入门 -- 简单示例 - 图4

    2)修改DeployServiceSpec结构体

    与 “1、预设CRD资源清单“ 里面的spec部分添加对应的方法,omitempty表示可为空

    Operator开发入门 -- 简单示例 - 图5

    3)在结构体DeployServiceStatus里添加这个CRD的状态说明

    Operator开发入门 -- 简单示例 - 图6

    3、修改controllers/deployservice_controller.go

    1)给模块启别名

    主要是appsv1、corev1,其他Goland自动导入。

    import (
        "context"
        appv1beta1 "github.com/fuyu89/test1/v2/api/v1beta1"
        "github.com/go-logr/logr"
        appsv1 "k8s.io/api/apps/v1"                 // 添加
        corev1 "k8s.io/api/core/v1"                    // 添加
        "k8s.io/apimachinery/pkg/runtime"
        "k8s.io/klog/v2"
        ctrl "sigs.k8s.io/controller-runtime"
        "sigs.k8s.io/controller-runtime/pkg/client"
        "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
    )
    


    2)添加日志处理器

    在 type DeployServiceReconciler struct 部分添加 Log logr.Logger:

    // DeployServiceReconciler reconciles a DeployService object
    type DeployServiceReconciler struct {
        client.Client
        Log    logr.Logger    // 添加日志处理器
        Scheme *runtime.Scheme
    }
    

    3)添加代码权限
    我们代码对k8s集群的deploy、service资源进行创建、删除、watch等操作的权限

    //+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete
    //+kubebuilder:rbac:groups="",resources=services,verbs=get;list;watch;create;update;patch;delete
    


    4)添加业务逻辑

    在此文件的 Reconcile 函数里添加基本的逻辑:实时监控我们用这个CRD创建的deploy和service资源,发现异常做什么动作(此处实现基本逻辑:让资源保持期望状态,有则更新,没有则创建)。

    func (r *DeployServiceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
        ctx = context.Background()
        // log := r.Log.WithValues("deployservice", req.NamespacedName)
        // 首先获取DeployService实例
        var deployservice appv1beta1.DeployService
        if err := r.Client.Get(ctx, req.NamespacedName, &deployservice); err != nil {
            return ctrl.Result{}, client.IgnoreNotFound(err)
        }
    
        // 调谐,获取当前状态,然后和期望状态进行对比
        // CreateOrUpdate Deployment
        var deploy appsv1.Deployment
        deploy.Name = deployservice.Name
        deploy.Namespace = deployservice.Namespace
        or, err := ctrl.CreateOrUpdate(ctx, r.Client, &deploy, func() error {
            // 调谐必须在这个函数中进行
            MutateDeployment(&deployservice, &deploy)
            return controllerutil.SetControllerReference(&deployservice, &deploy, r.Scheme)
        })
        if err != nil {
            return ctrl.Result{}, err
        }
    
        klog.V(2).Info("CreateOrUpdate", "Deployment", or)
        // log.Info("CreateOrUpdate", "Deployment", or)
    
        // CreateOrUpdate Service
        var svc corev1.Service
        svc.Name = deployservice.Name
        svc.Namespace = deployservice.Namespace
        or, err = ctrl.CreateOrUpdate(ctx, r.Client, &svc, func() error {
            // 调谐必须在这个函数中进行
            MutateService(&deployservice, &svc)
            return controllerutil.SetControllerReference(&deployservice, &svc, r.Scheme)
        })
        if err != nil {
            return ctrl.Result{}, err
        }
        klog.V(2).Info("CreateOrUpdate", "Service", or)
        // log.Info("CreateOrUpdate", "Service", or)
        return ctrl.Result{}, nil
    }
    

    5)添加实时Watch功能
    在最后的函数添加实时监测Deploy和Service的功能,这样如果把自定义的DS资源的deploy或service删了,会自动重建。主要添加Owns(&appsv1.Deployment{}). 和Owns(&corev1.Service{}).

    func (r *DeployServiceReconciler) SetupWithManager(mgr ctrl.Manager) error {
            return ctrl.NewControllerManagedBy(mgr).
                    For(&appv1beta1.DeployService{}).
                    Owns(&appsv1.Deployment{}).
                    Owns(&corev1.Service{}).
                    Complete(r)
    }
    

    4、添加controllers/resource.go
    添加controllers/resource.go,实现controolers/deployservice_controller.go里面的MutateDeployment(&deployservice, &deploy)和MutateService(&deployservice, &svc)方法,定义要创建的deploy和service的模板。

    package controllers
    
    import (
        "github.com/fuyu89/test1/v2/api/v1beta1"
        appsV1 "k8s.io/api/apps/v1"
        coreV1 "k8s.io/api/core/v1"
        metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    )
    
    func MutateDeployment(app *v1beta1.DeployService, deploy *appsV1.Deployment) {
        labels := map[string]string{"app": app.Name}
        selector := &metaV1.LabelSelector{MatchLabels: labels}
        deploy.Spec = appsV1.DeploymentSpec{
            Replicas: app.Spec.Size,
            Template: coreV1.PodTemplateSpec{ // Pod模板
                ObjectMeta: metaV1.ObjectMeta{Labels: labels},
                Spec:       coreV1.PodSpec{Containers: newContainers(app)},
            },
            Selector: selector,
        }
    }
    
    func newContainers(app *v1beta1.DeployService) []coreV1.Container {
        var containerPorts []coreV1.ContainerPort
        for _, svcPort := range app.Spec.Ports {
            containerPorts = append(containerPorts, coreV1.ContainerPort{
                ContainerPort: svcPort.TargetPort.IntVal,
            })
        }
        return []coreV1.Container{
            {
                Name:      app.Name,
                Image:     app.Spec.Image,
                Resources: app.Spec.Resources,
                Env:       app.Spec.Envs,
                Ports:     containerPorts,
            },
        }
    }
    
    func MutateService(app *v1beta1.DeployService, svc *coreV1.Service) {
        svc.Spec = coreV1.ServiceSpec{
            ClusterIP: svc.Spec.ClusterIP,
            Ports:     app.Spec.Ports,
            Type:      coreV1.ServiceTypeNodePort,
            Selector:  map[string]string{"app": app.Name},
        }
    }
    

    5、修改api/v1beta1/groupversion_info.go
    添加Kind = “DeployService”,即CRD名称。
    Operator开发入门 -- 简单示例 - 图7

    五、安装调试

    1、上传代码
    选中Goland中的项目目录test1;工具—>部署 —> 上传到 虚拟机A。

    2、在虚拟机A上编译安装

    cd /root/test1
    go mod tidy
    make
    make install
    kubectl get crd                        // 显示crd资源
    make run ENABLE_WEBHOOKS=false
    

    Operator开发入门 -- 简单示例 - 图8

    3、在虚拟机A上部署一个DeployServer资源测试

    # vim test-deployservice.yaml
    apiVersion: app.ydzs.io/v1beta1
    kind: DeployService
    metadata:
      name: deployservice-test
    spec:
      size: 2
      image: nginx:1.7.9
      ports:
        - port: 80
          targetPort: 80
          nodePort: 30002
    
    
    # kubectl apply -f test-deployservice.yaml
    # kubectl get deploy,svc
    # kubectl get pods
    

    六、打镜像部署


    **1、修改Makefile

    **
    打开Makefile,搜索docker-build,去掉后面的test,即编译镜像时跳过测试节点,打镜像更快。

    Operator开发入门 -- 简单示例 - 图9

    2、修改Dockerfile

    打开Dockerfile,在 go mod download前面加go代码:GOPROXY=https://goproxy.cn
    &&
    Operator开发入门 -- 简单示例 - 图10

    **3、在虚拟机A打镜像

    **
    在项目根目录下执行:

    make docker-build IMG=fuyu89/test1:v1.0
    

    **4、在虚拟机A打镜像部署

    **
    注意:默认部署到项目名-system命名空间,可以在config/default/kustomization.yaml里修改。

    kubectl create ns test1-system
    make deploy IMG=fuyu89/test1:v1.0
    kubec    get crd
    kubectl get pods,deploy,svc -n test1-system