Ich habe in »Die Elemente eines Infrastruktur-Systems« auf Seite 53 Anwendungs-Laufzeitumgebungen als Teil eines Modells vorgestellt, das die Elemente eines Systems in drei Schichten einordnet. In diesem Modell kombinieren Sie Ressourcen aus der Infrastruktur-Schicht, um Laufzeitplattformen bereitzustellen, auf die dann Anwendungen deployt werden können.
Anwendungs-Laufzeitumgebungen bestehen aus Infrastruktur-Stacks, die Sie mithilfe der in Kapitel 5 beschriebenen Infrastruktur-Management-Tools definieren und erstellen (siehe Abbildung 10-1).
Der Ausgangspunkt für das Designen und Implementieren von Anwendungs-Laufzeit-Infrastruktur ist, die Anwendungen zu verstehen, die sie nutzen werden. Welche Sprachen und Ausführungs-Stacks kommen zum Einsatz? Werden sie verpackt und auf Server oder Container deployt? Oder laufen sie als FaaS-Serverless-Code? Gibt es eine Anwendung, die an einen Ort deployt wird, oder sind es viele Services, die über ein Cluster verteilt werden? Wie sehen die Verbindungen und Datenanforderungen aus?
Die Antworten auf diese Fragen führen zu einem Verständnis für die Infrastruktur-Ressourcen, die die Anwendungs-Laufzeitschicht provisionieren und managen muss, um die Anwendungen betreiben zu können. Die Elemente der Anwendungs-Laufzeitschicht werden auf die Elemente der Infrastruktur-Plattform abgebildet, die ich in »Infrastruktur-Ressourcen« auf Seite 57 beschreibe. Dazu gehört eine Ausführungsumgebung, die auf Computing-Ressourcen aufbaut, Datenmanagement basierend auf Storage-Ressourcen und Connectivity, die sich aus Networking-Ressourcen zusammensetzt.
Abbildung 10-1: Die Anwendungsschicht ist aus Infrastruktur-Stacks zusammengestellt.
In diesem Kapitel ist jede dieser Beziehungen zusammengefasst, wobei ich mich darauf konzentriere, wie die Infrastruktur-Ressourcen zu Laufzeitplattformen für Anwendungen zusammengestellt werden können. Es legt die Grundlage für spätere Kapitel, die sich dann genauer damit befassen, wie diese Ressourcen als Code definiert und gemanagt werden können – Server als Code (siehe Kapitel 11) und Cluster als Code (siehe Kapitel 14).
Cloud-native Software ist so entworfen und implementiert, dass sie die dynamische Natur moderner Infrastruktur ausnutzt. Anders als Software früherer Generationen können Instanzen einer Cloud-nativen Anwendung transparent hinzugefügt, entfernt und auf der zugrunde liegenden Infrastruktur verschoben werden. Die eingesetzte Plattform allokiert dynamisch Computing- und Storage-Ressourcen und leitet Traffic an die Anwendung weiter (und von ihr weg). Die Anwendung integriert sich nahtlos mit Services wie Monitoring, Logging, Authentifizierung und Verschlüsselung.
Die Leute bei Heroku haben die Zwölf-Faktoren-Methode (https://12factor.net/de) entwickelt, um Anwendungen für den Einsatz auf Cloud-Infrastruktur zu bauen. Cloud-native wird als Begriff oft mit dem Kubernetes-Ökosystem verbunden.1
Viele Organisationen haben bestehende Softwareportfolios, die nicht Cloud-native sind. Einen Teil der Software wandeln sie vielleicht um oder schreiben ihn neu, sodass er Cloud-native wird, aber in vielen Fällen sind die Kosten im Vergleich zu den Vorteilen zu hoch. Eine anwendungsgesteuerte Infrastruktur-Strategie beinhaltet das Erstellen von Anwendungs-Laufzeitumgebungen mithilfe moderner und dynamischer Infrastruktur.
Teams stellen Anwendungs-Laufzeitumgebungen für neue Anwendungen bereit, die als Serverless-Code oder in Containern laufen. Auch liefern sie Infrastruktur zum Unterstützen bestehender Anwendungen. All diese Infrastruktur wird als Code definiert, provisioniert und gemanagt. Anwendungsgesteuerte Infrastruktur kann über eine Abstraktionsschicht dynamisch bereitgestellt werden (siehe »Eine Abstraktionsschicht bauen« auf Seite 327).
Das Implementieren einer anwendungsgetriebenen Strategie beginnt mit dem Analysieren der Laufzeit-Anforderungen für Ihr Anwendungsportfolio. Dann designen Sie die Anwendungs-Laufzeitlösungen, um diese Anforderungen zu erfüllen, und implementieren wiederverwendbare Stacks, Stack-Komponenten und andere Elemente, die Teams nutzen können, um Umgebungen für spezifische Anwendungen zusammenstellen zu können.
Ein deploybares Release einer Anwendung kann unterschiedliche Elemente beinhalten. Abgesehen von Dokumentation und anderen Metadaten gehören zu einem Anwendungs-Deployment möglicherweise:
Executables
Der Kern eines Release besteht aus der oder den ausführbaren Datei(en) – seien es Binaries oder interpretierte Skripte. Sie können Bibliotheken und andere Dateien, die von den Executables genutzt werden, ebenfalls zu dieser Kategorie zählen.
Viele Anwendungs-Deployment-Pakete nehmen Änderungen an der Serverkonfiguration vor. Dazu können Benutzer-Accounts gehören, unter denen Prozesse laufen, aber auch Ordnerstrukturen und Änderungen an Systemkonfigurations-Dateien.
Datenstrukturen
Nutzt eine Anwendung eine Datenbank, kann ein Deployment Schemata erstellen oder anpassen. Eine gegebene Version des Schemas korrespondiert normalerweise mit einer Version des Executables, daher ist es am besten, beide zusammenzuführen und gemeinsam zu deployen.
Referenzdaten
Ein Anwendungs-Deployment kann eine Datenbank oder anderen Storage mit einem initialen Datensatz befüllen. Dabei kann es sich um Referenzdaten handeln, die sich mit neuen Versionen geändert haben, oder um Beispieldaten, die Benutzerinnen und Benutzern dabei helfen, mit der Anwendung nach der ersten Installation loslegen zu können.
Connectivity
Ein Anwendungs-Deployment kann eine Netzwerkkonfiguration spezifizieren, wie zum Beispiel Netzwerk-Ports. Dazu können auch Elemente gehören, die die Connectivity unterstützen, wie zum Beispiel Zertifikate oder Schlüssel zum Verschlüsseln oder Authentifizieren von Verbindungen.
Konfigurationsparameter
Ein Anwendungs-Deployment-Paket kann Konfigurationsparameter setzen – entweder über das Kopieren von Konfigurationsdateien auf einen Server oder über das Übertragen von Einstellungen in eine Registry.
Sie können die Grenze zwischen Anwendung und Infrastruktur unterschiedlich ziehen. Vielleicht stecken Sie eine erforderliche Bibliothek in das Anwendungs-Deployment-Paket oder Sie provisionieren sie als Teil der Infrastruktur.
So enthält beispielsweise ein Container-Image normalerweise einen Großteil des Betriebssystems und die Anwendung, die darauf laufen soll. Ein immutabler Server oder ein immutabler Stack geht noch weiter und kombiniert Anwendung und Infrastruktur in einer einzelnen Entität. Am anderen Ende des Spektrums habe ich Infrastruktur-Code gesehen, der Bibliotheken und Konfigurationsdateien für eine bestimmte Anwendung provisioniert und im Anwendungspaket nur noch sehr wenig Elemente belässt.
Diese Frage ist auch relevant, wenn es um das Organisieren Ihrer Codebasis geht. Halten Sie Ihren Anwendungscode und den Infrastruktur-Code zusammen oder getrennt? Die Frage der Code-Grenzen wird in diesem Buch noch angesprochen (siehe Kapitel 18), aber ein Prinzip ist, dass es im Allgemeinen eine gute Idee ist, die Struktur Ihrer Codebasis mit Ihren deploybaren Elementen abzugleichen.
Anwendungen sind oft in Deployment-Paketen organisiert, wobei das Paketformat vom Typ der Laufzeitumgebung abhängt. Beispiele für Formate von Deployment-Paketen und zugehörigen Laufzeitumgebungen finden Sie in Tabelle 10-1.
Tabelle 10-1: Beispiele für Laufzeitziele und Anwendungspaketformate
Laufzeitziel |
Beispielpakete |
Server-Betriebssystem |
Red Hat RPM-Dateien, Debian .deb-Dateien, Windows MSI Installer-Pakete |
Laufzeit-Engine von Programmiersprachen |
Ruby Gems, Python PIP-Pakete, Java .jar-, .war- und .ear-Dateien |
Container-Laufzeitumgebung |
Docker-Images |
Anwendungs-Cluster |
Kubernetes Deployment Descriptors, Helm Charts |
FaaS Serverless |
Lambda Deployment-Pakete |
Ein Deployment-Paketformat ist ein Standard, der es Deployment-Tools oder Laufzeitumgebungen ermöglicht, die Elemente der Anwendung zu extrahieren und sie an den richtigen Stellen unterzubringen.
Server – echte oder virtuelle – sind die klassische Laufzeitplattform. Eine Anwendung wird mit einem Paketformat des Betriebssystems verpackt, wie zum Beispiel RPM, eine .deb-Datei oder ein Windows MSI. Oder es wird in einem Format für die Laufzeitumgebung der Programmiersprache verpackt, zum Beispiel als Ruby-Gem oder als .war-Datei in Java. In letzter Zeit haben Container-Images, wie zum Beispiel Docker-Images, als Format zum Verpacken und Deployen von Anwendungen auf Server an Beliebtheit gewonnen.
Das Definieren und Provisionieren von Servern als Code ist Thema von Kapitel 11. Es überlappt sich mit dem Anwendung-Deployment bezüglich der Entscheidung, ob und wie man Deployment-Befehle ausführt (siehe »Eine neue Server-Instanz konfigurieren« auf Seite 223).
Container ziehen Abhängigkeiten vom Betriebssystem in ein Anwendungspaket – das Container-Image (siehe Abbildung 10-2).1
Abbildung 10-2: Abhängigkeiten können auf dem Host-Betriebssystem installiert oder in Container verpackt werden.
Wenn Sie Abhängigkeiten in den Container aufnehmen, wird dieser größer als die typischen Betriebssystem- oder Sprachpakete, aber Sie erhalten dadurch eine Reihe von Vorteilen:
Anwendungen wurden schon auf Gruppen aus Servern deployt, als containerbasierte Anwendungs-Cluster noch nicht aktuell waren. Normalerweise hat man ein Server-Cluster (siehe »Computing-Ressourcen« auf Seite 58) und lässt auf jedem Server einen identischen Satz an Anwendungen laufen. Sie verpacken Ihre Anwendungen genauso wie für einen einzelnen Server, wiederholen dann aber den Deployment-Prozess für jeden Server im Pool (siehe Abbildung 10-3) – eventuell mit einem Remote-Command-Scripting-Tool wie Capistrano (https://capistranorb.com) oder Fabric (https://www.fabfile.org).
Abbildung 10-3: Anwendungen werden auf jeden Server in einem Cluster deployt.
Deployen Sie eine Anwendung auf mehrere Server, müssen Sie entscheiden, wie Sie das Deployment orchestrieren. Deployen Sie sie auf alle Server gleichzeitig? Müssen Sie währenddessen den gesamten Service offline nehmen? Oder aktualisieren Sie immer nur einen Server nach dem anderen? Sie können ein inkrementelles Deployment auf Server für progressive Deployment-Strategien wie die Blue/Green- oder Canary-Deployment-Pattern ausnutzen (mehr dazu finden Sie in »Live-Infrastruktur ändern« auf Seite 415).
Neben dem Deployen von Anwendungscode auf Server müssen Sie eventuell auch andere Elemente deployen – zum Beispiel Änderungen an Datenstrukturen oder an der Connectivity.
Wie in »Computing-Ressourcen« auf Seite 58 besprochen ist ein Anwendungs-Hosting-Cluster ein Pool aus Servern, auf der eine oder mehrere Anwendungen laufen. Anders als ein Server-Cluster, bei dem auf jedem Server der gleiche Satz an Anwendungen läuft, können auf den verschiedenen Servern in einem Anwendungs-Cluster unterschiedliche Gruppen von Anwendungsinstanzen laufen (siehe Abbildung 10-4).
Deployen Sie eine Anwendung auf das Cluster, entscheidet ein Scheduler, auf welchen Host-Servern Instanzen der Anwendung laufen sollen. Der Scheduler kann diese Verteilung ändern und Anwendungsinstanzen auf den Host-Servern anhand verschiedener Algorithmen und Einstellungen hinzufügen oder entfernen.
Abbildung 10-4: Anwendungen werden auf das Cluster deployt und auf Host-Knoten verteilt.
In der guten alten Zeit1 waren die verbreitetsten Anwendungs-Cluster Javabasiert (Tomcat, Websphere, Weblogic, JBoss und andere). Vor ein paar Jahren entstanden eine Reihe von Cluster-Management-Systemen, unter anderem Apache Mesos (https://oreil.ly/46lat) und DC/OS (https://dcos.io), die oft durch Google Borg inspiriert wurden.2 In letzter Zeit haben Systeme, die sich auf das Orchestrieren von Container-Instanzen fokussieren, die Anwendungsserver und Cluster-Orchestrierer überholt.
Das Definieren und Provisionieren von Clustern als Code ist Thema von Kapitel 14. Haben Sie ein Cluster, mag es einfach sein, eine einzelne Anwendung darauf zu deployen. Verpacken Sie die Anwendung mit Docker und befördern Sie sie in das Cluster. Aber komplexere Anwendungen mit mehr Elementen besitzen auch komplexere Anforderungen.
Bei modernen Anwendungen sind oft mehrere Prozesse und Komponenten beteiligt, die auf komplexer Infrastruktur deployt werden. Eine Laufzeitumgebung muss wissen, wie diese verschiedenen Elemente zu betreiben sind:
Unterschiedliche Laufzeitplattformen bieten auch unterschiedliche Funktionalität, und viele besitzen ihr eigenes Paket- und Konfigurationsformat. Häufig nutzen diese Plattformen ein Deployment-Manifest, das sich auf die eigentlichen Deployment-Artefakte bezieht (zum Beispiel ein Container-Image) und nicht auf eine Archiv-Datei, die alle deploybaren Elemente enthält. Beispiele für Deployment-Manifeste für Cluster-Anwendungen sind:
Die verschiedenen Deployment-Manifeste und -Pakete arbeiten auf unterschiedlichen Ebenen. Manche konzentrieren sich auf eine einzelne deploybare Einheit, sodass Sie ein Manifest für jede Anwendung brauchen. Andere definieren eine Zusammenstellung deploybarer Services. Je nach Tool hat eventuell jeder der Services innerhalb der Zusammenstellung ein eigenes Manifest mit einem weiteren Manifest auf höherer Ebene, das gemeinsame Elemente und Integrations-Parameter definiert.
Ein Manifest für das Deployment des ShopSpinner-Webservers könnte in Pseudocode wie in Listing 10-1 aussehen.
service:
name: webservers
organization: ShopSpinner
version: 1.0.3
application:
name: nginx
repository: containers.shopspinner.xyz
path: /images/nginx
tag: 1.0.3
instance:
count_min: 3
count_max: 10
health_port: 443
health_url: /alive
connectivity:
inbound:
id: https_inbound
port: 443
allow_from: $PUBLIC_INTERNET
ssl_cert: $SHOPSPINNER_PUBLIC_SSL_CERT
outbound:
port: 443
allow_to: [ $APPSERVER_APPLICATIONS.https_inbound ]
Dieses Beispiel legt fest, wo und wie das Container-Image zu finden ist (der Block container_image), wie viele Instanzen auszuführen sind und wie deren Zustand zu prüfen ist. Zudem werden Regeln für die eingehenden und ausgehenden Verbindungen definiert.
In Kapitel 3 habe ich ein paar Laufzeitplattformen für FaaS-Serverless-Anwendungen als Computing-Ressource aufgeführt (siehe »Computing-Ressourcen« auf Seite 58). Die meisten davon besitzen ihr eigenes Format für das Definieren der Anforderungen für die Anwendungs-Laufzeitumgebung und das Verpacken des Codes und der anderen benötigten Elemente für das Deployen auf eine Instanz der Laufzeitumgebung.
Beim Schreiben einer FaaS-Anwendung werden die Details, auf welchen Servern oder Containern Ihr Code laufen wird, vor Ihnen verborgen. Aber Ihr Code benötigt vermutlich Infrastruktur, damit er funktionieren kann. So sind vielleicht ein Netzwerk-Routing für eingehende und ausgehende Verbindungen, Storage oder Message Queues erforderlich. Ihr FaaS-Framework integriert sich vielleicht mit der zugrunde liegenden Infrastruktur-Plattform und provisioniert automatisch die benötigte Infrastruktur. Oder Sie müssen die Infrastruktur-Elemente selbst in einem eigenen Stack-Definitions-Tools definieren. Viele Stack-Tools wie Terraform oder CloudFormation lassen Sie Ihre FaaS-Code-Provisionierung als Teil eines Infrastruktur-Stacks deklarieren.
In Kapitel 14 geht es um das Definieren und Provisionieren von FaaS-Laufzeitumgebungen, um Ihren Code auszuführen.
An Daten wird beim Deployen und Ausführen von Anwendungen oft erst später gedacht. Wir provisionieren Datenbanken und Storage Volumes, aber wie bei vielen Teilen der Infrastruktur wird es erst dann richtig knifflig, wenn es um Änderungen geht. Das Anpassen von Daten und Strukturen ist zeitaufwendig, unangenehm und riskant.
Zum Anwendungs-Deployment gehört oft das Erstellen oder Ändern von Datenstrukturen – einschließlich dem Umwandeln bestehender Daten, wenn sich Strukturen verändern. Das Aktualisieren von Datenstrukturen sollte ein Aspekt der Anwendung und des Anwendungs-Deployment sein und nicht der Infrastruktur-Plattform oder Laufzeitumgebung. Aber die Infrastruktur- und Anwendungs-Laufzeit-Services müssen den Umgang mit Daten unterstützen, wenn sich Infrastruktur- und andere zugrunde liegende Ressourcen ändern oder sie ausfallen. In »Datenkontinuität in einem sich ändernden System« auf Seite 430 werden Möglichkeiten dafür vorgestellt.
Mancher Daten-Storage ist strikt strukturiert, wie zum Beispiel SQL-Datenbanken, während anderer unstrukturiert oder schemalos ist. Eine strikt strukturierte, schemabasierte Datenbank zwingt Daten in Strukturen und erlaubt es nicht, falsch formatierte Daten abzulegen. Anwendungen, die eine schemalose Datenbank verwenden, sind selbst für das Format der Daten verantwortlich.
Ein neues Anwendungsrelease kann auch eine Änderung der Datenstrukturen beinhalten. Bei einer schemabasierten Datenbank gehört dazu das Anpassen der Definition der Datenstrukturen in der Datenbank. So fügt das Release vielleicht neue Felder zu Datensätzen hinzu – ein klassisches Beispiel ist das Aufteilen eines einzelnen Felds »Name« in getrennte Felder für Vor- und Nachname.
Ändern sich Datenstrukturen, müssen unabhängig vom Typ der Datenbank alle bestehenden Daten in die neuen Strukturen umgewandelt werden. Teilen Sie ein »Name«-Feld auf, werden Sie einen Prozess benötigen, der die Namen in der Datenbank in ihre getrennten Felder aufteilt.
Das Ändern von Datenstrukturen und Umwandeln bestehender Daten wird als Schema-Migration bezeichnet. Es gibt eine Reihe von Werkzeugen und Bibliotheken, die Anwendungen und Deployment-Tools für diesen Prozess einsetzen können, unter anderem Flyway (https://flywaydb.org), DBDeploy (https://oreil.ly/CHBq4), Liquibase (https://www.liquibase.org) und db-migrate (https://oreil.ly/jG3qY).1 Beim Entwickeln kann man diese Tools nutzen, um inkrementell Datenbankänderungen als Code zu definieren. Die Änderungen können in die Versionsverwaltung eingecheckt und als Teil eines Releases mit ins Paket aufgenommen werden. Damit stellen Sie sicher, dass Datenbank-Schemata synchron zur Anwendungsversion bleiben, die auf eine Instanz deployt wird.
Teams können Strategien zur Datenbank-Evolution einsetzen, um sicher und flexibel Änderungen an Daten und Schemata zu managen. Diese Strategien sind auf agile Softwareentwicklungs-Methoden (unter anderem CI und CD) und Infrastructure as Code abgestimmt.1
Cloud-native Infrastruktur wird Anwendungen und Services dynamisch auf Anforderung zugeteilt. Manche Plattformen bieten Cloud-nativen Storage, aber auch Computing- und Networking-Ressourcen. Fügt das System eine Anwendungs-Instanz hinzu, kann es automatisch ein Storage-Device provisionieren und anbinden. Sie geben im Anwendungs-Deployment-Manifest die Storage-Anforderungen vor und legen Formatierungen oder beim Provisionieren zu ladende Daten fest (siehe Listing 10-2).
application:
name: db_cluster
compute_instance:
memory: 2 GB
container_image: db_cluster_node_application
storage_volume:
size: 50 GB
volume_image: db_cluster_node_volume
Dieses einfache Beispiel definiert, wie Knoten für ein dynamisch skalierendes Datenbank-Cluster zu erstellen sind. Die Plattform wird für jede Knoten-Instanz eine Instanz des Containers mit der Datenbank-Software erstellen und ein Disk Volume anbinden, das aus einem Image geklont wurde, das mit einem leeren Datenbanksegment initialisiert wird. Startet die Instanz, verbindet sie sich mit dem Cluster und synchronisiert Daten auf ihr lokales Volume.
Neben den Computing-Ressourcen zum Ausführen von Code und Storage-Ressourcen zum Aufbewahren der Daten benötigen Anwendungen Networking für ein- und ausgehende Verbindungen. Ein serverorientiertes Anwendungspaket, wie zum Beispiel für einen Webserver, konfiguriert vielleicht Netzwerk-Ports und trägt Encryption-Schlüssel für eingehende Verbindungen bei. Aber klassischerweise wurde darauf gebaut, dass jemand die Infrastruktur außerhalb des Servers getrennt konfiguriert.
Sie können das Adressing, Routing, Naming, die Firewall-Regeln und ähnliche Aspekte als Teil eines Infrastruktur-Stack-Projekts definieren und managen und die Anwendung dann in die sich daraus ergebende Infrastruktur deployen. Ein Cloud-nativerer Ansatz beim Networking ist, die Networking-Anforderungen als Teil des Anwendungs-Deployment-Manifests zu definieren und die Anwendungs-Laufzeitumgebung die Ressourcen dynamisch anfordern zu lassen.
Das sehen Sie teilweise in Listing 10-1, von dem ein Ausschnitt hier wiederholt wird:
application:
name: nginx
connectivity:
inbound:
id: https_inbound
port: 443
allow_from: $PUBLIC_INTERNET
ssl_cert: $SHOPSPINNER_PUBLIC_SSL_CERT
outbound:
port: 443
allow_to: [ $APPSERVER_APPLICATIONS.https_inbound ]
Dieses Beispiel definiert eingehende und ausgehende Verbindungen und bezieht sich dabei auf andere Teile des Systems. Das sind das öffentliche Internet, vermutlich ein Gateway und eingehende HTTPS-Ports für Anwendungsserver, die für das gleiche Cluster mit ihren eigenen Deployment-Manifesten definiert und deployt wurden.
Anwendungs-Laufzeitumgebungen stellen eine Reihe verbreiteter Services für Anwendungen bereit. Viele dieser Services lassen sich per Service Discovery finden.
Anwendungen und Services, die in einer Infrastruktur laufen, müssen häufig wissen, wie sie andere Anwendungen und Services finden können. So schickt beispielsweise eine Frontend-Webanwendung Requests an einen Backend-Service, um Transaktionen für die Benutzerinnen und Benutzer zu verarbeiten.
In einer statischen Umgebung ist das nicht schwierig. Anwendungen können einen bekannten Hostnamen für andere Services verwenden, der vielleicht in einer Konfigurationsdatei abgelegt ist, die Sie bei Bedarf aktualisieren können.
Aber in einer dynamischen Infrastruktur, in der die Position von Services und Servern variiert, ist ein reaktiverer Weg für das Finden von Services erforderlich.
Ein paar bekannte Discovery-Mechanismen sind:
Hartcodierte IP-Adressen
Reservieren Sie IP-Adressen für jeden Service. So kann der Monitoring-Server beispielsweise immer auf 192.168.1.5 laufen. Müssen Sie die Adressen ändern oder mehrere Instanzen eines Service laufen lassen (beispielsweise für einen gesteuerten Rollout eines großen Updates), müssen Sie Anwendungen neu bauen und neu deployen.
Hostfile-Einträge
Nutzen Sie die Serverkonfiguration, um die Datei /etc/hosts (oder ihr Äquivalent) auf jedem Server zu erzeugen und darin Service-Namen auf die aktuelle IP-Adresse abzubilden. Das ist eine unstrukturiertere Alternative zu DNS, aber ich habe sie schon im Einsatz gesehen, um veraltete DNS-Implementierungen zu umgehen.1
DNS (Domain Name System)
Verwenden Sie DNS-Einträge, um Servicenamen auf ihre aktuell IP-Adresse abzubilden – entweder über DNS-Einträge, die durch Code gemanagt werden, oder per DDNS (Dynamic DNS, https://oreil.ly/EhGir). DNS ist eine ausgereifte, gut unterstützte Lösung für dieses Problem.
Ressourcen-Tags
Infrastruktur-Ressourcen werden getaggt, um anzuzeigen, welche Services sie bereitstellen und wie der Kontext (zum Beispiel die Umgebung) aussieht. Zum Discovery gehört dann der Einsatz der Plattform-API, um Ressourcen mit den passenden Tags nachzuschlagen. Achten Sie darauf, den Anwendungscode dafür nicht mit der Infrastruktur-Plattform zu koppeln.
Konfigurations-Registry
Anwendungs-Instanzen können die aktuellen Connectivity-Details in einer zentralen Registry ablegen (siehe »Konfigurations-Registry« auf Seite 129), wo sie von anderen Anwendungen nachgeschlagen werden. Das kann nützlich sein, wenn Sie mehr Informationen als nur die Adresse benötigen – zum Beispiel Health- oder andere Statusdaten.
Sidecar
Parallel zu jeder Anwendungs-Instanz läuft ein eigener Prozess. Die Anwendung nutzt dieses Sidecar als Proxy für ausgehende Verbindungen, als Gateway für eingehende Verbindungen oder als Lookup-Service. Die Sidecars werden eine Methode für ein eigenes Netzwerk-Discovery benötigen. Diese Methode kann einer der anderen Discovery-Mechanismen sein, oder es kann sich um ein anderes Kommunikationsprotokoll handeln.1 Sidecars sind meist Teil eines Service Mesh (ich gehe auf Service Meshes in »Service Mesh« auf Seite 284 ein) und bieten oft mehr als nur Service Discovery. So kann sich ein Sidecar beispielsweise um Authentifizierung, Verschlüsselung, Logging und Monitoring kümmern.
API-Gateways
Ein API-Gateway ist ein zentralisierter HTTP-Service, der Routen und Endpoints definiert. Meist stellt er noch mehr Services bereit – zum Beispiel Authentifizierung, Verschlüsselung, Logging und Monitoring. Mit anderen Worten: Ein API-Gateway ähnelt einem Sidecar, ist aber zentralisiert statt verteilt.2
Vermeiden Sie eine harte Trennung zwischen Infrastruktur, Laufzeitumgebung und Anwendungen In der Theorie kann es nützlich sein, Anwendungs-Laufzeitumgebungen als vollständigen Satz an Services für die Entwicklung bereitzustellen und diese damit vor den Details der zugrunde liegenden Infrastruktur abzuschirmen. In der Praxis sind die Grenzen deutlich verschwommener. Verschiedene Personen und Teams benötigen Zugriff auf Ressourcen auf unterschiedlichen Abstraktionsebenen und mit unterschiedlich viel Kontrolle. Daher sollten Sie Systeme nicht mit absoluten Grenzen entwerfen und implementieren, sondern stattdessen Elemente definieren, die auf unterschiedliche Art und Weise zusammengestellt und den verschiedenen Nutzerinnen und Nutzern präsentiert werden. |
Der Zweck der Infrastruktur ist, auf ihr nützliche Anwendungen und Services zu betreiben. Die Ratschläge und Ideen in diesem Buch sollen Ihnen dabei helfen, Infrastruktur-Ressourcen so zusammenzufassen, dass sie dies auch tun. Ein anwendungsgesteuerter Ansatz für die Infrastruktur konzentriert sich auf die Laufzeitanforderungen von Anwendungen und hilft Ihnen dabei, Stacks, Server, Cluster und andere Konstruktionen aus der Zwischenschicht für das Betreiben von Anwendungen zu designen.