In diesem Beitrag unserer Reihe „Kubernetes Beratung“ konzentrieren wir uns auf Operatoren.
Bei Kubernetes-Operatoren handelt es sich weder um einen talentierten Ops-Spezialisten noch um ein Team, sondern um ein automatisiertes Operator-Framework, das auf Kubernetes (oder OpenShift) verwendet werden kann. Das Operator-Framework wurde im Jahre 2016 von CoreOS, jetzt Teil von RedHat und ab 2019 von IBM, eingeführt. Das Operators-Framework bietet eine hervorragende Gelegenheit, komplexe Kubernetes-Vorgänge zu verwalten. Hierdurch ist es bereits jetzt zu einem beliebten und zentralen Element von Cloud-nativen DevOps-Architekturen geworden.
K&C – WIR KREIEREN EINZIGARTIGE TECH-LÖSUNGEN SEIT ÜBER 20 JAHREN. WIR VERSCHAFFEN IHNEN IHREN WETTBEWERBSVORTEIL!
Schreiben Sie uns eine Nachricht, um Ihre individuellen Bedürfnisse oder das nächste Projekt zu besprechen.
In diesem Artikel erklären wir die Rolle, die Operatoren in einer skalierbaren und auf Kubernetes basierenden Anwendungsarchitektur spielen und warum sie zu einem so wichtigen Bestandteil der DevOps-Automatisierung geworden sind. Ein Großteil in diesem Beitrag wurde von IBMs großartigem Video zu Kubernetes-Operatoren inspiriert:
Im Folgenden eine schrittweise Anleitung zur Erstellung eines Operators mit Operator SDK und Ansible.
Als Kernbestandteil des Operator-Frameworks ist es sinnvoll, zunächst den Kubernetes Control Loop zu erläutern. Der K8 Control Loop überwacht („Observe“) den Status eines Clusters. Als Nächstes vergleicht Kubernetes den tatsächlichen mit dem gewünschten Status eines Clusters. Dieser Vergleichsprozess wird auch als „Diff“ bezeichnet.
Die letzte Phase eines Kubernetes Control Loops besteht darin, etwaige Diffs zu lösen, indem man auf diese einwirkt. Diese Phase des Control Loops wird daher auch als „Act“ bezeichnet.
Der Conrol Loop spiegelt den Kern der Funktionsweise von Kubernetes wider. Hierzu gibt es einen Controller, der auf den Loop für jede Standardressource, mit der Kubernetes geliefert wird, einwirkt.
Als Endnutzer besteht der erste Schritt darin, die ein oder andere YAML zu schreiben – die Spezifikation für die Anwendung. Beispielsweise kann eine Softwareverteilung durchgeführt werden, bei der einige Konfigurationen wie folgt definiert werden:
Nun haben wir eine Kubernetes-Ressource, die im Cluster bereitgestellt werden soll. Der Control Loop wird jetzt aktiviert und überprüft den Unterschied zwischen dem, was im Cluster vorgegeben wird und dem tatsächlichen Status. Kubernetes wird beispielsweise feststellen, dass es keine Pods gibt. Es wird dann auf diese Diff einwirken und Pods erstellen.
Eine komplexere Anwendung kann mehr als nur eine YAML haben. Es könnte eine zweite für das Backend geben. Dies wird ebenfalls in einem Cluster bereitgestellt, wobei anschließend ein Pod mithilfe der Controller und des Control Loops bereitgestellt wird.
Wenn wir nun die Anwendung skalieren, einige Änderungen vornehmen, Secrets und Umgebungsvariablen usw. hinzufügen möchten, müssen wir entweder jedes Mal neue Kubernetes-Ressourcen einrichten oder die vorhandenen bearbeiten. Dieser Vorgang kann zeitaufwändig werden.
Wie würden wir dieselbe Anwendung mit einem Operator bereitstellen? Der erste Schritt ist die Installation des Operators selbst. Das bedeutet natürlich, dass Sie einen Operator benötigen, der genau die von Ihnen gewünschte Arbeit erledigt. Sie können entweder einen Kubernetes-Operator von jemandem erstellen lassen beziehungsweise dies selbst erledigen, wenn Sie über den entsprechenden technischen Hintergrund verfügen oder Sie können einfach eine geeignete Option in der wachsenden Bibliothek finden, die auf OperatorHub zur Verfügung steht.
Das Erste, was jetzt im Kubernetes-Cluster benötigt wird, ist der Operator Lifecycle Manager (OLM). Das OLM verwaltet die installierten Operatoren. Als Nächstes wird der Operator im Cluster bereitgestellt.
Ein Kubernetes-Operator besteht aus zwei Hauptkomponenten:
Die CRD (Custom Resource Definition) wird im Gegensatz zu Standard-Kubernetes-Ressourcen wie Bereitstellungen und Pods entweder vom Benutzer oder vom Operator definiert, sodass YAML entgegen den benutzerdefinierten Konfiguration arbeiten kann.
Der Controller ist ein benutzerdefinierter Kubernetes Control Loop, der als Pod im Cluster und als Control Loop entlang der CRD operiert.
Was wäre der Unterschied, wenn der Operator für dieselbe benutzerdefinierte Bereitstellung der Anwendung wie im ersten Beispiel ohne Operator erstellt würde? Anstelle mehrerer Bereitstellungen, Konfigurationszuordnungen, Secrets usw. schreiben zu müssen, muss nur eine YAML bereitgestellt werden.
Benutzerdefinierte Konfigurationen können dem Operator zugewiesen werden oder die Standardeinstellungen verwenden. Der Operator wird dann direkt im Cluster bereitgestellt. Der Operator ist dafür verantwortlich, den Kubernetes Control Loop zu durchlaufen und herauszufinden, was ausgeführt werden muss.
Der Operator würde beispielsweise feststellen, dass einige Bereitstellungen und Pods erforderlich sind, wenn unsere Anwendung mit der Anwendung identisch ist, die im ersten Beispiel ohne Kubernetes-Controller bereitgestellt wurde.
Kubernetes-Operatoren liefern den Ansatz zur Verwaltung komplexer Anwendungen, der von Natur aus skalierbarer (und einfacher) sind als die Bereitstellung von Kubernetes-Clustern ohne Operator. Der Endnutzer muss sich somit nur um die Konfiguration kümmern, die ihm zur Verfügung gestellt wurde. Der Operator verwaltet den Control Loop sowie den Status der Anwendung.
Operatoren können zur Automatisierung einer Vielzahl von Prozessen beitragen. Nach der Bereitstellung einer Konfigurationsdatei können wir beispielsweise eine konfigurierte Umgebung oder einen konfigurierten Dienst wie Cache (Redis, Memchace), Proxy (NGNX, HAProxy), Datenbanken (MySQL), etc., in nur wenigen Sekunden erhalten.
Schauen Sie sich diesen spannenden Twitter-Beitrag über empfehlenswerte K8s-Operatoren an:
What are your favorite Kubernetes operators/controllers?
Criteria:
– open-source
– day-2 ops (i.e., no install-smth. operators)Mine is github .com/inlets/inlets-operator. I like it because it uses very few dependencies, and the codebase is small but not trivial.
Send links 🤗
— Ivan Velichko (@iximiuz) November 11, 2021
Es gibt heutzutage viele fantastische Operatoren, wie den Prometheus-Operator für den nativen Einsatz von Kubernetes und die Verwaltung von Prometheus sowie den dazugehörigen Überwachungskomponenten. In unserem Blog-Eintrag über die Einrichtung des Prometheus-Operators zur Kubernetes-Überwachung findet sich alles Wissenswerte rund um das Thema.
Bevor Sie jedoch gleich über die benutzerdefinierte Erstellung eines Operators nachdenken, ist es immer sinnvoll, vorab zu prüfen, was bereits vorhanden ist. Wie bereits erwähnt, ist OperatorHub eine großartige Ressource, in der die Kubernetes-Community eine Vielzahl von Operatoren teilt.
Was aber, wenn wir einen benutzerdefinierten Operator für etwas entwickeln möchten, das für eine bestimmte Anwendungsarchitektur typisch ist? Dafür gibt es verschiedene Möglichkeiten.
Mit dem Operator SDK können wir selbst mit dem Aufbau von Operatoren beginnen. Der einfachste Weg, um mit einem Operator zu beginnen, ist die Verwendung des Helm-Operators.
Der Helm-Ansatz ermöglicht es uns, ein Helm-Diagramm zu erstellen und dieses auf einen Operator anzuwenden. Dies kommt einem ausgereiften Operatoren für ein bereits vorhandenes Diagramm am nächsten. Wenn Sie mit Helm nicht besonders vertraut sind, könnte Sie unser Blog-Beitrag interessieren, in dem alles Wissenswerte rund um den Beitrag von Helm zu einer Kubernetes-Architektur behandelt wird.
Die Betreiberreife eines Operators lässt sich in fünf Stufen unterteilen:
Operator-Reifegrade (Quelle)
Helm selbst erreicht die ersten beiden Reifegrade.
Für Operatoren, die die Reifegrade 3-5 erreichen, sind Go und Ansible die beliebtesten Technologien. Mit dem Operator SDK können wir Operatoren mit Helm, Go, Ansible und anderen Technologien erstellen. Vielleicht interessiert Sie auch unser Ansible-Tutorial, das Sie Schritt für Schritt durch die Installation und Einrichtung dieses unglaublich nützlichen Open-Source-Tools von RedHat führt.
Der Operator, den wir im folgenden Beispiel mit Operator SDK und Ansible erstellen werden, führt zwei Kernfunktionen aus:
In diesem Abschnitt werden wir uns nicht auf den eigentlichen Installationsprozess konzentrieren. Eine schrittweise Anleitung zur Installation des Kubernetes Operator SDK finden Sie bei GitHub. Hier konzentrieren wir uns auf den Prozess, der zum Schreiben eines Operators benötigt wird.
Wir müssen zuerst ein neues Projekt erstellen, was wir über die CLI (Befehlszeilenschnittstelle) machen:
operator-sdk new my-first-operator --api-version=krusche.io/v1alpha1 --kind=/v1alpha1 --kind=ResourcesAndLimits --type=ansible cd my-first-operator
Dieser Befehl erstellt ein Projekt mit dem Operator, der sich mit der APIVersion krusche.io/v1alpha1 und Kind ResourcesAndLimits bei ResourcesAndLimits anmeldet.
Das Verzeichnis sollte folgendermaßen strukturiert werden:
Directory/File – Goal
build/ – Enthält Skripte, mit denen das Operator-SDK zusammengestellt und initialisiert wird
deploy/ – Enthält eine Reihe von Kubernetes-Manifesten, mit denen der Operator im Cluster bereitgestellt wird
roles/ – Enthält Ansible-Roles
watch.yaml – Enthält Group, Version, Kind und Methode zum Starten von Ansible
Die Datei watches enthält:
Hier ist ein Beispiel für die Datei Watches:
--- - version: v1alpha1 group: foo.example.com kind: Foo role: /opt/ansible/roles/Foo - version: v1alpha1 group: bar.example.com kind: Bar playbook: /opt/ansible/playbook.yml - version: v1alpha1 group: baz.example.com kind: Baz playbook: /opt/ansible/baz.yml reconcilePeriod: 0 manageStatus: false vars: foo: bar
Da unser Operator Namespaces erstellt, benötigt er die Rechte für ein Cluster, nicht nur für einen Namespace.
In der Datei deploy/role:
apiVersion: rbac.authorization.k8s.io/v1 #kind: Role kind: ClusterRole metadata: creationTimestamp: null name: my-first-operator
Außerdem müssen Namespaces, Resourcequotas, Limitranges zu Resources und apiGroups: «» zu Rules hinzugefügt werden.
apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: creationTimestamp: null name: my-first-operator rules: - apiGroups: - "" resources: - pods - services - services/finalizers - endpoints - persistentvolumeclaims - events - configmaps - secrets - namespaces - resourcequotas - limitranges
In der Datei deploy/role_binding.yaml müssen Sie die folgenden Änderungen vornehmen:
#kind: RoleBinding kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: my-first-operator subjects: - kind: ServiceAccount name: my-first-operator namespace: default roleRef: #kind: Role kind: ClusterRole name: my-first-operator apiGroup: rbac.authorization.k8s.io
In der Datei deploy/operator.yaml müssen Sie die folgende Änderung anwenden:
env: - name: WATCH_NAMESPACE value: "" #valueFrom: # fieldRef: # fieldPath: metadata.namespace
In roles/resourcesandlimits/tasks/main.yml ändern wir alles zu:
--- # tasks file for resourcesandlimits - name: Create namespace k8s: definition: kind: Namespace apiVersion: v1 metadata: name: '{{ meta.name }}' ignore_errors: true - name: Create Resource Quota k8s: definition: kind: ResourceQuota apiVersion: v1 metadata: name: '{{ meta.name }}-resourcequota' namespace: '{{ meta.name }}' spec: hard: limits.cpu: "{{ limits_cpu }}" limits.memory: "{{ limits_memory }}" requests.cpu: "{{ requests_cpu }}" requests.memory: "{{ requests_memory }}" requests.storage: "{{ requests_storage }}" pods: "{{ limit_pods }}" services: "{{ limit_services }}" services.loadbalancers: 0 services.nodeports: 0 replicationcontrollers: 0 - name: Create Limit Ranges k8s: definition: kind: LimitRange apiVersion: v1 metadata: name: '{{ meta.name }}-limitrange' namespace: '{{ meta.name }}' spec: limits: - type: Pod maxLimitRequestRatio: cpu: "{{ max_limit_request_ratio_cpu }}" memory: "{{ max_limit_request_ratio_memmory }}" - type: PersistentVolumeClaim max: storage: "{{ max_storage }}" min: storage: "{{ min_storage }}"
In der Datei deploy/crds/krusche.io_v1alpha1_resourcesandlimits_cr.yaml ändern wir ebenfalls alles zu:
apiVersion: krusche.io/v1alpha1 kind: ResourcesAndLimits metadata: name: developers-team-a spec: limitsCpu: 5 limitsMemory: 5Gi requestsCpu: 5 requestsMemory: 5Gi requestsStorage: 204Gi limitPods: 10 limitServices: 10 maxLimitRequestRatioCpu: 2 maxLimitRequestRatioMemmory: 2 maxStorage: 100Gi minStorage: 20Gi
Das gilt es zu beachten:
Variablen von CR:
Im nächsten Schritt stellen wir die CRD bereit:
kubectl create -f deploy/crds/krusche.io_resourcesandlimits_crd.yaml
Erstellen Sie den Operator und schreiben Sie Folgendes in die Registry:
operator-sdk build example/my-first-operator:0.0.1 docker push example/my-first-operator:0.0.1
Der nächste Schritt besteht darin, Docker-Image und imagePullPolicy in deploy/operator.yaml zu ändern:
sed -i 's|{{ REPLACE_IMAGE }}|example/my-first-operator:0.0.1|g' deploy/operator.yaml sed -i 's|{{ pull_policy\|default('\''Always'\'') }}|Always|g' deploy/operator.yaml
Für macOS:
sed -i "" 's|{{ REPLACE_IMAGE }}|example/my-first-operator:0.0.1|g' deploy/operator.yaml sed -i "" 's|{{ pull_policy\|default('\''Always'\'') }}|Always|g' deploy/operator.yaml
Der nächste Schritt sieht wie folgt aus:
kubectl create -f deploy/service_account.yaml kubectl create -f deploy/role.yaml kubectl create -f deploy/role_binding.yaml kubectl create -f deploy/operator.yaml
Nach dem erfolgreichen Start des Operators müssen wir unsere CR bereitstellen, um einen Namespace zu erstellen:
kubectl apply -f deploy/crds/krusche.io_v1alpha1_resourcesandlimits_cr.yaml
Schauen wir uns hierzu unseren CF etwas genauer an:
kubectl describe resourcesandlimits.krusche.io developers-team-a Name: developers-team-a Namespace: default Labels: <none> Annotations: kubectl.kubernetes.io/last-applied-configuration: {"apiVersion":"krusche.io/v1alpha1","kind":"ResourcesAndLimits","metadata":{"annotations":{},"name":"developers-team-a","namespace":"de... API Version: krusche.io/v1alpha1 Kind: ResourcesAndLimits Metadata: Creation Timestamp: 2019-12-17T11:53:34Z Generation: 1 Resource Version: 20309 Self Link: /apis/krusche.io/v1alpha1/namespaces/default/resourcesandlimits/developers-team-a UID: 0db952fb-2bed-4511-9309-9f9fbe11af66 Spec: Limit Pods: 10 Limit Services: 10 Limits Cpu: 5 Limits Memory: 5Gi Max Limit Request Ratio Cpu: 2 Max Limit Request Ratio Memmory: 2 Max Storage: 100Gi Min Storage: 20Gi Requests Cpu: 5 Requests Memory: 5Gi Requests Storage: 204Gi Status: Conditions: Ansible Result: Changed: 2 Completion: 2019-12-17T11:55:50.36218 Failures: 0 Ok: 4 Skipped: 0 Last Transition Time: 2019-12-17T11:53:34Z Message: Awaiting next reconciliation Reason: Successful Status: True Type: Running Events: <none>
Unser Operator sollte den Namespace developers-team-a erstellen:
kubectl get namespaces
Wir können sehen, dass unser Namespace erscheint:
NAME STATUS AGE default Active 3h16m developers-team-a Active 5m46s kube-node-lease Active 3h16m kube-public Active 3h16m kube-system Active 3h16m
Als Nächstes werfen wir einen Blick auf die Einstellungen des Namespace:
kubectl describe namespaces developers-team-a
Wir können sehen, dass die Einstellungen umgesetzt wurden:
Name: developers-team-a Labels: <none> Annotations: cattle.io/status: {"Conditions":[{"Type":"ResourceQuotaInit","Status":"True","Message":"","LastUpdateTime":"2019-12-17T11:53:43Z"},{"Type":"InitialRolesPopu... lifecycle.cattle.io/create.namespace-auth: true operator-sdk/primary-resource: default/developers-team-a operator-sdk/primary-resource-type: ResourcesAndLimits.krusche.io Status: Active Resource Quotas Name: developers-team-a-resourcequota Resource Used Hard -------- --- --- limits.cpu 0 5 limits.memory 0 5Gi pods 0 10 replicationcontrollers 0 0 requests.cpu 0 5 requests.memory 0 5Gi requests.storage 0 204Gi services 0 10 services.loadbalancers 0 0 services.nodeports 0 0 Resource Limits Type Resource Min Max Default Request Default Limit Max Limit/Request Ratio ---- -------- --- --- --------------- ------------- ----------------------- Pod cpu - - - - 2 Pod memory - - - - 2 PersistentVolumeClaim storage 20Gi 100Gi - -
Nun widmen wir uns einem weiteren Controller und fügen deployment Nginx zum Namespace hinzu.
Erstellung von deploy / crds / nginx_cr.yaml:
apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: name: nginx.krusche.io spec: group: krusche.io names: kind: Nginx listKind: NginxList plural: nginx singular: nginx scope: Namespaced subresources: status: {} validation: openAPIV3Schema: type: object x-kubernetes-preserve-unknown-fields: true versions: - name: v1alpha1 served: true storage: true
Und deploy/crds/nginx-1.17.6.yaml:
apiVersion: krusche.io/v1alpha1 kind: Nginx metadata: name: nginx-1-17-6 namespace: nginx-namespace spec: size: 5 version: 1.17.6
Die folgenden Zeilen werden der watches.yml-Datei hinzugefügt:
- version: v1alpha1 group: krusche.io kind: Nginx role: /opt/ansible/roles/nginx
Kopieren Sie das resourcesandlimits Directory und nennen Sie es um in Nginx:
cp -R roles/resourcesandlimits roles/nginx
Ändern Sie die Inhalte von roles/nginx/tasks/main.yml file zu:
--- - name: start nginx k8s: definition: kind: Deployment apiVersion: apps/v1 metadata: name: '{{ meta.name }}-nginx' namespace: '{{ meta.namespace }}' spec: replicas: "{{size}}" selector: matchLabels: app: nginx version: "nginx-{{version}}" template: metadata: labels: app: nginx version: "nginx-{{version}}" spec: containers: - name: nginx image: "nginx:{{version}}" ports: - containerPort: 80
Als Nächstes müssen wir den Operator mit der neuen Version des Docker-Images erstellen:
operator-sdk build example/my-first-operator:0.0.2 docker push example/my-first-operator:0.0.2
Ändern Sie das Docker-Image in deploy/operator.yaml:
sed -i 's|example/my-first-operator:0.0.1|example/my-first-operator:0.0.2|g' deploy/operator.yaml
Für macOS:
sed -i "" 's|example/my-first-operator:0.0.1|example/my-first-operator:0.0.2|g' deploy/operator.yaml
CR hinzufügen:
kubectl apply -f deploy/crds/nginx_cr.yaml
Operator updaten:
kubectl apply -f deploy/operator.yaml
Namespace erstellen und unsere CR bereitstellen:
kubectl create namespace nginx-namespace kubectl apply -f deploy/crds/nginx-1.17.6.yaml
Nun können wir alles prüfen:
kubectl -n nginx-namespace get pod NAME READY STATUS RESTARTS AGE kc-nginx-1-17-6-nginx-859d7dcf99-ddbzm 1/1 Running 0 24s kc-nginx-1-17-6-nginx-859d7dcf99-j884q 1/1 Running 0 24s kc-nginx-1-17-6-nginx-859d7dcf99-mvj5s 1/1 Running 0 24s kc-nginx-1-17-6-nginx-859d7dcf99-wpcfj 1/1 Running 0 24s kc-nginx-1-17-6-nginx-859d7dcf99-x7szg 1/1 Running 0 53s
Glückwunsch! Sie haben soeben Ihren eigenen Kubernetes-Operator erstellt.
Der von uns erstellte Operator übernimmt die K8-Ressourcen über die Kubernetes-API und automatisiert deren Verwaltung, ohne dass manuelle Eingriffe erforderlich sind. Letztendlich helfen Ihnen die Kubernetes-Operatoren bei der Automatisierung von manuellen Routinearbeiten und sollten daher so oft es geht genutzt werden,
Welcher Tech-Stack wurde verwendet? Alles, was wir zum Aufbau unseres Operators benötigt haben, waren das Operator SDK Framework sowie Ansible.
Wann gelingt IT Outsourcing?
(und wann nicht?)