API Deprecations in Kubernetes

Johannes Kleinlercher
6 min readJul 11, 2023

--

Auch wenn das Release von Kubernetes 1.0 schon 8 Jahre her ist, entwickelt sich K8s immer noch sehr rasch weiter. Zwangsläufig werden damit auch K8s-APIs weiterentwickelt und alte Versionen sind irgendwann nicht mehr unterstützt. In diesem Artikel lernen wir alle Hintergründe dazu und was aus Plattform-Engineering- und Dev-Sicht zu tun ist.

Photo by Suzanne D. Williams on Unsplash

Warum überhaupt drum kümmern?

In den allermeisten Fällen funktionieren die K8s-Objekte am Cluster weiterhin, auch wenn sie über eine K8s-API eingespielt wurden, die in der neuen K8s-Version entfernt wurde.

Die K8s-Objekte sind dann einfach automatisch in der neuen API-Version am Cluster verfügbar. Warum muss man sich dann überhaupt im Vorfeld um die deprecated/removed-APIs kümmern?

Sobald man die K8s-Manifeste wieder deployen will (vlt um einen bestimmten Stand herzustellen, eine neue Version seines Containers zu deployen oder eine Konfiguration zu ändern) schlägt das Deployment fehl. Wenn es sich dann auch noch um eine Third-Party-Applikation handelt, wo der Hersteller die neuen APIs erst mit der neuen Applikationsversion updatet, kann die Situation verzwickt werden.

Und auch bei eigenen Applikationen will man sich womöglich ausreichend Zeit lassen auf eine neue API-Version zu migrieren und das neue Verhalten erst mal ausgiebig testen.

K8s-Deprecation-Policy

Ab der Version 1.22 wurde ein 3-Releases-pro-Jahr-Rhythmus eingeführt. Mit jedem Release können APIs als deprecated (also “veraltet”) eingestuft werden. Damit sind die APIs zwar noch grundsätzlich verfügbar, können aber in einer der folgenden Releases entfernt werden.

Dabei gilt lt. Deprecation-Policy folgendes.

GA API Versionen (z.B. v1 oder v2)

können deprecated werden, dürfen aber nur mit einer Major-Version von K8s entfernt werden. Derzeit gibt es nur die Major-Version v1 von K8s und es ist aktuell auch nicht geplant neue Major-Versionen zu veröffentlichen, die GA APIs obsolet machen würden. Damit ist hier wenig Handlungsbedarf zu erwarten.

Beta API Versionen (z.B. v1beta1, v1beta2)

sind nach ihrer Deprecation noch mindestens 9 Monate oder 3 Releases verfügbar (der längere Zeitraum gilt). Hier gab es in den letzten Releases immer wieder APIs die davon betroffen waren. Allein zwischen v1.22 und v1.25 wurden 29 APIs entfernt, die vor v1.22 noch verfügbar waren.
Die Gefahr, dass Applikationen auf v1.22 noch tadellos funktionieren, aber auf v1.25 gar nicht mehr installierbar sind, ist also nicht so gering.

Alpha API Versionen (z.B. v1alpha1)

können jederzeit entfernt werden, auch ohne vorherige Deprecation. Meistens sind alpha-Features allerdings standardmäßig deaktiviert, können also ohne zutun gar nicht verwendet werden. Achtung bei alpha-APIs von CustomResources. Diese haben nichts mit dem Kubernetes-Release selbst zu tun. Es gibt z.B. alpha-APIs von ArgoCD, K8sGPT einigen Ingress-Controllern. Diese unterliegen aber einer DeprecationPolicy vom jeweiligen Produkt.

Welche API-Version wird verwendet

Am leichtesten ist es, das originale K8s-Manifest zu analysieren, das deployt wird. Wenn man einen GitOps-Approach verfolgt und damit alle Manifests in git speichert, tut man sich sehr leicht und kann diese Analysen sogar in seine git-Pipeline integrieren.

Wenn man allerdings direkt am K8s-Cluster wissen will, welche APIs verwendet wurden, wird das Thema leider etwas komplexer als man denkt. Wenn man nämlich glaubt, dass man z.B. mit

kubectl get -A cronjob.v1beta1.batch

alle Cronjobs der v1beta1 API am Cluster bekommt, dann irrt man.

Die API-Version, mit der man K8s-Objekte am K8s-Cluster deployt, oder vom K8s-Cluster abholt, muss nicht zwangsläufig die sein, die auch am K8s-Cluster gespeichert wird. So kann man ein Cronjob-Objekt in der Version batch/v1 mit kubectl apply am K8s-Cluster deployen, aber mit kubectl get cronjob.v1beta1.batch in der alten API-Version abholen. Umgekehrt kann jemand einen Cronjob in der Version batch/v1beta1 deployen und mit kubectl get cronjob wird derselbe Cronjob in der neuen API batch/v1 ausgegeben.

D.h. wenn man mit kubectl, ArgoCD oder anderen Tools K8s-Objekte deployt, muss die in den Manifesten angegebene Version nicht unbedingt die sein, wie sie auch in der Control-Plane gespeichert ist. Umgekehrt ist die Version, mit der man K8s-Objekte vom API-Server abfragt, nicht zwangsläufig die, die man auch beim Speichern der Objekte verwendet hat.

Details dazu sind 2018 in diesem Issue-Comment diskutiert worden. Auch im Abschnitt API groups and versioning oder etwas ausführlicher in einem SIG-Architecture Paper erklärt.

Analysen und Tools

Wie man trotzdem feststellen kann, welche API-Version man beim Deployment verwendet hat, hängt davon ab welche Tools man für das Deployment verwendet.

kubectl und kustomize

Hier hilft die Annotation kubectl.kubernetes.io/last-applied-configuration . Diese Annotation enthält den tatsächlichen Inhalt des Konfigurationsfiles, mit dem das Objekt erstellt wurde. Und dort ist auch die apiVersion enthalten, die im Konfigurationsfile definiert wurde.

In folgendem Beispiel sieht man den Output von kubectl get cronjob demo-deprecated-cron-job

apiVersion: batch/v1
kind: CronJob
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"batch/v1beta1","kind":"CronJob","metadata":{"annotations":{},"name":"demo-deprecated-cron-job","namespace":"dep-apis-test"},"spec":{"concurrencyPolicy":"Replace","jobTemplate":{"spec":{"template":{"spec":{"containers":[{"args":["-e","console.log(new Date().toString());"],"image":"node:14-alpine","imagePullPolicy":"Always","name":"demo-cron-job"}],"restartPolicy":"OnFailure"}}}},"schedule":"*/1 * * * *","successfulJobsHistoryLimit":10}}
creationTimestamp: "2023-07-11T14:29:40Z"
generation: 1
name: demo-deprecated-cron-job
namespace: dep-apis-test
resourceVersion: "731211"
uid: cc96bb93-43ab-4e3a-8f01-4822f68622c9
spec:
[...]

Hier sieht man auch dass die apiVersion vom Objekt batch/v1 ist, lt. last-applied-configuration Annotation aber in der Version batch/v1beta1applied worden ist. Aber Achtung: nur mit kubectl apply wird diese Annotation gesetzt. kubectl replace oder kubectl create erstellen diese Annotation nicht bzw. löschen sie sogar! Details dazu in dieser Dokumentation.

Helm3

Helm3 speichert ebenfalls die originale Chart-Konfiguration in einem speziellen Secret im Ziel-Namespace ab, das man auswerten kann. Den Inhalt kann man sich z.B. so ausgeben lassen:

kubectl get secret sh.helm.release.v1.vault.v1 -o json | jq .data.release | tr -d '"' | base64 -d | base64 -d | gzip -d

ArgoCD

ArgoCD verwendet immer kubectl, auch bei Helm-Charts. Standardmäßig wird kubectl apply angewendet. Man kann aber optional auch die Replace-Funktion verwenden, die dann aber dazu führt dass das die oben erwähnte Annotation verschwindet!

Analysetools

Natürlich ist es mühsam die oben erwähnten Informationen händisch zu sammeln und auszuwerten. Wir haben uns gängige Analysetools angesehen. Gleich vorweg: aktuell hat hier Pluto die Nase vorn. Warum, wird im folgenden erklärt.

Kubent

Kubent kann sowohl lokale Files, deployte Helm-Charts und die Annotation last-applied-configuration analysieren. Helm-Charts können auch lokal gerendert und als stdin in kubent analysiert werden. Leider gibt es bekannte Probleme bei älteren Clusterversionen, weshalb z.B. Cronjobs unter v1.21 nicht analysiert werden.

Pluto

Pluto deckt genauso wie Kubent lokale Files wie auch In-Cluster-Analysen ab, hat aber keine bekannten Einschränkungen in älteren Clustern. Außerdem hat es eine bessere Erweiterbarkeit für Custom-Version-Checks als Kubent.

Trivy

Das populäre Security-Scanning-Tool trivy von AquaSec kann in der aktuellen Version leider noch keine last-applied-config Annotation analysieren. Wurde aber von uns in https://github.com/aquasecurity/trivy/issues/4784 eingemeldet und wird bereits gefixt. Sobald trivy dieses Feature offiziell ausrollt, sollte man nochmal einen Blick drauf werfen.

Kubepug

Kubepug hat leider noch einige offene Probleme im Verfahren APIs als deprecated zu markieren als auch zu erkennen ob sie verwendet wurden. Für uns scheidet diese Tool leider komplett aus. Es wird aber noch aktiv daran entwickelt, also vlt ändert sich daran noch etwas.

Renovate

Das populäre Update-Tool renovate kann auch deprecated K8s-APIs erkennen. Allerdings natürlich nur Files in git-Repos und nicht im Cluster und hier auch nur native K8s-Manifests, keine Helm-Charts. Allerdings erstellt Renovate dann auch gleich Pull-Requests, was natürlich sehr angenehm ist. Renovate gibt aber zu, dass diese PRs Fehler haben können, weil sie nur die API-Version ändern und nicht mögliche andere notwendige Änderungen in der spec.

Fazit

Deprecated K8s-APIs sollte man nicht unterschätzen und im Application-Livecycle sowohl in der CD-Pipeline als auch im Cluster kontinuierlich prüfen.

Die Suxess-IT hilft hier gerne in Ihrer Umgebung das passende Tool zu integrieren, damit deprecated oder removed APIs kein Kopfzerbrechen mehr bereiten.

Was für die Migration der K8s-Objekte zu tun ist und wie man Helm-Charts flexibel für unterschiedliche Versionen gestalten kann, erfahren Sie in einer der nächsten Stories.

--

--