Introductie van shell-operator: het maken van operators voor Kubernetes is nu nog eenvoudiger

Er zijn al artikelen op onze blog geweest waar we het over hebben operatormogelijkheden in Kubernetes en hoe schrijf zelf een eenvoudige operator. Deze keer willen we onze Open Source-oplossing onder uw aandacht brengen, die het maken van operators naar een supereenvoudig niveau brengt - bekijk shell-operator!

Waarom?

Het idee van een shell-operator is vrij eenvoudig: abonneer u op gebeurtenissen van Kubernetes-objecten en wanneer deze gebeurtenissen worden ontvangen, start u een extern programma en geeft u informatie over de gebeurtenis:

Introductie van shell-operator: het maken van operators voor Kubernetes is nu nog eenvoudiger

De behoefte daaraan ontstond toen er tijdens het opereren van clusters kleine taken opdoken die we heel graag op de juiste manier wilden automatiseren. Al deze kleine taken zijn opgelost met behulp van eenvoudige bash-scripts, hoewel het, zoals je weet, beter is om operators in Golang te schrijven. Het is duidelijk dat investeren in de volledige ontwikkeling van een operator voor elk van zulke kleine taken niet effectief zou zijn.

Operator in 15 minuten

Laten we eens kijken naar een voorbeeld van wat er kan worden geautomatiseerd in een Kubernetes-cluster en hoe de shell-operator kan helpen. Een voorbeeld zou het volgende zijn: het repliceren van een geheim om toegang te krijgen tot het docker-register.

Pods die afbeeldingen uit een privéregister gebruiken, moeten in hun manifest een link bevatten naar een geheim met gegevens voor toegang tot het register. Dit geheim moet in elke naamruimte worden gemaakt voordat er pods worden gemaakt. Dit kan handmatig worden gedaan, maar als we dynamische omgevingen opzetten, wordt de naamruimte voor één applicatie veel. En als er ook geen 2-3 toepassingen zijn... wordt het aantal geheimen erg groot. En nog iets over geheimen: ik zou van tijd tot tijd de sleutel voor toegang tot het register willen wijzigen. Eventueel, handmatige handelingen als oplossing volkomen ineffectief — we moeten het creëren en bijwerken van geheimen automatiseren.

Eenvoudige automatisering

Laten we een shellscript schrijven dat elke N seconden wordt uitgevoerd en naamruimten controleert op de aanwezigheid van een geheim, en als er geen geheim is, wordt het aangemaakt. Het voordeel van deze oplossing is dat het lijkt op een shellscript in cron - een klassieke en begrijpelijke benadering voor iedereen. Het nadeel is dat er in de periode tussen de lanceringen een nieuwe naamruimte kan worden gecreëerd en dat deze enige tijd geheim zal blijven, wat zal leiden tot fouten bij het lanceren van pods.

Automatisering met shell-operator

Om ons script correct te laten werken, moet de klassieke cron-start worden vervangen door een start wanneer een naamruimte wordt toegevoegd: in dit geval kunt u een geheim aanmaken voordat u het gebruikt. Laten we eens kijken hoe we dit kunnen implementeren met behulp van de shell-operator.

Laten we eerst eens naar het script kijken. Scripts in termen van shell-operatoren worden hooks genoemd. Elke haak wanneer deze met een vlag wordt uitgevoerd --config informeert de shell-operator over zijn bindingen, d.w.z. op welke evenementen het moet worden gelanceerd. In ons geval zullen we gebruiken onKubernetesEvent:

#!/bin/bash
if [[ $1 == "--config" ]] ; then
cat <<EOF
{
"onKubernetesEvent": [
  { "kind": "namespace",
    "event":["add"]
  }
]}
EOF
fi

Hier wordt beschreven dat we geïnteresseerd zijn in het toevoegen van evenementen (add) objecten van het type namespace.

Nu moet je de code toevoegen die wordt uitgevoerd wanneer de gebeurtenis plaatsvindt:

#!/bin/bash
if [[ $1 == "--config" ]] ; then
  # конфигурация
cat <<EOF
{
"onKubernetesEvent": [
{ "kind": "namespace",
  "event":["add"]
}
]}
EOF
else
  # реакция:
  # узнать, какой namespace появился
  createdNamespace=$(jq -r '.[0].resourceName' $BINDING_CONTEXT_PATH)
  # создать в нём нужный секрет
  kubectl create -n ${createdNamespace} -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
  ...
data:
  ...
EOF
fi

Geweldig! Het resultaat was een klein, mooi script. Om het “nieuw leven in te blazen”, zijn er nog twee stappen over: bereid de afbeelding voor en start deze in het cluster.

Een afbeelding voorbereiden met een haak

Als je naar het script kijkt, zie je dat de commando's worden gebruikt kubectl и jq. Dit betekent dat de afbeelding de volgende dingen moet hebben: onze hook, een shell-operator die gebeurtenissen monitort en de hook uitvoert, en de commando's die door de hook worden gebruikt (kubectl en jq). Hub.docker.com heeft al een kant-en-klaar image waarin shell-operator, kubectl en jq zijn verpakt. Het enige dat overblijft is het toevoegen van een eenvoudige haak Dockerfile:

$ cat Dockerfile
FROM flant/shell-operator:v1.0.0-beta.1-alpine3.9
ADD namespace-hook.sh /hooks

$ docker build -t registry.example.com/my-operator:v1 . 
$ docker push registry.example.com/my-operator:v1

Draait in een cluster

Laten we opnieuw naar de hook kijken en deze keer opschrijven welke acties en met welke objecten deze in het cluster uitvoert:

  1. abonneert zich op evenementen voor het maken van naamruimten;
  2. creëert een geheim in een andere naamruimte dan degene waar het wordt gelanceerd.

Het blijkt dat de pod waarin onze afbeelding wordt gelanceerd toestemming moet hebben om deze acties uit te voeren. Dit kunt u doen door uw eigen ServiceAccount aan te maken. De toestemming moet worden gedaan in de vorm van ClusterRole en ClusterRoleBinding, omdat we zijn geïnteresseerd in objecten uit het hele cluster.

De uiteindelijke beschrijving in YAML ziet er ongeveer zo uit:

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: monitor-namespaces-acc

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
  name: monitor-namespaces
rules:
- apiGroups: [""]
  resources: ["namespaces"]
  verbs: ["get", "watch", "list"]
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["get", "list", "create", "patch"]

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: monitor-namespaces
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: monitor-namespaces
subjects:
  - kind: ServiceAccount
    name: monitor-namespaces-acc
    namespace: example-monitor-namespaces

U kunt de samengestelde image als een eenvoudige implementatie starten:

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: my-operator
spec:
  template:
    spec:
      containers:
      - name: my-operator
        image: registry.example.com/my-operator:v1
      serviceAccountName: monitor-namespaces-acc

Voor het gemak wordt er een aparte naamruimte gemaakt waar de shell-operator wordt gestart en de gemaakte manifesten worden toegepast:

$ kubectl create ns example-monitor-namespaces
$ kubectl -n example-monitor-namespaces apply -f rbac.yaml
$ kubectl -n example-monitor-namespaces apply -f deployment.yaml

Dat is alles: de shell-operator zal starten, zich abonneren op gebeurtenissen voor het maken van naamruimten en de hook uitvoeren wanneer dat nodig is.

Introductie van shell-operator: het maken van operators voor Kubernetes is nu nog eenvoudiger

Dus de een eenvoudig shellscript werd een echte operator voor Kubernetes en werkt als onderdeel van een cluster. En dit alles zonder het complexe proces van het ontwikkelen van operators in Golang:

Introductie van shell-operator: het maken van operators voor Kubernetes is nu nog eenvoudiger

Er is nog een voorbeeld over deze kwestie...Introductie van shell-operator: het maken van operators voor Kubernetes is nu nog eenvoudiger

We zullen de betekenis ervan in meer detail onthullen in een van de volgende publicaties.

filtering

Objecten volgen is goed, maar er moet vaak wel op gereageerd worden het wijzigen van enkele objecteigenschappen, bijvoorbeeld om het aantal replica's in Deployment te wijzigen of om objectlabels te wijzigen.

Wanneer er een gebeurtenis binnenkomt, ontvangt de shell-operator het JSON-manifest van het object. We kunnen de eigenschappen selecteren die ons interesseren in deze JSON en de haak slaan alleen als ze veranderen. Hiervoor is een veld beschikbaar jqFilter, waar u de jq-expressie moet opgeven die op het JSON-manifest wordt toegepast.

Als u bijvoorbeeld wilt reageren op wijzigingen in labels voor implementatieobjecten, moet u het veld filteren labels uit het veld metadata. De configuratie zal als volgt zijn:

cat <<EOF
{
"onKubernetesEvent": [
{ "kind": "deployment",
  "event":["update"],
  "jqFilter": ".metadata.labels"
}
]}
EOF

Deze jqFilter-expressie verandert het lange JSON-manifest van Deployment in een korte JSON met labels:

Introductie van shell-operator: het maken van operators voor Kubernetes is nu nog eenvoudiger

shell-operator zal de hook alleen uitvoeren als deze korte JSON verandert, en wijzigingen in andere eigenschappen worden genegeerd.

Hook-lanceringscontext

Met de hook-configuratie kunt u verschillende opties voor evenementen opgeven, bijvoorbeeld 2 opties voor evenementen van Kubernetes en 2 planningen:

{"onKubernetesEvent":[
  {"name":"OnCreatePod",
  "kind": "pod",
  "event":["add"]
  },
  {"name":"OnModifiedNamespace",
  "kind": "namespace",
  "event":["update"],
  "jqFilter": ".metadata.labels"
  }
],
"schedule": [
{ "name":"every 10 min",
  "crontab":"* */10 * * * *"
}, {"name":"on Mondays at 12:10",
"crontab": "* 10 12 * * 1"
]}

Een kleine uitweiding: ja, shell-operator ondersteunt scripts in crontab-stijl uitvoeren. Meer details zijn te vinden in documentatie.

Om te onderscheiden waarom de hook is gestart, maakt de shell-operator een tijdelijk bestand aan en geeft het pad ernaartoe in een variabele door aan de hook BINDING_CONTEXT_TYPE. Het bestand bevat een JSON-beschrijving van de reden voor het uitvoeren van de hook. Elke 10 minuten zal de hook bijvoorbeeld draaien met de volgende inhoud:

[{ "binding": "every 10 min"}]

... en maandag begint het hiermee:

[{ "binding": "every 10 min"}, { "binding": "on Mondays at 12:10"}]

Voor onKubernetesEvent Er zullen meer JSON-triggers zijn, omdat het bevat een beschrijving van het object:

[
 {
 "binding": "onCreatePod",
 "resourceEvent": "add",
 "resourceKind": "pod",
 "resourceName": "foo",
 "resourceNamespace": "bar"
 }
]

De inhoud van de velden kan worden afgeleid uit hun namen en er kunnen meer details worden gelezen documentatie. Een voorbeeld van het ophalen van een resourcenaam uit een veld resourceName het gebruik van jq is al getoond in een hook die geheimen repliceert:

jq -r '.[0].resourceName' $BINDING_CONTEXT_PATH

U kunt op vergelijkbare wijze andere velden verkrijgen.

Wat is het volgende?

In de projectrepository, in /voorbeelden mappen, zijn er voorbeelden van hooks die klaar zijn om op een cluster te draaien. Als je je eigen hooks schrijft, kun je deze als basis gebruiken.

Er is ondersteuning voor het verzamelen van statistieken met behulp van Prometheus. De beschikbare statistieken worden in de sectie beschreven METRISCH.

Zoals je misschien wel kunt raden, is de shell-operator geschreven in Go en gedistribueerd onder een Open Source-licentie (Apache 2.0). Wij zullen dankbaar zijn voor elke ontwikkelingshulp project op GitHub: en sterren, en problemen, en pull-verzoeken.

Om de sluier van geheimhouding op te heffen, zullen wij u ook informeren dat de shell-operator is klein onderdeel van ons systeem dat add-ons geïnstalleerd in het Kubernetes-cluster up-to-date kan houden en verschillende automatische acties kan uitvoeren. Lees meer over dit systeem vertelde letterlijk op maandag op HighLoad++ 2019 in St. Petersburg - we zullen binnenkort de video en het transcript van dit rapport publiceren.

We hebben een plan om de rest van dit systeem open te stellen: de add-on-operator en onze verzameling haken en modules. Trouwens, de add-on-operator is er al beschikbaar op github, maar de documentatie ervoor is nog onderweg. De release van de verzameling modules staat gepland voor de zomer.

Stay tuned!

PS

Lees ook op onze blog:

Bron: www.habr.com

Voeg een reactie