{"id":31424,"date":"2021-08-31T12:30:21","date_gmt":"2021-08-31T11:30:21","guid":{"rendered":"https:\/\/www.inovex.de\/?p=31424"},"modified":"2023-05-23T08:55:47","modified_gmt":"2023-05-23T06:55:47","slug":"kubernetes-api-dynamic-admission-controller","status":"publish","type":"post","link":"https:\/\/www.inovex.de\/de\/blog\/kubernetes-api-dynamic-admission-controller\/","title":{"rendered":"Kubernetes Dynamic Admission Controller selbst bauen"},"content":{"rendered":"<p>Das Herzst\u00fcck von Kubernetes ist seine API sowie die Erweiterbarkeit dieser. Heute m\u00f6chten wir die Kubernetes API um eine eigene Komponente erweitern: einen Admission Controller.<!--more--><\/p>\n<p>Admission Controller sind Code. Sie fangen Requests an die Kubernetes API ab und modifizieren oder validieren diese, bevor die Inhalte dieser Requests persistiert werden. Admission Controller haben drei Operation Modes: Sie k\u00f6nnen Requests validieren, ver\u00e4ndern oder beide vorherigen Operationen durchf\u00fchren. Das unten stehende Schaubild erl\u00e4utert den (zum Verst\u00e4ndnis nicht vollst\u00e4ndigen) Flow eines Requests von der Entgegennahme des API-Servers bis hin zur Persistierung.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-31425 size-full\" src=\"https:\/\/www.inovex.de\/wp-content\/uploads\/webhookflow.png.png\" alt=\"\" width=\"1303\" height=\"202\" srcset=\"https:\/\/www.inovex.de\/wp-content\/uploads\/webhookflow.png.png 1303w, https:\/\/www.inovex.de\/wp-content\/uploads\/webhookflow.png-300x47.png 300w, https:\/\/www.inovex.de\/wp-content\/uploads\/webhookflow.png-1024x159.png 1024w, https:\/\/www.inovex.de\/wp-content\/uploads\/webhookflow.png-768x119.png 768w, https:\/\/www.inovex.de\/wp-content\/uploads\/webhookflow.png-400x62.png 400w, https:\/\/www.inovex.de\/wp-content\/uploads\/webhookflow.png-360x56.png 360w\" sizes=\"auto, (max-width: 1303px) 100vw, 1303px\" \/><\/p>\n<p>Kubernetes liefert von Haus aus eine ganze Menge Admission Controller mit und viele davon sind bereits per default aktiviert. Eine aktuelle Liste aller ab Werk aktivierten Admission Controller findet sich hier: <a href=\"https:\/\/kubernetes.io\/docs\/reference\/command-line-tools-reference\/kube-apiserver\/#options\">kube-apiserver options<\/a> unter: -enable-admission-plugins<\/p>\n<p>Nicht verwirren lassen: In der offiziellen Dokumentation werden die Begriffe Admission Controller und Admission Plugin synonym verwendet. Im weiteren Verlauf des Blog Posts nutzen wir den Begriff Admission Controller oder die Begriffe <strong>Validating Webhook<\/strong> und <strong>Mutating Webhook<\/strong> zur genaueren Abgrenzung.<\/p>\n<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_82_2 counter-hierarchy ez-toc-counter ez-toc-custom ez-toc-container-direction\">\n<div class=\"ez-toc-title-container\"><p class=\"ez-toc-title\" style=\"cursor:inherit\"><\/p>\n<\/div><nav><ul class='ez-toc-list ez-toc-list-level-1 ' ><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/www.inovex.de\/de\/blog\/kubernetes-api-dynamic-admission-controller\/#Warum-sollte-ich-Admission-Controller-nutzen\" >Warum sollte ich Admission Controller nutzen?<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/www.inovex.de\/de\/blog\/kubernetes-api-dynamic-admission-controller\/#Grundlegende-Architektur-eines-Admission-Controllers\" >Grundlegende Architektur eines Admission Controllers<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/www.inovex.de\/de\/blog\/kubernetes-api-dynamic-admission-controller\/#Entwicklung-eines-eigenen-Admission-Controllers\" >Entwicklung eines eigenen Admission Controllers<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/www.inovex.de\/de\/blog\/kubernetes-api-dynamic-admission-controller\/#Validating-Webhook-implementieren\" >Validating Webhook implementieren<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-5\" href=\"https:\/\/www.inovex.de\/de\/blog\/kubernetes-api-dynamic-admission-controller\/#Mutating-Webhook-implementieren\" >Mutating Webhook implementieren<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-6\" href=\"https:\/\/www.inovex.de\/de\/blog\/kubernetes-api-dynamic-admission-controller\/#Aufsetzen-der-lokalen-Testumgebung\" >Aufsetzen der lokalen Testumgebung<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-7\" href=\"https:\/\/www.inovex.de\/de\/blog\/kubernetes-api-dynamic-admission-controller\/#Docker-Image-bauen-und-verfuegbar-machen\" >Docker Image bauen und verf\u00fcgbar machen<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-8\" href=\"https:\/\/www.inovex.de\/de\/blog\/kubernetes-api-dynamic-admission-controller\/#Zertifikate\" >Zertifikate<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-9\" href=\"https:\/\/www.inovex.de\/de\/blog\/kubernetes-api-dynamic-admission-controller\/#Deployment-und-Tests\" >Deployment und Tests<\/a><\/li><\/ul><\/nav><\/div>\n<h2><span class=\"ez-toc-section\" id=\"Warum-sollte-ich-Admission-Controller-nutzen\"><\/span>Warum sollte ich Admission Controller nutzen?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>M\u00f6glicherweise m\u00f6chte man gewisse Best Practices in seinen Clustern enforcen. Man k\u00f6nnte hier u. a. auch <a href=\"https:\/\/github.com\/kyverno\/kyverno\">Kyverno<\/a> (dar\u00fcber hat mein Kollege Simon Dreher <a href=\"https:\/\/www.inovex.de\/de\/blog\/ersatz-kubernetes-podsecuritypolicies-alternativen\/\">hier<\/a> ausf\u00fchrlicher berichtet) benutzen, aber auch ein eigener Admission Controller ist gar nicht so schwer. Prinzipiell stehen einem damit viele M\u00f6glichkeiten zur Verf\u00fcgung. Einige Beispiele f\u00fcr Use Cases w\u00e4ren:<\/p>\n<ul>\n<li>Man m\u00f6chte sicherstellen, dass Deployments oder Pods einen securityContext verwenden<\/li>\n<li>oder man m\u00f6chte als Cluster Admin bei jedem Deployment den Timestamp als eigenes Label oder Annotation setzen<\/li>\n<li>oder es soll sichergestellt werden, dass eine bestimmte Namenskonvention f\u00fcr Deployments und Services eingehalten wird.<\/li>\n<\/ul>\n<p>Sollten wir also sicherstellen wollen, dass Pods in unserem Cluster einen securityContext gesetzt haben, habe ich zwei M\u00f6glichkeiten:<\/p>\n<ul>\n<li>Ich kann die Erstellung des Pods mittels eines Validating Controllers ablehnen und (optional) dem\/der Anwender:in \/ Entwickler:in eine Fehlermeldung ausgeben oder<\/li>\n<li>ich kann mittels eines Mutating Controllers einen default securityContext setzen, sollte das \u00fcbermittelte Deployment Manifest diesen nicht definiert haben.<\/li>\n<\/ul>\n<h2><span class=\"ez-toc-section\" id=\"Grundlegende-Architektur-eines-Admission-Controllers\"><\/span>Grundlegende Architektur eines Admission Controllers<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Bevor wir uns an die Entwicklung eines eigenen Admission Controllers machen, schauen wir uns noch kurz an, wo ein eigen entwickelter Admission Controller in die Gesamtarchitektur von Kubernetes passt. Grundlegend bestehen Admission Controller aus zwei Komponenten:<\/p>\n<p>Aus einem Kubernetes API Objekt sowie einem Webhook Server, der Anfragen in Form eines <em>AdmissionReview<\/em> Objekts in JSON entgegennimmt, verarbeitet und mit einem <em>AdmissionReview<\/em> Objekt in JSON antwortet.<\/p>\n<p>Beim ersten Lesen mag das verwirrend klingen, jedoch unterscheiden sich Anfrage und Antwort im <a href=\"https:\/\/pkg.go.dev\/k8s.io\/api@v0.21.0\/admission\/v1#AdmissionReview\">Inhalt des AdmissionReview Objektes<\/a>.<\/p>\n<p>Um festzulegen, welche Objekte bei welcher Operation zur Validierung oder Modifikation an welche Controller gesendet werden, m\u00fcssen <em>ValidatingWebhookConfiguration<\/em>&#8211; oder <em>MutatingWebhookConfiguration<\/em>-Objekte angelegt werden. Diese Objekte stellen gemeinsam mit dem Webhook Server die drei Bausteine eines Admission Controllers dar.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"Entwicklung-eines-eigenen-Admission-Controllers\"><\/span>Entwicklung eines eigenen Admission Controllers<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Um das neu Erlernte zu vertiefen, entwickeln wir unseren eigenen Admission Controller und werden diesen lokal auf einem <em><a href=\"https:\/\/kind.sigs.k8s.io\/docs\/user\/quick-start\/\">kind Cluster<\/a><\/em> provisionieren und testen. Wir m\u00f6chten beide zuvor genannten M\u00f6glichkeiten (Validating and Mutating) implementieren. Zum einen wollen wir Deployments verhindern, denen das Feld runAsNonRoot innerhalb des SecurityContext fehlt (Validating Webhook) und zum anderen m\u00f6chten wir Deployments immer mit einer Timestamp Annotation versehen (Mutating Webhook).<\/p>\n<p>Realisieren werden wir das ganze in Go und damit wir direkt mit der Implementierung starten k\u00f6nnen, findet sich unter <a href=\"https:\/\/github.com\/inovex\/blog-dynamicadmissioncontrol_template\">https:\/\/github.com\/inovex\/blog-dynamicadmissioncontrol_template<\/a> ein Template Repository. Der Code dieses Repositories basiert zu gro\u00dfen Teilen auf der Referenzimplementierung des Admission Webhook Servers.<\/p>\n<p>Einziger Unterschied: In der Referenzimplementierung werden sowohl <em>v1beta1<\/em> als auch <em>v1<\/em> Version der <em>admissionregistration.k8s.io<\/em> API verwendet. Ab Kubernetes Version 1.22 ist <em>v1beta1<\/em> jedoch deprecated, daher setzen wir hier in unserem Beispiel ausschlie\u00dflich auf die stabile und auch l\u00e4ngerfristig verf\u00fcgbare Version <em>v1<\/em>.<\/p>\n<p>Das Template Repository enth\u00e4lt bereits einen grundlegenden funktionierenden Webhook Server, jedoch fehlt diesem die Logik f\u00fcr das validieren oder modifizieren von Requests. Die Logik f\u00fcr die beiden Funktionen befindet sich in der Datei <strong>webhook.go<\/strong>, genauer in den beiden selbst beschreibenden Funktionen validate und mutate.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"Validating-Webhook-implementieren\"><\/span>Validating Webhook implementieren<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Unter <a href=\"https:\/\/kubernetes.io\/docs\/reference\/access-authn-authz\/extensible-admission-controllers\/#request\">Webhook Request<\/a> finden wir das Format des <em>AdmissionReview<\/em>-Objektes, das der API Server an unseren Webhook Server senden wird. Unserer validate-Funktion wird das <em>AdmissionReview<\/em>-Objekt \u00fcbergeben. Um nun zu \u00fcberpr\u00fcfen, ob der SecurityContext gesetzt wurde, greifen wir auf das eigentlich zu erstellende Objekt innerhalb des <em>AdmissionReview<\/em>-Objektes zu.<\/p>\n<p>Da wir wissen, dass wir unseren Webhook Server lediglich f\u00fcr die Validierung von Deployments verwenden wollen und dies auch sp\u00e4ter in unserer <em>ValidatingWebhookConfiguration<\/em> konfigurieren werden, k\u00f6nnen wir das JSON-Objekt einfach mittels json.Unmarshal in unser appsv1.Deployment struct packen.<\/p>\n<p>Mittels <em>reflect.ValueOf(deploy.Spec.Template.Spec.SecurityContext.RunAsNonRoot).IsNil()<\/em> \u00fcberpr\u00fcfen wir nun, ob das Feld gesetzt ist. (Wir gehen davon aus, dass unsere Entwickler:innen wissen, was Sie tun, und erwarten hier <strong>kein<\/strong> <em>true<\/em> als Wert des Feldes.)<\/p>\n<p>Nach unserer Pr\u00fcfung geben wir das <em>AdmissionReview<\/em>-Objekt wieder an unsere serve-Funktion zur\u00fcck und sind damit schon mit unserem <em>ValidatingWebhook<\/em> fertig. Die Funktion sieht dann am Ende in etwa so aus:<\/p>\n<pre class=\"theme:github font:ubuntu-mono lang:go decode:true\">func validate(ar admissionv1.AdmissionReview) *admissionv1.AdmissionResponse {\r\n\tklog.Info(\"entering the validate func\")\r\n\r\n\t\/\/ Preparing our review response\r\n\treviewResponse := admissionv1.AdmissionResponse{}\r\n\treviewResponse.Allowed = true\r\n\r\n\traw := ar.Request.Object.Raw\r\n\r\n\tvar deploy appsv1.Deployment\r\n\t\/\/ Unmarshalling the data in the deployment struct\r\n\tif err := json.Unmarshal(raw, &amp;deploy); err != nil {\r\n\t\tklog.Errorf(\"Could not unmarshal raw object: %v\", err)\r\n\t\treturn toAdmissionResponse(err)\r\n\t}\r\n\t\/\/ Check if RunAsNonRoot is set\r\n\t\/\/ It may be set to false in edge cases, but it needs to be set\r\n\tif reflect.ValueOf(deploy.Spec.Template.Spec.SecurityContext.RunAsNonRoot).IsNil() {\r\n\t\terr := errors.New(\"need to set RunAsNonRoot\")\r\n\t\treturn toAdmissionResponse(err)\r\n\t}\r\n\r\n\treturn &amp;reviewResponse\r\n}<\/pre>\n<h2><span class=\"ez-toc-section\" id=\"Mutating-Webhook-implementieren\"><\/span>Mutating Webhook implementieren<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Wie initial erw\u00e4hnt, m\u00f6chten wir einen Timestamp als Annotation setzen. Daf\u00fcr m\u00fcssen wir das zu erstellende Objekt anpassen, dies geschieht \u00fcber ein Verfahren namens JSON Patch.<\/p>\n<p>Wir holen uns mittels Go Standardlibrary und <em>time.Now()<\/em> einen aktuellen Zeitstempel und speichern diesen in der Variable <em>date<\/em>. Anschlie\u00dfend bauen wir uns aus diesem Zeitstempel und unserem JSON Patch das passende Objekt zusammen, das wir dem APIServer mitgeben m\u00f6chten.<\/p>\n<p>Man k\u00f6nnte m\u00f6glicherweise erwarten, das Objekt der Begierde direkt im Code anzupassen und an den API Server zur\u00fcckzusenden, um den gew\u00fcnschten Effekt zu erzielen. Das AdmissionResponse Objekt erwartet JSON Patches jedoch in einem eigens daf\u00fcr vorgesehenen Feld. Den Patch selbst nimmt dann der API Server vor.<\/p>\n<p>In Code sieht das ganze dann so aus:<\/p>\n<pre class=\"theme:github font:ubuntu-mono lang:go decode:true\">func mutate(ar admissionv1.AdmissionReview) *admissionv1.AdmissionResponse {\r\n\tklog.Infoln(\"entering the mutate func\")\r\n\r\n\t\/\/ Preparing our review Response\r\n\treviewResponse := admissionv1.AdmissionResponse{}\r\n\t\/\/ As we are mutating here and are just adding an annotation, we will allow this operation\r\n\treviewResponse.Allowed = true\r\n\r\n\t\/\/ Getting the current date for the timestamp annotation\r\n\tdate := time.Now()\r\n\r\n\t\/\/ Creating our Patch Operation (This is just for demonstration purposes) - see also: https:\/\/tools.ietf.org\/html\/rfc6902\r\n\taddTimeStampAnnotation := `[{ \"op\": \"add\", \"path\": \"\/metadata\/annotations\/deployment_timestamp\", \"value\": \"` + date.String() + `\" }]`\r\n\r\n\t\/\/ Adding the Timestamp to the Object\r\n\treviewResponse.Patch = []byte(addTimeStampAnnotation)\r\n\r\n\tpt := admissionv1.PatchTypeJSONPatch\r\n\treviewResponse.PatchType = &amp;pt\r\n\treturn &amp;reviewResponse\r\n}<\/pre>\n<p>Damit haben wir auch unseren <em>MutatingWebhook<\/em> fertig gestellt und k\u00f6nnen uns nun an das Deployment in unserem Test-Setup mit einen lokalen <em>kind Cluster<\/em> machen.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"Aufsetzen-der-lokalen-Testumgebung\"><\/span>Aufsetzen der lokalen Testumgebung<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>F\u00fcr das Aufsetzen einer lokalen Testumgebung in Form eines <em>kind Clusters<\/em> verweisen wir hier an der Stelle an die <a href=\"https:\/\/kind.sigs.k8s.io\/docs\/user\/quick-start\/#creating-a-cluster\">offizielle Dokumentation<\/a>.<\/p>\n<p>Ein beliebiges Kubernetes Cluster mit von dir erreichbarer Registry funktioniert nat\u00fcrlich ebenfalls.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"Docker-Image-bauen-und-verfuegbar-machen\"><\/span>Docker Image bauen und verf\u00fcgbar machen<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Das Template-Repository enth\u00e4lt ein <strong>Makefile<\/strong>, welches alle Kommandos zum Bauen, Deployen und Testen unseres Webhook Servers b\u00fcndelt. Um unsere Anwendung \u00fcber das mitgelieferte Dockerfile zu bauen und in unserem <em>kind Cluster<\/em> bereitzustellen, nutzen wir die beiden Befehle:<\/p>\n<pre class=\"lang:default decode:true\">make build\r\nmake pushimage<\/pre>\n<p>Nun steht unser <em>Webhook Server<\/em> Image f\u00fcr ein Deployment in unserem <em>kind Cluster<\/em> zur Verf\u00fcgung. Welche Kommandos hinter den beiden\u00a0<em>make<\/em> Kommandos stecken, kann man \u00fcber das Betrachten des\u00a0<em>Makefiles<\/em> herausfinden.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"Zertifikate\"><\/span>Zertifikate<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Bevor wir nun unseren Admission Controller deployen und nutzen k\u00f6nnen, m\u00fcssen wir noch valide Zertifikate erstellen und in unserem <em>kind Cluster<\/em> verf\u00fcgbar machen. Hierf\u00fcr nutzen wir <a href=\"https:\/\/github.com\/cloudflare\/cfssl\">cfssl<\/a> und das bereitgestellten Zertifikatsrequest in der Datei <em>certs\/csr.json<\/em>, mit dem wir ein self-signed Zertifikat f\u00fcr unseren <em>Webhook Server<\/em> erstellen k\u00f6nnen.<\/p>\n<p>Achtung: In einem produktiven Setup keine self-signed Zertifikate verwenden.\u00a0 \ud83d\ude42<\/p>\n<p>Mittels der nachfolgenden Befehle erstellen wir ein selbst signiertes Zertifikat und legen dieses als Secret mit dem Namen inovex-webhook-certs in unserem <em>kind Cluster<\/em> an:<\/p>\n<pre class=\"lang:default decode:true\">cfssl selfsign inovex-webhook.default.svc csr.json | cfssljson -bare selfsigned\r\nkubectl create secret tls --key selfsigned-key.pem --cert selfsigned.pem inovex-webhook-certs<\/pre>\n<p>Bevor wir nun mit dem Deployment starten k\u00f6nnen, m\u00fcssen wir der WebhookConfiguration, die wir vorbereitet in der deployment.yml finden, noch das <em>caBundle<\/em> unseres Zertifikats mitgeben. Denn, laut Definition:<\/p>\n<blockquote><p>caBundle is a PEM encoded CA bundle which will be used to validate the webhook&#8217;s server certificate.<\/p><\/blockquote>\n<p>Da wir ein self-signed\u00a0 Zertifikat nutzen, setzen wir den Output des folgenden Kommandos in unserer deployment.yaml als Value f\u00fcr den Key caBundle:<\/p>\n<pre class=\"lang:default decode:true\"># On Linux:\r\nbase64 -w0 selfsigned.pem\r\n\r\n# On Darwin:\r\nbase64 selfsigned.pem<\/pre>\n<p>Fast auf der Zielgeraden angekommen, sollten wir uns aber noch einmal kurz die <em>MutatingWebhookConfiguration<\/em> bzw. <em>ValidatingWebhookConfiguration<\/em> in der <em>deployment.yml<\/em> unseres Template Repositories genauer ansehen.<\/p>\n<pre class=\"lang:yaml decode:true\">---\r\napiVersion: admissionregistration.k8s.io\/v1\r\nkind: ValidatingWebhookConfiguration\r\nmetadata:\r\n  name: inovex-webhook-validate\r\nwebhooks:\r\n  - name: webhook-validate.inovex.io\r\n    clientConfig:\r\n      service:\r\n        name: inovex-webhook\r\n        namespace: default\r\n        path: \"\/validate\"\r\n        port: 8443\r\n      caBundle: \"$CA_BUNDLE\"\r\n    rules:\r\n      - operations: [\"CREATE\",\"UPDATE\"]\r\n        apiGroups: [\"apps\"]\r\n        apiVersions: [\"v1\"]\r\n        resources: [\"deployments\"]\r\n        scope: \"*\"\r\n    failurePolicy: Fail\r\n    admissionReviewVersions: [\"v1\"]\r\n    sideEffects: None\r\n    timeoutSeconds: 10\r\n[...]<\/pre>\n<p>Was machen wir hier? Grob gesagt definieren wir Regeln, unter denen unser Webhook Server kontaktiert werden soll und wo dieser denn zu finden ist. Diese Regeln definieren wir unterhalb des \u201erules\u201c keys und im Falle einer <em>CREATE<\/em> oder einer <em>UPDATE<\/em> Operation eines Deployments wird unser Webhook Server mit den Werten der <em>clientConfig<\/em> auf Port 8443 und unter dem Pfad \/validate aufgerufen. Die <em>MutatingWebhookConfiguration<\/em> ist unterscheidet sich lediglich in der Pfadangabe und den Metadaten. F\u00fcr n\u00e4here Informationen zur <em>ValidatingWebhookConfiguration<\/em> oder <em>MutatingWebhookConfiguration<\/em> sowie der weiteren Optionen empfehlen wir die <a href=\"https:\/\/kubernetes.io\/docs\/reference\/access-authn-authz\/extensible-admission-controllers\/#webhook-configuration\">Kubernetes API Referenz.<\/a><\/p>\n<h2><span class=\"ez-toc-section\" id=\"Deployment-und-Tests\"><\/span>Deployment und Tests<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Nun haben wir alle Voraussetzungen erf\u00fcllt, die wir f\u00fcr das Deployment unseres Admission Controllers ben\u00f6tigen. Mithilfe des Kommandos<\/p>\n<pre class=\"lang:default decode:true\">make deploy<\/pre>\n<p>k\u00f6nnen wir unseren selbst entwickelten Admission Controller deployen. Im Optimalfall erhalten wir dann nach einem<\/p>\n<pre class=\"lang:default decode:true\">kubectl get pods -n default<\/pre>\n<p>folgendes Ergebnis:<\/p>\n<pre class=\"lang:zsh decode:true\">$ kubectl get pods -n default\r\nNAME                             READY   STATUS    RESTARTS   AGE\r\ninovex-webhook-bf69cf847-c22zq   1\/1     Running   0          24h<\/pre>\n<p>Unser Webhook Server steht also nun bereit.<\/p>\n<p>Die soeben erstellte <em>ValidatingWebhookConfiguration<\/em>\/<em>MutatingWebhookConfiguration<\/em> k\u00f6nnen wir uns mit:<\/p>\n<pre class=\"lang:default decode:true \">$ kubectl get validatingwebhookconfigurations.admissionregistration.k8s.io\r\n<\/pre>\n<p>beziehungsweise:<\/p>\n<pre class=\"lang:default decode:true\">$ kubectl get mutatingwebhookconfigurations.admissionregistration.k8s.io<\/pre>\n<p>anzeigen lassen.<\/p>\n<p>Da wir in unseren <em>WebhookConfigurations<\/em> den Service unseres Webhook Servers als Ziel angeben, werfen wir auch noch schnell einen Blick auf unseren Service um sicherzustellen, dass das Anlegen auch hier erfolgreich war:<\/p>\n<pre class=\"lang:default decode:true\">$ kubectl get svc -n default\r\nNAME             TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE\r\ninovex-webhook   ClusterIP   10.96.198.140   &lt;none&gt;        8443\/TCP   24h<\/pre>\n<p>In unserem Template Repository haben wir f\u00fcr den Test unseres Setups eine <em>test_deployment.yml <\/em>Datei vorbereitet. In dieser befinden sich zwei Deployments.<br \/>\nDas erste Deployment hat den Namen <strong>testapp-failed<\/strong> und sollte sich aufgrund des fehlenden <em>SecurityContexts<\/em> nicht ausrollen lassen. Das zweite Deployment hat den Namen <em>testapp<\/em> und besitzt eine <strong>g\u00fcltige<\/strong> Konfiguration. Hier ist <em>runAsNonRoot<\/em> unterhalb des <em>securityContext<\/em> gesetzt und bei einem Deployment sollte unser <em>Validating Webhook<\/em> das Deployment erlauben. Der von uns entwickelte <em>MutatingWebhook<\/em> sollte vor der Persistierung noch einen aktuellen Timestamp in die Annotationen des Deployments packen.<\/p>\n<p>\u00dcber<\/p>\n<pre class=\"lang:default decode:true\">make test \r\n\r\noder \r\n\r\nkubectl apply -f test_deployment.yml<\/pre>\n<p>k\u00f6nnen wir unseren Test starten:<\/p>\n<pre class=\"lang:default decode:true\">deployment.apps\/testapp created\r\nError from server: error when creating \"test_deployment.yml\": admission webhook \"webhook-validate.inovex.io\" denied the request: need to set RunAsNonRoot<\/pre>\n<p>Wir sehen nun, dass unser Deployment names <em>testapp<\/em> erfolgreich ausgerollt wurde. Das Deployment <em>testapp-failed<\/em> wurde mit unserer oben definierten Fehlermeldung abgewiesen. Mission erfolgreich.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Das Herzst\u00fcck von Kubernetes ist seine API sowie die Erweiterbarkeit dieser. Heute m\u00f6chten wir die Kubernetes API um eine eigene Komponente erweitern: einen Admission Controller.<\/p>\n","protected":false},"author":252,"featured_media":31570,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"inline_featured_image":false,"ep_exclude_from_search":false,"footnotes":""},"tags":[377,356,114,101],"service":[414,432,879],"coauthors":[{"id":252,"display_name":"Alexander Huck","user_nicename":"ahuck"}],"class_list":["post-31424","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","tag-development","tag-go","tag-kubernetes","tag-security","service-cloud","service-devops","service-security"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.5 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Kubernetes Dynamic Admission Controller selbst bauen - inovex GmbH<\/title>\n<meta name=\"description\" content=\"Kubernetes Admission Controller haben drei Operation Modes: Sie k\u00f6nnen Requests validieren, ver\u00e4ndern oder beide vorherigen Operationen durchf\u00fchren.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.inovex.de\/de\/blog\/kubernetes-api-dynamic-admission-controller\/\" \/>\n<meta property=\"og:locale\" content=\"de_DE\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Kubernetes Dynamic Admission Controller selbst bauen - inovex GmbH\" \/>\n<meta property=\"og:description\" content=\"Kubernetes Admission Controller haben drei Operation Modes: Sie k\u00f6nnen Requests validieren, ver\u00e4ndern oder beide vorherigen Operationen durchf\u00fchren.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.inovex.de\/de\/blog\/kubernetes-api-dynamic-admission-controller\/\" \/>\n<meta property=\"og:site_name\" content=\"inovex GmbH\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/inovexde\" \/>\n<meta property=\"article:published_time\" content=\"2021-08-31T11:30:21+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2023-05-23T06:55:47+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.inovex.de\/wp-content\/uploads\/Kubernetes-Dynamic-Admission-Controller.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1920\" \/>\n\t<meta property=\"og:image:height\" content=\"1080\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Alexander Huck\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:image\" content=\"https:\/\/www.inovex.de\/wp-content\/uploads\/Kubernetes-Dynamic-Admission-Controller-1024x576.png\" \/>\n<meta name=\"twitter:creator\" content=\"@inovexgmbh\" \/>\n<meta name=\"twitter:site\" content=\"@inovexgmbh\" \/>\n<meta name=\"twitter:label1\" content=\"Verfasst von\" \/>\n\t<meta name=\"twitter:data1\" content=\"Alexander Huck\" \/>\n\t<meta name=\"twitter:label2\" content=\"Gesch\u00e4tzte Lesezeit\" \/>\n\t<meta name=\"twitter:data2\" content=\"9\u00a0Minuten\" \/>\n\t<meta name=\"twitter:label3\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data3\" content=\"Alexander Huck\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/kubernetes-api-dynamic-admission-controller\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/kubernetes-api-dynamic-admission-controller\\\/\"},\"author\":{\"name\":\"Alexander Huck\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#\\\/schema\\\/person\\\/414614ef14ad539c916d487ec475eae4\"},\"headline\":\"Kubernetes Dynamic Admission Controller selbst bauen\",\"datePublished\":\"2021-08-31T11:30:21+00:00\",\"dateModified\":\"2023-05-23T06:55:47+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/kubernetes-api-dynamic-admission-controller\\\/\"},\"wordCount\":1621,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/kubernetes-api-dynamic-admission-controller\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/Kubernetes-Dynamic-Admission-Controller.png\",\"keywords\":[\"Development\",\"go\",\"Kubernetes\",\"Security\"],\"articleSection\":[\"General\",\"Infrastructure\"],\"inLanguage\":\"de\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/kubernetes-api-dynamic-admission-controller\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/kubernetes-api-dynamic-admission-controller\\\/\",\"url\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/kubernetes-api-dynamic-admission-controller\\\/\",\"name\":\"Kubernetes Dynamic Admission Controller selbst bauen - inovex GmbH\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/kubernetes-api-dynamic-admission-controller\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/kubernetes-api-dynamic-admission-controller\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/Kubernetes-Dynamic-Admission-Controller.png\",\"datePublished\":\"2021-08-31T11:30:21+00:00\",\"dateModified\":\"2023-05-23T06:55:47+00:00\",\"description\":\"Kubernetes Admission Controller haben drei Operation Modes: Sie k\u00f6nnen Requests validieren, ver\u00e4ndern oder beide vorherigen Operationen durchf\u00fchren.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/kubernetes-api-dynamic-admission-controller\\\/#breadcrumb\"},\"inLanguage\":\"de\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/kubernetes-api-dynamic-admission-controller\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"de\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/kubernetes-api-dynamic-admission-controller\\\/#primaryimage\",\"url\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/Kubernetes-Dynamic-Admission-Controller.png\",\"contentUrl\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/Kubernetes-Dynamic-Admission-Controller.png\",\"width\":1920,\"height\":1080,\"caption\":\"Kubernetes Dynamic Admission Controller routing requests\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/kubernetes-api-dynamic-admission-controller\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Kubernetes Dynamic Admission Controller selbst bauen\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#website\",\"url\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/\",\"name\":\"inovex GmbH\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"de\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#organization\",\"name\":\"inovex GmbH\",\"url\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"de\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/2021\\\/03\\\/inovex-logo-16-9-1.png\",\"contentUrl\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/2021\\\/03\\\/inovex-logo-16-9-1.png\",\"width\":1921,\"height\":1081,\"caption\":\"inovex GmbH\"},\"image\":{\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#\\\/schema\\\/logo\\\/image\\\/\"},\"sameAs\":[\"https:\\\/\\\/www.facebook.com\\\/inovexde\",\"https:\\\/\\\/x.com\\\/inovexgmbh\",\"https:\\\/\\\/www.instagram.com\\\/inovexlife\\\/\",\"https:\\\/\\\/www.linkedin.com\\\/company\\\/inovex\",\"https:\\\/\\\/www.youtube.com\\\/channel\\\/UC7r66GT14hROB_RQsQBAQUQ\"]},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/#\\\/schema\\\/person\\\/414614ef14ad539c916d487ec475eae4\",\"name\":\"Alexander Huck\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"de\",\"@id\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/Alexander-Huck_avatar_1629379967-96x96.png6c51c45fcdae7b216412729be89dc3a3\",\"url\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/Alexander-Huck_avatar_1629379967-96x96.png\",\"contentUrl\":\"https:\\\/\\\/www.inovex.de\\\/wp-content\\\/uploads\\\/Alexander-Huck_avatar_1629379967-96x96.png\",\"caption\":\"Alexander Huck\"},\"url\":\"https:\\\/\\\/www.inovex.de\\\/de\\\/blog\\\/author\\\/ahuck\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Kubernetes Dynamic Admission Controller selbst bauen - inovex GmbH","description":"Kubernetes Admission Controller haben drei Operation Modes: Sie k\u00f6nnen Requests validieren, ver\u00e4ndern oder beide vorherigen Operationen durchf\u00fchren.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.inovex.de\/de\/blog\/kubernetes-api-dynamic-admission-controller\/","og_locale":"de_DE","og_type":"article","og_title":"Kubernetes Dynamic Admission Controller selbst bauen - inovex GmbH","og_description":"Kubernetes Admission Controller haben drei Operation Modes: Sie k\u00f6nnen Requests validieren, ver\u00e4ndern oder beide vorherigen Operationen durchf\u00fchren.","og_url":"https:\/\/www.inovex.de\/de\/blog\/kubernetes-api-dynamic-admission-controller\/","og_site_name":"inovex GmbH","article_publisher":"https:\/\/www.facebook.com\/inovexde","article_published_time":"2021-08-31T11:30:21+00:00","article_modified_time":"2023-05-23T06:55:47+00:00","og_image":[{"width":1920,"height":1080,"url":"https:\/\/www.inovex.de\/wp-content\/uploads\/Kubernetes-Dynamic-Admission-Controller.png","type":"image\/png"}],"author":"Alexander Huck","twitter_card":"summary_large_image","twitter_image":"https:\/\/www.inovex.de\/wp-content\/uploads\/Kubernetes-Dynamic-Admission-Controller-1024x576.png","twitter_creator":"@inovexgmbh","twitter_site":"@inovexgmbh","twitter_misc":{"Verfasst von":"Alexander Huck","Gesch\u00e4tzte Lesezeit":"9\u00a0Minuten","Written by":"Alexander Huck"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.inovex.de\/de\/blog\/kubernetes-api-dynamic-admission-controller\/#article","isPartOf":{"@id":"https:\/\/www.inovex.de\/de\/blog\/kubernetes-api-dynamic-admission-controller\/"},"author":{"name":"Alexander Huck","@id":"https:\/\/www.inovex.de\/de\/#\/schema\/person\/414614ef14ad539c916d487ec475eae4"},"headline":"Kubernetes Dynamic Admission Controller selbst bauen","datePublished":"2021-08-31T11:30:21+00:00","dateModified":"2023-05-23T06:55:47+00:00","mainEntityOfPage":{"@id":"https:\/\/www.inovex.de\/de\/blog\/kubernetes-api-dynamic-admission-controller\/"},"wordCount":1621,"commentCount":0,"publisher":{"@id":"https:\/\/www.inovex.de\/de\/#organization"},"image":{"@id":"https:\/\/www.inovex.de\/de\/blog\/kubernetes-api-dynamic-admission-controller\/#primaryimage"},"thumbnailUrl":"https:\/\/www.inovex.de\/wp-content\/uploads\/Kubernetes-Dynamic-Admission-Controller.png","keywords":["Development","go","Kubernetes","Security"],"articleSection":["General","Infrastructure"],"inLanguage":"de","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.inovex.de\/de\/blog\/kubernetes-api-dynamic-admission-controller\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.inovex.de\/de\/blog\/kubernetes-api-dynamic-admission-controller\/","url":"https:\/\/www.inovex.de\/de\/blog\/kubernetes-api-dynamic-admission-controller\/","name":"Kubernetes Dynamic Admission Controller selbst bauen - inovex GmbH","isPartOf":{"@id":"https:\/\/www.inovex.de\/de\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.inovex.de\/de\/blog\/kubernetes-api-dynamic-admission-controller\/#primaryimage"},"image":{"@id":"https:\/\/www.inovex.de\/de\/blog\/kubernetes-api-dynamic-admission-controller\/#primaryimage"},"thumbnailUrl":"https:\/\/www.inovex.de\/wp-content\/uploads\/Kubernetes-Dynamic-Admission-Controller.png","datePublished":"2021-08-31T11:30:21+00:00","dateModified":"2023-05-23T06:55:47+00:00","description":"Kubernetes Admission Controller haben drei Operation Modes: Sie k\u00f6nnen Requests validieren, ver\u00e4ndern oder beide vorherigen Operationen durchf\u00fchren.","breadcrumb":{"@id":"https:\/\/www.inovex.de\/de\/blog\/kubernetes-api-dynamic-admission-controller\/#breadcrumb"},"inLanguage":"de","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.inovex.de\/de\/blog\/kubernetes-api-dynamic-admission-controller\/"]}]},{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/www.inovex.de\/de\/blog\/kubernetes-api-dynamic-admission-controller\/#primaryimage","url":"https:\/\/www.inovex.de\/wp-content\/uploads\/Kubernetes-Dynamic-Admission-Controller.png","contentUrl":"https:\/\/www.inovex.de\/wp-content\/uploads\/Kubernetes-Dynamic-Admission-Controller.png","width":1920,"height":1080,"caption":"Kubernetes Dynamic Admission Controller routing requests"},{"@type":"BreadcrumbList","@id":"https:\/\/www.inovex.de\/de\/blog\/kubernetes-api-dynamic-admission-controller\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.inovex.de\/de\/"},{"@type":"ListItem","position":2,"name":"Kubernetes Dynamic Admission Controller selbst bauen"}]},{"@type":"WebSite","@id":"https:\/\/www.inovex.de\/de\/#website","url":"https:\/\/www.inovex.de\/de\/","name":"inovex GmbH","description":"","publisher":{"@id":"https:\/\/www.inovex.de\/de\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.inovex.de\/de\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"de"},{"@type":"Organization","@id":"https:\/\/www.inovex.de\/de\/#organization","name":"inovex GmbH","url":"https:\/\/www.inovex.de\/de\/","logo":{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/www.inovex.de\/de\/#\/schema\/logo\/image\/","url":"https:\/\/www.inovex.de\/wp-content\/uploads\/2021\/03\/inovex-logo-16-9-1.png","contentUrl":"https:\/\/www.inovex.de\/wp-content\/uploads\/2021\/03\/inovex-logo-16-9-1.png","width":1921,"height":1081,"caption":"inovex GmbH"},"image":{"@id":"https:\/\/www.inovex.de\/de\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/inovexde","https:\/\/x.com\/inovexgmbh","https:\/\/www.instagram.com\/inovexlife\/","https:\/\/www.linkedin.com\/company\/inovex","https:\/\/www.youtube.com\/channel\/UC7r66GT14hROB_RQsQBAQUQ"]},{"@type":"Person","@id":"https:\/\/www.inovex.de\/de\/#\/schema\/person\/414614ef14ad539c916d487ec475eae4","name":"Alexander Huck","image":{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/www.inovex.de\/wp-content\/uploads\/Alexander-Huck_avatar_1629379967-96x96.png6c51c45fcdae7b216412729be89dc3a3","url":"https:\/\/www.inovex.de\/wp-content\/uploads\/Alexander-Huck_avatar_1629379967-96x96.png","contentUrl":"https:\/\/www.inovex.de\/wp-content\/uploads\/Alexander-Huck_avatar_1629379967-96x96.png","caption":"Alexander Huck"},"url":"https:\/\/www.inovex.de\/de\/blog\/author\/ahuck\/"}]}},"_links":{"self":[{"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/posts\/31424","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/users\/252"}],"replies":[{"embeddable":true,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/comments?post=31424"}],"version-history":[{"count":7,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/posts\/31424\/revisions"}],"predecessor-version":[{"id":45665,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/posts\/31424\/revisions\/45665"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/media\/31570"}],"wp:attachment":[{"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/media?parent=31424"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/tags?post=31424"},{"taxonomy":"service","embeddable":true,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/service?post=31424"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.inovex.de\/de\/wp-json\/wp\/v2\/coauthors?post=31424"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}