Viele Organisationen und Teams konzentrieren sich auf Prozesse und Tools, um Server und andere Infrastruktur zu bauen, kümmern sich aber nicht um Änderungen. Ist eine Änderung erforderlich – um ein Problem zu beheben, Sicherheits-Patches anzuwenden oder Software zu aktualisieren –, behandelt sie das als ungewöhnliches Ereignis. Ist jede Änderung eine Ausnahme, können Sie sie nicht automatisieren. Diese Mentalität ist der Grund dafür, warum viele Organisationen inkonsistente und unzuverlässige Systeme besitzen. Und darum verbringen viele von uns viel Zeit damit, zwischen dem Bearbeiten nerviger Tickets und dem Löschen von Feuern hin- und herzuspringen.
Die einzige Konstante bei unseren Systemen ist, dass sie sich ändern. Definieren wir unsere Systeme als Code, lassen sie auf dynamischen Infrastruktur-Plattformen laufen und liefern diesen Code mit einer Änderungs-Pipeline auf die Systeme aus, können wir Änderungen routiniert und einfach vornehmen. Werden unsere Systeme immer nur per Code und über Pipelines erstellt und geändert, können wir ihre Konsistenz sicherstellen und uns darauf verlassen, dass sie sich an alle Richtlinien halten, die wir benötigen.
»Was gibt es auf einem Server« auf Seite 206 und »Woher Dinge kommen« auf Seite 207 beschreiben, was es so alles auf einem Server gibt und wo das herkommt. Alles auf einem gegebenen Server entstammt einer definierten Quelle – sei es eine OS-Installation, Systempaket-Repositories oder Serverkonfigurationscode. Jede Änderung, die Sie an einem Server vornehmen müssen, zieht eine Änderung an einem dieser Dinge nach sich.
In diesem Kapitel geht es darum, wie Sie Elemente auf Servern ändern, indem Sie den Code anpassen, der definiert, wo das Element herkommt, und ihn auf die eine oder andere Art und Weise anwenden. Durch das Umsetzen eines zuverlässigen, automatisierten Änderungsprozesses für Ihre Server stellen Sie sicher, dass Sie Änderungen auf Ihren Systemen schnell und zuverlässig ausrollen können. Halten Sie alle Ihre Server mit den neuesten geprüften Paketen und Konfigurationen und mit minimalem Aufwand aktuell.
Es gibt eine Reihe von Patterns zum Anwenden von Konfigurationscode auf Server – unter anderem das sofortige Anwenden jeder Änderung, das kontinuierliche Synchronisieren und das neue Bauen von Servern, um sie zu ändern. Eine andere Dimension beim Umsetzen von Änderungen ist, wie Sie Ihr Tool zum Anwenden der Anpassung auf einem Server laufen lassen – ob die Konfiguration gepusht oder gepullt wird. Und schließlich gibt es noch eine ganze Reihe weiterer Ereignisse im Lebenszyklus eines Servers – vom Pausieren über das erneute Bauen bis hin zum Löschen eines Servers.
Es gibt ein Antipattern und zwei Patterns zur Entscheidung, wann Änderungen auf eine Server-Instanz angewendet werden sollten.
Auch bekannt als: Ad-hoc-Automation.
Beim Antipattern Apply on Change wird der Konfigurationscode nur dann auf einen Server angewendet, wenn eine spezifische Änderung ansteht.
Stellen Sie sich beispielsweise ein Team vor, das mehrere Tomcat-Anwendungsserver betreibt. Die Teammitglieder nutzen ein Ansible-Playbook, um Tomcat beim Erstellen einer neuen Server-Instanz zu installieren und zu konfigurieren, aber wenn der Server einmal läuft, lassen sie Ansible nicht mehr laufen, solange sie es nicht benötigen. Wird eine neue Version von Tomcat releast, passen sie ihr Playbook an und wenden es auf ihre Server an.
In der extremsten Version dieses Antipattern wenden Sie Code nur auf genau die Server an, die Sie auch ändern wollen.
Das Team in unserem Beispiel bemerkt, dass einer seiner Anwendungsserver mehr Traffic abzubekommen scheint, und die Last macht Tomcat instabil. Die Teammitglieder nehmen ein paar Änderungen an ihrem Playbook vor, um die Tomcat-Konfiguration für eine höhere Last zu optimieren, und wenden es dann auf den Server an, der die Probleme hat. Aber sie wenden es nicht auf die anderen Anwendungsserver an, weil diese die Änderung nicht benötigen.
System- und Netzwerkadministratoren haben Server traditionell per Hand gemanagt. Müssen Sie eine Änderung vornehmen, melden Sie sich am relevanten Server an und setzen sie um. Warum sollten Sie es auch anders machen? Selbst Personen, die Skripte verwenden, tendieren dazu, diese Skripte für eine spezifische Änderung zu schreiben und auszuführen. Das Antipattern Apply on Change ist eine Erweiterung dieser Arbeitsweise, bei der ein Tool für Infrastructure as Code statt eines manuellen Befehls oder eines Einmal-Skripts zum Einsatz kommt.
Das Anwenden von Code bei Bedarf für eine spezifische Änderung kann für einen einzelnen, temporären Server in Ordnung sein. Aber es ist keine passende Methode für das nachhaltige Managen einer ganzen Gruppe von Servern.
Wenden Sie Konfigurationscode nur für das Umsetzen einer spezifischen Änderung an, haben Sie lange Phasen, in denen der Code auf eine bestimmte Server-Instanz nie angewendet wird. Geschieht dies dann doch irgendwann, kann das Ganze aufgrund anderer Unterschiede auf dem zu ändernden Server fehlschlagen.
Server tendieren dazu, sich zu ändern, wenn Sie ihnen nicht genug Aufmerksamkeit widmen. Jemand nimmt vielleicht eine Änderung manuell vor – vielleicht als schnellen Fix bei einem Ausfall. Jemand anderes hat das System mit einer neueren OS-Version oder einem aktualisierten Anwendungspaket gepatcht. Das fällt in die Kategorie schneller Änderungen, von denen wir sicher sind, dass sie schon nichts kaputt machen werden. Und in die Kategorie der Änderungen, an die wir uns eine Woche später schon nicht mehr erinnern (weil es nur eine kleine Änderung war), bis wir viele Stunden damit verbracht haben, etwas zu debuggen, dass dann doch nicht mehr läuft.
Das Problem wird schlimmer, wenn wir Änderungen nur auf ein paar Server anwenden, auf andere aber nicht. Denken Sie an das vorige Beispiel, bei dem das Team Code zum Optimieren der Performance auf nur einen Server anwendet. Später wird jemand eine andere Änderung auf die Anwendungsserver anwenden müssen.
Dann werden als Nebeneffekt des Codes für die neue Änderung auch die vorherigen Anpassungen zur Performance-Optimierung auf die Server angewendet, die sie noch nicht haben. Die vorige Änderung hat eventuell unerwartete Effekte auf andere Server. Schlimmer noch: Die Person, die den Code anwendet, hat die Performance-Optimierung vielleicht schon völlig vergessen, daher dauert es viel länger, die Ursache für dadurch entstandene Probleme zu finden.
Ist man es gewohnt, Änderungen von Hand oder durch Einmal-Skripte vorzunehmen, tendiert man auch eher dazu, mit Code zur Serverkonfiguration genauso umzugehen. Dann sieht man das Tool – Ansible, Chef, Puppet – als Skripting-Tool – mit etwas komischer Syntax. Meist führt man das Tool dann auch noch per Hand vom lokalen Computer aus, statt eine Pipeline oder einen anderen Orchestrierungs-Service damit zu beauftragen.
Das Antipattern Apply on Change kommt eher zusammen mit dem Push-Configuration-Pattern (siehe »Pattern: Push Server Configuration« auf Seite 236) als mit dem Pull-Configuration-Pattern (siehe »Pattern: Pull Server Configuration« auf Seite 238) zum Einsatz. Die Alternativen zu diesem Antipattern sind Continuous Synchronization oder Immutable Server (siehe »Pattern: Immutable Server« auf Seite 234).
Auch bekannt als: Scheduled Server Configuration Update.
Zur Continuous Configuration Synchronization gehört das wiederholte und häufige Anwenden des Konfigurationscodes auf einen Server – unabhängig davon, ob sich der Code geändert hat. Dadurch werden unerwartete Unterschiede zurückgenommen oder aufgedeckt, die sich möglicherweise eingeschlichen haben – sei es auf dem Server oder an anderen Ressourcen, die vom Konfigurationscode genutzt werden.
Wir möchten gerne glauben, dass die Serverkonfiguration vorhersagbar ist. Habe ich Code auf einen Server angewendet, sollte sich nichts mehr ändern, bis ich den Code das nächste Mal anwende. Und wenn ich den Code nicht verändert habe, sollte es nichts anzuwenden geben. Aber Server – und Server-Code – sind hinterhältig.
Manchmal ändert sich ein Server aus offensichtlichen Gründen, wie zum Beispiel, dass sich jemand anmeldet und von Hand eine Änderung vornimmt. Die Leute nehmen so gerne kleinere Änderungen vor, weil sie nicht glauben, dass das irgendwelche Probleme machen wird. Oft ist genau das aber ein Fehler. In anderen Fällen kümmert sich das Team um gewisse Aspekte eines Servers mit einem anderen Tool oder Prozess. So nutzen manche Teams beispielsweise ein spezialisiertes Tool, um Server zu aktualisieren und zu patchen – insbesondere für Sicherheits-Patches.
Auch wenn sich ein Server nicht geändert hat, kann ein mehrfaches Anwenden von Serverkonfigurationscode zu Unterschieden führen. So nutzt der Code vielleicht Parameter aus einer zentralen Konfigurations-Registry. Ändert sich einer dieser Parameter, tut der Code eventuell bei der nächsten Ausführung auf einem Server etwas anderes.
Pakete sind eine weitere externe Quelle für Änderungen. Installiert Ihr Konfigurationscode ein Paket aus einem Repository, aktualisiert er vielleicht dabei das Paket auf eine neuere Version, wenn diese verfügbar gemacht wurde. Sie können versuchen, Paketversionen festzulegen, aber das bringt Sie auf einen von zwei gleich schlechten Wegen. Auf Weg 1 wird Ihr System irgendwann viele veraltete Pakete enthalten, auch solche mit Sicherheitslücken, die Angreifern gut bekannt sind. Auf Weg 2 investieren Sie und Ihr Team sehr viel Aufwand in das manuelle Aktualisieren von Paketversionsnummern in Ihrem Server-Code.
Durch das regelmäßige erneute Anwenden Ihres Serverkonfigurationscodes (eventuell über einen automatisierten Zeitplan) stellen Sie sicher, dass all Ihre Server konsistent konfiguriert sind. Auch werden so alle Unterschiede, aus welcher Quelle auch immer, eher früher als später angewendet.
Es ist einfacher, eine Continuous Synchronization zu implementieren als deren größte Alternative immutable Server. Die meisten Tools für Infrastructure as Code – zum Beispiel Ansible, Chef und Puppet – sind mit diesem Pattern im Hinterkopf entworfen worden. Änderungen lassen sich schneller und mit weniger Unterbrechungen anwenden, indem eine bestehende Server-Instanz aktualisiert wird, als eine neue Instanz zu bauen.
Führt ein automatisierter Prozess die Serverkonfiguration für Ihre Server durch, gibt es ein gewisses Risiko, dass etwas kaputtgeht. All das, was sich unerwartet ändern kann (wie weiter oben beschrieben), ist genau das, was einen Server ausfallen lassen kann. Um dies im Griff zu behalten, sollten Sie ein effektives Monitoring-System im Einsatz haben, das Sie bei Problemen benachrichtigt, und einen guten Prozess zum Testen und Ausliefern von Code, bevor Sie Änderungen auf Produktivsysteme anwenden.
Wie schon erwähnt sind die meisten Tools zur Serverkonfiguration durch Code dafür entworfen worden, kontinuierlich zu laufen. Die spezifischen Mechanismen, die sie nutzen, werden in »Pattern: Push Server Configuration« auf Seite 236 und »Pattern: Pull Server Configuration« auf Seite 238 beschrieben.
Die meisten Implementierungen zur Continuous Synchronization laufen nach einem Zeitplan. Sie haben eine Möglichkeit, die Ausführungszeitpunkte je nach Server zu variieren, sodass nicht alle Ihre Server gleichzeitig aufwachen und ihre Konfiguration ausführen.1 Aber manchmal wollen Sie Code schneller anwenden – vielleicht, um einen Fix umzusetzen oder ein Software-Deployment zu unterstützen. Die verschiedenen Tools haben dafür unterschiedliche Lösungen im Angebot.
Die Continuous Synchronization wird entweder über das Push-Configuration-Pattern (siehe »Pattern: Push Server Configuration« auf Seite 236) oder über das Pull-Configuration-Pattern (siehe »Pattern: Pull Server Configuration« auf Seite 238) implementiert. Die Alternative zu diesem Pattern sind immutable Server (siehe »Pattern: Immutable Server« auf Seite 234).
Ein immutabler Server ist eine Server-Instanz, deren Konfiguration nie verändert wird. Sie liefern Änderungen aus, indem Sie eine neue Server-Instanz mit der geänderten Konfiguration erstellen und diese nutzen, um den bestehenden Server zu ersetzen.1
Immutable Server verringern das Risiko durch Änderungen. Statt eine Änderung auf eine laufende Server-Instanz anzuwenden, erzeugen Sie eine neue Server-Instanz. Sie haben die Möglichkeit, die neue Instanz zu testen und sie dann gegen die vorige Instanz auszutauschen. Dann können Sie prüfen, ob die neue Instanz korrekt arbeitet, bevor Sie die alte Instanz zerstören, oder Sie wechseln wieder zurück, falls etwas schiefgeht.
Organisationen, die eine starke Kontrolle und Konsistenz ihrer Serverkonfiguration benötigen, finden immutable Server eventuell hilfreich. So entscheidet sich beispielsweise eine Telekommunikationsfirma, die Tausende von Server-Images nutzt, dafür, Änderungen nicht auf laufende Server anzuwenden, und bevorzugt die garantierte Stabilität ihrer Konfiguration.
Für das Implementieren immutabler Server brauchen Sie einen robusten automatisierten Prozess zum Bauen, Testen und Aktualisieren von Server-Images (wie in Kapitel 13 beschrieben). Ihr System- und Ihr Anwendungsdesign müssen das Austauschen von Server-Instanzen ohne das Unterbrechen von Services unterstützen (in »Live-Infrastruktur ändern« auf Seite 415 finden Sie ein paar Ideen dazu).
Trotz ihres Namens ändern sich immutable Server durchaus.2
Es kann sich ein Konfigurationsdrift einschleichen, insbesondere, wenn sich jemand an den Servern anmelden und manuell Änderungen vornehmen kann, statt nur dem Prozess zur Konfigurationsänderung zu folgen und neue Server-Instanzen zu bauen. Daher sollten Teams, die immutable Server einsetzen, sorgfältig auf den »Frischegrad« ihrer laufenden Instanzen achten. Es ist möglich, immutable Server mit dem Antipattern Apply on Change zu kombinieren (siehe »Antipattern: Apply on Change« auf Seite 230), was zu Servern führt, die eine lange Zeit unverändert laufen, ohne dabei Patches und Verbesserungen zu bekommen, die später gebaute Server erhalten. Teams sollten auch darüber nachdenken, den Zugang zu Servern zu deaktivieren oder die Möglichkeit für den Zugriff und das manuelle Ändern von Services nur über eine »Break Glass«-Prozedur zu erlauben.1
Die meisten Teams, die immutable Server nutzen, legen einen Großteil ihrer Konfiguration in Server-Images ab und bevorzugen dabei gebackene Images (siehe »Server-Images backen« auf Seite 225) gegenüber dem Ausbacken von Instanzen. Daher ist eine Pipeline oder ein Satz von Pipelines zum automatischen Bauen und Aktualisieren von Server-Images eine Grundlage für immutable Server.
Sie können die Konfiguration in immutablen Server-Instanzen ausbacken (wie in »Eine Server-Instanz ausbacken« auf Seite 224 beschrieben), solange Sie nach dem Erstellen keine Änderungen mehr an der Instanz vornehmen. Bei einer strikteren Form der immutablen Server werden jegliche Unterschiede zwischen Server-Instanzen vermieden. Bei diesem Vorgehen erstellen und testen Sie ein Server-Image und transportieren es dann von einer Umgebung in die nächste. Weil sich mit jeder Server-Instanz gar nichts oder nur wenig ändert, verringern Sie das Risiko von Problemen, die sich von einer Umgebung zur nächsten einschleichen.
Es wird eher dazu tendiert, Server zu backen (siehe »Server-Images backen« auf Seite 225), um immutable Server zu unterstützen. Continuous Synchronization (siehe »Pattern: Continuous Configuration Synchronization« auf Seite 232) ist der entgegengesetzte Ansatz, bei dem regelmäßig Änderungen auf laufende Server-Instanzen angewendet werden. Immutable Server sind eine Untermenge der immutablen Infrastruktur (siehe »Immutable Infrastruktur« auf Seite 395).
Server patchen Viele Teams sind daran gewöhnt, das Patchen von Servern als speziellen, eigenen Prozess zu betrachten. Aber wenn Sie eine automatisierte Pipeline haben, die Änderungen an Server weitergibt, und Server-Code kontinuierlich auf bestehende Server synchronisiert, können Sie diesen Prozess auch verwenden, um Ihre Server gepatcht zu halten. |
|
Einige Teams, mit denen ich zusammengearbeitet habe, holen sich die neuesten Sicherheits-Patches für ihre Betriebssysteme und andere zentrale Pakete, testen sie und liefern sie auf einer wöchentlichen Basis aus – manchmal sogar täglich. |
|
Dieser schnelle und häufige Patching-Prozess hat den CIO bei einem meiner Kunden beeindruckt, als in der Presse prominent über eine Sicherheitslücke in einem zentralen OS-Paket berichtet wurde. Der CIO forderte, dass wir alles stehen und liegen lassen und einen Plan erstellen, wie wir mit der Sicherheitslücke umzugehen gedenken – einschließlich einer Schätzung, wie lange es dauern wird und wie hoch die Kosten für das Umlenken von Ressourcen auf dieses Problem sind. Er war angenehm überrascht, als wir ihm mitteilten, dass der Patch, der das Problem behob, schon als Teil des morgendlichen Routine-Updates ausgerollt worden war. |
Nachdem ich Patterns für die Frage beschrieben haben, wann Sie Konfigurationscode auf Server anwenden, geht es in diesem Abschnitt nun um Patterns, mit denen Sie entscheiden, wie Sie den Code auf einer Server-Instanz anwenden.
Diese Patterns sind für das Ändern von Servern relevant, insbesondere als Teil eines Continuous-Synchronization-Prozesses (siehe »Pattern: Continuous Configuration Synchronization« auf Seite 232). Aber sie kommen auch beim Bauen neuer Server-Instanzen zum Einsatz, um die Konfiguration in einer Instanz auszubacken (siehe »Eine Server-Instanz ausbacken« auf Seite 224). Und Sie müssen ein Pattern auswählen, um die Konfiguration beim Bauen von Server-Images auszuwählen (siehe Kapitel 13).
Mit einer Server-Instanz – sei es eine neue, die Sie bauen, eine temporäre Instanz, die Sie zum Bauen eines Image verwenden, oder eine bestehende Instanz – gibt es zwei Patterns zum Ausführen eines Server-Konfigurationstools zum Anwenden von Code. Eines ist Push, das andere Pull.
Beim Push-Server-Configuration-Pattern verbindet sich ein Prozess, der außerhalb der neuen Server-Instanz läuft, mit dem Server und führt Code aus, lädt ihn herunter und wendet ihn an.
Teams nutzen Push, um keine Serverkonfigurationstools auf Server-Images installieren zu müssen.
Das Push-Pattern ist nützlich, wenn Sie mehr Kontrolle über das Timing von Konfigurations-Updates auf bestehende Server brauchen. Haben Sie beispielsweise Ereignisse wie Software-Deployments mit einer Folge von Aktivitäten über mehrere Server hinweg, können Sie diese mit einem zentralen Prozess implementieren, der den Vorgang orchestriert.
Das Push-Configuration-Pattern erfordert die Möglichkeit, sich mit Server-Instanzen zu verbinden und den Konfigurationsprozess über das Netzwerk ausführen zu können. Diese Anforderung kann zu einer Sicherheitslücke führen, da sie einen Vektor öffnet, den angreifende Gruppen eventuell nutzen können, um sich mit Ihren Servern zu verbinden und unautorisierte Änderungen vorzunehmen.
Es kann umständlich sein, eine Push-Konfiguration für Server-Instanzen auszuführen, die automatisch von der Plattform erzeugt werden (siehe »Die Plattform für das automatische Erstellen von Servern konfigurieren« auf Seite 219) – zum Beispiel beim Autoscaling oder durch ein automatisches Recovery. Aber es ist möglich.
Eine Möglichkeit für die Push Server Configuration ist, dass das Serverkonfigurationstool von einem lokalen Computer ausgeführt wird. Wie in »Code von einem zentralisierten Service anwenden lassen« auf Seite 389 beschrieben ist es aber besser, Tools von einem zentralen Server oder Service aus laufen zu lassen, um eine Konsistenz sicherzustellen und die Kontrolle über den Prozess zu behalten.
Manche Serverkonfigurationstools besitzen eine Serveranwendung, die die Verbindungen mit Maschinen-Instanzen managt, wie zum Beispiel Ansible Tower. Einige Firmen bieten SaaS-Services für eine Remote-Konfiguration Ihrer Server-Instanzen, auch wenn viele Organisationen Fremdfirmen ungern so viel Kontrolle über ihre Infrastruktur überlassen wollen.
In anderen Fällen implementieren Sie selbst einen zentralen Service, um die Serverkonfigurationstools auszuführen. Am häufigsten habe ich bei Teams gesehen, dass sie ihre CI- oder CD-Serverprodukte dafür genutzt haben. Sie implementieren CI-Jobs oder Pipeline-Stages, die das Konfigurationstool für einen bestimmten Satz an Servern laufen lassen. Der Job wird abhängig von Events gestartet, wie zum Beispiel aufgrund von Änderungen am Serverkonfigurationscode oder beim Erstellen neuer Umgebungen.
Das Serverkonfigurationstool muss sich mit Server-Instanzen verbinden können. Auch wenn manche Tools dafür ein eigenes Netzwerkprotokoll verwenden, greifen die meisten auf SSH zurück. Jede Server-Instanz muss SSH-Verbindungen vom Server-Tool akzeptieren und es ihm erlauben, mit ausreichend Berechtigungen zu laufen, damit es seine Konfigurationsänderungen anwenden kann.
Es ist sehr wichtig, für diese Verbindungen eine starke Authentifizierung und ein gutes Secrets-Management zu nutzen. Denn ansonsten schaffen Sie sich in Ihrer Landschaft damit eine große Sicherheitslücke.
Beim Erstellen und Konfigurieren einer neuen Server-Instanz können Sie dynamisch ein neues Authentifikations-Secret generieren, wie zum Beispiel einen SSH-Schlüssel. Die meisten Infrastruktur-Plattformen bieten eine Möglichkeit, beim Erstellen einer neuen Instanz einen Schlüssel zu setzen. Ihr Serverkonfigurationstool kann dann dieses Secret verwenden und den Schlüssel möglicherweise deaktivieren und verwerfen, sobald er nicht mehr erforderlich ist.
Müssen Sie Konfigurationsänderungen auf bestehende Server-Instanzen anwenden, wie zum Beispiel bei der Continuous Synchronization, brauchen Sie eine langfristigere Methode zum Authentifizieren von Verbindungen vom Konfigurationstool. Die einfachste Methode ist das Installieren eines einzelnen Schlüssels auf all Ihren Server-Instanzen. Aber dieser eine Schlüssel ist eine Angriffsmöglichkeit. Ist er bekannt geworden, besitzt eine angreifende Gruppe eventuell den Zugriff auf alle Server in Ihrer Landschaft.
Eine Alternative ist das Setzen eines eigenen Schlüssels für jede Server-Instanz. Dabei ist wichtig, dass Sie den Zugriff auf diese Schlüssel so managen, dass er es dem Serverkonfigurationstool erlaubt, auf sie zuzugreifen, während Sie gleichzeitig das Risiko reduzieren, unerlaubten Personen den gleichen Zugriff zu ermöglichen – zum Beispiel, weil der Server kompromittiert wird, auf dem das Tool läuft. Die Empfehlungen in »Secrets als Parameter nutzen« auf Seite 133 sind hier wichtig.
Ein Ansatz, den viele Organisationen verfolgen, ist, mehrere Server oder Services zu nutzen, die die Serverkonfiguration managen. Die Landschaft wird in unterschiedliche Sicherheitsbereiche unterteilt, und jede Serverkonfigurations-Serviceinstanz hat nur Zugriff auf einen dieser Bereiche. Diese Unterteilung kann das Ausmaß einer Kompromittierung verringern.
Die Alternative zum Push-Pattern ist das Pull-Server-Configuration-Pattern.
Beim Pull-Server-Configuration-Pattern läuft ein Prozess auf der Server-Instanz selbst, wo er Konfigurationscode herunterlädt und anwendet. Dieser Prozess wird ausgelöst, wenn eine neue Server-Instanz erzeugt wird. Für bestehende Instanzen unter dem Continuous-Synchronization-Pattern läuft der Prozess meist anhand eines Zeitplans, wacht regelmäßig auf und wendet die aktuelle Synchronisation an.
Pull-basierte Serverkonfiguration vermeidet es, dass Server-Instanzen eingehende Verbindungen von einem zentralen Server akzeptieren müssen, wodurch die Angriffsoberfläche verringert wird. Das Pattern vereinfacht das Konfigurieren von Instanzen, die automatisch durch die Infrastruktur-Plattform erstellt wurden, wie zum Beispiel durch Autoscaling oder beim automatischen Recovery (siehe »Die Plattform für das automatische Erstellen von Servern konfigurieren« auf Seite 219).
Sie können eine Pull-basierte Serverkonfiguration implementieren, wenn Sie Server-Images bauen oder verwenden können, auf denen Ihr Serverkonfigurationstool vorinstalliert ist.
Pull Configuration nutzt ein Server-Image, auf dem das Serverkonfigurationstool vorinstalliert ist. Holen Sie die Konfiguration für neue Server-Instanzen per Pull, konfigurieren Sie das Image so, dass das Tool beim ersten Booten ausgeführt wird.
cloud-init (https://oreil.ly/-2y6B) ist ein häufig verwendetes Tool zum automatischen Ausführen solcher Prozesse. Sie können Parameter an die neue Server-Instanz über die API Ihrer Infrastruktur übergeben und dabei sogar Befehle zum Ausführen und Parameter zur Übergabe an das Serverkonfigurationstool einbinden (siehe Listing 12-1).
server:
source_image: stock-linux-1.23
memory: 2GB
vnet: ${APPSERVER_VNET}
instance_data:
- server_tool: servermaker
- parameter: server_role=appserver
- parameter: code_repo=servermaker.shopspinner.xyz
Konfigurieren Sie das Skript so, dass Konfigurationscode beim Starten von einem zentralen Repository heruntergeladen und angewendet wird. Nutzen Sie Continuous Synchronization, um laufende Server zu aktualisieren, sollte der Setup-Prozess das konfigurieren – egal, ob es über das Ausführen eines Hintergrundprozesses für das Serverkonfigurationstool oder über einen Cron-Job läuft, der das Tool nach einem Zeitplan startet.
Auch wenn Sie keine eigenen Server-Images bauen, sind auf den meisten der von Public-Cloud-Anbietern bereitgestellten Images cloud-init und häufig genutzte Serverkonfigurationstools vorinstalliert.
Ein paar weitere Tools, unter anderem Saltstack, nutzen einen Messaging- und Event-basierten Ansatz, um die Serverkonfiguration anzustoßen. Auf jeder Server-Instanz läuft ein Agent, der sich mit einem gemeinsamen Service Bus verbindet, von dem er Befehle zum Anwenden von Konfigurationscode erhält.
Die Alternative zum Pull-Pattern ist das Push-Server-Configuration-Pattern (siehe »Pattern: Push Server Configuration« auf Seite 236).
Die meisten Serverkonfigurationstools stellen einen zentralen Service zur Verfügung, den Sie auf einer Maschine oder in einem Cluster betreiben können, um die Verteilung von Konfigurationscode und -parametern und andere Aktivitäten zentral steuern zu können. Manche Teams ziehen es allerdings vor, ohne einen zentralen Service zu arbeiten.
Der Hauptgrund für das Dezentralisieren der Konfiguration ist das Vereinfachen des Infrastruktur-Managements. Ein Konfigurations-Server ist ein weiteres Element, das man managen muss, und es kann ein Single Point of Failure sein. Ist der Konfigurations-Server down, können Sie keine neuen Maschinen bauen, was ihn zu einer Abhängigkeit beim Disaster Recovery macht. Konfigurations-Server können zudem ein Performance-Flaschenhals sein und müssen eventuell skalierbar sein, um Verbindungen von Hunderten oder Tausenden von Server-Instanzen verarbeiten zu können.
Um eine dezentralisierte Konfiguration zu implementieren, installieren Sie das Serverkonfigurationstool im Offlinemodus und führen es aus, wie zum Beispiel durch den Einsatz von chef-solo statt chef-client. Sie können das Tool von einem Skript aus starten, das in einem zentralen Datei-Repository nach der neuesten Version des Serverkonfigurationscodes sucht und diesen herunterlädt. Der Code wird lokal auf Server-Instanzen gespeichert, sodass das Tool auch dann laufen kann, wenn das Datei-Repository nicht verfügbar ist.
Ein zentrales Datei-Repository kann auch ein Single Point of Failure oder ein Performance-Flaschenhals sein – so wie ein Konfigurations-Server. Aber in der Praxis gibt es viele einfache, hochverfügbare und performante Optionen für das Hosten statischer Dateien wie Serverkonfigurationen. Dazu gehören Webserver, Netzwerk-Dateiserver und Object Storage Services wie AWS S3.
Eine andere Möglichkeit zum Implementieren des dezentralisierten Pattern ist das Zusammentragen von Serverkonfigurationscode in einem Systempaket wie einer .rpm- oder .deb-Datei und das Hosten in einem privaten Paket-Repository. Ein regelmäßig laufender Prozess führt yum update oder apt-get update aus, wodurch das Paket installiert oder aktualisiert und der Serverkonfigurationscode in ein lokales Verzeichnis kopiert wird.
Das Erstellen, Ändern und Zerstören von Server-Instanzen sind die wichtigsten Ereignisse im Lebenszyklus eines Servers. Aber es gibt in einem erweiterten Lebenszyklus noch andere interessante Phasen – unter anderem das Stoppen und erneute Starten eines Servers (Abbildung 12-1), das Ersetzen und das Wiederherstellen eines ausgefallenen Servers.
Als die meisten unserer Server echte Maschinen waren, die über eine Steckdose mit Strom versorgt wurden, haben wir sie immer wieder einmal heruntergefahren, um die Hardware anzupassen, oder sie rebootet, wenn wir bestimmte Betriebssystem-Komponenten aktualisiert haben.
Abbildung 12-1: Server-Lebenszyklus – stoppen und erneut starten
Virtuelle Server werden immer noch gestoppt und neu gestartet, manchmal auch aus den gleichen Gründen – um virtuelle Hardware neu zu konfigurieren oder einen OS-Kernel zu aktualisieren. Gelegentlich fahren wir Server auch herunter, um Hosting-Kosten zu sparen. Manche Teams fahren beispielsweise Entwicklungs- und Testserver am Abend oder Wochenende herunter, wenn sie von niemandem genutzt werden.
Lassen sich Server allerdings leicht neu bauen, zerstören viele Teams die Server einfach, wenn sie nicht gebraucht werden, und erstellen sie neu, wenn man sie wieder nutzen will. Das geschieht zum Teil, weil es einfacher ist und weil es etwas mehr Geld sparen kann, als wenn man sie einfach herunterfährt.
Aber das Zerstören und erneute Bauen von Servern statt eines Herunterfahrens und Vorhaltens hat auch mit der Philosophie zu tun, Server wie »Vieh« statt wie »Haustiere« zu betrachten (wie in »Vieh statt Haustiere« auf Seite 46 erwähnt). Teams stoppen und starten ihre Server lieber, statt sie neu zu bauen, weil sie kein Zutrauen in ihre Fähigkeit haben, Server neu zu erstellen. Häufig liegt die Herausforderung darin, Anwendungsdaten zu speichern und wiederherzustellen. In »Datenkontinuität in einem sich ändernden System« auf Seite 430 werden Möglichkeiten beschrieben, diese Herausforderung zu meistern.
Mit einer Richtlinie, Server nicht zu stoppen und wieder neu zu starten, ist Ihr Team gezwungen, zuverlässige Prozesse und Tools zum neuen Bauen von Servern und zum Aufrechterhalten zu implementieren.
Wenn Sie Server stoppen, kann das auch Wartungsaktivitäten komplizierter machen. Konfigurations-Updates und System-Patches lassen sich nicht auf gestoppte Server anwenden. Je nachdem, wie Sie solche Updates managen, erhalten die Server sie beim nächsten Start oder gar nicht.
Einer der vielen Vorteile eines Umzugs von realen auf virtuelle Server ist, wie einfach sich Server-Instanzen bauen und ersetzen lassen. Viele der in diesem Buch beschriebenen Patterns und Praktiken – einschließlich immutabler Server (siehe »Pattern: Immutable Server« auf Seite 234) und dem häufigen Aktualisieren von Server-Images – bauen auf der Fähigkeit auf, einen laufenden Server durch das Bauen eines neuen zu ersetzen (siehe Abbildung 12-2).
Die Idee beim Ersetzen einer Server-Instanz ist, eine neue Instanz zu erstellen, ihre Readiness zu überprüfen, andere Infrastruktur und Systeme so umzukonfigurieren, dass sie die neue Instanz verwenden, zu testen, dass sie korrekt arbeitet, und dann die alte Instanz zu zerstören.
Abhängig von den Anwendungen und Systemen, die die Server-Instanz nutzen, kann es möglich sein, das Ersetzen ohne oder zumindest mit nur minimaler Downtime durchzuführen. Mehr dazu finden Sie in »Live-Infrastruktur ändern« auf Seite 415.
Abbildung 12-2: Server-Lebenszyklus – eine Server-Instanz ersetzen
Manche Infrastruktur-Plattformen besitzen Funktionalität zum Automatisieren dieses Prozesses. So wenden Sie vielleicht eine Konfigurationsänderung auf die Definition von Servern in einem Autoscaling-Server-Cluster an und legen fest, dass ein neues Server-Image genutzt werden soll, das Sicherheits-Patches enthält. Die Plattform fügt dann automatisch neue Instanzen hinzu, prüft ihren Zustand und entfernt alte.
In anderen Fällen müssen Sie eventuell die Server selbst austauschen. Eine Möglichkeit in einem Pipeline-basierten Change-Delivery-System ist Expand and Contract. Erst pushen Sie eine Änderung, die den neuen Server hinzufügt, dann pushen Sie eine weitere Änderung, die den alten Server entfernt. In »Expand and Contract« auf Seite 419 finden Sie mehr dazu.
Cloud-Infrastruktur ist nicht notwendigerweise zuverlässig. Manche Provider, unter anderem AWS, warnen explizit davon, dass sie Server-Instanzen ohne Ankündigung beenden können – zum Beispiel, wenn sie sich dazu entscheiden, die zugrunde liegende Hardware zu ersetzen.1 Aber auch Provider mit stärkeren Verfügbarkeitsgarantien haben Hardwareausfälle, die gehostete Systeme betreffen.
Der Prozess zum Wiederherstellen eines ausgefallenen Servers entspricht dem Prozess zum Ersetzen eines Servers. Ein Unterschied liegt in der Reihenfolge der Aktivitäten – Sie erstellen den neuen Server nach dem Zerstören des alten und nicht zuvor (siehe Abbildung 12-3). Der andere Unterschied liegt darin, dass Sie einen Server normalerweise beabsichtigt ersetzen, während Ausfälle weniger gewollt sind.2
Wie beim Ersetzen einer Server-Instanz kann das Wiederherstellen eine manuelle Aktion sein, oder Sie sind dazu in der Lage, Ihre Plattform und andere Services so zu konfigurieren, dass Ausfälle automatisch erkannt und die Server wiederhergestellt werden.
Abbildung 12-3: Server-Lebenszyklus – eine ausgefallene Server-Instanz wiederherstellen
Wir haben ein paar Patterns für den Zeitpunkt des Anwendens von Konfigurationsänderungen auf Server betrachtet – bei einer neuen Änderung, durch das kontinuierliche Anwenden auf laufende Server oder durch das Erzeugen neuer Instanzen. Wir haben uns auch Patterns angeschaut, bei denen es darum geht, wie Änderungen angewendet werden – Push und Pull. Schließlich sind wir noch auf ein paar andere Ereignisse im Lebenszyklus von Servern eingegangen – unter anderem das Stoppen, Ersetzen und Wiederherstellen von Servern.
Dieses Kapitel deckt zusammen mit dem vorigen Kapitel zum Erstellen von Servern die wichtigsten Ereignisse im Leben eines Servers ab. Aber viele der Ansätze zum Erstellen und Ändern von Servern, die wir besprochen haben, funktionieren, indem Server-Images angepasst werden, die Sie dann zum Erstellen oder Aktualisieren mehrerer Server-Instanzen nutzen. Daher schaut sich das nächste Kapitel Vorgehensweisen zum Definieren, Bauen und Aktualisieren von Server-Images an.