Hinweis perev. : Mit diesem Artikel von Scott Rahner, einem Ingenieur bei Dow Jones, setzen wir die Reihe zahlreicher Materialien fort, die erklären, wie Kubernetes funktioniert, wie seine grundlegenden Komponenten funktionieren, miteinander verbunden und verwendet werden. Diesmal ist dies ein praktischer Hinweis mit Beispielcode zum Erstellen eines Hooks in Kubernetes, der vom Autor unter dem Vorwand demonstriert wurde, automatisch Beiwagencontainer zu erstellen.
(Foto von Gordon A. Maxwell, im Internet gefunden.)Als ich anfing, Beiwagencontainer und Servicegitter zu untersuchen, musste ich verstehen, wie der Schlüsselmechanismus funktioniert - das automatische Einsetzen eines Beiwagencontainers. Bei der Verwendung von Systemen wie Istio oder Consul wird beim Bereitstellen des Containers mit der Anwendung plötzlich der bereits konfigurierte Envoy-Container in seinem Pod angezeigt
(eine ähnliche Situation tritt bei Conduit auf, über das wir zu Beginn des Jahres geschrieben haben - ca. Transl.) . Was? Wie? Also begann meine Forschung ...
Für diejenigen, die es nicht wissen, ist ein Beiwagen-Container ein Container, der neben den Containern der Anwendung bereitgestellt wird, um dieser Anwendung auf irgendeine Weise zu „helfen“. Ein Beispiel für eine solche Verwendung ist ein Proxy zum Verwalten des Datenverkehrs und zum Beenden von TLS-Sitzungen, ein Container zum Streamen von Protokollen und Metriken, ein Container zum Scannen von Sicherheitsproblemen ... Die Idee besteht darin, verschiedene Aspekte der gesamten Anwendung von der Geschäftslogik zu isolieren, indem für jeden separate Container verwendet werden Funktionen.
Bevor ich fortfahre, werde ich meine Erwartungen skizzieren. Der Zweck dieses Artikels besteht nicht darin, die Feinheiten und Verwendungsszenarien von Docker, Kubernetes, Service Mesh usw. zu erläutern, sondern einen leistungsstarken Ansatz zur Erweiterung der Funktionen dieser Technologien aufzuzeigen. Der Artikel richtet sich an diejenigen, die bereits mit dem Einsatz dieser Technologien vertraut sind oder zumindest viel darüber gelesen haben. Um den praktischen Teil in Aktion zu testen, benötigen Sie einen Computer mit bereits konfigurierten Docker und Kubernetes. Der einfachste Weg, dies zu tun, ist
https://docs.docker.com/docker-for-windows/kubernetes/ (ein Windows-Handbuch, das mit Docker für Mac funktioniert).
(Hinweis perev .: Als Alternative zu Benutzern von Linux- und * nix-Systemen können wir Minikube anbieten.)Gesamtbild
Schauen wir uns zunächst Kubernetes an:
Kube Arch lizenziert unter CC BY 4.0Wenn Sie etwas für Kubernetes bereitstellen möchten, müssen Sie das Objekt an kube-apiserver senden. Dies geschieht meistens durch Übergeben von Argumenten oder einer YAML-Datei an kubectl. In diesem Fall durchläuft der API-Server mehrere Phasen, bevor die Daten direkt in etcd abgelegt und die entsprechenden Aufgaben geplant werden:

Diese Reihenfolge ist wichtig, um zu verstehen, wie das Einsetzen von Beiwagencontainern funktioniert. Insbesondere müssen Sie auf die Zugangskontrolle achten, bei der Kubernetes Objekte vor dem Speichern validiert und gegebenenfalls ändert
(weitere Einzelheiten zu diesem Schritt finden Sie im Kapitel "Zugangskontrolle" in diesem Artikel - ca. Übersetzung) . Mit Kubernetes können Sie auch
Webhooks registrieren, die benutzerdefinierte Validierungen und
Mutationen durchführen können .
Das Erstellen und Registrieren Ihrer Hooks ist jedoch nicht so einfach und gut dokumentiert. Ich musste mehrere Tage damit verbringen, die Dokumentation zu lesen und erneut zu lesen sowie den Istio und Consul Code zu analysieren. Und wenn es um den Code für einige der API-Antworten ging, verbrachte ich mindestens einen halben Tag damit, zufällige Versuche und Irrtümer durchzuführen.
Nachdem das Ergebnis erreicht wurde, wird es meiner Meinung nach unfair sein, es nicht mit Ihnen allen zu teilen. Es ist einfach und gleichzeitig effektiv.
Code
Der Name Webhook spricht für sich selbst - es ist ein HTTP-Endpunkt, der die in Kubernetes definierte API implementiert. Sie erstellen einen API-Server, den Kubernetes aufrufen kann, bevor Sie sich mit Bereitstellungen befassen. Ich musste mich hier mit Schwierigkeiten auseinandersetzen, da nur wenige Beispiele verfügbar sind, von denen einige nur Komponententests von Kubernetes sind, andere inmitten einer riesigen Codebasis versteckt sind ... und alle in Go geschrieben sind. Aber ich habe mich für eine günstigere Option entschieden - Node.js:
const app = express(); app.use(bodyParser.json()); app.post('/mutate', (req, res) => { console.log(req.body) console.log(req.body.request.object) let adminResp = {response:{ allowed: true, patch: Buffer.from("[{ \"op\": \"add\", \"path\": \"/metadata/labels/foo\", \"value\": \"bar\" }]").toString('base64'), patchType: "JSONPatch", }} console.log(adminResp) res.send(adminResp) }) const server = https.createServer(options, app);
( index.js )Der Pfad zur API - in diesem Fall
/mutate
- kann beliebig sein (er sollte nur der YAML entsprechen, die in Zukunft an Kubernetes übergeben wird). Für ihn ist es wichtig, den vom API-Server empfangenen JSON zu sehen und zu verstehen. In diesem Fall ziehen wir nichts aus JSON heraus, aber es kann in anderen Szenarien nützlich sein. Im obigen Code aktualisieren wir JSON. Dafür sind zwei Dinge erforderlich:
- Lernen und verstehen Sie JSON Patch .
- Konvertieren Sie einen JSON-Patch-Ausdruck korrekt in ein Array von Bytes, die mit base64 codiert sind.
Sobald dies erledigt ist, müssen Sie nur noch die Antwort mit einem sehr einfachen Objekt an den API-Server übergeben. In diesem Fall fügen wir jedem Pod, der zu uns kommt, das Label
foo=bar
.
Bereitstellung
Nun, wir haben Code, der Anforderungen vom Kubernetes-API-Server akzeptiert und darauf reagiert, aber wie wird er bereitgestellt? Und wie kann man Kubernetes dazu bringen, uns diese Anfragen umzuleiten? Sie können einen solchen Endpunkt überall dort bereitstellen, wo Sie den Kubernetes-API-Server erreichen können. Am einfachsten ist es, den Code im Kubernetes-Cluster selbst bereitzustellen, wie in diesem Beispiel beschrieben. Ich habe versucht, das Beispiel so einfach wie möglich zu gestalten, daher verwende ich für alle Aktionen nur Docker und kubectl. Beginnen wir mit der Erstellung eines Containers, in dem der Code ausgeführt wird:
FROM node:8 USER node WORKDIR /home/node COPY index.js . COPY package.json . RUN npm install # TLS CMD node index.js
( Dockerfile )Anscheinend ist hier alles sehr einfach. Nehmen Sie das Community-Image vom Knoten und legen Sie den Code darin ab. Jetzt können Sie eine einfache Montage durchführen:
docker build . -t localserver
Der nächste Schritt besteht darin, eine Bereitstellung in Kubernetes zu erstellen:
apiVersion: apps/v1 kind: Deployment metadata: name: webhook-server spec: replicas: 1 selector: matchLabels: component: webhook-server template: metadata: labels: component: webhook-server spec: containers: - name: webhook-server imagePullPolicy: Never image: localserver
( deploy.yaml )Beachten Sie, wie wir auf das gerade erstellte Bild angespielt haben? Es könnte genauso gut ein Pod sein und etwas anderes, mit dem wir einen Dienst in Kubernetes verbinden können. Definieren Sie nun diesen Service:
apiVersion: v1 kind: Service metadata: name: webhook-service spec: ports: - port: 443 targetPort: 8443 selector: component: webhook-server
In Kubernetes wird also ein Endpunkt mit einem internen Namen angezeigt, der auf unseren Container verweist. Der letzte Schritt besteht darin, Kubernetes mitzuteilen, dass der API-Server diesen Dienst aufrufen soll, wenn er bereit ist,
Mutationen vorzunehmen:
apiVersion: admissionregistration.k8s.io/v1beta1 kind: MutatingWebhookConfiguration metadata: name: webhook webhooks: - name: webhook-service.default.svc failurePolicy: Fail clientConfig: service: name: webhook-service namespace: default path: "/mutate" # base64- rootCA.crt # `cat rootCA.crt | base64 | tr -d '\n'` # . caBundle: "==" rules: - operations: [ "CREATE" ] apiGroups: [""] apiVersions: ["v1"] resources: ["pods"]
( hook.yaml )Der Name und der Pfad hier können beliebig sein, aber ich habe versucht, sie so aussagekräftig wie möglich zu gestalten. Wenn Sie den Pfad ändern, müssen Sie den entsprechenden Code in JavaScript ändern. Webhook-
failurePolicy
ist ebenfalls
failurePolicy
- sie bestimmt, ob das Objekt gespeichert werden soll, wenn der Hook einen Fehler zurückgibt oder fehlschlägt. In diesem Fall weisen wir Kubernetes an, die Verarbeitung nicht fortzusetzen. Schließlich die Regeln: Sie ändern sich je nachdem, welche API-Aufrufe Sie von Kubernetes erwarten. In diesem Fall müssen wir, da wir versuchen, das Einfügen eines Beiwagencontainers zu emulieren, Anforderungen abfangen, um einen Pod zu erstellen.
Das ist alles! So einfach ... aber was ist mit Sicherheit? RBAC ist ein Aspekt, der im Artikel nicht behandelt wird. Ich gehe davon aus, dass Sie das Beispiel in Minikube oder in Kubernetes ausführen, das mit Docker für Windows / Mac geliefert wird. Ich werde Ihnen jedoch ein weiteres notwendiges Element erläutern. Der Kubernetes-API-Server greift nur mit HTTPS auf Endpunkte zu, sodass für die Anwendung SSL-Zertifikate erforderlich sind. Sie müssen Kubernetes auch mitteilen, wer die Zertifizierungsstelle des Stammzertifikats ist.
TLS
Nur zu Demonstrationszwecken (!!!) habe ich der
Dockerfile
Code
Dockerfile
, um eine
Dockerfile
zu erstellen und damit das Zertifikat zu signieren:
RUN openssl genrsa -out rootCA.key 4096 RUN openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 1024 -out rootCA.crt \ -subj "/C=US/ST=New Jersey/L=Princeton /O=Dow Jones/OU=PIB/CN=*.default.svc/emailAddress=scott.rahner@dowjones.com" RUN openssl genrsa -out webhook.key 4096 RUN openssl req -new -key webhook.key -out webhook.csr \ -subj "/C=US/ST=New Jersey/L=Princeton /O=Dow Jones/OU=PIB/CN=webhook-service.default.svc/emailAddress=scott.rahner@dowjones.com" RUN openssl x509 -req -in webhook.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out webhook.crt -days 1024 -sha256 RUN cat rootCA.crt | base64 | tr -d '\n'
( Dockerfile )Bitte beachten Sie: Der letzte Schritt besteht darin, eine einzelne Zeile mit der in base64 codierten Stammzertifizierungsstelle anzuzeigen. Dies ist genau das, was für die Hook-Konfiguration erforderlich ist. Kopieren Sie diese Zeile in Ihren weiteren Tests
caBundle
in das Feld
caBundle
Datei
caBundle
.
Dockerfile
wirft Zertifikate direkt in
WORKDIR
, sodass JavaScript sie nur von dort nimmt und für den Server verwendet:
const privateKey = fs.readFileSync('webhook.key').toString(); const certificate = fs.readFileSync('webhook.crt').toString();
Jetzt unterstützt der Code den HTTPS-Start und teilt Kubernetes mit, wo wir zu finden sind und welchem Vertrauenszentrum wir vertrauen sollen. Es bleibt nur noch alles in einen Cluster einzubetten:
kubectl create -f deployment.yaml kubectl create -f service.yaml kubectl create -f hook.yaml
Fassen Sie zusammen
Deployment.yaml
startet einen Container, der die Hook-API über HTTPS bereitstellt, und gibt einen JSON-Patch zurück, um das Objekt zu ändern.Service.yaml
stellt einen Endpunkt für den Container webhook-service.default.svc
- webhook-service.default.svc
.Hook.yaml
teilt dem API-Server mit, wo er uns finden soll: https://webhook-service.default.svc/mutate
.
Versuchen wir es im Geschäft!
Alles wird in einem Cluster bereitgestellt - es ist Zeit, den Code in Aktion zu testen. Dazu fügen wir einen neuen Pod / eine neue Bereitstellung hinzu. Wenn alles richtig funktioniert, muss der Hook ein zusätzliches Label
foo
hinzufügen:
apiVersion: apps/v1 kind: Deployment metadata: name: test spec: replicas: 1 selector: matchLabels: component: test template: metadata: labels: component: test spec: containers: - name: test image: node:8 command: [ "/bin/sh", "-c", "--" ] args: [ "while true; do sleep 30; done;" ]
( test.yaml ) kubectl create -f test.yaml
Ok, wir haben gesehen, wie der
deployment.apps test created
... aber hat es geklappt?
kubectl describe pods test Name: test-6f79f9f8bd-r7tbd Namespace: default Node: docker-for-desktop/192.168.65.3 Start Time: Sat, 10 Nov 2018 16:08:47 -0500 Labels: component=test foo=bar
Großartig! Obwohl
test.yaml
ein einzelnes Label (
component
) gegeben wurde, erhielt der resultierende Pod zwei:
component
und
foo
.
Hausaufgaben
Aber warte! Verwenden wir diesen Code, um einen Beiwagencontainer zu erstellen? Ich habe gewarnt, dass ich zeigen werde,
wie man einen Beiwagen hinzufügt ... Und jetzt, mit dem Wissen und dem Code:
https://github.com/dowjones/k8s-webhook - experimentieren Sie und finden Sie heraus, wie Sie Ihren automatisch eingefügten Beiwagen herstellen können. Es ist ganz einfach: Sie müssen nur den richtigen JSON-Patch vorbereiten, der der Testbereitstellung einen zusätzlichen Container hinzufügt. Viel Spaß beim Orchestrieren!
PS vom Übersetzer
Lesen Sie auch in unserem Blog: