Рубрики
Uncategorized

Intersystems Kubernetes Оператор Deep Diver: Введение в операторы Kubernetes

Введение Несколько ресурсов говорят нам, как запустить ирису в кластере Куберанес, например, de … Теги с DevOps, Intersystems, Kubernetes.

Введение

Несколько ресурсов говорят нам, как запустить ирису в кластере Кубератеса, таких как Развертывание раствора INTERSYSTEMS IRIS на EKS с использованием действий GitHub и Развертывание раствора INTERSYSTEMS IRIS на GKE с использованием действий GitHub Отказ Эти методы работают, но они требуют, чтобы вы создали Kubernetes, проявляющиеся и шлем диаграммы, которые могут быть довольно трудоемкими. Чтобы упростить развертывание IRIS, Межсистемы Разработан удивительный инструмент под названием оператор intersystems Kubernetes (IKO). Ряд официальных ресурсов объясняют использование IKO подробно, например Новое видео: Оператор Intersystems Iris Kubernetes и Intersystems Kubernetes Оператор Отказ

Документация Kubernetes Говорит, что операторы заменяют оператор человека, который знает, как бороться с сложными системами в Куберане. Они предоставляют системные настройки в форме Пользовательские ресурсы Отказ Оператор включает в себя пользовательский контроллер, который читает эти настройки, и выполняет шаги. Настройки определяют правильность настройки и обслуживания вашего приложения. Пользовательский контроллер представляет собой простой стручок, развернутый в Kubernetes. Таким образом, вообще говоря, все, что вам нужно сделать, чтобы сделать операторную работу, развернут POD контроллера и определить его настройки в пользовательских ресурсах. Вы можете найти пояснение высокоуровневых операторов в Как объяснить операторы Kubernetes на простом английском Отказ Также бесплатный O’Reilly EBook Доступен для скачивания. В этой статье у нас будет ближе взглянуть на то, какие операторы и то, что делает их галочками. Мы также напишем наш собственный оператор.

Предпосылки и настройка

Чтобы следовать, вам нужно будет установить следующие инструменты: своего рода

$ kind --version
kind version 0.9.0

Голанг

$ go version
go version go1.13.3 linux/amd64

kubebuilder

$ kubebuilder version
Version: version.Version{KubeBuilderVersion:"2.3.1"…

kubectl

$ kubectl version
Client Version: version.Info{Major:"1", Minor:"15", GitVersion:"v1.15.11"...

Оператор-SDK.

$ operator-sdk version
operator-sdk version: "v1.2.0"…

Пользовательские ресурсы

API Ресурсы это Важная концепция в Кубебене. Эти ресурсы позволяют вам взаимодействовать с Kubernetes через конечные точки HTTP, которые могут быть сгруппированы и версии. Стандартный API может быть продлен с Пользовательские ресурсы , который требует, чтобы вы предоставляете пользовательское определение ресурсов (CRD). Посмотрите на Продлить API Kubernetes с помощью CustomSourceefinitions Страница для подробной информации. Вот пример CRD:

$ cat crd.yaml 
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: irises.example.com
spec:
  group: example.com
  version: v1alpha1
  scope: Namespaced
  names:
    plural: irises
    singular: iris
    kind: Iris
    shortNames:
    - ir
  validation:
    openAPIV3Schema:
      required: ["spec"]
      properties:
        spec:
          required: ["replicas"]
          properties:
            replicas:
              type: "integer"
              minimum: 0

В приведенном выше примере мы определяем ресурс API GVK (Group/Version/Ride) в качестве примера.com/v1alpha1/iris с репликами как единственное обязательное поле. Теперь давайте определим пользовательский ресурс на основе нашего CRD:

$ cat crd-object.yaml 
apiVersion: example.com/v1alpha1
kind: Iris
metadata:
  name: iris
spec:
  test: 42
  replicas: 1

В нашем пользовательском ресурсе мы можем определить любые поля в дополнение к репликам, которые требуются CRD. После того, как мы развертываем вышеупомянутые два файла, наш пользовательский ресурс должен быть виден стандартным Kubectl. Давайте запустим Kubernetes локально использовать добрый , а затем запустите следующие команды kubectl:

$ kind create cluster
$ kubectl apply -f crd.yaml
$ kubectl get crd irises.example.com
NAME                 CREATED AT
irises.example.com   2020-11-14T11:48:56Z

$ kubectl apply -f crd-object.yaml
$ kubectl get iris
NAME   AGE
iris   84s

Хотя мы установили сумму реплики для нашей радужки, ничто на самом деле не происходит в данный момент. Ожидается. Нам нужно развернуть контроллер — объект, который может прочитать наш пользовательский ресурс и выполнять некоторые действия на основе настроек. На данный момент давайте убрать то, что мы создали:

$ kubectl delete -f crd-object.yaml
$ kubectl delete -f crd.yaml

Контроллер

Контроллер можно записать на любом языке. Мы будем использовать Голанг Как «родной» «родной» Кубернес. Мы могли бы написать логику контроллера с нуля, но хорошие люди от Google и Redhat дали нам ногу. Они создали два проекта, которые могут генерировать код оператора, который потребует только минимальных изменений — Kubebuilder и Оператор-SDK Отказ Эти два сравниваются на Kubebuilder VS Оператор-SDK Страница, а также здесь: В чем разница между KubeBuilder и Operator-SDK # 1758 Отказ

Kubebuilder

Удобно начать наше знакомство с Kubebuilder в Книга Kubebuilder страница. Учебник: ноль для оператора за 90 минут Видео от сопровождающего Kubebuilder может помочь также.

Образец реализации проекта KubeBuilder можно найти в Образец-контроллер-Kubebuilder и в Kubebuilder — образец-контроллер репозитории.

Давайте легаем нового проекта оператора:

$ mkdir iris
$ cd iris
$ go mod init iris # Creates a new module, name it iris
$ kubebuilder init --domain myardyas.club # An arbitrary domain, used below as a suffix in the API group

Леса включает в себя много файлов и проявления. Файл Main.go, например, является въездной точкой кода. Импортирует Библиотека времени выполнения контроллера , инстанцирует и управляет специальным менеджером, который отслеживает работу контроллера. Ничего изменить ни в одном из этих файлов.

Давайте создадим CRD:

$ kubebuilder create api --group test --version v1alpha1 --kind Iris
Create Resource [y/n]
y
Create Controller [y/n]
y
…

Опять же, многие файлы генерируются. Они подробно описаны в Добавление нового API страница. Например, вы можете увидеть, что файл для доброй ириса добавляется в API/V1ALPHA1/IRIS_TYPES.GO. В нашем первом образце CRD мы определили необходимые поля реплик. Давайте создадим идентичное поле здесь, на этот раз в Irisspec структура. Мы также добавим Развертывание знамена поле. Количество репликов должно быть также видно в Статус Раздел, поэтому нам нужно сделать следующие изменения:

$ vim api/v1alpha1/iris_types.go
…
type IrisSpec struct {
        // +kubebuilder:validation:MaxLength=64
        DeploymentName string `json:"deploymentName"`
        // +kubebuilder:validation:Minimum=0
        Replicas *int32 `json:"replicas"`
}
…
type IrisStatus struct {
        ReadyReplicas int32 `json:"readyReplicas"`
}
…

После редактирования API мы перейдем к редактированию контроллера Boeterplate. Вся логика должна быть определена в методе согласования (этот пример в основном взяты из mykind_controller.go ). Мы также добавляем пару вспомогательных методов и переписать метод SetupwithManager.

$ vim controllers/iris_controller.go
…
import (
...
// Leave the existing imports and add these packages
        apps "k8s.io/api/apps/v1"
        core "k8s.io/api/core/v1"
        apierrors "k8s.io/apimachinery/pkg/api/errors"
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
        "k8s.io/client-go/tools/record"
)
// Add the Recorder field to enable Kubernetes events
type IrisReconciler struct {
        client.Client
        Log    logr.Logger
        Scheme *runtime.Scheme
        Recorder record.EventRecorder
}
…
// +kubebuilder:rbac:groups=test.myardyas.club,resources=iris,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=test.myardyas.club,resources=iris/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;delete
// +kubebuilder:rbac:groups="",resources=events,verbs=create;patch

func (r *IrisReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
    ctx := context.Background()
    log := r.Log.WithValues("iris", req.NamespacedName)
    // Fetch Iris objects by name
    log.Info("fetching Iris resource")
    iris := testv1alpha1.Iris{}
    if err := r.Get(ctx, req.NamespacedName, &iris); err != nil {
        log.Error(err, "unable to fetch Iris resource")
        return ctrl.Result{}, client.IgnoreNotFound(err)
    }
    if err := r.cleanupOwnedResources(ctx, log, &iris); err != nil {
        log.Error(err, "failed to clean up old Deployment resources for Iris")
        return ctrl.Result{}, err
    }

    log = log.WithValues("deployment_name", iris.Spec.DeploymentName)
    log.Info("checking if an existing Deployment exists for this resource")
    deployment := apps.Deployment{}
    err := r.Get(ctx, client.ObjectKey{Namespace: iris.Namespace, Name: iris.Spec.DeploymentName}, &deployment)
    if apierrors.IsNotFound(err) {
        log.Info("could not find existing Deployment for Iris, creating one...")

        deployment = *buildDeployment(iris)
        if err := r.Client.Create(ctx, &deployment); err != nil {
            log.Error(err, "failed to create Deployment resource")
            return ctrl.Result{}, err
        }

        r.Recorder.Eventf(&iris, core.EventTypeNormal, "Created", "Created deployment %q", deployment.Name)
        log.Info("created Deployment resource for Iris")
        return ctrl.Result{}, nil
    }
    if err != nil {
        log.Error(err, "failed to get Deployment for Iris resource")
        return ctrl.Result{}, err
    }

    log.Info("existing Deployment resource already exists for Iris, checking replica count")

    expectedReplicas := int32(1)
    if iris.Spec.Replicas != nil {
        expectedReplicas = *iris.Spec.Replicas
    }
    
    if *deployment.Spec.Replicas != expectedReplicas {
        log.Info("updating replica count", "old_count", *deployment.Spec.Replicas, "new_count", expectedReplicas)            
        deployment.Spec.Replicas = &expectedReplicas
        if err := r.Client.Update(ctx, &deployment); err != nil {
            log.Error(err, "failed to Deployment update replica count")
            return ctrl.Result{}, err
        }

        r.Recorder.Eventf(&iris, core.EventTypeNormal, "Scaled", "Scaled deployment %q to %d replicas", deployment.Name, expectedReplicas)

        return ctrl.Result{}, nil
    }

    log.Info("replica count up to date", "replica_count", *deployment.Spec.Replicas)
    log.Info("updating Iris resource status")
    
    iris.Status.ReadyReplicas = deployment.Status.ReadyReplicas
    if r.Client.Status().Update(ctx, &iris); err != nil {
        log.Error(err, "failed to update Iris status")
        return ctrl.Result{}, err
    }

    log.Info("resource status synced")
    return ctrl.Result{}, nil
}

// Delete the deployment resources that no longer match the iris.spec.deploymentName field
func (r *IrisReconciler) cleanupOwnedResources(ctx context.Context, log logr.Logger, iris *testv1alpha1.Iris) error {
    log.Info("looking for existing Deployments for Iris resource")

    var deployments apps.DeploymentList
    if err := r.List(ctx, &deployments, client.InNamespace(iris.Namespace), client.MatchingField(deploymentOwnerKey, iris.Name)); err != nil {
        return err
    }
    
    deleted := 0
    for _, depl := range deployments.Items {
        if depl.Name == iris.Spec.DeploymentName {
            // Leave Deployment if its name matches the one in the Iris resource
            continue
        }

        if err := r.Client.Delete(ctx, &depl); err != nil {
            log.Error(err, "failed to delete Deployment resource")
            return err
        }

        r.Recorder.Eventf(iris, core.EventTypeNormal, "Deleted", "Deleted deployment %q", depl.Name)
        deleted++
    }

    log.Info("finished cleaning up old Deployment resources", "number_deleted", deleted)
    return nil
}

func buildDeployment(iris testv1alpha1.Iris) *apps.Deployment {
    deployment := apps.Deployment{
        ObjectMeta: metav1.ObjectMeta{
            Name:            iris.Spec.DeploymentName,
            Namespace:       iris.Namespace,
            OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(&iris, testv1alpha1.GroupVersion.WithKind("Iris"))},
        },
        Spec: apps.DeploymentSpec{
            Replicas: iris.Spec.Replicas,
            Selector: &metav1.LabelSelector{
                MatchLabels: map[string]string{
                    "iris/deployment-name": iris.Spec.DeploymentName,
                },
            },
            Template: core.PodTemplateSpec{
                ObjectMeta: metav1.ObjectMeta{
                    Labels: map[string]string{
                        "iris/deployment-name": iris.Spec.DeploymentName,
                    },
                },
                Spec: core.PodSpec{
                    Containers: []core.Container{
                        {
                            Name:  "iris",
                            Image: "store/intersystems/iris-community:2020.4.0.524.0",
                        },
                    },
                },
            },
        },
    }
    return &deployment
}

var (
    deploymentOwnerKey = ".metadata.controller"
)

// Specifies how the controller is built to watch a CR and other resources 
// that are owned and managed by that controller
func (r *IrisReconciler) SetupWithManager(mgr ctrl.Manager) error {
    if err := mgr.GetFieldIndexer().IndexField(&apps.Deployment{}, deploymentOwnerKey, func(rawObj runtime.Object) []string {
        // grab the Deployment object, extract the owner...
        depl := rawObj.(*apps.Deployment)
        owner := metav1.GetControllerOf(depl)
        if owner == nil {
            return nil
        }
        // ...make sure it's an Iris...
        if owner.APIVersion != testv1alpha1.GroupVersion.String() || owner.Kind != "Iris" {
            return nil
        }

        // ...and if so, return it
        return []string{owner.Name}
    }); err != nil {
        return err
    }

    return ctrl.NewControllerManagedBy(mgr).
        For(&testv1alpha1.Iris{}).
        Owns(&apps.Deployment{}).
        Complete(r)
}

Чтобы сделать рабочую силу для регистрации событий, нам нужно добавить еще одну строку в файл Main.go:

if err = (&controllers.IrisReconciler{
                Client: mgr.GetClient(),
                Log:    ctrl.Log.WithName("controllers").WithName("Iris"),
                Scheme: mgr.GetScheme(),
                Recorder: mgr.GetEventRecorderFor("iris-controller"),
        }).SetupWithManager(mgr); err != nil {

Теперь все готово к созданию оператора. Давайте сначала установим CRD, используя Makefile Target Установить:

$ cat Makefile
…
# Install CRDs into a cluster
install: manifests
        kustomize build config/crd | kubectl apply -f -
...
$ make install

Вы можете взглянуть на полученный файл CRD YAML в каталоге CONFIG/CRD/CASES/. Теперь проверьте существование CRD в кластере:

$ kubectl get crd
NAME                      CREATED AT
iris.test.myardyas.club   2020-11-17T11:02:02Z

Давайте запустим наш контроллер в другом терминале, локально (не в Kubernetes) — просто чтобы увидеть, действительно ли это работает:

$ make run
...
2020-11-17T13:02:35.649+0200 INFO controller-runtime.metrics metrics server is starting to listen {"addr": ":8080"}
2020-11-17T13:02:35.650+0200 INFO setup starting manager
2020-11-17T13:02:35.651+0200 INFO controller-runtime.manager starting metrics server {"path": "/metrics"}
2020-11-17T13:02:35.752+0200 INFO controller-runtime.controller Starting EventSource {"controller": "iris", "source": "kind source: /, Kind="}
2020-11-17T13:02:35.852+0200 INFO controller-runtime.controller Starting EventSource {"controller": "iris", "source": "kind source: /, Kind="}
2020-11-17T13:02:35.853+0200 INFO controller-runtime.controller Starting Controller {"controller": "iris"}
2020-11-17T13:02:35.853+0200 INFO controller-runtime.controller Starting workers {"controller": "iris", "worker count": 1}
...

Теперь, когда у нас установлено CRD и контроллер, все, что нам нужно сделать, это создать экземпляр нашего пользовательского ресурса. Шаблон можно найти в файле config/samples/example.com_v1alpha1_iris.yaml. В этом файле нам нужно внести изменения, похожие на те, которые в CRD-Object.yaml:

$ cat config/samples/test_v1alpha1_iris.yaml
apiVersion: test.myardyas.club/v1alpha1
kind: Iris
metadata:
  name: iris
spec:
  deploymentName: iris
  replicas: 1

$ kubectl apply -f config/samples/test_v1alpha1_iris.yaml

После краткой задержки, вызванной необходимостью вытащить изображения IRIS, вы должны увидеть бегущую IRIS POD:

$ kubectl get deploy
NAME   READY   UP-TO-DATE   AVAILABLE   AGE
iris   1/1     1            1           119s

$ kubectl get pod
NAME                   READY   STATUS    RESTARTS   AGE
iris-6b78cbb67-vk2gq   1/1     Running   0          2m42s

$ kubectl logs -f -l iris/deployment-name=iris

Вы можете открыть портал Iris, используя команду kubectl Port-Forward:

$ kubectl port-forward deploy/iris 52773

Перейти к http://localhost: 52773/csp/sys/utilhome.csp в вашем браузере. Что делать, если мы изменим количество репликов в CRD? Давайте сделаем и применим это изменение:

$ vi config/samples/test_v1alpha1_iris.yaml
…
  replicas: 2
$ kubectl apply -f config/samples/test_v1alpha1_iris.yaml

Теперь вы должны увидеть другой IRIS POD.

$ kubectl get events
...
54s         Normal   Scaled                    iris/iris                   Scaled deployment "iris" to 2 replicas
54s         Normal   ScalingReplicaSet         deployment/iris             Scaled up replica set iris-6b78cbb67 to 2

Вопросы журнала в терминале, где контроллер в запущенном отчете Успешно примирение:

2020-11-17T13:09:04.102+0200 INFO controllers.Iris replica count up to date {"iris": "default/iris", "deployment_name": "iris", "replica_count": 2}
2020-11-17T13:09:04.102+0200 INFO controllers.Iris updating Iris resource status {"iris": "default/iris", "deployment_name": "iris"}
2020-11-17T13:09:04.104+0200 INFO controllers.Iris resource status synced {"iris": "default/iris", "deployment_name": "iris"}
2020-11-17T13:09:04.104+0200 DEBUG controller-runtime.controller Successfully Reconciled {"controller": "iris", "request": "default/iris"}

Хорошо, наши контроллеры, кажется, работают. Теперь мы готовы развернуть этот контроллер внутри Kubernetes в качестве стручка. Для этого нам нужно создать контейнер Controller Docker и толкать его в реестр. Это может быть любой реестр, который работает с Kubernetes — Dockerhub, ECR, GCR и так далее. Мы будем использовать локальные (добрые) kubernetes, поэтому давайте разверним контроллер в локальный реестр, используя сценарий Wide-with-registry.sh, доступный из Местный реестр страница. Мы можем просто удалить текущий кластер и воссоздать его:

$ kind delete cluster
$ ./kind_with_registry.sh
$ make install
$ docker build . -t localhost:5000/iris-operator:v0.1 # Dockerfile is autogenerated by kubebuilder
$ docker push localhost:5000/iris-operator:v0.1
$ make deploy IMG=localhost:5000/iris-operator:v0.1

Контроллер будет развернут в пространство имен IRIS-System. В качестве альтернативы, вы можете отсканировать все POD, чтобы найти пространство имен, как Kubectl Получите Pod -a ):

$ kubectl -n iris-system get po
NAME                                      READY   STATUS    RESTARTS   AGE
iris-controller-manager-bf9fd5855-kbklt   2/2     Running   0          54s

Давайте проверим журналы:

$ kubectl -n iris-system logs -f -l control-plane=controller-manager -c manager

Вы можете поэкспериментировать с изменением отчисления реплик в CRD и соблюдайте, как эти изменения отражены в количестве экземпляров IRIS.

Оператор-SDK.

Еще один удобный инструмент для генерации кода оператора — Оператор SDK Отказ Чтобы получить первоначальную идею этого инструмента, посмотрите на это Учебное пособие Отказ Вы должны Установить оператор-SDK первый. Для нашего простого применения процесс будет выглядеть похоже на тот, который мы работали с KubeBuilder (вы можете удалить/создать добрый кластер с реестром Docker, прежде чем продолжить). Запустить в другом каталоге:

$ mkdir iris
$ cd iris
$ go mod init iris
$ operator-sdk init --domain=myardyas.club
$ operator-sdk create api --group=test --version=v1alpha1 --kind=Iris
# Answer two 'yes'

Теперь измените Irisspec. и Irisstatus Структуры в том же файле — API/V1ALPHA1/IRIS_TYPES.GO. Мы будем использовать один и тот же файл Iris_Controller.go, поскольку мы сделали в KubeBuiter. Не забудьте добавить Рекордер поле в файле main.go. Поскольку KubeBuilder и Operator-SDK используют разные версии пакетов Golang, вы должны добавить контекст в Setupwithmanager Функция в контроллерах/Iris_Controller.go:

ctx := context.Background()
if err := mgr.GetFieldIndexer().IndexField(ctx, &apps.Deployment{}, deploymentOwnerKey, func(rawObj runtime.Object) []string {

Затем установите CRD и оператор (убедитесь, что работает добрый кластер работает):

$ make install
$ docker build . -t localhost:5000/iris-operator:v0.2
$ docker push localhost:5000/iris-operator:v0.2
$ make deploy IMG=localhost:5000/iris-operator:v0.2

Теперь вы должны увидеть CRD, оператор POD и IRIS POD (ы), похожие на те, которые мы видели, когда мы работали с KubeBuilder.

Вывод

Хотя контроллер включает в себя много кода, вы видели, что изменение реплик IRIS — это просто вопрос изменения строки в пользовательском ресурсе. Вся сложность скрыта в реализации контроллера. Мы посмотрели на то, как может быть создан простой оператор с использованием удобных инструментов лесов. Наш оператор заботился только о Replicas Iris. Теперь представьте, что нам на самом деле нужно иметь данные ириса сохраняться на диске — это потребует в состоянии состояния и постоянных объемов. Кроме того, нам понадобится сервис и, возможно, вход за внешний доступ. Мы должны быть в состоянии установить версию IRIS и пароль системы, зеркалирование и/или ECP и так далее. Вы можете себе представить, что сумма работы межсистемы должны были выполнить, чтобы упростить развертывание IRIS, скрывая всю логику, специфичную IRIS внутри оператора. В следующей статье мы собираемся посмотреть на оператор IRIS (IKO) более подробно и расследовать свои возможности в более сложных сценариях.

Оригинал: «https://dev.to/intersystems/intersystems-kubernetes-operator-deep-dive-introduction-to-kubernetes-operators-54ol»