Beispiel für einen Kubernetes Mutating Admission Controller
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.
[
{
"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.
#!/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.
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.
$ 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.