Wie kam dieser Beiwagencontainer hier [bei Kubernetes] an?

Ursprünglicher Autor: Scott Rahner
  • Übersetzung
Hinweis trans. A: In diesem Artikel, der von Scott Rahner - einem Ingenieur bei Dow Jones - verfasst wurde, setzen wir den Zyklus aus zahlreichen Materialien fort, die verfügbar sind, um zu erklären, wie Kubernetes funktioniert, wie sie funktionieren, miteinander verbunden sind und ihre grundlegenden Komponenten verwenden. Diesmal handelt es sich um eine praktische Notiz mit einem Beispiel für Code zum Erstellen eines Hooks in Kubernetes, der vom Autor unter dem Vorwand der automatischen Erstellung von Sidecar-Containern demonstriert wird.


(Der Autor des Fotos - Gordon A. Maxwell, im Internet gefunden.)

Als ich anfing, die Seitenwagenbehälter und das Servicegitter zu studieren, musste ich verstehen, wie der Schlüsselmechanismus funktioniert - das automatische Einsetzen eines Seitenwagenbehälters. Tatsächlich im Fall von Systemen wie Istio oder Konsul, wenn deploe Container - Anwendung plötzlich in seinem pod'e erscheint bereits Envoy Container konfiguriert (eine ähnliche Situation tritt in der Conduit, die wir schrieben zu Beginn des Jahres - ca. Stifte ..) . Was Wie So begann meine Forschung ...

Für diejenigen, die es nicht wissen, ist ein Sidecar-Container ein Container, der neben den Containern der Anwendung bereitgestellt wird, um dieser Anwendung in irgendeiner 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 für das Streaming von Protokollen und Metriken, ein Container zum Durchsuchen von Sicherheitsproblemen. Die Idee besteht darin, verschiedene Aspekte der gesamten Anwendung von der Geschäftslogik zu isolieren, indem für jeden der Container separate Container verwendet werden Funktionen.

Bevor ich fortfahre, werde ich meine Erwartungen beschreiben. Der Zweck dieses Artikels besteht nicht darin, die Feinheiten und Szenarien der Verwendung von Docker, Kubernetes, Service-Meshes usw. zu erläutern, sondern einen leistungsstarken Ansatz zur Erweiterung der Fähigkeiten 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 eine Maschine mit bereits konfigurierten Docker und Kubernetes. Der einfachste Weg dazu ist https://docs.docker.com/docker-for-windows/kubernetes/ (eine Windows-Anweisung, die in Docker für Mac funktioniert). (Hinweis. Trans.: Als Alternative zu Benutzern von Linux und * nix-Systemen können wir Minikube anbieten .)

Gesamtbild


Lassen Sie uns zunächst mit Kubernetes etwas näher kommen:


Kube Arch , lizenziert unter CC BY 4.0

Wenn Sie etwas an Kubernetes übergeben möchten, müssen Sie das Objekt an kube-apiserver senden. Meist wird dies durch Übergabe von Argumenten oder einer YAML-Datei an kubectl erreicht. In diesem Fall durchläuft der API-Server mehrere Schritte, bevor Daten direkt auf etcd abgelegt und die entsprechenden Aufgaben geplant werden:



Diese Reihenfolge ist wichtig, um zu verstehen, wie das Einfügen von Sidecar-Containern funktioniert. Insbesondere ist es notwendig , darauf zu achten Eintritt der Kontrolle , bei der Kubernetes und validiert, falls erforderlich, ändern Objekte vor speichern , um sie (mehr zu diesem Punkt, siehe Kap. „Access Control“ in diesem Artikel- ca. Trans.) . Mit Kubernetes können Sie auch Webhooks registrieren , die benutzerdefinierte Validierungen und Änderungen (Mutationen) durchführen können .

Das Erstellen und Registrieren Ihrer Hooks ist jedoch nicht so einfach und gut dokumentiert. Ich musste einige Tage lang die Dokumentation lesen und erneut lesen sowie den Istio- und den Consul-Code 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 Fehler zu machen.

Wenn das Ergebnis erreicht ist, denke ich, wird es unehrlich sein, es nicht mit Ihnen allen zu teilen. Es ist einfach und gleichzeitig effektiv.

Code


Der Name webhook spricht für sich - dies ist der HTTP-Endpunkt, der die in Kubernetes definierte API implementiert. Sie erstellen einen API-Server, den Kubernetes aufrufen kann, bevor er sich mit der Bereitstellung befasst. Hier hatte ich Schwierigkeiten, da es nur wenige Beispiele gibt, von denen einige nur Kubernetes-Tests sind, andere in einer riesigen Codebasis versteckt sind ... und alle in Go geschrieben sind. Aber ich habe eine günstigere Option gewählt - 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 erst später der an Kubernetes übergebenen YAML entsprechen). Es ist wichtig, dass Sie die vom API-Server empfangene JSON sehen und verstehen. In diesem Fall ziehen wir nichts aus JSON heraus. Dies kann jedoch in anderen Skripts nützlich sein. Im obigen Code aktualisieren wir JSON. Dafür sind zwei Dinge erforderlich:

  1. Lernen und verstehen Sie den JSON-Patch .
  2. Konvertieren Sie den JSON-Patch-Ausdruck korrekt in ein mit base64 codiertes Byte-Array.

Sobald dies erledigt ist, müssen Sie nur die Antwort mit einem sehr einfachen Objekt an den API-Server senden. In diesem Fall fügen wir das Etikett zu foo=barjedem Pod hinzu, der zu uns kommt.

Bereitstellung


Nun, wir haben Code, der Anfragen vom Kubernetes-API-Server akzeptiert und darauf reagiert, aber wie können wir ihn beheben? Und wie kann man Kubernetes zwingen, diese Anfragen umzuleiten? Sie können einen solchen Endpunkt überall bereitstellen, den der Kubernetes-API-Server erreichen kann. Am einfachsten ist es, den Code im Kubernetes-Cluster selbst bereitzustellen, was wir in diesem Beispiel tun werden. 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 dem Erstellen 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 Bild aus dem Knoten aus der Community und legen Sie den Code dort ab. Jetzt können Sie einen einfachen Build ausführen:

docker build . -t localserver

Der nächste Schritt ist das Erstellen der Bereitstellung in Kubernetes:

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 neu erstellte Image verwiesen haben? Es hätte genauso gut eine Pod sein können und alles andere, an das wir den Dienst mit Kubernetes verbinden können. Nun definieren wir diesen Service:

apiVersion: v1
kind: Service
metadata:
  name: webhook-service
spec:
  ports:
  - port: 443
    targetPort: 8443
  selector:
component: webhook-server

Auf diese Weise wird ein Endpunkt mit einem internen Namen, der unseren Container angibt, in Kubernetes angezeigt. Der letzte Schritt besteht darin, Kubernetes mitzuteilen, dass der API-Server diesen Service aufrufen soll, wenn er bereit ist, Änderungen (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: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUdHekNDQkFPZ0F3SUJBZ0lKQU1jcTN6UHZDQUd0TUEwR0NTcUdTSWIzRFFFQkN3VUFNSUdqTVFzd0NRWUQKVlFRR0V3SlZVekVUTUJFR0ExVUVDQXdLVG1WM0lFcGxjbk5sZVRFVE1CRUdBMVVFQnd3S1VISnBibU5sZEc5dQpJREVTTUJBR0ExVUVDZ3dKUkc5M0lFcHZibVZ6TVF3d0NnWURWUVFMREFOUVNVSXhIakFjQmdOVkJBTU1GWGRsClltaHZiMnN0S2k1a1pXWmhkV3gwTG5OMll6RW9NQ1lHQ1NxR1NJYjNEUUVKQVJZWmMyTnZkSFF1Y21Gb2JtVnkKUUdSdmQycHZibVZ6TG1OdmJUQWVGdzB4T0RFd016RXhOalU1TURWYUZ3MHlNVEE0TWpBeE5qVTVNRFZhTUlHagpNUXN3Q1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0F3S1RtVjNJRXBsY25ObGVURVRNQkVHQTFVRUJ3d0tVSEpwCmJtTmxkRzl1SURFU01CQUdBMVVFQ2d3SlJHOTNJRXB2Ym1Wek1Rd3dDZ1lEVlFRTERBTlFTVUl4SGpBY0JnTlYKQkFNTUZYZGxZbWh2YjJzdEtpNWtaV1poZFd4MExuTjJZekVvTUNZR0NTcUdTSWIzRFFFSkFSWVpjMk52ZEhRdQpjbUZvYm1WeVFHUnZkMnB2Ym1WekxtTnZiVENDQWlJd0RRWUpLb1pJaHZjTkFRRUJCUUFEZ2dJUEFEQ0NBZ29DCmdnSUJBTHRpTU5mL1l3d0RkcHlPSUhja2FQK3J6NmdxYXBhWmZ2a0JndHVZK3BYQVZnNWc5M1RISmlPdlJYUnAKeG9UZ1o0RlA4N0V3R0NXRUZxZTRFRjh5UUxCK1NvWHBxUmRrWlVLYlM3eDVJNnNDb0h1dFJXaURpd3piV3lGawp3UnppeXpyMTQzN2wzYWxadU9VNkl5bU9mVDlETzdRaDNnY01HOEprQ09aVlVOelVIN3J4WmtieGg3M1lXNW5ZCjhSMU5tZDJ3cm1IWkVWc2JmS21GTlhvZjFueWtRcXMyMUQxT1FwQ3A1VDB5QU9penZlaW9OS3VsQVVpcjNVQ0EKSmNYYWpMMGZVS1ZIcGVTbGlhWXdKZmZNSDFqOElqSDZTdm5TdG9qQWlWdnJHb1ZKUlFqRXFLQkpYVGMyaHZCWQpCcjJqdGdQb25WWnBBTFphbktha0JTV1cyZ25oZVFKaHpKOGhkMXlEU0x6dFFKb2JkOHZUMEZ5bHZaQzY3aURnCmROb1NWbHBaQlpDSVIxTldaRVdGbTlTWWtKLzZ6emVqMFZpWnp2aFBYdm9GelZEVGZoMEwzQWljUTZlWTNzcEMKV0Fmb2VTcFUxaEVJeG92SmdwVkpMbnRaWkhyN1RJQ05CNlV5QnFVUzhEa0lTMkhnWkh2MTd1VjA3bTFzZDZDMApDUnV5YmZHQ0l2RGNwMCtzMjF6TENXemJuS3BzaFo5UkYvYWhXMW11cVN2dGt0WXlYOFVySlpKT1h3Z0NKenhLCmdwZGs3YlA4Y3ZkRWxUZDduQXRJbjZPcm42VWlVUnFpSXY1VSt0bmIvOVlrNDIxVzdlT2NxZ3JqTEY4eUo5ckIKN0hBYlhGRjM5OW5NMlBtYkZIV2FROG1xeWo0L0kxNm9tTHVsUGZvekVWK0xvMXVwQWdNQkFBR2pVREJPTUIwRwpBMVVkRGdRV0JCUnVKaTcyS0U5bWhpejZvYVhkSXlpbGpTeXhkVEFmQmdOVkhTTUVHREFXZ0JSdUppNzJLRTltCmhpejZvYVhkSXlpbGpTeXhkVEFNQmdOVkhSTUVCVEFEQVFIL01BMEdDU3FHU0liM0RRRUJDd1VBQTRJQ0FRQlQKS28wczJTTWZkSzdkRS9ZdFBwQ2lQNDVBK0xJSjVKd0l2dWdiUlNGeVRUSEU0akhVRTdQdWc3VHdGNC93YnJFZwpNN1F3OWUxbDA1M2lheWRFOS9sUlVDbzN4TnVVcU5jU2lCK3RIOE54dURHUUw5NHBuWTdTR3FuRjBDMlZ2d2x2CmxaYUQxNU41cVdvTVJrQU54VXRPRGFaWEdLcS94VVBSQWdNMHFtbXc5ZnIwaXAvQzFjVGMyVVhlejlGNTMvV2cKV1FNempWbUNTNGlnckR1a1FBNWxodFRlYUlzK3pxNk9ZeWNiN01KR1JBL0NhcnpDL1VuZExMbmhsdEtITkJhMwp0TDFVVUJCTzBMdmdMaE8zVk9nRENOazJYVmZzVHFueEUrTGp6R2dmUnRqYjE5L0p1d2V2OW00Y3ZzUlZESGVMCk9oQ0lvenorUHRLWHBwVDFWd1VRbFZlOG5ic2RiVnNZWmt4Q3llcGpMUTJ5TXNUUXdoa2NncGRiTnYzbTMvRC8Kc3N5ZS9iZnphUGFXVEE1R0d5emhXdXlENDZPT1lCUFlhZzd0aFFneXRvOWRpSWNDSHNMQ3BVZm1FQ1d6TERBYgozK2NadnZnYXZybFJCZjN2cVhrVlZxT1NLNGxna25iUEZJc0YvbnFIanM2WXI5Tktiai9sRGlBalRYaVdQdFRmClJzd0JodndveDJnK21zd0prQytId0cvckZ1RXFDdklTaFJGWlEvMDgyL0F5ekpYRlE3SlV3eHluL0dTQXlGZUsKL1Y3T01XTEhUeVd4Vkg4eVBCZ1JSVE1CK3NrOEVQQndveFRLSjZnLytTbmdkNXM1ZEx6ZDhpSTlsVHdxWDZBTApzNU1OY2NobFRWVU9RYnFGWXBKc3FTUTlIVlB2bjZDckRlTGlxTlNKQVE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="
    rules:
      - operations: [ "CREATE" ]
        apiGroups: [""]
        apiVersions: ["v1"]
        resources: ["pods"]
( hook.yaml )

Der Name und der Pfad können hier beliebig sein, aber ich habe versucht, sie so sinnvoll wie möglich zu machen. Wenn Sie den Pfad ändern, müssen Sie den entsprechenden Code in Javascript ändern. Der Webhook failurePolicyist auch wichtig - er bestimmt, ob das Objekt gespeichert werden soll, wenn der Hook einen Fehler zurückgibt oder nicht funktioniert. In diesem Fall weisen wir Kubernetes an, die Verarbeitung nicht fortzusetzen. Zum Schluss noch die rules ( rules): Sie ändern sich je nach den API-Aufrufen, die Sie von Kubernetes erwarten. In diesem Fall müssen Anforderungen abgefangen werden, um einen Pod zu erstellen, da wir versuchen, das Einfügen eines Beiwagencontainers zu emulieren.

Das ist alles! So einfach ... aber was ist mit der Sicherheit? RBAC ist ein Aspekt, der in dem Artikel nicht behandelt wird. Ich gehe davon aus, dass Sie das Beispiel in Minikube oder in Kubernetes ausführen, das mit dem Docker für Windows / Mac geliefert wird. Ich werde jedoch über ein weiteres notwendiges Element berichten. Der Kubernetes-API-Server greift nur auf HTTPS-Endpunkte zu, sodass für die Anwendung SSL-Zertifikate erforderlich sind. Sie müssen auch Kubernetes mitteilen, wer die Zertifizierungsstelle für das Stammzertifikat ist.

Tls


Nur zu Demonstrationszwecken (!!!) habe ich Dockerfileetwas Code hinzugefügt , um eine Stammzertifizierungsstelle 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 )

Hinweis: Die letzte Stufe - zeigt eine einzelne Zeile mit der Stammzertifizierungsstelle an , die in base64 codiert ist. Dies ist genau das, was für die Hook-Konfiguration erforderlich ist. Kopieren Sie daher diese Zeichenfolge in Ihren weiteren Tests unbedingt in das caBundleDateifeld hook.yaml. Dockerfilees wirft Zertifikate direkt ein WORKDIR, also nimmt Javascript sie einfach von dort und verwendet es für den Server:

const privateKey = fs.readFileSync('webhook.key').toString();
const certificate = fs.readFileSync('webhook.crt').toString();
//…const options = {key: privateKey, cert: certificate};
const server = https.createServer(options, app);

Jetzt unterstützt der Code den Start von HTTPS und teilt Kubernetes mit, wo er uns finden kann und welchem ​​Authentifizierungscenter er vertrauen sollte. Es bleibt nur noch alles in einem Cluster einzuschließen:

kubectl create -f deployment.yaml
kubectl create -f service.yaml
kubectl create -f hook.yaml

Wir fassen zusammen


  • Deployment.yaml Startet einen Container, der den API-Hook über HTTPS bereitstellt, und gibt einen JSON-Patch zur Änderung des Objekts zurück.
  • Service.yaml- stellt einen Endpunktcontainer bereit webhook-service.default.svc.
  • Hook.yamlsagte API-Server, wo wir zu finden: https://webhook-service.default.svc/mutate.

Lass es uns im Geschäft versuchen!


Alles wird in einem Cluster bereitgestellt. Es ist an der Zeit, den Code in Aktion auszuprobieren. Dies wird durch Hinzufügen eines neuen Pods / Bereitstellung erreicht. Wenn alles korrekt funktioniert, muss der Hook ein zusätzliches Label hinzufügen foo:

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 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

Wunderbar! Obwohl Sie test.yamlein einzelnes Label set ( component) hatten, erhielt der resultierende Pod zwei: componentund foo.

Hausaufgaben


Aber warte! Wollten wir diesen Code verwenden, um einen Sidecar-Container zu erstellen? Ich habe gewarnt, dass ich Ihnen zeigen werde, wie Sie einen Beiwagen hinzufügen ... Und jetzt, mit dem Wissen und dem Code, den Sie erhalten haben: https://github.com/dowjones/k8s-webhook - experimentieren Sie und erkunden Sie, wie Ihr Beiwagen automatisch eingefügt wird. 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:


Jetzt auch beliebt: