Infrastructure as Code entwickelte sich zunächst als Möglichkeit zum Konfigurieren von Servern. Systemadministratoren haben Shell-, Batch- und Perl-Skripte geschrieben. CFEngine unterstützte als Erste deklarative, idempotente DSLs zum Installieren von Paketen und Managen von Konfigurationsdateien auf Servern, Puppet und Chef folgten. Diese Tools gingen davon aus, dass Sie mit einem bestehenden Server beginnen – oft mit einem realen Server in einem Rack, manchmal mit einer virtuellen Maschine per VMware und später als Cloud-Instanz.
Jetzt konzentrieren wir uns entweder auf Infrastruktur-Stacks, in denen Server nur ein Element sind, oder wir arbeiten mit Container-Clustern, in denen es sich bei den Servern um ein zugrunde liegendes Detail handelt. Und selbst Teams, die Cluster betreiben, müssen normalerweise Server für die Host-Knoten bauen und laufen lassen.
Server sind komplexer als andere Infrastruktur-Typen (wie Networking oder Storage). Es gibt mehr Komponenten und Variationen, daher verbringen die meisten Systemteams immer noch ziemlich viel Zeit mit Betriebssystemen, Paketen und Konfigurationsdateien.
Dieses Kapitel stellt Ansätze zum Bauen und Managen von Serverkonfigurationen als Code vor. Es beginnt mit dem Inhalt von Servern (was muss konfiguriert werden) und dem Lebenszyklus von Servern (wann geschehen Konfigurationsaktivitäten). Dann geht es weiter zu Code und Tools zur Serverkonfiguration. Der zentrale Inhalt dieses Kapitels dreht sich um verschiedene Möglichkeiten, Server-Instanzen zu erstellen, Server so weit vorzubereiten, dass sich mehrere konsistente Instanzen erstellen lassen, und Ansätze zum Anwenden der Serverkonfiguration über den Lebenszyklus des Servers hinweg.
Es kann nützlich sein, den Lebenszyklus eines Servers als mehrere Übergangsphasen zu betrachten, wie dies in Abbildung 11-1 dargestellt ist.
Abbildung 11-1: Der grundlegende Lebenszyklus eines Servers
Der hier gezeigte grundlegende Lebenszyklus besteht aus drei Übergangsphasen:
Dieses Kapitel dreht sich um das Erstellen und Konfigurieren von Servern. In Kapitel 12 geht es um das Vornehmen von Änderungen an Servern, und Kapitel 13 beschreibt das Erstellen und Aktualisieren von Server-Images, die Sie zum Erstellen von Server-Instanzen einsetzen können.
Es ist sinnvoll, über die verschiedenen Elemente auf einem Server und ihre Herkunft nachzudenken.
Ein Weg ist dabei, die Elemente in Software, Konfiguration und Daten zu unterteilen. Diese Kategorien (beschrieben in Tabelle 11-1) helfen dabei, zu verstehen, wie Tools zum Konfigurations-Management eine bestimmte Datei oder einen Satz an Dateien behandeln sollten.
Der Unterschied zwischen Konfiguration und Daten liegt darin, ob Automations-Tools automatisch den Inhalt der Datei managen. So gehört beispielsweise ein Systemlog zwar zur Infrastruktur, aber ein Tool zur automatisierten Konfiguration behandelt es als Daten. Und wenn eine Anwendung beispielsweise ihre Accounts und Voreinstellungen für die Benutzer in Dateien auf dem Server speichert, behandelt das Serverkonfigurationstool diese Datei ebenfalls als Daten.
Tabelle 11-1: Kategorien von Elementen auf einem Server
Element-Typ |
Beschreibung |
Behandlung durch das Konfigurations-Management |
Software |
Anwendungen, Bibliotheken und anderer Code. Das müssen keine ausführbaren Dateien sein – es kann sich um so gut wie jede Datei handeln, die statisch ist und sich nicht von einem System zum anderen unterscheidet. Ein Beispiel dafür sind Datendateien mit Zeitzonen auf einem Linux-System. |
Stellt sicher, dass sie auf jedem relevanten Server gleich ist; kümmert sich nicht darum, was sich darin befindet. |
Konfiguration |
Dateien, die steuern, wie das System und/oder die Anwendung arbeitet. Die Inhalte können sich von Server zu Server unterscheiden – abhängig von den Rollen, Umgebungen, Instanzen und so weiter. Diese Dateien werden als Teil der Infrastruktur gemanagt – im Gegensatz zu Konfigurationsdateien für Anwendungen. Besitzt eine Anwendung beispielsweise ein UI zum Verwalten der Benutzerprofile, würden die Datendateien, die diese Profile speichern, aus Infrastruktur-Sicht nicht als Konfiguration betrachtet werden, sondern als Daten. Aber eine Konfigurationsdatei für eine Anwendung, die auf dem Dateisystem abgelegt und durch die Infrastruktur verwaltet wird, würde als Konfiguration angesehen werden. |
Baut die Dateiinhalte auf dem Server; stellt sicher, dass sie konsistent sind. |
Daten |
Dateien, die durch das System und die Anwendungen erzeugt und aktualisiert werden. Die Infrastruktur kann für diese Daten zum Teil verantwortlich sein, wie zum Beispiel zum Verteilen, Sichern oder Replizieren. Aber sie behandelt den Inhalt der Dateien als Blackbox und kümmert sich nicht darum. Datenbank-Datendateien und Logdateien sind Beispiele für solche Daten. |
Normales Auftreten und Ändern; muss sie eventuell sichern, kümmert sich aber nicht um die Inhalte. |
Software, Konfiguration und Daten, aus denen sich eine Server-Instanz zusammensetzt, können hinzugefügt werden, wenn der Server erstellt und konfiguriert wird oder wenn er sich ändert. Es gibt eine Reihe möglicher Quellen für diese Elemente (siehe Abbildung 11-2):
Basis-Betriebssystem
Das Installations-Image für das Betriebssystem kann eine physische Festplatte, eine ISO-Datei oder ein Stock-Server-Image sein. Der Prozess der OS-Installation wählt eventuell optionale Komponenten aus.
OS-Paket-Repositories
Die OS-Installation oder ein Post-Installations-Konfigurations-Schritt kann ein Tool starten, das Pakete aus einem oder mehreren Repositories herunterlädt und installiert (in Tabelle 10-1 finden Sie mehr zu OS-Paketformaten). OS-Anbieter stellen normalerweise ein Repository mit unterstützten Paketen bereit. Sie können zudem Fremd-Repositories für Open-Source- oder kommerzielle Pakete einsetzen. Außerdem können Sie ein internes Repository für Pakete betreiben, die in der Firma entwickelt oder kuratiert wurden.
Sprach-, Framework- und andere Plattform-Repositories
Neben OS-spezifischen Paketen installieren Sie eventuell Pakete für Sprachen oder Frameworks, wie Java-Bibliotheken oder Ruby-Gems. Wie bei OS-Paketen holen Sie vielleicht Pakete aus Repositories, die von Sprachanbietern, Fremdherstellern oder internen Gruppen betreut werden.
Nicht-Standard-Pakete
Manche Anbieter und interne Gruppen bieten Software mit ihren eigenen Installern an oder solche, die neben dem Ausführen eines Standard-Paket-Management-Tools noch mehrere Schritte erfordern.
Separates Material
Eventuell fügen Sie noch Dateien außerhalb einer Anwendung oder Komponente hinzu oder verändern welche – zum Beispiel ergänzen Sie Benutzer-Accounts oder setzen lokale Firewall-Regeln.
Abbildung 11-2: Pakete und andere Elemente einer Server-Instanz
Die nächste Frage dreht sich darum, wie Sie Server-Instanzen beim Erstellen oder Ändern um Elemente erweitern.
Die erste Generation von Tools für Infrastructure as Code hat sich auf die automatisierte Serverkonfiguration konzentriert. Einige der Tools in diesem Bereich sind:
Viele dieser Tools nutzen einen Agenten, der auf jedem Server installiert ist und dem Configuration-Pattern folgt (siehe »Pattern: Pull Server Configuration« auf Seite 238). Sie stellen einen Agenten bereit, den Sie entweder als Service installieren oder periodisch aus einem Cron-Job ausführen können. Andere Tools sind so entworfen, dass sie auf einem zentralen Server laufen und sich mit jedem gemanagten Server verbinden (orientiert am Push-Pattern, siehe »Pattern: Push Server Configuration« auf Seite 236).
Sie können sowohl das Push- wie auch das Pull-Pattern mit den meisten dieser Tools implementieren. Verwenden Sie ein Tool wie Ansible, das für das Push-Modell designt ist, können Sie es auf Servern vorinstallieren und per Cron ausführen. Nutzen Sie andererseits ein Tool wie Chef oder Puppet, die beide einen Agenten zum Ausführen mit dem Pull-Modell bereitstellen, können Sie stattdessen einen Befehl auf einem zentralen Server ausführen, der sich auf jeder Maschine anmeldet und den Client startet. Daher sind Sie durch das Tool nicht auf ein Pattern eingeschränkt.
Viele Hersteller von Serverkonfigurationstools bieten Repository-Server an, auf denen man Konfigurationscode hosten kann. Beispiele dafür sind Ansible Tower, Chef Server und Puppetmaster. Diese Tools haben eventuell noch zusätzliche Funktionalität, wie zum Beispiel eine Konfigurations-Registry (siehe »Konfigurations-Registry« auf Seite 129), Dashboards, Logging oder eine zentralisierte Ausführung.
Wählt man einen Anbieter, der ein Ökosystem von Tools bereitstellt, das alles abdeckt, vereinfacht das natürlich die Arbeit des Infrastruktur-Teams. Aber es ist hilfreich, wenn Elemente des Ökosystems durch andere Tools ausgetauscht werden können, sodass das Team die Elemente auswählen kann, die ihre Bedürfnisse am besten. Nutzen Sie beispielsweise mehrere Tools, die mit einer Konfigurations-Registry integrieren, wählen Sie vielleicht eher eine eigenständige, allgemein einsetzbare Konfigurations-Registry statt einer, die mit Ihrem Serverkonfigurationstool verknüpft ist.
Die Codebasis ist ein signifikanter Teil im Leben eines modernen Infrastruktur-Teams. Die Prinzipien und Richtlinien aus Kapitel 4 gelten für Server-Code, Stack-Code und so weiter.
Server-Konfigurationscode scheint es wie jedem Code zu gefallen, mit der Zeit zu einem unübersichtlichen Chaos anzuwachsen. Es sind Disziplin und gute Gewohnheiten erforderlich, um das zu vermeiden. Zum Glück erhalten Sie mit diesen Tools Möglichkeiten, den Code in unterschiedlichen Einheiten zu organisieren und zu strukturieren.
Viele Serverkonfigurationstools lassen Sie Code in Modulen organisieren. So besitzt Ansible beispielsweise Playbooks, Chef gruppiert Rezepte in Cookbooks und Puppet nutzt Module mit Manifesten. Sie können diese Module individuell organisieren, versionieren und bereitstellen.
Rollen sind ein Weg, eine Gruppe von Modulen für das Anwenden auf einen Server zu kennzeichnen und deren Zweck zu definieren. Anders als bei anderen Konzepten scheinen sich die meisten Tool-Anbieter dafür auf den Begriff Rolle geeinigt zu haben, statt sich selbst einen auszudenken.1
Viele dieser Tools unterstützen zudem Erweiterungen für den Kern ihres Ressourcenmodells. Eine Sprache für das Werkzeug stellt ein Modell von Server-Ressourcen-Entitäten bereit – zum Beispiel Anwender, Pakete und Dateien. So können Sie beispielsweise einen Chef Lightweight Resource Provider (LWRP) schreiben, um eine Java-Anwendungsressource zu ergänzen, die Anwendungen installiert und konfiguriert, die von Ihren Entwicklungsteams geschrieben wurden. Diese Erweiterungen ähneln Stack-Modulen (siehe Kapitel 16).
In Kapitel 15 finden Sie eine allgemeinere Diskussion über das Modularisieren von Infrastruktur mit Hinweisen zu gutem Design.
Sie sollten jedes Code-Modul zur Serverkonfiguration (zum Beispiel jedes Cookbook oder Playbook) entlang eines einzelnen, abgeschlossenen Aspekts designen und schreiben. Dieser Rat orientiert sich am Software-Designprinzip der Separation of Concerns (https://oreil.ly/CPXDu). Ein üblicher Ansatz ist, ein eigenes Modul für jede Anwendung zu haben.
Ein Beispiel: Sie erstellen ein Modul zum Managen eines Anwendungsservers wie Tomcat. Der Code im Modul würde die Tomcat-Software installieren, Benutzer-Accounts und -Gruppen anlegen, unter denen sie laufen soll, und Ordner mit den korrekten Berechtigungen erstellen, in denen Logs und andere Dateien abgelegt werden. Er würde die Konfigurationsdateien für den Tomcat bauen und darin unter anderem die Ports und Einstellungen konfigurieren. Der Modul-Code würde den Tomcat zudem in eine Reihe von Services integrieren, wie zum Beispiel Log-Aggregation, Monitoring oder Process Management.
Viele Teams finden es nützlich, Serverkonfigurationsmodule abhängig von ihrem Einsatzzweck zu entwerfen. So können Sie Ihre Module beispielsweise in Bibliotheksmodule und Anwendungsmodule unterteilen (bei Chef wären das Bibliotheks-Cookbooks und Anwendungs-Cookbooks).
Ein Bibliotheksmodul verwaltet ein Kernkonzept, das von Anwendungsmodulen wiederverwendet werden kann. Nehmen wir das Beispiel mit dem Tomcat-Modul: Sie könnten das Modul so entwerfen, dass es Parameter übernimmt und damit genutzt werden kann, um eine Instanz von Tomcat zu konfigurieren, die sich für unterschiedliche Zwecke optimieren lässt.
Ein Anwendungsmodul – auch als Wrapper-Modul bezeichnet – importiert ein oder mehrere Bibliotheksmodule und setzt ihre Parameter für einen spezifischeren Zweck. Das ShopSpinner-Team könnte ein Servermaker-Modul bauen, das Tomcat zum Ausführen ihre Produkt-Katalog-Anwendung installiert, und ein zweites, das Tomcat für die Kundenmanagement-Anwendung erstellt.1 Beide Module nutzen eine gemeinsame Bibliothek, die Tomcat installiert.
Wie bei jedem Code sollten Sie Code zur Serverkonfiguration testen und zwischen den Umgebungen weiterreichen können, bevor Sie ihn auf Ihre Produktivsysteme anwenden. Manche Teams versionieren all ihre Server-Code-Module gemeinsam und reichen sie auch zusammen als eine Einheit weiter, indem sie sie entweder gemeinsam bauen (siehe »Pattern: Build-Time Project Integration« auf Seite 369) oder das Bauen und Testen getrennt vornehmen, bevor sie sie für den Einsatz in Produktivumgebungen zusammenstellen (siehe »Pattern: Delivery-Time Project Integration« auf Seite 373). Andere versionieren jedes Modul getrennt und reichen es auch alleine weiter (siehe »Pattern: Apply-Time Project Integration« auf Seite 375).
Behandeln Sie jedes Modul als eigenständige Einheit, müssen Sie sich um alle Abhängigkeiten zwischen diesen kümmern. Viele Tools zur Serverkonfiguration ermöglichen es Ihnen, die Abhängigkeiten für ein Modul in einem Deskriptor zu beschreiben. In einem Chef-Cookbook führen Sie beispielsweise Abhängigkeiten im Feld depends der Metadatendatei für das Cookbook auf (https://oreil.ly/_4eEP). Das Tool zur Serverkonfiguration nutzt diese Spezifikation, um abhängige Module herunterzuladen und auf eine Server-Instanz anzuwenden. Das Abhängigkeitsmanagement für Server-Konfigurationsmodule funktioniert genauso wie die Systeme zum Software-Paketmanagement – zum Beispiel für RPMs oder Python-PIP-Pakete.
Sie müssen auch verschiedene Versionen Ihres Serverkonfigurationscodes speichern und verwalten. Oft haben Sie eine Version des Codes aktuell auf die Produktivumgebungen angewandt, während andere Versionen entwickelt und getestet werden.
In den Kapiteln 18 und 19 gehe ich genauer auf das Organisieren, Verpacken und Bereitstellen Ihres Infrastruktur-Codes ein.
Wie schon erwähnt ist eine Serverrolle eine Möglichkeit, eine Gruppe von Serverkonfigurationsmodulen zu definieren, die auf einen Server angewendet werden. Eine Rolle kann auch ein paar Standardparameter setzen. So könnten Sie beispielsweise eine Rolle application-server erstellen:
role: application-server
server_modules:
- tomcat
- monitoring_agent
- logging_agent
- network_hardening
parameters:
- inbound_port: 8443
Weisen Sie diese Rolle einem Server zu, wenden Sie die Konfiguration für Tomcat, Monitoring- und Logging-Agenten und das Netzwerk-Hardening an. Zudem wird ein Parameter für einen eingehenden Port gesetzt – vermutlich einer, den das Modul network_hardening verwendet, um diesen Port in der lokalen Firewall zu öffnen, während es alles andere dicht macht.
Rollen können unübersichtlich werden, daher sollten Sie sie bedacht und konsistent einsetzen. Sie können feingranulare Rollen nutzen, die Sie kombinieren, um spezifische Server zu erstellen. Sie weisen einem gegebenen Server mehrere Rollen zu – wie zum Beispiel ApplicationServer, MonitoredServer und PublicFacingServer. Jede dieser Rollen enthält eine kleine Zahl von Servermodulen für einen eng begrenzten Zweck.
Alternativ können Sie High-Level-Rollen einsetzen, die mehr Module enthalten. Dann hat jeder Server vermutlich nur eine Rolle, die recht spezifisch sein kann – zum Beispiel ShoppingServiceServer oder JiraServer.
Oft nutzt man Rollenvererbung. Sie definieren eine Basisrolle, die Software und Konfiguration enthält, die für alle Server zum Einsatz kommen. Diese Rolle kann beispielsweise Netzwerk-Hardening, administrative Benutzer-Accounts und Agenten für das Monitoring und Logging enthalten. Spezifischere Rollen, wie zum Beispiel für Anwendungsserver oder Container-Hosts, übernehmen die Basisrolle und ergänzen dann ein paar zusätzliche Module und Parameter zur Serverkonfiguration:
role: base-role
server_modules:
- monitoring_agent
- logging_agent
- network_hardening
role: application-server
include_roles:
- base_role
server_modules:
- tomcat
parameters:
- inbound_port: 8443
role: shopping-service-server
include_roles:
- application-server
server_modules:
- shopping-service-application
Dieses Codebeispiel definiert drei Rollen in einer Hierarchie. Die Rolle shopping-service-server erbt alles von der Rolle application-server und fügt ein Modul zum Installieren der spezifischen Anwendung hinzu. Die Rolle application-server erbt von der Rolle base-role und ergänzt den Tomcat-Server und die Konfiguration für die Netzwerk-Ports. Die Rolle base-role definiert einen gemeinsamen Satz allgemein nutzbarer Konfigurationsmodule.
In Kapitel 8 wurde erklärt, wie automatisiertes Testen und CD in der Theorie auf Infrastruktur angewendet werden, während Sie in Kapitel 9 Ansätze kennengelernt haben, wie Sie dies mit Infrastruktur-Stacks implementieren. Viele der Konzepte in diesen beiden Kapiteln gelten auch für das Testen von Server-Code.
Es gibt eine sich selbst verstärkende Dynamik zwischen dem Design und dem Testen von Code. Es ist einfacher, Tests für eine sauber strukturierte Codebasis zu schreiben und zu betreuen. Und durch das Schreiben von Tests (und Ihren Einsatz für ihr erfolgreiches Bestehen) sind Sie gezwungen, diese klare Struktur beizubehalten. Indem Sie jede Änderung in eine Pipeline stecken, die Ihre Tests ausführt, helfen Sie Ihrem Team dabei, die Disziplin zum kontinuierlichen Refaktorieren für ein Minimieren der technischen und Design-Schulden aufrechtzuerhalten.
Die weiter oben beschriebene Struktur von Serverrollen, die aus Modulen zur Serverkonfiguration zusammengesetzt werden, die wiederum in Bibliotheksmodulen und Anwendungsmodulen organisiert sein können, passt gut zu einer progressiven Teststrategie (siehe »Progressives Testen« auf Seite 148). Eine Folge von Pipeline-Stages kann jedes dieser Module mit zunehmender Integration testen, wie in Abbildung 11-3 zu sehen ist.
Abbildung 11-3: Server-Code-Module progressiv testen
Eine eigene Stage testet jedes Code-Modul, wann immer eine Änderung für dieses Modul eingecheckt wird. Die Serverrolle besitzt ebenfalls eine Test-Stage. Diese Stage führt die Tests immer dann aus, wenn sich eines der Module ändert, das von dieser Rolle genutzt wird und wenn dessen Test erfolgreich war. Die Stage zum Testen der Rolle läuft außerdem, wenn eine Änderung am Code der Rolle vorgenommen wird – zum Beispiel beim Hinzufügen oder Entfernen eines Moduls oder bei Änderungen an den Parametern.
Es ist knifflig, zu entscheiden, was Sie bei Server-Code testen sollen. Sie landen dann wieder bei der Frage, ob es einen Wert hat, deklarativen Code zu testen (siehe »Herausforderung: Tests für deklarativen Code haben häufig nur einen geringen Wert« auf Seite 143). Module für Server-Code – insbesondere in einer gut designten Codebasis – tendieren dazu, klein, einfach und fokussiert zu sein. Ein Modul, das eine Java JVM installiert, kann beispielsweise aus einer einzelnen Anweisung mit ein paar Parametern bestehen:
package:
name: java-${JAVA_DISTRIBUTION}
version: ${JAVA_VERSION}
In der Praxis besitzen wahrscheinlich selbst einfach erscheinende Module zum Installieren von Paketen mehr Code, es werden Dateipfade und Konfigurationsdateien angepasst, und vielleicht kommt auch noch ein Benutzer-Account dazu. Aber oft gibt es nicht viel zu testen, das nicht einfach den Code selbst neu formuliert.
Tests sollten sich auf häufige Probleme, variable Ergebnisse und die Kombination von Code konzentrieren.
Denken Sie an häufige Probleme, Dinge, die in der Praxis gerne schiefgehen, und wie Sie eine erfolgreiche Umsetzung prüfen können. Bei einer einfachen Paketinstallation wollen Sie vielleicht prüfen, dass der Befehl im Standardpfad verfügbar ist. Ein Test würde also den Befehl aufrufen und sicherstellen, dass er ausgeführt wurde:
given command 'java -version' {
its(exit_status) { should_be 0 }
}
Liefert das Paket abhängig von den übergebenen Parametern sehr unterschiedliche Ergebnisse, sollten Sie Tests schreiben, um sicherzustellen, dass es sich in den unterschiedlichen Situationen korrekt verhält. Beim JVM-Installationspaket könnten Sie den Test mit unterschiedlichen Werten für die Java-Distribution schreiben und beispielsweise prüfen, dass der Befehl java unabhängig von der gewählten Distribution verfügbar ist.
In der Praxis steigt der Wert des Testens mit zunehmender Integration von mehr Elementen. Daher schreiben Sie vielleicht mehr Tests für die Anwendungsserver-Rolle und führen sie aus, als für die Module, die die Rolle enthält.
Das gilt insbesondere, wenn diese Module untereinander integriert sind und miteinander interagieren. Ein Modul, das Tomcat installiert, wird vermutlich nicht mit einem kollidieren, das einen Monitoring-Client installiert, aber ein Modul zum Absichern des Servers könnte zu Problemen führen. In diesem Fall werden Sie vielleicht Tests haben wollen, die auf der Anwendungsserver-Rolle laufen, um sicherzustellen, dass der Anwendungsserver auch dann läuft und Requests annimmt, wenn Ports versperrt wurden.
Die meisten automatisierten Tests für Server-Code führen Befehle auf einem Server oder in einer Container-Instanz aus und überprüfen die Ergebnisse. Diese Tests können die Existenz und den Status von Ressourcen kontrollieren, wie zum Beispiel Pakete, Dateien, Benutzer-Accounts und laufende Prozesse. Tests können sich auch Ergebnisse anschauen – sich beispielsweise mit einem Netzwerk-Port verbinden, um zu prüfen, ob ein Service das erwartete Ergebnis zurückliefert.
Zu den bekanntesten Tools für das Testen von Bedingungen auf einem Server gehören unter anderem Inspec (https://www.inspec.io), Serverspec (https://serverspec.org) und Terratest (https://oreil.ly/W6H5Q).
Die Strategien zum Testen vollständiger Infrastruktur-Stacks beinhalten das Ausführen von Offline- und Onlinetests (siehe »Offline-Test-Stages für Stacks« auf Seite 165 und »Online-Test-Stages für Stacks« auf Seite 168). Bei Onlinetests werden Elemente auf der Infrastruktur-Plattform erzeugt. Normalerweise können Sie Server-Code offline mit Containern oder lokalen virtuellen Maschinen testen.
Infrastruktur-Entwicklerinnen und -Entwickler, die lokal arbeiten, können eine Container-Instanz oder eine lokale VM mit einer minimalen Betriebssystem-Installation erzeugen, den Server-Konfigurationscode anwenden und dann die Tests laufen lassen.
Pipeline-Stages, die Server-Code testen, können das Gleiche tun und die Container-Instanz oder VM auf dem Agenten laufen lassen (zum Beispiel dem Jenkins-Knoten, der den Job ausführt). Alternativ könnte die Stage eine eigenständige Container-Instanz in einem Container-Cluster starten. Das funktioniert gut, wenn die Pipeline-Orchestrierung selbst containerisiert ist.
Sie können den Ratschlägen für Stacks zum Orchestrieren von Server-Code-Tests folgen (siehe »Test-Orchestrierung« auf Seite 183). Dazu gehört das Schreiben von Skripten, die die Vorbereitungen für das Testen treffen – zum Beispiel Container-Instanzen hochfahren –, bevor die Tests ausgeführt und die Ergebnisse zusammengefasst werden. Sie sollten die gleichen Skripte zum lokalen Testen laufen lassen, die Sie auch zum Testen in Ihrem Pipeline-Service nutzen, damit die Ergebnisse konsistent sind.
Der weiter oben beschriebene grundlegende Server-Lebenszyklus beginnt mit dem Erstellen einer neuen Server-Instanz. Ein umfassenderer Lebenszyklus enthält Schritte für das Erstellen und Aktualisieren von Server-Images, aus denen Sie Server-Instanzen erstellen können. Aber um dieses Thema kümmern wir uns in einem anderen Kapitel (Kapitel 13). Hier soll unser Lebenszyklus mit dem Erstellen einer Instanz beginnen, wie in Abbildung 11-4 zu sehen ist.
Abbildung 11-4: Eine Server-Instanz erstellen
Zum vollständigen Prozess des Erstellens eines Servers gehört das Provisionieren der Server-Instanz, sodass sie bereit für den Einsatz ist. Zu den Aktivitäten beim Erstellen und Provisionieren einer Server-Instanz gehören unter anderem:
Diese Punkte schließen sich nicht gegenseitig aus – Ihr Prozess zum Erstellen eines Servers kann eine oder mehrere Methoden für das Ausführen dieser unterschiedlichen Aktivitäten nutzen.
Es gibt eine Reihe von Mechanismen, die Sie verwenden können, um das Erstellen einer Server-Instanz anzustoßen. Schauen wir uns jeden einzelnen an.
Infrastruktur-Plattformen bieten Tools zum Erstellen neuer Server an, meist über ein Web-UI, als Befehlszeilentool und manchmal auch als GUI-Anwendung. Immer dann, wenn Sie einen neuen Server erstellen, wählen Sie die gewünschten Optionen aus – unter anderem das Ausgangs-Image, die zu allokierenden Ressourcen und die Networking-Details:
$mycloud server new \
--source-image=stock-linux-1.23 \
--memory=2GB \
--vnet=appservers
Es ist zwar nützlich, mit UIs und Befehlszeilen-Tools herumzuspielen, um mit einer Plattform zu experimentieren, aber Server, die von anderen gebraucht werden, sollten man auf diesem Weg nicht erstellen. Die Prinzipien, die weiter oben für das Erstellen und Konfigurieren von Stacks besprochen wurden (siehe »Patterns zum Konfigurieren von Stacks« auf Seite 110) gelten auch für Server. Setzen Sie Optionen manuell (wie in »Antipattern: Manual Stack Parameters« auf Seite 110 beschrieben), passieren schneller Fehler und die Wahrscheinlichkeit für inkonsistent konfigurierte Server, nicht planbare Systeme und zu viel Wartungsarbeiten steigt.
Sie können Skripte schreiben, die Server konsistent erzeugen. Das Skript verpackt ein Befehlszeilen-Tool oder nutzt die API der Infrastruktur-Plattform, um eine Server-Instanz zu erstellen und die Konfigurationsoptionen im Skript-Code oder über Konfigurationsdateien zu setzen. Ein Skript, das Server erstellt, kann wiederverwendet werden, ist konsistent und transparent. Dies ist das gleiche Konzept wie das Scripted Parameters Pattern für Stacks (siehe »Pattern: Scripted Parameters« auf Seite 114):
mycloud server new \
--source-image=stock-linux-1.23 \
--memory=2GB \
--vnet=appservers
Dieses Skript ist identisch mit dem vorigen Befehlszeilenbeispiel. Aber weil sich der Befehl in einem Skript befindet, können andere Personen in meinem Team sehen, wie ich den Server erstellt habe. Sie können mehr Server erstellen und darauf vertrauen, dass sich diese genauso verhalten wie der von mir erstellte.
Vor Terraform und anderen Tools zum Stack-Management schrieben die meisten Teams, mit denen ich zusammengearbeitet habe, Skripte zum Erstellen von Servern. Wir haben diese Skripte im Allgemeinen durch Konfigurationsdateien anpassbar gemacht – wie beim Stack-Configuration-Pattern (siehe »Pattern: Stack Configuration Files« auf Seite 117). Aber wir haben zu viel Zeit mit dem Verbessern und Korrigieren dieser Skripte verbracht.
Mit einem Stack-Management-Tool (siehe Kapitel 5) können Sie einen Server im Kontext von anderen Infrastruktur-Ressourcen definieren (siehe Listing 11-1). Das Tool nutzt die Plattform-API, um die Server-Instanz zu erstellen oder zu aktualisieren.
server:
source_image: stock-linux-1.23
memory: 2GB
vnet: ${APPSERVER_VNET}
Es gibt eine Reihe von Gründen, warum der Einsatz eines Stack-Tools zum Erstellen von Servern so praktisch ist. Einer ist, dass sich das Tool um Logik kümmert, die Sie sonst in Ihrem eigenen Skript implementieren müssten, wie zum Beispiel die Behandlung von Fehlern. Ein weiterer Vorteil eines Stacks ist, dass dieser die Integration mit anderen Infrastruktur-Elementen umsetzt – zum Beispiel das Verbinden des Servers mit Netzwerkstrukturen und Storage, der im Stack definiert ist. Der Code in Listing 11-1 setzt den Parameter vnet mit einer Variablen ${APPSERVER_VNET}, die sich vermutlich auf eine Netzwerkstruktur bezieht, die in einem anderen Teil des Stack-Codes definiert ist.
Die meisten Infrastruktur-Plattformen können automatisch aufgrund bestimmter Umstände neue Server-Instanzen erstellen. Zwei häufige Fälle sind Autoscaling, das Hinzufügen von Servern, um eine wachsende Last verarbeiten zu können, und Auto-Recovery, das Ersetzen einer Server-Instanz, wenn diese ausfällt. Sie können das normalerweise durch Stack-Code definieren (siehe Listing 11-2).
server_cluster:
server_instance:
source_image: stock-linux-1.23
memory: 2GB
vnet: ${APPSERVER_VNET}
scaling_rules:
min_instances: 2
max_instances: 5
scaling_metric: cpu_utilization
scaling_value_target: 40%
health_check:
type: http
port: 8443
path: /health
expected_code: 200
wait: 90s
Dieses Beispiel weist die Plattform an, mindestens zwei und höchstens fünf Instanzen des Servers laufen zu lassen. Die Plattform ergänzt oder entfernt Instanzen nach Bedarf, um die CPU-Auslastung nahe bei 40 Prozent zu halten.
Die Definition enthält zudem einen Health Check. Dieser Check führt einen HTTP-Request an /health auf Port 8443 aus und geht davon aus, dass der Server in Ordnung ist, wenn der Request einen HTTP-Response-Code 200 zurückgibt. Liefert der Server den erwarteten Code nicht innerhalb von 90 Sekunden, nimmt die Plattform an, dass der Server ausgefallen ist, zerstört ihn und erzeugt eine neue Instanz.
In Kapitel 3 habe ich Bare-Metal Clouds erwähnt, die Hardware-Server dynamisch provisionieren. Der entsprechende Prozess enthält meist die folgenden Schritte:
Es gibt eine Reihe von Tools, um diesen Prozess zu managen, unter anderem:
Statt ein OS-Installations-Image zu booten, könnten Sie auch ein vorbereitetes Server-Image booten. Damit schaffen Sie die Gelegenheit, ein paar der anderen Methoden zum Vorbereiten von Servern zu implementieren, die im nächsten Abschnitt beschrieben werden.
FaaS-Events können dabei helfen, einen Server zu provisionieren FaaS-Serverless-Code kann beim Provisionieren eines neuen Servers helfen. Ihre Plattform kann Code an unterschiedlichen Stellen im Prozess ausführen – vor, während und nach dem Erstellen einer neuen Instanz. Beispiele sind das Zuweisen von Sicherheitsrichtlinien zu einem neuen Server, das Registrieren bei einem Monitoring-Service oder das Ausführen eines Serverkonfigurationstools. |
Wie schon erwähnt (siehe »Woher Dinge kommen« auf Seite 207) gibt es eine Reihe von Quellen für die Inhalte auf einem neuen Server, unter anderem die Installation des Betriebssystems, Paket-Downloads aus Repositories und eigene Konfigurationsdateien und Anwendungsdateien, die auf den Server kopiert werden.
Sie können das zwar alles zusammensetzen, wenn Sie den Server erstellen, es gibt aber auch eine Reihe von Möglichkeiten, Serverinhalte im Voraus vorzubereiten. Diese Ansätze können den Prozess des Bauens eines Servers optimieren, ihn beschleunigen und vereinfachen und es leichter machen, mehrere konsistente Server zu erstellen. Im Folgenden stelle ich ein paar der Ansätze vor. Danach werden Optionen für das Anwenden der Konfiguration vor, während und nach dem Provisionierungsprozess beschrieben.
Viele Infrastruktur-Plattformen machen es leicht, einen laufenden Server zu duplizieren. Das Hot-Cloning eines Servers auf diese Art und Weise geht schnell und einfach, und Sie erhalten so Server, die zum Zeitpunkt des Klonens konsistent sind.
Das Klonen eines laufenden Servers kann nützlich sein, wenn Sie einen Server untersuchen oder dort Fehler beheben wollen, ohne ihn in seiner eigentlichen Arbeit zu unterbrechen. Aber es birgt auch Risiken. Eines ist, dass eine Kopie eines Produktivservers, die Sie zum Experimentieren erstellen, die Produktivumgebung beeinflussen kann. Ist beispielsweise ein geklonter Anwendungsserver so konfiguriert, dass er die Produktivdatenbank nutzt, verändern oder beschädigen Sie eventuell unabsichtlich Produktivdaten.
Geklonte Server, die in der Produktivumgebung laufen, besitzen im Allgemeinen historische Daten des Originalservers, wie zum Beispiel Logdateien. Diese Datenverschmutzung kann die Fehlerbehebung verkomplizieren. Ich habe schon gesehen, wie Leute viel Zeit mit Fehlermeldungen verbracht haben, die letztendlich gar nicht von dem Server kam, auf dem sie debuggt haben.
Und geklonte Server sind nicht wirklich reproduzierbar. Zwei Server, die vom gleichen Ursprungsserver, aber zu unterschiedlichen Zeiten geklont wurden, werden sich unterscheiden, und Sie können nicht einfach zurückkehren, einen dritten Server bauen und genau wissen, ob und wie er sich von den anderen unterscheidet. Geklonte Server sind eine Ursache für Konfigurationsdrift (siehe »Konfigurationsdrift« auf Seite 48).
Statt neue Server zu bauen, indem ein laufender Server direkt geklont wird, können Sie einen Snapshot eines laufenden Servers erstellen und Ihre Server daraus erstellen. Auch dies wird von den meisten Infrastruktur-Plattformen durch Befehle und API-Aufrufe erleichtert, mit denen Sie ein statisches Image erstellen. So können Sie so viele Server erzeugen, wie Sie möchten, und dabei sicher sein, dass jeder einzelne identisch zum Ausgangsserver ist.
Erstellen Sie einen Server aus einem Snapshot, der von einem laufenden Server gemacht wurde, erhalten Sie allerdings auch viele der Nachteile des Hot-Clonings. Der Snapshot kann durch Logs, Konfiguration und andere Daten des Originalservers verschmutzt sein. Es ist effektiver, von Grund auf ein sauberes Server-Image zu erzeugen.
Ein Server-Image ist ein Snapshot, den Sie aus sauberen, bekannten Quellen erstellen, sodass Sie mehrere, konsistente Server-Instanzen erzeugen können. Das tun Sie eventuell mit den gleichen Features der Infrastruktur-Plattform, mit denen Sie auch einen Snapshot eines Live-Servers erstellen, aber die Original-Server-Instanz wird nie als Teil Ihres Gesamtsystems verwendet. So stellen Sie sicher, dass jeder neue Server sauber ist.
Ein typischer Prozess für das Erstellen eines Server-Image sieht so aus:
Manchmal werden Server-Images als Golden Images bezeichnet. Auch wenn manche Teams diese Images von Hand bauen – und dabei vielleicht eine Checkliste mit Schritten abarbeiten –, sehen Sie beim Lesen dieses Buchs vermutlich sofort die Vorteile des Automatisierens dieses Prozesses, wenn Server-Images als Code gemanagt werden. Das Bauen und Bereitstellen von Server-Images ist Thema von Kapitel 13.
Dieses Kapitel hat beschrieben, was die Elemente eines Servers sind, woher sie kommen, wie man eine neue Server-Instanz erzeugt und worin der Wert des Vorbereitens von Server-Images liegt. Das letzte Puzzleteil beim Erstellen von Servern und beim Provisionieren ist das automatisierte Anwenden des Server-Konfigurationscodes auf neue Server. Es gibt in diesem Prozess eine Reihe von Stellen, an denen Sie das umsetzen können, wie in Abbildung 11-5 zu sehen ist.
Abbildung 11-5: Wo die Konfiguration im Server-Lebenszyklus angewendet werden kann
Die Konfiguration kann angewendet werden, wenn das Server-Image erstellt wird, wenn die Server-Instanz aus dem Image erzeugt wird oder wenn der Server läuft:
Ein Server-Image konfigurieren
Sie wenden die Konfiguration an, wenn Sie ein Server-Image bauen, das genutzt wird, um mehrere Server-Instanzen zu erstellen. Betrachten Sie es als einmaliges Konfigurieren, das dann mehrfach zum Einsatz kommt. Das wird oft als Backen (Baking) eines Server-Image bezeichnet.
Eine neue Server-Instanz konfigurieren
Sie wenden die Konfiguration an, wenn Sie eine neue Server-Instanz erstellen. So konfigurieren Sie viele Male. Das wird manchmal als Ausbacken (Frying) einer Server-Instanz bezeichnet.
Eine laufende Server-Instanz konfigurieren
Sie wenden die Konfiguration auf einen Server an, der schon im Einsatz ist. Ein häufiger Grund dafür ist das Umsetzen einer Änderung, wie zum Beispiel das Anwenden von Sicherheits-Patches. Ein weiterer Grund ist das Zurücknehmen von Änderungen, die außerhalb der automatisierten Konfiguration vorgenommen wurden, um die Konsistenz sicherzustellen. Manche Teams wenden auch die Konfiguration an, um einen bestehenden Server umzuwandeln – zum Beispiel einen Webserver in einen Anwendungsserver umzubauen.
Die letzte dieser drei Optionen – das Konfigurieren einer laufenden Server-Instanz – geschieht meist, um einen Server zu ändern, war Thema von Kapitel 12 ist. Die ersten beiden sind Optionen für das Anwenden einer Konfiguration beim Erstellen eines neuen Servers, daher passen sie besser in den Rahmen dieses Kapitels. Die entscheidende Frage ist, den richtigen Zeitpunkt zum Anwenden der Konfiguration auf einen neuen Server zu bestimmen – jede neue Server-Instanz ausbacken oder in das Server-Image backen?
Wie beschrieben gehört zum Ausbacken eines Servers das Anwenden der Konfiguration, wenn Sie die neue Server-Instanz erstellen. Sie können das ins Extreme treiben, indem Sie jedes Server-Image auf ein absolutes Minimum reduzieren und alles für einen gegebenen Server Spezifische in das Live-Image einbauen. So haben neue Server immer die neuesten Änderungen, unter anderem System-Patches, die neuesten Versionen der Softwarepakete und aktuelle Konfigurationsoptionen. Das Ausbacken eines Servers ist ein Beispiel für eine Delivery-Time Integration (siehe »Pattern: Delivery-Time Project Integration« auf Seite 373).
Dieser Ansatz vereinfacht das Image-Management. Es gibt nicht viele Images – vermutlich nur eines für jede in der Infrastruktur genutzte Kombination aus Hardware und OS-Version. So kann es beispielsweise ein Image für 64-Bit-Windows 2019 und jeweils eines für 32-Bit- und 64-Bit-Ubuntu 18.x geben. Die Images müssen nicht sehr häufig aktualisiert werden, da sich an ihnen nicht sehr viel ändert. Und Sie können immer auch noch die neuesten Patches anwenden, wenn Sie einen Server provisionieren.
Manche Teams, mit denen ich gearbeitet habe, haben sich bei der Umsetzung der Infrastruktur-Automation frühzeitig darauf festgelegt, Server auszubacken. Das Aufsetzen des Toolings und der Prozesse für das Verwalten der Server-Images erfordert viel Aufwand. Wir haben diese Arbeit in unser Backlog übernommen, um sie nach dem Bauen unseres zentralen Infrastruktur-Managements zu erledigen.
In anderen Fällen ist das Ausbacken sinnvoll, weil Server hochverfügbar sind. Eine Hosting-Firma, die ich kenne, lässt Kundinnen und Kunden aus einer großen Zahl von Anpassungen für die Server wählen. Die Firma managt nur sehr wenige Basis-Server-Images und steckt mehr Aufwand in das Automatisieren, dass jeden neuen Server konfiguriert.
Es gibt eine Reihe potenzieller Probleme beim Installieren und Konfigurieren von Elementen eines Servers, wenn eine Instanz erzeugt wird. Dazu gehören:
Geschwindigkeit
Aktivitäten, die beim Bauen eines Servers notwendig sind, kosten Zeit. Diese zusätzliche Zeit kann insbesondere dann ein Problem sein, wenn es darum geht, Server hochzufahren, um Lastspitzen abzufangen oder um nach Ausfällen wieder Services anzubieten.
Effizienz
Zum Konfigurieren eines Servers gehört oft das Herunterladen von Paketen aus Repositories über das Netzwerk. Das kann unnötig und langsam sein. Müssen Sie beispielsweise in kurzer Zeit 20 Server hochfahren, ist es unwirtschaftlich, jeden von ihnen die gleichen Patches und Anwendungsinstaller herunterladen zu lassen.
Abhängigkeiten
Das Konfigurieren eines Servers hängt meist von Artefakt-Repositories und anderen Systemen ab. Ist eine dieser Quellen offline oder nicht erreichbar, können Sie keinen neuen Server erstellen. Das kann insbesondere in einem Notfall schmerzhaft sein, wenn Sie schnell viele Server neu bauen müssen. In solchen Situationen sind eventuell auch Netzwerk-Devices oder Repositories nicht verfügbar, was zu einem komplexen geordneten Graphen der Systeme führt, um herauszufinden, welche davon zuerst neu gestartet oder neu gebaut werden müssen, um die nächsten Systeme starten zu können.
Am anderen Ende des Spektrums liegt der Ansatz, beim Erstellen von Servern fast alles in das Server-Image hinein zu konfigurieren. Das Bauen neuer Server geht dann sehr schnell und einfach vonstatten, weil Sie nur eine instanzspezifische Konfiguration vornehmen müssen. Das Backen eines Server-Image ist ein Beispiel für die Build-Time Integration (siehe »Pattern: Build-Time Project Integration« auf Seite 369).
Die Vorteile des Backens ergeben sich aus den Nachteilen des Ausbackens. Es ist besonders sinnvoll, die Konfiguration in Server-Images zu backen, die viele gleiche Server-Instanzen nutzen, und wenn Sie dazu in der Lage sein müssen, Server häufig und schnell erzeugen zu müssen.
Eine Herausforderung beim Backen von Server-Images ist, dass Sie das Tooling und die Automations-Prozesse aufsetzen müssen, mit denen es leicht wird, neue Versionen zu aktualisieren und auszurollen. Wird beispielsweise ein wichtiger Sicherheits-Patch für Ihr Betriebssystem oder für zentrale Pakete, die in Ihre Server-Images gebacken sind, veröffentlicht, wollen Sie schnell neue Images bauen und sie ohne große Unterbrechung auf Ihre Server ausrollen können. Das ist Thema von Kapitel 13.
In der Praxis nutzen die meisten Teams eine Kombination aus Backen und Ausbacken, um neue Server zu konfigurieren. Sie können einen Mittelweg dafür finden, welche Konfiguration Sie im Server-Image vornehmen und welche beim Erstellen einer Server-Instanz. Manche Konfigurationselemente lassen sich auch in beiden Teilen des Prozesses anwenden.
Die wichtigsten Überlegungen bei der Frage, wann die richtige Zeit zum Anwenden einer bestimmten Konfiguration ist, sind der Zeitaufwand für die Änderung und deren Häufigkeit. Dinge, deren Anwenden länger braucht und die sich seltener ändern, sind klare Kandidaten für das Backen in das Server-Image. So können Sie beispielsweise Anwendungsserver-Software auf ein Server-Image installieren und damit das Hochfahren mehrerer Server-Instanzen deutlich beschleunigen und in diesem Prozess Netzwerk-Bandbreite einsparen.
Am anderen Ende dieser Skala liegen Dinge, die sich schneller installieren lassen oder häufiger ändern – diese werden besser ausgebacken. Ein Beispiel dafür sind Anwendungen, die in der Firma entwickelt wurden. Einer der häufigsten Anwendungsfälle für das Hochfahren neuer Server bei Bedarf ist das Testen als Teil eines Software-Release-Prozesses.
Hochproduktive Entwicklungsteams sorgen vielleicht für ein Dutzend oder mehr neue Builds ihrer Anwendung pro Tag, wobei sie auf einen CI-Prozess setzen, der jeden Build automatisch deployt und testet. Das Backen eines neuen Server-Image für jeden neuen Anwendungs-Build ist für diese Arbeitsgeschwindigkeit zu langsam, daher ist es effizienter, die Anwendung beim Erstellen des Testservers zu deployen.
Eine andere Möglichkeit, wie Teams Backen und Ausbacken kombinieren, ist, so viel wie möglich in Server-Images zu backen, aktualisierte Versionen aber auszubacken. Ein Team backt vielleicht Server-Images mit niedrigerer Geschwindigkeit – wöchentlich oder monatlich. Müssen sie etwas aktualisieren, das sie normalerweise in das Image backen würden, wie zum Beispiel einen Sicherheits-Patch oder Verbesserungen an der Konfiguration, können sie dies in den Prozess zum Erstellen der Server stecken, um es zusammen mit dem gebackenen Image auch noch auszubacken.
Ist der Zeitpunkt gekommen, ein aktualisiertes Image zu backen, stecken sie die Updates mit hinein und entfernen sie gleichzeitig aus dem Erstellungsprozess. So kann das Team schnell neue Änderungen übernehmen, ohne sich viel Overhead aufzuhalsen. Teams nutzen das oft in Kombination mit dem kontinuierlichen Anwenden von Code (siehe »Pattern: Continuous Configuration Synchronization« auf Seite 232), um bestehende Server zu aktualisieren, ohne sie neu bauen zu müssen.
Die meisten Tools, die zum Erstellen von Servern auf den oben besprochenen Wegen zum Einsatz kommen (siehe »Eine neue Server-Instanz erstellen« auf Seite 216) – seien es Befehlszeilentools, Plattform-API-Aufrufe oder ein Stack-Management-Tool, bieten eine Möglichkeit an, Code zur Serverkonfiguration anzuwenden. So sollte beispielsweise ein Stack-Management-Tool eine Syntax zum Unterstützen verbreiteter Tools oder zum Ausführen eines beliebigen Befehls auf einer neuen Server-Instanz besitzen, wie in »Stack-Code führt mein fiktives Server-Konfigurationstool aus.« auf Seite 227 gezeigt wird.
server:
source_image: stock-linux-1.23
memory: 2GB
vnet: ${APPSERVER_VNET}
configure:
tool: servermaker
code_repo: servermaker.shopspinner.xyz
server_role: appserver
parameters:
app_name: catalog_service
app_version: 1.2.3
Dieser Code führt das Servermaker-Tool aus, übergibt ihm den Hostnamen des Servers, der den Code für die Serverkonfiguration hostet, die Rolle, die auf den Server angewendet werden soll (appserver), und ein paar Parameter für den Serverkonfigurationscode (app_name und app_version).
Manche Tools erlauben es Ihnen auch, Serverkonfigurationscode oder auszuführende Shell-Befehle direkt in den Code für den Stack einzubetten. Es kann verlockend sein, darüber die Logik für die Serverkonfiguration zu implementieren, und für einfache Situationen kann das auch in Ordnung sein. Aber in den meisten Fällen wächst dieser Code bezüglich Größe und Komplexität an. Daher ist es besser, den Code zu extrahieren, um Ihre Codebasis sauber und wartbar zu halten.
Dieses Kapitel hat eine Reihe von Aspekten beim Erstellen und Provisionieren neuer Server angesprochen. Zu den Elementen auf einem Server gehören Software, Konfiguration und Daten. Diese werden meist aus der Installation des Basis-Betriebssystems und Paketen diverser Repositories geholt, unter anderem aus denen, die vom Betriebssystem und von Sprachanbietern, Fremdfirmen und internen Teams verwaltet werden. Typischerweise bauen Sie einen Server, indem Sie Inhalte aus einem Server-Image kombinieren und ein Server-Konfigurationstool nutzen, um zusätzliche Pakete und Konfiguration anzuwenden.
Um einen Server zu erstellen, können Sie ein Befehlszeilentool ausführen oder eine Benutzeroberfläche nutzen, aber es ist besser, einen Code-gesteuerten Prozess zu verwenden. Heutzutage werden Sie eher kein eigenes Skript dafür schreiben, sondern Ihr Stack-Management-Tool verwenden. Auch wenn ich ein paar unterschiedliche Ansätze zum Bauen von Servern beschreibe, empfehle ich im Allgemeinen, Server-Images zu bauen.