Hinter den Kulissen

Wie Knative unsere Frontend-Previews vereinfacht

Matthias Renaud
8.6.2023
Bilder: Christian Walker

Preview-Deployments sind immer so eine Sache, vor allem, wenn man sie nicht aufräumt. Was Preview-Deployments überhaupt sind und wie wir sie in Form gebracht haben, erzähle ich euch hier.

Wer mit Deployments in irgendeiner Form zu tun hat, weiss, dass man diese auch gelegentlich mal updaten muss, und wer updated, will auch testen können (und auch wer nicht will, muss trotzdem, aber das ist ein anderes Thema). Bei Frontend-Belangen kommt die Schwierigkeit hinzu, dass oftmals mehrere unterschiedliche Änderungen gleichzeitig getestet werden müssen, weshalb wir nicht einfach das bestehende Test-Deployment überschreiben dürfen. Dafür gibt’s die Preview-Deployments. Dabei handelt es sich um Frontend-Deployments, die zum Testen einer spezifischen Änderung da sind, und die mitunter in grösserer Anzahl parallel laufen müssen. Damit Nicht-Tech-Spezialisten sich diese anschauen können, ohne dafür einen Kurs besuchen zu müssen, sollten sie auch einfach im Browser anzusteuern sein.

Deployments für Kinder und solche, die es werden wollen

Bei uns laufen all diese Deployments auf Kubernetes, spezifischer: Auf Azure Kubernetes Service (AKS), was uns einige Vorteile bringt, aber uns nicht alles abnimmt. GKE alias Google Kubernetes Engine haben wir übrigens auch im Einsatz, aber nicht für diesen Use Case. Insbesondere das Aufräumen macht auch AKS nicht freiwillig, was bei regulären Deployments (wo neuere Versionen ältere überschreiben) kein Problem ist, aber bei Frontend-Previews, die sich eben gerade nicht gegenseitig überschreiben dürfen und darum bisweilen in unvorhersagbarer Anzahl präsent sind, eben schon. Die sollten nach Gebrauch wieder ordentlich weggeräumt werden. Aber davon wollten wir schon als Kinder nichts wissen, und weil IT-Leute im Grunde genommen lediglich grossgewachsene Achtjährige sind, darf von uns hier auch nicht allzu viel erwartet werden. Wir hatten also eine Menge Deployments am Start, und mit ihnen die zugewandten Ressourcentypen Service und Ingress, und niemanden, der regelmässig aufräumt. Könnten wir wenigstens die Pods, die für CPU- und RAM-Verbrauch verantwortlich sind, auf null halten, bis sie gebraucht werden?

Horizontal Pod Autoscaler to the rescue! Wenn das denn nur so einfach wäre… der skaliert nämlich nicht auf null, bzw. skaliert er ein Deployment mit null Replicas nicht hoch, weil er dazu mindestens einen Pod bräuchte, dessen CPU- und RAM-Verbrauch er messen kann. Wir mussten also alle Preview-Deployments mit mindestens einem Pod am Laufen halten, für den Fall, dass jemand sich das anschauen will – und unsere Frontend-Teams deployen sehr fleissig. Das kostet Ressourcen, die anderen Deployments dann nicht zur Verfügung stehen, und die man durch mehr oder grössere Nodes ausgleichen muss – geschenkt gibt es die aber freilich nicht. Eine andere Lösung musste also her, und nach etwas Recherche-Arbeit fanden wir sie in Knative.

Und was kann dieses… – wie spricht man das überhaupt aus?

Red Hat sagt: kay-nay-tiv. Wir benutzen die Serving-Komponente davon. Für Eventing setzen wir stattdessen auf KEDA, aber das ist eine andere Geschichte und soll ein andermal erzählt werden – auch Features wie Traffic Splitting benutzen wir aktuell nicht.

Grundsätzlich sehen Knative-Servings ganz ähnlich aus wie die handelsübliche Trias Deployment – Service – Ingress, setzen aber auf eigene Custom Resource Definitions und sind in der Lage, von null zu skalieren, sobald Requests eintreffen. Das erlaubt uns, mal grundsätzlich von allen Pull Requests ein Preview Deployment zu builden und auszurollen, ohne Systemressourcen dafür reservieren zu müssen – unabhängig davon, ob das dann von irgendjemandem angeschaut wird oder nicht. Ein dedizierter, Knative-eigener Ingress-Controller namens Kourier hilft ausserdem dabei, die Requests zu der gewünschten Instanz zu routen, ohne manuell etwas einstellen zu müssen.

So sieht ein reguläres Deployment aus:

Und so eines, das mit Knative erstellt worden ist:

Service as a Service

Selbstverständlich sollen sich unsere Feature-Teams nicht mit Knative oder anderen infrastrukturverwandten Themen mehr herumschlagen müssen als nötig. Wir, Team Bender, sind eines von mehreren Platform-Engineering-Teams, und nebst Kubernetes auch mit dafür zuständig, den Feature-Teams das Deployen so komfortabel wie möglich zu machen. Darum haben wir uns schon vor einiger Zeit mal daran gemacht, eine ganze Sammlung von Helm-Charts anzulegen, die alle schlimmlichen Details so weit abstrahieren, dass jedes Team nur ein einfaches values.yaml-File mit den wichtigsten Angaben in seinem Repository haben muss.

Das Team Bender in Aktion
Das Team Bender in Aktion

Das Bereitstellen von Knative war im Wesentlichen eine zweigeteilte Sache:

  1. Die Installation von Knative selber auf unseren Clustern: Dazu mussten wir die passenden Manifeste in unsere Cluster-Setup-Pipeline einhängen, die das alles für uns erledigt, und auch erlaubt, einen neuen Cluster weitestgehend automatisiert zu deployen und zu switchen, fast ohne dass jemand etwas davon mitbekommt. Aber das ist eine andere Geschichte und soll ein andermal erzählt werden.
  2. Das Zurverfügungstellen der bereits erwähnten Helm-Charts: Unser reguläres Deployment-Chart war hier als Vorlage eigentlich schon ziemlich in Ordnung. Mit ein bisschen Copy & Paste und etwas Herumschrauben hier und da, hatten wir schon recht bald etwas, was wir ohne allzu schlechtes Gewissen auf unsere nichtsahnenden Kolleginnen und Kollegen loslassen konnten (vermuten wir jedenfalls, denn “schlechtes Gewissen” mussten wir erst im Wörterbuch nachschauen).

Der Kourier-Ingress-Controller hat uns dann in Zusammenarbeit mit Wildcard-DNS dabei geholfen, jedes Preview-Deployment unter seiner Pull-Request-ID zur Verfügung zu stellen, und los konnte die Sache gehen.

Irgendwelche Probleme?

Probleme nicht, aber Herausforderungen. Kourier als zusätzlicher Ingress-Controller hat uns etwas Zusatzaufwand gekostet. Grundsätzlich setzen wir auf Nginx. Da hätten wir es schon bevorzugt, dies auch bei Knative so zu handhaben, da Ingress Rules oftmals controller-spezifisch konfiguriert werden müssen. Weil unsere Preview-Deployments alle eine Wildcard-Subdomain verwenden, die ansonsten für nichts anderes gebraucht wird, konnten wir den Traffic ziemlich einfach zu Kourier leiten, ohne dass sich Nginx hier zuständig hätte fühlen müssen.

Leider aber haben Knative-Deployments an einigen Stellen etwas andere Performance-Charakteristika als reguläre Deployments, so dass wir sie nur eingeschränkt für Lasttests nutzen können. Wir haben den Kourier-Controller in Verdacht, der auch an einigen Stellen buffert, wo das der Nginx nicht tut, aber mehr als Kaffeesatzlesen ist das an dieser Stelle nicht. Zum Glück herrscht in einer IT-Abteilung an Kaffeesatz selten Mangel.

Ein weitere Herausforderung war, dass Knative keine hostPath-Mounts unterstützt. Datadog, das wir als Monitoring-Lösung einsetzen, benötigt diese aber, um seine Config in jeden Pod mounten zu können, und so sind wir für den Moment metrik- und alertlos, was Preview-Deployments betrifft. Reguläre Deployments benutzen kein Knative, darum ist das halb so wild, auch wenn allfällige Probleme hier schon zu erkennen natürlich schön wäre. Die Details sind noch ein bisschen nebulös, aber eine grundlegende Vorstellung, wie das zu lösen wäre, haben wir schon.

Die grössere Herausforderung war das oben erwähnte Aufräumen. Das macht nämlich auch Knative nicht von selbst.

Old preview deployments do not spark joy

Knative erstellt für jedes Preview-Deployment mehrere Services, damit ältere und neuere Versionen nebeneinander laufen können. Und weil wir, was das Aufräumen betrifft, mit unterschiedlicher Motivation zu Werke gehen, sind im Frontend-Namespace irgendwann mal tausende von K8s-Ressourcen herumgelegen. Kubernetes findet das nicht sonderlich gut, weil dann in diesem Namespace kein Service seine Pods mehr findet, bis man mal seine innere Marie Kondō beschwört, und tut, was getan werden muss. Gemerkt haben wir das, weil auch die regulären Test-Frontend-Deployments nicht mehr liefen, mit denen sich Knative denselben Namespace teilt – nicht die beste Idee, wie wir feststellen mussten. Offensichtlich war es nicht genug, einfach nur die Anzahl Pods auf null zu halten.

Also haben wir Shell-Skripte gebaut (etwas altschul, aber immer noch zweckmässig), die von regelmässig laufenden Pipelines ausgeführt werden, und die alle Preview-Deployments, die älter sind als 14 Tage ebenso automatisch löschen wie alle, deren Pull Requests abgeschlossen worden sind. So sind unsere Namespaces immer wieder schön sauber, und Kubernetes hat keine Mühe mehr, seiner Arbeit nachzugehen.

Ein bisschen Statistik

Ein kurzer Blick in den Namespace (mit grosszügiger Unterstützung von kubectl und grep) zeigt, dass aktuell (Stand 01.06.2023, 12:52) 43 Preview-Deployments dort wohnen. Preview-Pods sind es deren drei. 40 Deployments laufen also momentan mit null Pods, wo es vor Knative jeweils mindestens einer gewesen wäre. Die meisten Nodes in unserem Test-Cluster verfügen über 16 CPU-Cores und 32 GiB RAM – all diese Preview-Pods, die nicht laufen, weil wir sie nicht brauchen, sparen uns rund einen halben Node. In der Praxis ist das eher ein ganzer Node. Das klingt nicht nach viel, ist für uns aber doch eine spürbare Einsparung und wahrscheinlich auch nur die Spitze des Eisbergs: All die Pods, die zu den Preview-Deployments gehört hätten, die wir nicht mehr sehen, weil wir sie regelmässig aufräumen, deren Zahl dürfte noch einiges höher gewesen sein.

Zur Illustration hier zwei aufschlussreiche Diagramme, die anhand der Anzahl Pods über die Zeit unsere Einsparungen anschaulich darstellen:

ohne Knative.
ohne Knative.
mit Knative
mit Knative

Ich weiss, da sind nicht allzuviele Details drin, aber ich habe immerhin die Achsen beschriftet.

Wie geht’s weiter?

Natürlich sind wir bei der Implementation von Knative noch lange nicht am Ende angelangt. Manch eine der erwähnten Herausforderungen liegt immer noch zum Anpacken bereit, einiges könnte noch performanter sein, und wer weiss, ob wir nicht schon morgen eine bessere Alternative zu Knative finden.

Hattet ihr schon mit ähnlichen Anwendungsfällen zu tun? Wie habt ihr’s gelöst, benutzt ihr Knative oder was ganz anderes? Ideen, Anmerkungen, Fragen, die Lottozahlen von morgen – gern alles in die Kommentare!

54 Personen gefällt dieser Artikel



Tech
Folge Themen und erhalte Updates zu deinen Interessen

Diese Beiträge könnten dich auch interessieren

  • Hinter den Kulissen

    Mehr KI und noch mehr Preistransparenz – das «Hackfest» liefert Ideen und Ergebnisse

    von Martin Jungfer

  • Hinter den Kulissen

    Von Daten zu Taten: Produktentwicklung im Umbruch (Teil 1)

    von Ronny Wullschleger

  • Hinter den Kulissen

    Lego und iPhone: Danach sucht die Kundschaft am häufigsten

    von Manuel Wenk

10 Kommentare

Avatar
later