Neuigkeiten von trion.
Immer gut informiert.

Beispiel für einen Kubernetes Mutating Admission Controller

Kubernetes

Im vorherigen Beitrag zu Kubernetes Admission Controller wurde beschrieben, wie das Konzept eines Admission Controllers in Kubernetes allgemein funktioniert. Anhand eines in Python geschriebenen Mutating Admission Controller wird im folgenden gezeigt, wie einfach ein Admission Controller implementiert werden kann.

Als Beispiel soll dazu ein Anwendungsfall aus der Kubernetes-Praxis dienen: Wenn Kubernetes-(Batch-)Jobs erfolgreich beendet wurden, sollen die zugehörigen Job-Objekte in der Regel entfernt werden. Das verschafft auf der einen Seite einen besseren Überblick, zum anderen wird unnötiger Overhead auf der Control Plane vermieden.
Dazu bietet Kubernetes auch ein Feature: Den TTL-Controller für Resourcen im Zustand finished. Aktuell ist dazu das Feature Gate TTLAfterFinished für den Controller-Manager und den API-Server in Kubernetes zu aktivieren. Anschließend prüft der TTL Controller bei Job Resourcen, ob das Property .spec.ttlSecondsAfterFinished gesetzt ist. Dann werden Jobs, die entweder completed oder failed sind durch den Controller nach Ablauf der entsprechenden Zeit entfernt.

Doch nicht immer werden Kubernetes-Job-Objekte bereits mit einem Wert für das ttlSecondsAfterFinished Attribut versehen. Um genau das sicherzustellen, soll nun ein entsprechender Mutating Admission Controller entwickelt werden.

Die eigentliche Veränderung einer API Anfrage durch einen Mutating Admission Controller bei Kubernetes erfolgt durch Operationen, die als JSON Patch formuliert werden.
Die JSON Patches lassen sich prinzipiell mit jeder Programmiersprache erstellen. Im Beispiel wird Python für den Mutating Admission Controller als Webhook verwendet.

Beispiel eines dekodierteren JSON Patch
[
   {
      "op":"add",
      "path":"/spec/ttlSecondsAfterFinished",
      "value":3600
   }
]

Als Rahmen dient ein einfacher Python HTTP Server, der die eigenlichen Anfragen dann an einen Controller delegiert. Da die Anwendung später in einem Container laufen soll, ist wichtig zu beachten, dass nicht nur das Loopback-Interface, sondern 0.0.0.0 als Repräsentation für alle Schnittstellen als Listen-Adresse verwendet wird.
Da eine TLS Absicherung vorgesehen ist, wird ein entsprechendes Zertifikat aus dem Ordner certs verwendet. Als Port wird 4443 verwendet, damit sind keine besonderen Rechte verbunden.

Python HTTP Server mit TLS Absicherung
#!/usr/bin/python

from http.server import HTTPServer, SimpleHTTPRequestHandler
import ssl
from controller import Controller

print ("Starting...")

port = 4443
httpd = HTTPServer(("", port), Controller)
httpd.socket = ssl.wrap_socket(httpd.socket, certfile="./certs/tls.crt", keyfile="./certs/tls.key", server_side=True)

print("\n\nServer running on https://0.0.0.0:" + str(port))
print("\n\n")

httpd.serve_forever()

Der Mutating Admission Webhook selbst ist dann relativ einfach gehalten. Erhält er eine Anfrage, wird diese zunächst kopiert: Die Kopie wird ggf. verändert und anschließend mit dem Original verglichen. Ermittelte Differenzen werden dann als JSON-Patch-Operationen als Bestandteil der Admission-Response kommuniziert.

Python Mutating Admission Controller
from http.server import BaseHTTPRequestHandler
import json
import jsonpatch
import copy
import base64

class Controller(BaseHTTPRequestHandler):
  def do_POST(self):
    self.send_response(200)
    self.send_header('Content-type', "application/json")
    self.end_headers()

    data_string = self.data_string = self.rfile.read(int(self.headers['Content-Length']))

    input_spec = json.loads(data_string)
    modified_spec = copy.deepcopy(input_spec)

    self.add_ttl(modified_spec["request"]["object"]["spec"])

    patch = jsonpatch.JsonPatch.from_diff(input_spec["request"]["object"], modified_spec["request"]["object"])

    admission_response = {
       "allowed": True,
       "uid": input_spec["request"]["uid"],
       "patch": base64.b64encode(str(patch).encode()).decode(),
       "patchtype": "JSONPatch"
    }
    admissionReview = {
       "response": admission_response
    }
    self.wfile.write(bytes(json.dumps(admissionReview), "UTF-8"))
    return

  def add_ttl(self, job_spec):
    if hasattr(job_spec, 'ttlSecondsAfterFinished') == False:
      job_spec["ttlSecondsAfterFinished"] = 3600

Die Anwendung kann nun auch sehr einfach getestet werden: Nach einem lokalen Start, ggf. ohne TLS oder mit einem selbst signierten Zertifikat, wird eine JSON-Anfrage per curl oder Postman abgeschickt.

Test des Kubernetes Mutating Admission Controller Webhook mit curl
$ curl --insecure -d '{"kind":"AdmissionReview","apiVersion":"admission.k8s.io/v1beta1","request":{"uid":"23be6d67-6541-11e9-8bfc-001e063605c7","kind":{"group":"batch","version":"v1","kind":"Job"},"resource":{"group":"batch","version":"v1","resource":"jobs"},"namespace":"drone","operation":"CREATE","userInfo":{"username":"kubernetes-admin","groups":["system:masters","system:authenticated"]},"object":{"kind":"Job","apiVersion":"batch/v1","metadata":{"name":"demo","creationTimestamp":null,"labels":{"run":"demo"}},"spec":{"parallelism":1,"completions":1,"backoffLimit":6,"template":{"metadata":{"creationTimestamp":null,"labels":{"run":"demo"}},"spec":{"containers":[{"name":"demo","image":"nginx","resources":{},"terminationMessagePath":"/dev/termination-log","terminationMessagePolicy":"File","imagePullPolicy":"Always"}],"restartPolicy":"OnFailure","terminationGracePeriodSeconds":30,"dnsPolicy":"ClusterFirst","securityContext":{},"schedulerName":"default-scheduler"}}},"status":{}},"oldObject":null,"dryRun":false}}' -XPOST https://ttlmac-service.drone

{"response": {"allowed": true, "uid": "23be6d67-6541-11e9-8bfc-001e063605c7", "patch": "W3sib3AiOiAiYWRkIiwgInBhdGgiOiAiL3NwZWMvdHRsU2Vjb25kc0FmdGVyRmluaXNoZWQiLCAidmFsdWUiOiAzNjAwfV0=", "patchtype": "JSONPatch"}}

Im nächsten Artikel wird erklärt, wie das Deployment des Mutating Admission Controller in einem Kubernetes Cluster konfiguriert werden kann: Konfiguration eines Kubernetes Mutating Admission Controller.




Zu den Themen Kubernetes, Docker und Cloud Architektur bieten wir sowohl Beratung, Entwicklungsunterstützung als auch passende Schulungen an:

Auch für Ihren individuellen Bedarf können wir Workshops und Schulungen anbieten. Sprechen Sie uns gerne an.

Feedback oder Fragen zu einem Artikel - per Twitter @triondevelop oder E-Mail freuen wir uns auf eine Kontaktaufnahme!

Los geht's!

Bitte teilen Sie uns mit, wie wir Sie am besten erreichen können.