Alles über Update-Prozesse und Paketaktualisierungen auf verteilten Systemen
Die zunehmende Virtualisierung von Maschinen hat viele Vorteile, bringt aber auch eine Reihe von Herausforderungen mit sich. Eine davon ist die kontinuierliche Aktualisierung der Pakete auf einer stetig wachsenden Anzahl von Systemen. Das eine Mal soll ein aktueller Sicherheitspatch möglichst zeitnah eingespielt werden, ein anderes Mal muss die Applikation des Kunden im vordefinierten Zeitfenster auf den neuesten Stand gebracht werden: Die Anforderungen sind unterschiedlich und nicht immer einfach unter einen Hut zu bringen. Und je mehr der Serverpark wächst, desto schwieriger wird es, die Übersicht zu wahren.
Bei VSHN machen wir Systemupdates üblicherweise einmal pro Woche, jeweils dienstags. Dies ermöglicht es uns, bei eventuellen Problemen in aller Ruhe reagieren zu können, bevor alle ins Wochenende abgerauscht sind und lässt gleichzeitig zu Beginn der Woche genügend Zeit um aktuelle Probleme wahrnehmen und entsprechend planen zu können. Im Laufe der Zeit sind wir auf so manche Herausforderungen gestossen, die die Problematik mit sich bringt. Nicht für alle haben wir bereits befriedigende Lösungen gefunden – bei einigen scheint es uns gar keine „wirkliche“ Lösung zu geben – doch möchten wir hier die Möglichkeit wahrnehmen, unsere Überlegungen und Lösungen zu einigen der Schwierigkeiten darzulegen, die wohl verbreiteter auftreten dürften.
Kundeninformation
Viele Aktualisierungen betreffen Details: es werden spezifische Fehler gefixt, von denen der Kunde üblicherweise gar nichts mitbekommt. Innerhalb einer OS-Revision sollten Updates normalerweise die Funktionsweise nicht verändern (vorausgesetzt, Programmfehler wurden nicht explizit ausgenutzt). Dennoch gibt es immer wieder Aktualisierungen, die Kernfunktionalitäten des Kundensystems betreffen – ein Update der eingesetzten Datenbanksoftware zum Beispiel, oder des Webservers – oder einen Neustart von Teilen des Systems, manchmal auch der ganzen Maschine erfordern. In jedem Fall ist damit zu rechnen, dass ein Serviceunterbruch stattfindet, und sei es auch nur für wenige Sekunden. Entsprechend ist es wichtig, dass der Kunde rechtzeitig und umfassend informiert ist. Dies gestaltet sich aber nicht immer einfach, da die Kundenbedürfnisse teils sehr unterschiedlich sind:
- Die meisten unserer Kunden verstehen Deutsch und wünschen auch, in ihrer Muttersprache zu kommunizieren. Mit anderen hingegen wird Englisch gesprochen, wenn nicht gar Portugiesisch, Holländisch oder Finnisch.
- Ein individuelles Ansprechen der Kunden mit Namen ist wünschenswert, aber bei manueller Ausführung sehr zeit-intensiv und tendenziell fehleranfällig.
- Die verschiedenen Systeme haben verschiedene Anforderungen: einen Kunden mit mariadb interessiert es nicht, ob ein Upgrade von postgresql ansteht und jenem, der nginx einsetzt, sind die Sicherheitsprobleme von Apache egal.
- Je nach SLA („Service Level Agreement“) findet die Ausführung zu verschiedenen Zeiten statt. Manche Kunden wünschen die Aktualisierung aus Kostengründen stets zu Bürozeiten, andere nur diejenigen von unkritischen Paketen ohne erwartete Auswirkung auf die Kundenapplikation und bei dritten wiederum finden alle Updates grundsätzlich im „Maintenance Window“ in der Nacht statt. Idealerweise wird dem Kunden die geltende Abmachung regelmässig kommuniziert – und zwar rechtzeitig vor dem Update, so dass er falls nötig für diese Woche ein anderes Vorgehen beantragen kann – zum Beispiel, weil an diesem Tag eine Präsentation stattfindet.
Ursprünglich informierten wir jeden Kunden einzeln per E-Mail, doch konnte sich dieses Modell nicht allzu lange halten. Nur schon aus Zeitgründen kamen wir nicht umhin, stattdessen auf eine generelle Webseite zu verweisen, auf der über die jeweils anstehenden Subsysteme informiert wird. Dafür setzen wir heute Cachet ein und benutzen es auch als generelle Status-Informationsseite bei auftretenden Störungen. Der Kunde kann sich die Informationen selbstständig abonnieren und erhält damit nicht nur die wöchentliche e-Mail über anstehende Updates automatisch, sondern wird auch zeitnah informiert, wenn einmal generelle Probleme mit der Infrastruktur auftreten. Der Server läuft selbstverständlich in einem anderen Rechenzentrum und in einem unabhängig gerouteten Netz, damit bei eventuell auftretenden Problemen auch tatsächlich informiert werden kann.
Gibt es schliesslich während des Update-Prozesses Schwierigkeiten, so sollen die Kunden auch darüber zeitnah informiert werden – so detailliert wie nötig (aber auch nicht übertrieben pointiert) und mit einer Angabe, wie wir planen, die Herausforderung in Zukunft anzugehen.
Checkliste
Mitarbeiter, die selbst nicht an der Aktualisierung teil nahmen, sollten zumindest grob darüber Auskunft geben können, was geschah und weshalb. Gerade wenn einmal etwas Gröberes schief läuft und die ausführenden Mitarbeiter vielleicht bis frühmorgens mit Reparaturarbeiten beschäftigt waren, ist es wichtig, dass ausgeschlafene Kollegen informieren können. Eine entsprechende Dokumentation ist deshalb unumgänglich, wenn auch – gerade, wenn’s mal wieder etwas länger dauerte – nicht immer ganz einfach fertigzustellen. Wir setzen auf eine technisch simple, aber in der Praxis recht effektive Checkliste in unserem internen Wiki; das zugehörige Template wird fast jede Woche angepasst, erweitert und mit Links zu Hinweisen, wie bei eventuellen Problemen vorgegangen werden kann versehen. Nach jeder Gruppe von Servern die gleichzeitig abgearbeitet wurden, wird alles Unvorhergesehene protokolliert und das entsprechende Häkchen gesetzt. Dies ermöglicht es uns auch, im Nachhinein nachzuvollziehen, wo der Prozess noch verbessert werden kann.
Aktualisierungstools
Die Effizienz der Updates steht und fällt mit dem verwendeten Werkzeug. Leider ist uns bis anhin kein abschliessend befriedigendes Tool untergekommen; die Umsetzung einiger wünschenswerter Features ist aber auch nicht ganz trivial. Da wir momentan nur Debian- oder RedHat-basierte Linuxdistributionen einsetzen, reicht es glücklicherweise bisher zumindest aus, dass das eingesetzte Tool nebst apt und deb auch noch yum/dnf und rpm versteht.
Zur Zeit setzen wir apt-dater der IBH IT-Service GmbH ein, das wir im Laufe der Zeit in der Konfiguration an unsere Bedürfnisse angepasst haben. Es gibt durchaus noch Verbesserungspotential, aber der Aufwand, selbst etwas zu entwickeln dürfte doch ziemlich hoch sein. Folgende Anforderungen wären grundsätzlich wünschenswert:
- Schnelle Übersicht über anstehende Aktualisierungen. Eine Idee wäre, anstatt zu jedem Server die veralteten Pakete aufzulisten, den umgekehrten Ansatz zu implementieren: Eine Liste aller Pakete, die auf mindestens einem Host ein Update benötigen. Super wäre dann ein Interface, in dem man unkritische Pakete ausblenden könnte (Stichwort „Tinder-Style“), um sich schnell auf die wesentlichen, kritischen Pakete zu konzentrieren.
- Updaten soll einfach sein – aber doch nicht zu einfach. Es ist in der Vergangenheit schon vorgekommen, dass Server versehentlich aktualisiert wurden, weil ein einzelner Tastendruck eine ganze Reihe von Aktionen ausführen kann – praktisch nicht unterbrechbar. Dass „Update“ im apt-Jargon dann auch noch fürs Updaten der Metainformationen (und nicht der eigentlichen Pakete) steht macht die Ausgangslage auch nicht einfacher. In der Praxis ist dies aber nur ein Problem, wenn stets neue Mitarbeiter die Upgrades übernehmen.
- Nebst der Update-Funktionalität sollten auch auto-removes automatisiert ausgeführt werden können.
- apt-dater parst die Ausgaben der apt-Prozesse auf den Zielsystemen und warnt zum Beispiel bei Vorkommen des Worts „error“. Prinzipiell eine gute Idee – aber wenn der Update-Prozess den Satz „no error occured“ enthält kann das Abnicken jeder einzelnen Meldung ziemlich mühsam werden. Dieses Problem haben wir mittlerweile mittels detaillierterer Konfiguration lösen können.
- Anders als yum verlangt apt standardmässig gerne mal eine manuelle Intervention. Das hat durchaus seine Vorteile; auch hier gilt aber: muss das auf jedem Server einzeln bestätigt werden (teilweise auch mehrfach, wenn beispielsweise noch mehrere ältere Kernel installiert sind), so kann einiges an Zeit verloren gehen.
Paket – Cache
Ein Problem, das uns lange beschäftigte, war der Umgang mit Repositories, die zeitweise nicht erreichbar oder in inkonsistem Zustand sind. Gerade bei Ubuntu-Mirrors kommt es immer mal wieder vor, dass die Indizes nicht korrekt erstellt werden und eine Viertelstunde gewartet werden muss, bis der nächste cronjob diese flickt. Das bedeutete dann Zwangspausen – manchmal mehrmals täglich. Auch gibt es immer mal wieder Pakete, die während des Tages neu hinzukommen. Werden diese dann eingespielt, so geht das „Kanarienvogelprinzip“ – wir wollen zuerst auf Staging- und weniger wichtigen Systemen updaten, um eventuelle Probleme frühzeitig zu erkennen – verloren. Und last but not least sind viele Mirrors so konfiguriert, dass sie nur eine begrenzte Zahl von Anfragen von einer IP oder einem IP-Range zulassen, um DOS-Attacken zu verhindern. Kommen nun eine grosse Anzahl von Anfragen praktisch gleichzeitig, zum Beispiel von einem Kunden, bei dem alle Systeme hinter einer gemeinsamen Firewall stehen, so kann nur ein kleiner Teil davon beantwortet werden und der Vorgang muss mehrere Male wiederholt werden, bis alle Systeme einmal bedient wurden.
Einen grossen Fortschritt erreichten wir für all diese Probleme mit der Einführung eines Paket-Caches. Grob gesagt führten wir einen Proxy ein, über den alle Repositories geleitet werden. Am Morgen des Update-Tages holen nun alle Systeme die anstehenden Pakete ab, ohne sie zu installieren – sie versuchen dies drei Mal, um die obengenannten Probleme mit Indizes zu umgehen. Der Paket-Cache lädt die Pakete herunter, liefert sie aus und speichert die Versionen. Der erste Schritt der eigentlichen Update-Prozedur besteht sodann im „Einfrieren“ dieses Caches: ab diesem Zeitpunkt werden nur noch genau die Versionen von Indizes und Paketen ausgeliefert, die dann aktuell waren (Ausnahme: Wird im Laufe des Tages ein Paket angefordert, das noch nicht im Cache vorhanden ist, zum Beispiel bei einer Neuinstallation, so lädt der Cache die zu diesem Zeitpunkt aktuelle Version nach).
Während das Einpflegen der entsprechenden Konfigurationen einen gewissen – pro Repository einmaligen – Zusatzaufwand bedeutet, konnten wir mit dieser Methode die Updateprozedur deutlich beschleunigen – ganz zu schweigen von der massiven Diminuierung des Frustfaktors.
Systemgruppierung
Wie bereits bei der Kundeninformation ausgeführt, haben unterschiedliche Systeme unterschiedliche Anforderungen. Eine Testumgebung, wenn auch aus der selben Anzahl und Typisierung an Servern wie ein Livesystem bestehend, kann typischerweise gut zu Bürozeiten aktualisiert werden – ja, soll dies sogar explizit, um eventuelle Probleme rechtzeitig festzustellen und entsprechend darauf reagieren zu können – während die Produktivumgebung nur eine minimale Downtime in der Nacht erlaubt. Gleichzeitig ist es aber für einen effizienten Vorgang auch wünschenswert, möglichst wenige Gruppen zu bilden, die jeweils gemeinsam aktualisiert werden können. Und selbstredend müssen neu erstellte Systeme automatisiert in den Prozess aufgenommen werden, damit sie nicht vergessen gehen.
Wir haben mit einer Reihe von Namensschemen für die Gruppen experimentiert und sind inzwischen dabei angekommen, die gleichzeitig abzuarbeitenden System in Gruppen zusammen zu fassen, die dem Namensschema „hhmm-prio-common_name“ folgen, also z.B. „2200-20-night_main“ für die erste grosse Gruppe der Server, die ab 22:00 Uhr anfallen. Jede Gruppe ist in der Checkliste aufgeführt und erst wenn alle zugehörigen Server fertig gestellt und das Monitoring wieder komplett zufrieden ist schreiten wir zur nächsten Gruppe fort.
Um das Problem der Erreichbarkeit zu vereinfachen setzten wir ursprünglich auf die einfache Lösung, das Update-Tool auf einem Monitoring-Host zu installieren, da von da aus sowieso schon VPNs zu allen relevanten Maschinen bestanden. Inzwischen haben wir aber sowohl das Monitoring via VPN als auch die ssh-Zugänge durch bessere Methoden ersetzt. Konkret kennt unser puppet die benötigten Jumphosts, um jeden gemanageten Server zu erreichen und mittels dem selbst entwickelten „sshop“-Tool wird nicht nur die entsprechende ssh-Konfiguration, sondern auch gleich jene für apt-dater geschrieben.
Reboots
Wird ein Sicherheitsleck im Kernel bekannt, so ist ein Reboot unumgänglich; aber auch ein Update einer kritischen Komponente wie openssl kann oft einfacher fertiggestellt werden, indem die ganze Maschine neu gestartet wird (anstatt jeder betroffene Service, der die Bibliothek geladen hat, einzeln). Bei Reboots gibt es aber immer eine Reihe von potentiellen Fehlerquellen, die verstärkter Aufmerksamkeit bedürfen:
- Die Sicherheitsanforderungen einer Reihe von Kunden verlangen, dass alle Daten verschlüsselt abgelegt sein müssen. Um die Harddisks verfügbar zu machen, muss bei einem Reboot entsprechend der Schlüssel eingegeben werden. Klassischerweise geschieht dies via Konsole – diese muss also erreichbar sein und das Passwort bekannt (gleichzeitig aber kompliziert genug, um sicher zu sein und nur via speziell und mehrfach abgesicherten Zugang abrufbar). Um dies zu umgehen setzen wir darauf, das Hauptsystem unverschlüsselt zu lassen und nur die kritischen Daten auf einer eigenen Partition zu verschlüsseln, welche dann beim Bootprozess nicht gemountet wird. Wir loggen uns nach dem Reboot normal via ssh auf dem Server ein und starten einen speziellen Service, der die Passworteingabe, das Mounten und das Starten der davon abhängigen Services – je nach System via sysvinit, upstart oder systemd – übernimmt. Selbstverständlich sind die Hosts, auf welchen dies vonnöten ist, in der Checkliste aufgeführt und werden spezifisch gemonitort, damit sie garantiert nicht vergessen gehen können.
- Mounts von anderen Servern werden manchmal nicht automatisch geladen, da eine Nichterreichbarkeit des Wirtsystems ansonsten den Bootvorgang unterbrechen oder zumindest massiv verlangsamen würde. Entsprechend sind benötigte Daten teilweise erst nach einem puppet-run erreichbar; dieser soll also zeitnah nach dem Booten erfolgen.
- Services müssen reboot-sicher konfiguriert werden; das heisst sie müssen nach einem Reboot wieder gestartet werden und sollen den Betrieb möglichst reibungslos wieder aufnehmen. Das ist manchmal einfacher gesagt als getan: wenn eine Datenbank neu indiziert oder grosse Datenmengen geladen werden müssen kann das schon mal eine Weile dauern.
- Die Reihenfolge von Reboots bei mehreren zusammengehörenden Servern ist oft relevant: So sollen Datenbank und Fileserver idealerweise vor Applikationsservern neu gestartet werden und diese wiederum vor Webservern und Applikationsfirewalls. Auch dürfen in Clustern zusammengehörende Server nur sequentiell rebooted werden. Dies stellen wir mit der erwähnten Gruppierung der Systeme sicher.
- Ebenfalls mit der Gruppierung lösen wir ein Problem, das zwar trivial erscheint, aber ansonsten in der Hitze des Gefechts dennoch übersehen werden kann: Bei redundanten Systemen ist stets darauf zu achten, dass das Backup erst aktualisiert wird, wenn der gesamte Vorgang auf dem Primärsystem erfolgreich abgeschlossen wurde. So wird nicht nur die Redundanz getestet, sondern es werden eventuelle Probleme auch tatsächlich nach dem Update des ersten Systems entdeckt – und nicht erst, wenn schon beide Maschinen verändert wurden.
Monitoring
Obwohl die heutigen virtuellen Maschinen innert weniger Sekunden wieder betriebsbereit sind, haben Updates und insbesondere Reboots zumindest kurze Ausfälle zur Folge. Ein dadurch verpasster puppet-run kann sich aber schnell mal für eine halbe Stunde im Monitoring niederschlagen. Entsprechend ist immer downtime einzuplanen. Ebenso sollte für mindestens rund eine Stunde nach Abschluss der Arbeiten etwas genauer aufs Monitoring geschaut werden: manchmal offenbaren sich Probleme erst, nachdem puppet auf zwei verschiedenen Maschinen durchgelaufen ist (Stichwort: exportierte Ressourcen). Ausserdem hilft es enorm, wenn Abhängigkeiten im Monitoring korrekt erfasst sind: der zeitweise Unterbruch eines VPN darf nicht dazu führen, dass sämtliche Services auf sämtlichen sich dahinter befindlichen Servern als nicht erreichbar erkannt werden.
Schlussfolgerungen und Ausblick
Der Update-Prozess ist ein sich ständig wandelnder, der kontinuierlich verbessert wird. Währenddem wir im Laufe der Jahre schon viele Details perfektioniert haben gibt es immer noch Verbesserungspotential. Und für die Zukunft planen wir eine gewisse Automatisierung auch dieser Aufgabe; doch wollen wir 100% sicher sein, dass dabei nichts schief gehen kann und so ist hier noch einiges an Entwicklungsarbeit einzubringen. Da es sich aber in der jetztigen Form immer noch um einen recht zeitintensiven und wöchentlichen Prozess handelt lohnt sich dies gewiss. Sind wir dann erst mal soweit, wird dies sicher wieder mit einem ausführlichen Blogpost abgerundet. Bis dann!