Sicherheitskopien von OpenShift Projekten

Keine Kommentare

Dr. Jekylls Elixir verdankt seine Wirkung einer ‘unbekannten Verunreinigung’ des Rezepts. Dies führt dazu, dass Dr. Jekyll seine schwindenden Vorräte nicht auffrischen kann und so die Kontrolle über sein gefährliches Alter Ego verliert. Ähnlich kann es uns mit Konfigurationsfehlern ergehen. Oft ist es fast unmöglich zu erkennen, weshalb ein älterer, ohne große Sorgfalt erstellter Versionsstand funktioniert hat, während alle Bemühungen, ihn zu reproduzieren, fehlschlagen. Ich möchte zeigen, dass regelmäßige Sicherheitskopien uns an dieser Stelle weiterhelfen können.

Ich möchte zwischen zwei verschiedenen Ansätzen zu solchen Backups unterscheiden. Zum einen gibt es ein weiteres Fläschchen im Kühlschrank. Sein Inhalt gleicht dem des ursprünglichen Elixirs. Man kann es mit einem Datenbank Snapshot vergleichen. Zum anderen können wir uns einen Laborbericht zum Elixir vorstellen. Dieser stellt vermutlich unsere einzige Chance dar, die ‘unbekannte Verunreinigung’ zu identifizieren und nach Wunsch zu reproduzieren.

In vielen Fällen ist das Fläschchen im Kühlschrank dem Laborbericht vorzuziehen. Man kann sich darunter ein Backup der zentralen etcd Datenbank des Kubernetes Masters vorstellen. Ich möchte mich dennoch auf den Laborbericht konzentrieren. Unter Zeitdruck mag dieser wenig hilfreich erscheinen, aber er bietet eine detaillierte, visuell lesbare Perspektive auf einen Zeitpunkt, zu dem unser Dienst korrekt funktioniert hat.

Obwohl dieser Ansatz nur in Ausnahmefällen die Wiederherstellung eines ganzen Clusters ermöglicht, können wir ein Projekt unter die Lupe nehmen, seine Bestandteile isolieren und hoffentlich die fast unsichtbare Konfigurationseinstellung identifizieren, die den Unterschied zwischen einem erfolgreichen und einem fehgeschlagenen Deployment ausmacht.

Der Vorgang erfordert keine Datenbanksperre. Wir erstellen gültige, eingerückte JSON Objekte, keine Binärartefakte. Wir erzeugen eine gewisse Last, aber keine Unterbrechung.

Man könnte annehmen, dass Laborberichte nicht nötig sind, da unsere Infrastruktur komplett aus Quellcode aufgebaut wird. Alle wichtigen Details sind in Git abgelegt. Sollte da nicht das Rezept reichen? Komplette Übereinstimmung von Infrastuktur und Quellcode bleibt leider meist ein unerfüllter Wunsch. Wer kann behaupten, nie Feineinstellungen an Diensten vorzunehmen (ein Port hier, ein angepasster Health Check da); wer reißt regelmäßig die Cluster ab, um sie komplett neu aufzusetzen? Wir sind anfälliger für Dr. Jekylls Dilemma als wir annehmen.

Export der Projekte

Das Skript export_project.sh im Git Repository openshift/openshift-ansible-contrib stellt unseren Ausgangspunkt dar. Wir verwenden eine angepasste Version (s. Pull Request, inzwischen in Branch Master übernommen).

Eine Stärke der Kubernetes Objekt-Datenbank ist, dass einzelne Objekte als JSON/YAML serialisiert und mit gewöhnlichen Kommandozeilentools gefiltert werden können. Wir bestimmen zum einen, welche Objekte für uns interessant sind, und zum anderen, welche Eigenschaften ausgelassen werden können. So können wir gewöhnlich auf Verwaltungseigenschaften wie .status verzichten.

oc export wird in einer zukünftigen Version entfernt, also benutzen wir oc get -o json (nachbereitet mit jq) um unsere Objektdefinitionen zu exportieren. Pods sind ein gutes Beispiel. Die meisten Podeigenschaften sind für unsere Zwecke wertvoll, aber es gibt auch einige, die wegfallen können: Zusätzlich zur bereits erwähnten Eigenschaft .status filtern wir .metadata.uid, .metadata.selfLink, .metadata.resourceVersion, .metadata.creationTimestamp und .metadata.generation aus.

Hierbei kommt es zu einer gewissen Duplizierung von Eigenschaften. Wir sichern Pod und ReplicationController Definitionen, aber wir sichern außerdem Deploymentkonfigurationen. Wenn ein Objekt des Typs DeploymentConfiguration vorliegt, sind Pods und ReplicationController gewissermaßen redundant. Dennoch scheint es ratsam, alle drei Objekte aufzubewahren. Es ist nicht nötig, von einer bestimmten Deploymentsequenz auszugehen, und einzelne Objekteigenschaften (ein Healthcheck des Pods, zum Beispiel) könnten angepasst worden sein. Wir können die Möglichkeit einer unscheinbaren und dennoch entscheidenden Veränderung nicht ausschließen.

Ich möchte auch betonen, dass dieser Ansatz weder Images noch Applikationsdaten (ob in emptyDir Verzeichnissen oder persistent abgelegt) sichert. Er ergänzt Sicherheitskopien der etcd Datenbank und Dateisysteme, aber er kann sie nicht ersetzen.

Welche Veränderungen wurden am Skript vorgenommen? Der Pull Request adressiert drei Probleme: Der Exportprozess bricht nicht mehr ab wenn ein Ressourcentyp nicht erkannt wird. Er gibt lediglich eine Warnung aus. So können ältere OpenShift Versionen unterstützt werden. Ganz ähnlich fährt der Export fort wenn der Master Nutzern Zugriff auf einen Ressourcentyp verwehrt. Damit wird nicht-Admins die Nutzung des Skripts ermöglicht. Drittens gibt der Export stets gültiges JSON aus. Die ‘stacked JSON’ Ausgabe des Originals wird zwar von jq und auch oc unterstützt, aber es scheint für den Backupgebrauch zu riskant, sich darauf zu verlassen, dass wir zu einem späteren Zeitpunkt mit ungültigem JSON arbeiten können. python -m json.tool, zum Beispiel, erfordert gültiges JSON mit einem Rootelement und lehnt die Ausgabe des ursprünglichen Skripts ab. ‘Stacked JSON’ ist eine ausgezeichnete Wahl für die Übertragung von Zeitreihendaten wie Logeinträgen, aber nicht für Sicherheitskopien von JSON Objekten.

Automatisierte Backups

Jetzt können wir uns der Automatisierung widmen. Gehen wir von nächtlichen Projektbackups aus. Wir erstellen Exports für alle Projekte, komprimieren die Ausgabe, fügen einen Datumstempel hinzu und legen das Archiv in permanentem Speicher ab. Wenn das gelingt, rotieren wir die Archive indem wir alle Dateien löschen, die älter als eine Woche sind. Die Parameter (wann und wie oft der Export ausgeführt wird, die Vorhaltezeit, usw.) werden dem CronJob-Template bei der Erstellung mitgegeben.

Angenommen, wir haben das System eingerichtet und gestartet. Was genau geschieht im Projekt cluster-backup?

Grafische Darstellung des Backup Dienstes
Abb. 1 Backup Dienst

Das nächtliche CronJob Objekt erstellt einen Pod, der das Skript project_export.sh für alle Projekte im Cluster ausführt. Auf dem Pod sind lediglich oc und jq installiert. Es mag praktisch erscheinen, diesem Pod die Wiederherstellung aus gezogenen Backups zu ermöglichen, aber wir sollten dieser Versuchung widerstehen, da dies sehr großzügigen Schreibzugriff auf den Cluster erfordern würde.

Der permanente Speicher ist mit Zugriffsmodus ReadWriteMany ausgestattet, so dass wir auch während eines laufenden Backups unsere Archive erreichen können. Dies geschieht mit Hilfe eines stets laufenden Pods, der sonst exakt dem kurzlebigen Pod des CronJobs entspricht:

$ oc project cluster-backup
$ POD=$(oc get po | grep Running | cut -d' ' -f1)
$ oc exec ${POD} -- ls -1 /openshift-backup
openshift-backup20180911.zip
openshift-backup20180912.zip
openshift-backup20180913.zip

Rollenkonzept

Der Aspekt Rechtevergabe ist entscheidend. Der technische User des Pods erhält cluster-reader Rechte und eine zusätzliche, für diesen Zweck erstellte Clusterrolle secret-reader. Ihre Definition ist folgende:

kind: ClusterRole
apiVersion: v1
metadata:
  name: ${NAME}-secret-reader
rules:
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["get", "list"]

Das ist eine sehr mächtige Rolle insbesondere in Verbindung mit cluster-reader Privilegien. Die Hauptsache ist jedoch, dass es eine strikt lesende Rolle ist. Ein besonders wertvoller Aspekt von maßgeschneiderten Clusterrollen ist, dass sie uns davor bewahren, technische User mit cluster-admin Rechten zu versehen.

Unsere Sicherung der Projekte darf nicht an der Entscheidung scheitern, Ressourcen wie Secrets oder Routen auszuschließen. Man sollte auch nicht in die Verlegenheit kommen, Teile des Exportskripts auszukommentieren, nur damit das Backup durchläuft. Die Rechtevergabe erfolgt über die Definition des technischen Users. Jeder Ressourcentyp wird auf Verfügbarkeit und Zugriffsberechtigung geprüft. Exportiert wird nur das, was der technische User sehen kann.

an alternative overview centred on rights and permissions
Abb. 2 Rollenkonzept

Administratorzugang wird nur beim Erstellen des Projekts benötigt. Die Annahme ist, dass dies durch angemeldete (menschliche) Nutzer geschieht. Wie Abb. 2 zeigt, erhält der Pod, der die eigentliche Arbeit ausführt, ‘security context constraint restricted’ und ‘security context non-privileged’. Im wesentlichen hat der technische User des Pods lesenden Zugriff auf die etcd Datenbank und Schreibberechtigung für den permanenten Backup Speicher.

Wo fange ich an – und warum?

Um das Projekt aufzusetzen, reicht die folgende Eingabe:

$ git clone https://github.com/gerald1248/openshift-backup
$ make -C openshift-backup

Wer nicht auf den nächtlichen CronJob warten möchte, kann wie oben die Variable POD setzen und eine Sicherheitskopie aller Projekte erstellen:

$ oc exec ${POD} openshift-backup
Exporting 'rc' resources to myproject/rcs.json
Exporting 'rolebindings' resources to myproject/rolebindings.json
Skipped: list empty
Exporting 'serviceaccounts' resources to myproject/serviceaccounts.json
...

Das Ergebnis ist ein Archiv im Ordner /openshift-backup mit einem Dateinamen gemäß openshift-backupyyyymmdd.zip. In jedem Fall sollte man prüfen, ob das Backup korrekt erstellt worden ist. Es ist zum Beispiel denkbar, dass kein permanenter ‘ReadWriteMany’ Speicher provisioniert werden konnte. Man kann das Skript project_import.sh (es ist neben project_export.sh im Repository openshift/openshift-ansible-contrib abgelegt) benutzen, um ein Projekt zur Zeit wiederherzustellen. Oft ist das Backup aber eher als Analysewerkzeug zu betrachten. Meistens interessiert uns nicht ein ganzes Projekt und schon gar nicht der ganze Cluster. Der größte Nutzen liegt darin, je nach Bedarf einzelne Objekte neu erstellen zu können.

Kubernetes und damit OpenShift verwaltet eine sehr große Anzahl von Objekten für ein durchschnittliches Projekt. Jedes von ihnen könnte durch oc edit oder oc patch verändert worden sein. Es mag auch gewisse Eigenschaften nicht besitzen, die in dem in Git versionierten YAML Dokument vorhanden sind. Kubernetes verschluckt nicht selten falsch eingerückte Eigenschaften.

Es gibt viele Wege, unbewusst ‘unbekannte Verunreinigungen’ in unsere Infrastruktur zu schmuggeln. Wenn wir bedenken wie gering die die benötigte CPU-Last ist, und wie wenig permanenten Speicher eine Woche nächtlicher Projektbackups belegt, scheint es mir sinnvoll, stets Laborberichte in der Hinterhand zu behalten für den Fall, dass die Fläschchen im Kühlschrank zur Neige gehen.

Gerald Schmidt

Verbringt zu viel Zeit mit Wolken, manchmal öffentlich, manchmal privat. An wolkenfreien Tagen sitzt er an Open Source Projekten von ungewissem Nutzen.

Kommentieren

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.