[K8S] Оператор (Введение)

Go Kubernetes

1. Начало работы и подготовка среды

1.1 Введение

  • OperatorЭто способ упаковки, запуска и управления приложениями k8s. это покрываетCRD(CustomResourceDeftination) + AdmissionWebhook + Controller, и разверните его на K8S в форме Deployment.

    • CRDИспользуется для определения декларативного API (yaml), программа всегда будет заставлять наименьшую единицу планирования (POD) стремиться к этому состоянию через это определение;
    • AdmissionWebhookДекларативные поля декларации (yaml) и валидации (валидации), используемые для перехвата запроса на отправку мутации (модификации);
    • ControllerГлавный контроллер отслеживает события создания/обновления/удаления ресурса и запускаетReconcileфункционировать как ответ. Весь процесс настройки называетсяReconcile Loop(согласованный цикл), по сути, заключается в стремлении POD к состоянию, требуемому определением CRD;
  • Operatorблок-схема

    image.png

  • KubebuilderЭто основа для разработки оператора, который может генерировать CRD, веб-перехватчик, код и конфигурацию контроллера, а также предоставляет k8s.go-client.

Основные понятия (цитата):Наггетс.Талант/пост/696287…

1.2 Подготовка окружающей среды

  • go version v1.16+.
  • docker version 18.03+.
  • kubectl version v1.20.3+.

1.2.0 Необходимые знания

  • Требуется хорошее знание основ k8s

1.2.1 среда k8s (сервер)

  • Подготовьте доступную среду k8s, будь то отдельная машина или кластер.推荐1.20以上
  • Можно использовать на сервере или локально, рекомендуется использовать на сервере

1.2.2 куббилдер (локальный)

Doc: book.cry be builder.IO/quick-start…

  • Загрузите kubebuilder соответствующей платформы, если вы используете разработку для Mac, скачайте Mac;
  • После завершения загрузки поместите бинарникkubebuilderДобавьте к переменным среды, например:
    # mv kubebuilder_darwin_amd64 /usr/local/bin/kubebuilder
    # chmod a+x /usr/local/bin/kubebuilder
    # kubebuilder version
    // Version: main.version{KubeBuilderVersion:"3.2.0", KubernetesVendor:"1.22.1", GitCommit:"b7a730c84495122a14a0faff95e9e9615fffbfc5", BuildDate:"2021-10-29T18:32:16Z", GoOs:"darwin", GoArch:"amd64"}
    

1.2.3 кубектл (локальный)

  • Для управления удалённым сервером через kubectl обязательно добавьте его в переменную окружения и используйте kubeconfig по умолчанию, в противном случае позже нужно будет модифицировать сгенерированный kubebuilderMakefile.

1.2.4 Докер (локальный или серверный)

  • Docker не разбирает, что это такое, в основном используется для упаковки образов при публикации проектов, а также генерируется kubebuilder.Makefileиспользуется в;

    • ps: 个人考虑到本地 mac 没有位置安装Docker,所以在服务器安装,但需要注意的是linux和mac使用的controller-gen及kustomize 是不一样;需要在linux使用kubebuilder重新生成一个新的项目用以覆盖现有项目的bin目录。

2. Проект

Справочный адрес:GitHub.com/shadow-от рабов…

2.1 Создание проекта

2.1.1 Создание каталога

  • Китайские иероглифы, пробелы, специальные символы, подчеркивания не допускаются, допускаются только дефисы "-"
    # mkdir -p /usr/local/k8s-operator
    # cd /usr/local/k8s-operator
    

2.1.2 Создать проект

  • инициировать проект, создать домен (домен).

    # kubebuilder init --domain shadow.com
    
  • Создайте API, создайте группу, версию и вид.

    # kubebuilder create api --group myapp --version v1 --kind Redis
    
    • После завершения вышеуказанного создания определение следующего CRD выглядит следующим образом:test.yaml
      apiVersion: myapp.shadow.com/v1
      kind: Redis
      ...
      
  • Структура проекта после создания

    image.png

2.1.3 Создать CRD

  • При желании установить префикс ресурса (可选), config/default/customization.yaml

    ...
    # 可以修改前缀
    namePrefix: shadow-operator-
    ...
    
  • В k8s-operator/api/v1/redis_types.go поле атрибута в RedisSepc, где я создал Name, Port, Replicas;

    image.png

  • install crd

    # make install
    
  • Просмотр crd, доступный черезkubectl describe crd redis.myapp.shadow.comПроверьте, является ли определение crd только что установленным полем;

    # kubectl get crd
    NAME                                       CREATED AT
    ...
    redis.myapp.shadow.com                     2021-11-16T09:35:42Z
    
  • Если CRD изменен, его необходимо переустановить.

    # make uninstall
    # make install
    

2.1.4 Контроллер запуска

  • Наша основная логикаReconcileДля реализации в ядре функция будет многократно запускаться событиями, k8s-operator/controllers/redis_controller.go

    ...
    func (r *RedisReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    _ = log.FromContext(ctx)
    
        // TODO(user): your logic here
        
        redis := &myappv1.Redis{}
        if err := r.Get(ctx, req.NamespacedName, redis); err != nil {
            fmt.Println(err)
        } else {
            fmt.Println("得到对象", redis.Spec)
        }
    }
    ...
    
  • Запустите контроллер и обратите внимание на вывод консоли

    # make run
    

2.1.5 Тестирование Yaml

  • Отредактируйте файл yaml, k8s-operator/config/samples/myapp_v1_redis.yaml

    apiVersion: myapp.shadow.com/v1
    kind: Redis
    metadata:
      name: shadow
      namespace: default
    spec:
      name: shadow
      port: 2378
      replicas: 3
    
  • apply

    # kubectl apply -f config/samples/myapp_v1_redis.yaml
    
  • Просмотр консольного вывода контроллера

    ...
    得到对象 {Name:shadow Port:2378 Replicas:3} 
    ...
    

2.2 Простая проверка полей CRD

Doc: book.cry be builder.IO/reference/no...

2.2.1 Демонстрация

  • k8s-operator/api/v1/redis_type.go с добавлением специальных аннотаций для ограничения значения порта. Дополнительные аннотации см. в документе выше.

    • +kubebuilder:validation:Minimum:=2000
    • +kubebuilder:validation:Maximum:=2380
    type RedisSpec struct {
       // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
       // Important: Run "make" to regenerate code after modifying this file
    
       // Foo is an example field of Redis. Edit redis_types.go to remove/update
       //Foo string `json:"foo,omitempty"`
       Name string `json:"name,omitempty"`
    
       // validation: https://book.kubebuilder.io/reference/markers/crd-validation.html
    
       //+kubebuilder:validation:Minimum:=2000
       //+kubebuilder:validation:Maximum:=2380
       Port int `json:"port,omitempty"`
    
       Replicas int `json:"replicas,omitempty"`
    }
    
  • Переустановите CRD

    # make install
    # make uninstall
    
  • Проверьте эффект

    • config/samples/myapp_v1_redis.yaml
      apiVersion: myapp.shadow.com/v1
      kind: Redis
      metadata:
        name: shadow
        namespace: default
      spec:
        port: 2390
        replicas: 3
        name: shadow
      
    • После выполнения вы можете увидеть соответствующую ошибку предела
      # kubectl apply -f config/samples/myapp_v1_redis.yaml 
      The Redis "shadow" is invalid: spec.port: Invalid value: 2390: spec.port in body should be less than or equal to 2380
      

2.3 Создание вебхука (модификация и проверка)

2.3.1 Необходимые знания

  • Что такое вебхук? Вебхук — это отдельный ресурс, который можно разрабатывать самостоятельно.
  • мы обычно проходимMutatingWebhookиValidatingWebhookДля перехвата запросов API эти два вебхука также включены по умолчанию в k8s (если они не включены, включите их вручную). Содержание запроса здесьYamlсодержание документа.

2.3.2 Создание вебхука

  • Выполните команду создания
    # kubebuilder create webhook --group myapp --version v1 --kind Redis --defaulting --programmatic-validation
    
  • После завершения создания сгенерируйте api/v1/redis_webhook.go.
    ...
    func (r *Redis) Default() {
       redislog.Info("default", "name", r.Name)
    
       // TODO(user): fill in your defaulting logic.
    }
    
    // TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation.
    //+kubebuilder:webhook:path=/validate-myapp-shadow-com-v1-redis,mutating=false,failurePolicy=fail,sideEffects=None,groups=myapp.shadow.com,resources=redis,verbs=create;update,versions=v1,name=vredis.kb.io,admissionReviewVersions=v1
    
    var _ webhook.Validator = &Redis{}
    
    // ValidateCreate implements webhook.Validator so a webhook will be registered for the type
    func (r *Redis) ValidateCreate() error {
       redislog.Info("validate create", "name", r.Name)
    
       // 增加:如果是资源名字为 shadow 则不允许创建 
       if r.Name == "shadow" {
          return fmt.Errorf("error name.")
       }
       // TODO(user): fill in your validation logic upon object creation.
       return nil
    }
    ...
    

2.3.3 Развертывание в среде k8s

  • Поскольку веб-перехватчик развертывается в сети, мы обычно выполняем проверку сертификата через ЦС, поэтому вот веб-перехватчик, который можно использовать в Интернете.

  • Создайте менеджер сертификатов k8s, который является плагином k8s.

    • Скачайте и примените cert-manager.yaml

      # wget https://github.com/jetstack/cert-manager/releases/download/v1.6.1/cert-manager.yaml
      
      # kubectl apply -f cert-manager.yaml
      
    • Проверьте, завершено ли создание

      # kubectl  get  pods  -A
      NAMESPACE                NAME                                                  READY   STATUS    RESTARTS   AGE
      cert-manager             cert-manager-55658cdf68-9559b                         1/1     Running   0          7d18h
      cert-manager             cert-manager-cainjector-967788869-hl472               1/1     Running   0          7d18h
      cert-manager             cert-manager-webhook-7b86bc6578-spdct
      ...
      
  • Откройте конфигурацию config/default/kustomization.yaml.

    ...
    ...
    bases:
    - ../crd
    - ../rbac
    - ../manager
    # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in
    # crd/kustomization.yaml
    - ../webhook
    # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required.
    - ../certmanager
    # [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'.
    #- ../prometheus
    
    patchesStrategicMerge:
    # Protect the /metrics endpoint by putting it behind auth.
    # If you want your controller-manager to expose the /metrics
    # endpoint w/o any authn/z, please comment the following line.
    - manager_auth_proxy_patch.yaml
    
    # Mount the controller config file for loading manager configurations
    # through a ComponentConfig type
    #- manager_config_patch.yaml
    
    # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in
    # crd/kustomization.yaml
    - manager_webhook_patch.yaml
    
    # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'.
    # Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks.
    # 'CERTMANAGER' needs to be enabled to use ca injection
    - webhookcainjection_patch.yaml
    
    # the following config is for teaching kustomize how to do var substitution
    vars:
    # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix.
    - name: CERTIFICATE_NAMESPACE # namespace of the certificate CR
      objref:
        kind: Certificate
        group: cert-manager.io
        version: v1
        name: serving-cert # this name should match the one in certificate.yaml
      fieldref:
        fieldpath: metadata.namespace
    - name: CERTIFICATE_NAME
      objref:
        kind: Certificate
        group: cert-manager.io
        version: v1
        name: serving-cert # this name should match the one in certificate.yaml
    - name: SERVICE_NAMESPACE # namespace of the service
      objref:
        kind: Service
        version: v1
        name: webhook-service
      fieldref:
        fieldpath: metadata.namespace
    - name: SERVICE_NAME
      objref:
        kind: Service
        version: v1
        name: webhook-service
    
  • развернуть (该步骤作者是在服务器上执行的,因为本地没有安装docker), и вам нужно предварительно выбрать зеркальный репозиторий, гавань или реестр докеров.

    # make install
    # make docker-build  IMG=192.168.6.102:5000/shadow-redis:v1
    # make docker-push  IMG=192.168.6.102:5000/shadow-redis:v1
    # make deploy IMG=192.168.6.102:5000/shadow-redis:v1
    
    • Просмотр развернутого веб-перехватчика
      # kubectl get  mutatingwebhookconfigurations
      NAME                                             WEBHOOKS   AGE
      shadow-operator-mutating-webhook-configuration   1          82m
      ...
      
      # kubectl  get validatingwebhookconfigurations
      NAME                                               WEBHOOKS   AGE
      shadow-operator-validating-webhook-configuration   1          47h
      ...
      

2.3.4 Запуск теста

  • Поскольку для проверки TLS развернут cert-manager, вебхук не запускается локально, поэтому отключите вебхук и напрямую используйте для проверки контроллер среды k8s;

    • main.go, найтиSetupWebhookWithManagerзакомментировать после
    //if err = (&myappv1.Redis{}).SetupWebhookWithManager(mgr); err != nil {
    // setupLog.Error(err, "unable to create webhook", "webhook", "Redis")
    // os.Exit(1)
    //}
    
  • запускать

    # make run
    
  • проверять

    # kubectl apply -f config/samples/myapp_redis_v1.yaml
    Error from server (error name.): error when creating "config/samples/myapp_v1_redis.yaml": admission webhook "vredis.kb.io" denied the request: error name.
    

2.4 Управление ресурсами POD

  • Каждый шаг требует перезагрузки контроллера
    # make run
    
  • Создайте вспомогательный каталог
    # mkdir -p k8s-operator/helper
    

2.4.1 Добавить/удалить/изменить

  • Основные реализованные функции

    • создавать ресурсы;
    • удалить ресурсы;
    • масштабирование реплики;
    • POD автоматически перестраивается после удаления;
    • записывать события;
  • helper/redis_helper.go

    
    package helper
    
    import (
       "context"
       "fmt"
       corev1 "k8s.io/api/core/v1"
       "k8s.io/apimachinery/pkg/runtime"
       "k8s.io/apimachinery/pkg/types"
       v1 "shadow.com/v1/api/v1"
       "sigs.k8s.io/controller-runtime/pkg/client"
       "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
    )
    // 生成 POD name
    func GetRedisPodNames(redisConfig *v1.Redis) []string {
       podNames := make([]string, redisConfig.Spec.Replicas)
       fmt.Printf("%+v", redisConfig)
       for i := 0; i < redisConfig.Spec.Replicas; i++ {
          podNames[i] = fmt.Sprintf("%s-%d", redisConfig.Name, i)
       }
    
       fmt.Println("PodNames: ", podNames)
       return podNames
    }
    
    //  判断 redis  pod 是否能获取
    func IsExistPod(podName string, redis *v1.Redis, client client.Client) bool {
       err := client.Get(context.Background(), types.NamespacedName{
          Namespace: redis.Namespace,
          Name:      podName,
       },
          &corev1.Pod{},
       )
    
       if err != nil {
          return false
       }
       return true
    }
    // 是否存在于finalizers,finalizers 是人为删除动作添加的,
    // 只要finalizers有值则删除无法顺利进行,直到finalizers为空;
    func IsExistInFinalizers(podName string, redis *v1.Redis) bool {
       for _, fPodName := range redis.Finalizers {
          if podName == fPodName {
             return true
    
          }
       }
       return false
    }
    
    func CreateRedis(client client.Client, redisConfig *v1.Redis, podName string, schema *runtime.Scheme) (string, error) {
       if IsExistPod(podName, redisConfig, client) {
          return "", nil
       }
       // 建立 POD 对象
       newPod := &corev1.Pod{}
       newPod.Name = podName
       newPod.Namespace = redisConfig.Namespace
       newPod.Spec.Containers = []corev1.Container{
          {
             Name:            podName,
             Image:           "redis:5-alpine",
             ImagePullPolicy: corev1.PullIfNotPresent,
             Ports: []corev1.ContainerPort{
                {
                   ContainerPort: int32(redisConfig.Spec.Port),
                },
             },
          },
       }
    
       // set owner reference,使用ControllerManager为我们管理 POD
       // 这个就和ReplicateSet是一个道理
       err := controllerutil.SetControllerReference(redisConfig, newPod, schema)
       if err != nil {
          return "", err
       }
       // 创建 POD
       err = client.Create(context.Background(), newPod)
       return podName, err
    }
    
  • controllers/redis_controller.go

    /*
    Copyright 2021 shadow.
    
    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 controllers
    
    import (
            "context"
            "fmt"
            corev1 "k8s.io/api/core/v1"
            metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
            "k8s.io/apimachinery/pkg/runtime"
            "k8s.io/apimachinery/pkg/types"
            "k8s.io/client-go/tools/record"
            "k8s.io/client-go/util/workqueue"
            "shadow.com/v1/helper"
            ctrl "sigs.k8s.io/controller-runtime"
            "sigs.k8s.io/controller-runtime/pkg/client"
            "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
            "sigs.k8s.io/controller-runtime/pkg/event"
            "sigs.k8s.io/controller-runtime/pkg/handler"
            "sigs.k8s.io/controller-runtime/pkg/log"
            "sigs.k8s.io/controller-runtime/pkg/reconcile"
            "sigs.k8s.io/controller-runtime/pkg/source"
    
            myappv1 "shadow.com/v1/api/v1"
    )
    
    // RedisReconciler reconciles a Redis object
    type RedisReconciler struct {
            client.Client
            Scheme      *runtime.Scheme
            EventRecord record.EventRecorder
    }
    
    //+kubebuilder:rbac:groups=myapp.shadow.com,resources=redis,verbs=get;list;watch;create;update;patch;delete
    //+kubebuilder:rbac:groups=myapp.shadow.com,resources=redis/status,verbs=get;update;patch
    //+kubebuilder:rbac:groups=myapp.shadow.com,resources=redis/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 Redis 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.10.0/pkg/reconcile
    func (r *RedisReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
            _ = log.FromContext(ctx)
    
            // TODO(user): your logic here
    
            redis := &myappv1.Redis{}
            if err := r.Get(ctx, req.NamespacedName, redis); err != nil {
                    fmt.Println(err)
            } else {
                    //  如果不为空 则正在删除
                    if !redis.DeletionTimestamp.IsZero() {
                            return ctrl.Result{}, r.clearRedis(ctx, redis)
                    }
    
                    fmt.Printf("得到对象 %+v \n", redis.Spec)
                    podNames := helper.GetRedisPodNames(redis)
                    isEdit := false
                    for _, podName := range podNames {
                            podName, err := helper.CreateRedis(r.Client, redis, podName, r.Scheme)
    
                            if err != nil {
                                    return ctrl.Result{}, err
                            }
                            
                            if podName == "" {
                                    continue
                            }
                            // 如果存在于 finalizers 证明已经创建了,跳过即可
                            if controllerutil.ContainsFinalizer(redis, podName) {
                                    continue
                            }
    
                            redis.Finalizers = append(redis.Finalizers, podName)
                            isEdit = true
                    }
    
                    //  副本收缩
                    if len(redis.Finalizers) > len(podNames) {
                            r.EventRecord.Event(redis, corev1.EventTypeNormal, "Upgrade", "副本收缩")
                            isEdit = true
                            err := r.rmIfSurplus(ctx, podNames, redis)
                            if err != nil {
                                    return ctrl.Result{}, err
                            }
                    }
    
                    if isEdit {
                            r.EventRecord.Event(redis, corev1.EventTypeNormal, "Updated", "更新 shadow-redis")
                            err = r.Client.Update(ctx, redis)
                            if err != nil {
                                    return ctrl.Result{}, err
                            }
                            // 下面说到增加RedisNum 来查看当前存在的POD数量,则需要打开下面的代码
                            // redis.Status.RedisNum = len(redis.Finalizers)
                            err = r.Status().Update(ctx, redis)
                            return ctrl.Result{}, err
                    }
    
                    return ctrl.Result{}, nil
    
            }
    
            return ctrl.Result{}, nil
    }
    
    // 收缩副本 ['redis0','redis1']   ---> podName ['redis0']
    func (r *RedisReconciler) rmIfSurplus(ctx context.Context, podNames []string, redis *myappv1.Redis) error {
            for i := 0; i < len(redis.Finalizers)-len(podNames); i++ {
                    err := r.Client.Delete(ctx, &corev1.Pod{
                            ObjectMeta: metav1.ObjectMeta{
                                    Name: redis.Finalizers[len(podNames)+i], Namespace: redis.Namespace,
                            },
                    })
    
                    if err != nil {
                            return err
                    }
            }
            redis.Finalizers = podNames
            return nil
    }
    
    func (r *RedisReconciler) clearRedis(ctx context.Context, redis *myappv1.Redis) error {
            podList := redis.Finalizers
            for _, podName := range podList {
                    err := r.Client.Delete(ctx, &corev1.Pod{
                            ObjectMeta: metav1.ObjectMeta{
                                    Name:      podName,
                                    Namespace: redis.Namespace,
                            },
                    })
    
                    if err != nil {
                            fmt.Println("清除Pod异常:", err)
                    }
            }
    
            redis.Finalizers = []string{}
            return r.Client.Update(ctx, redis)
    }
    
    func (r *RedisReconciler) podDeleteHandler(event event.DeleteEvent, limitInterface workqueue.RateLimitingInterface) {
            fmt.Println("被删除的对象名称是", event.Object.GetName())
    
            for _, ref := range event.Object.GetOwnerReferences() {
                    // 因为会获取到所有被删除的pod,所以进行一次判断
                    if ref.Kind == "Redis" && ref.APIVersion == "myapp.shadow.com/v1" {
                            // 重新推送队列,进行 reconcile
                            limitInterface.Add(reconcile.Request{
                                    NamespacedName: types.NamespacedName{
                                            Name:      ref.Name,
                                            Namespace: event.Object.GetNamespace(),
                                    },
                            })
                    }
            }
    }
    
    // SetupWithManager sets up the controller with the Manager.
    func (r *RedisReconciler) SetupWithManager(mgr ctrl.Manager) error {
            return ctrl.NewControllerManagedBy(mgr).
                    For(&myappv1.Redis{}).
                    // 监控资源,并对delete动作进行操作
                    Watches(&source.Kind{Type: &corev1.Pod{}}, handler.Funcs{DeleteFunc: r.podDeleteHandler}).
                    Complete(r)
    }
    
    
  • После завершения модификации протестируйте ее, если вы столкнулись с перехватом webhook, вы можете пройти тест.kubectl deleteПросто удалите (измените, подтвердите);

    • myapp_redis_v1.yaml

      apiVersion: myapp.shadow.com/v1
      kind: Redis
      metadata:
        name: shadow
        namespace: default
      spec:
        port: 2379
        replicas: 3
        name: shadow
      
    • Создайте

      # kubectl apply -f config/samples/myapp_redis_v1.yaml
      # kubectl get Redis
      # kubectl get pods
      
    • Уменьшите копию, измените myapp_redis_v1.yaml

      apiVersion: myapp.shadow.com/v1
      kind: Redis
      metadata:
        name: shadow
        namespace: default
      spec:
        port: 2379
        replicas: 2
        name: shadow
      
      # kubectl apply -f config/samples/myapp_redis_v1.yaml
      
    • Удалить

      # kubectl delete Redis shadow
      
    • Просмотр зарегистрированных событий

      # kubectl describe Redis shadow
      

2.4.2 Проверка

  • kubectl get RedisПо умолчанию отображаются только два поля Имя и Возраст.Здесь поле RedisNum расширено для подсчета количества созданных POD.

    • api/v1/redis_type.go, добавить статус

       type RedisStatus struct {
          // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
          // Important: Run "make" to regenerate code after modifying this file
          RedisNum int `json:"redis_num"`
      
       }
      
       //下面两个注释对应的是 kubectl get Redis 看到的状态  
       //+kubebuilder:printcolumn:JSONPath=".status.redis_num",name=REDIS_NUM,type=integer
       //+kubebuilder:printcolumn:JSONPath=".metadata.creationTimestamp",name=AGE,type=date
      
    • controllers/redis_controller.go, откройте комментарии ниже

      // redis.Status.RedisNum = len(redis.Finalizers)
      
  • Переустановите CRD и перезапустите контроллер.

    # make install
    # make run
    
  • Проверять

    # kubectl get Redis
    NAME     REDIS_NUM   AGE
    shadow   3           21m
    

3. Напишите в конце

  • Позже, когда фактический боевой проект будет завершен, фактическая боевая глава и исходный код Opeartor будут обновлены.
  • 👈 (левый) кликПойдем.