Dieser Beitrag ist eines von vier Tutorials, die Ihnen dabei helfen, die Konzepte aus „Microservices March 2023: Erste Schritte mit der Bereitstellung von Microservices“ in die Praxis umzusetzen:
- So stellen Sie Microservices bereit und konfigurieren sie (dieser Beitrag)
- So verwalten Sie Secrets in Containern sicher
- So automatisieren Sie Canary-Releases für Microservices mit GitHub Actions
- So nutzen Sie OpenTelemetry-Tracing, um Ihre Microservices besser zu verstehen
Alle Apps müssen konfiguriert werden, doch bei der Konfiguration eines Microservices sind möglicherweise andere Aspekte zu berücksichtigen als bei einer monolithischen App. Orientierung für beide Arten von Apps bietet Faktor 3 (Konfiguration in der Umgebung speichern) der Zwölf-Faktoren-App. Diese Empfehlungen lassen sich jedoch an Microservices-Apps anpassen. Insbesondere können wir anpassen, wie wir die Servicekonfiguration definieren, einem Service die Konfiguration bereitstellen und einen Service als Konfigurationswert für andere Services verfügbar machen, die möglicherweise von ihm abhängen.
Um ein grundlegendes Verständnis dafür zu entwickeln, wie Faktor 3 für Microservices angepasst werden kann – insbesondere im Hinblick auf Best Practices für Konfigurationsdateien, Datenbanken und die Serviceerkennung –, lesen Sie Best Practices für die Konfiguration von Microservices-Apps in unserem Blog. Dieser Beitrag eignet sich hervorragend, um dieses Wissen in die Praxis umzusetzen.
Hinweis: In diesem Tutorial möchten wir einige grundlegende Konzepte veranschaulichen und nicht zeigen, wie Microservices in einer Produktionsumgebung richtig bereitgestellt werden. Obwohl eine echte Microservices-Architektur zum Einsatz kommt, sind einige wichtige Einschränkungen zu beachten:
- In diesem Tutorial wird kein Framework zur Container-Orchestrierung wie Kubernetes oder Nomad verwendet. So können Sie sich mit den Konzepten von Microservices vertraut machen, ohne sich in den Besonderheiten eines bestimmten Frameworks zu verlieren. Die hier vorgestellten Muster lassen sich auf ein System übertragen, auf dem eines dieser Frameworks ausgeführt wird.
- Die Dienste sind eher auf leichte Verständlichkeit als auf strenge Softwareentwicklungsprinzipien ausgelegt. Es geht darum, die Rolle eines Dienstes im System und seine Kommunikationsmuster zu betrachten, nicht die Einzelheiten des Codes. Weitere Informationen finden Sie in den README-Dateien der einzelnen Dienste.
Tutorialübersicht
Dieses Tutorial veranschaulicht, wie sich die Konzepte von Faktor 3 auf Microservices-Anwendungen anwenden lassen. In vier Aufgaben lernen Sie einige gängige Konfigurationsmuster für Microservices kennen und stellen einen Dienst mithilfe dieser Muster bereit und konfigurieren ihn:
- In „Herausforderung 1“ und „Herausforderung 2“ untersuchen Sie das erste Muster. Dabei geht es darum, wo Sie die Konfiguration für eine Microservices-Anwendung ablegen. Dafür gibt es drei typische Speicherorte:
- Der Anwendungscode
- Das Bereitstellungsskript für die Anwendung
- Externe Quellen, auf die das Bereitstellungsskript zugreift
- In Challenge 3 richten Sie zwei weitere Muster ein: Sie machen die App über NGINX als Reverse-Proxy von außen zugänglich und aktivieren die Serviceerkennung mit Consul.
- In Challenge 4 implementieren Sie das letzte Muster: Sie verwenden eine Instanz Ihres Microservices als „Job Runner“, der einmalig eine von seiner üblichen Funktion abweichende Aktion ausführt (in diesem Fall die Emulation einer Datenbankmigration).
In diesem Tutorial kommen vier Technologien zum Einsatz:
- messenger – Eine einfache Chat-API mit Funktionen zum Speichern von Nachrichten, die für dieses Tutorial erstellt wurde
- NGINX Open Source – Ein Einstiegspunkt für den messenger-Dienst und das Gesamtsystem
- Consul – Eine dynamische Dienstregistrierung und ein Schlüssel-Wert-Speicher
- RabbitMQ – Ein beliebter Open-Source-Message-Broker, der Diensten die asynchrone Kommunikation ermöglicht

Sehen Sie sich dieses Video an, um einen Überblick über das Tutorial zu erhalten. Die gezeigten Schritte stimmen zwar nicht genau mit diesem Beitrag überein, helfen aber dabei, die Konzepte zu verstehen.
 
Voraussetzungen und Einrichtung
Voraussetzungen
Um das Tutorial in Ihrer eigenen Umgebung durchzuführen, benötigen Sie:
- Eine Linux-/Unix-kompatible Umgebung
- Grundkenntnisse der Linux-Befehlszeile, JavaScript und
bash(der gesamte Code und alle Befehle werden jedoch bereitgestellt und erklärt, sodass Sie auch mit begrenzten Vorkenntnissen erfolgreich sein können) - Docker und Docker Compose
- Node.js 19.x oder höher
- Wir haben Version 19.x getestet, gehen aber davon aus, dass auch neuere Versionen von Node.js funktionieren.
- Ausführliche Informationen zur Installation von Node.js finden Sie in der README im Repository des messenger-Dienstes. Sie können auch
asdfinstallieren, um exakt dieselbe Node.js-Version zu verwenden, die in den Containern zum Einsatz kommt.
curl(auf den meisten Systemen bereits installiert)- Die vier unter „Tutorial-Übersicht“ aufgeführten Technologien: messenger (wird im nächsten Abschnitt heruntergeladen), NGINX Open Source, Consul und RabbitMQ.
Einrichtung
- Starten Sie eine Terminalsitzung (in den folgenden Anweisungen wird diese als „App-Terminal“ bezeichnet).
- Erstellen Sie in Ihrem Home-Verzeichnis das Verzeichnis microservices-march und klonen Sie die GitHub-Repositorys für dieses Tutorial dorthin. (Sie können auch einen anderen Verzeichnisnamen verwenden und die Anweisungen entsprechend anpassen.) Hinweis: Im gesamten Tutorial wird die Eingabeaufforderung der Linux-Befehlszeile weggelassen, damit Sie die Befehle leichter kopieren und in Ihr Terminal einfügen können. Die Tilde (
~) steht für Ihr Home-Verzeichnis.
mkdir ~/microservices-marchcd ~/microservices-march git clone https://github.com/microservices-march/platform.git --branch mm23-twelve-factor-start git clone https://github.com/microservices-march/messenger.git --branch mm23-twelve-factor-start- Wechseln Sie zum platform-Repository und starten Sie Docker Compose:
cd platformdocker compose up -d --build- Dadurch werden sowohl RabbitMQ als auch Consul gestartet, die in den nachfolgenden Aufgaben verwendet werden.
- Das
-d-Flag weist Docker Compose an, die Verbindung zu den Containern zu trennen, sobald sie gestartet wurden (andernfalls bleiben die Container mit Ihrem Terminal verbunden). - Das
--build-Flag weist Docker Compose an, beim Start alle Images neu zu erstellen. Dadurch wird sichergestellt, dass die ausgeführten Images bei möglichen Änderungen an Dateien auf dem neuesten Stand bleiben.
- Das
- Wechseln Sie zum messenger-Repository und starten Sie Docker Compose:
cd ../messengerdocker compose up -d --build- Dadurch wird die PostgreSQL-Datenbank für den Dienst messenger gestartet, die wir im weiteren Verlauf des Tutorials als messenger-database bezeichnen.
Aufgabe 1: Konfiguration von Microservices auf Anwendungsebene definieren
In dieser Aufgabe richten Sie die Konfiguration am ersten der drei Orte ein, die wir uns in diesem Tutorial ansehen: auf Anwendungsebene. (Aufgabe 2 veranschaulicht den zweiten und dritten Ort: Bereitstellungsskripte und externe Quellen.)
Die Twelve-Factor-App schließt Konfiguration auf Anwendungsebene ausdrücklich aus, da sich eine solche Konfiguration nicht zwischen verschiedenen Bereitstellungsumgebungen ändern muss (die in der Twelve-Factor-App als „Deploys“ bezeichnet werden). Der Vollständigkeit halber behandeln wir dennoch alle drei Arten, denn jede Kategorie erfordert bei der Entwicklung, Erstellung und Bereitstellung eines Dienstes eine andere Vorgehensweise.
Der messenger-Dienst ist in Node.js geschrieben. Der Einstiegspunkt befindet sich in app/index.mjs im messenger-Repository. Diese Zeile der Datei:
app.use(express.json());ist ein Beispiel für eine Konfiguration auf Anwendungsebene. Dadurch wird das Express-Framework so konfiguriert, dass Anfragetexte vom Typ application/json in JavaScript-Objekte deserialisiert werden.
Diese Logik ist eng mit Ihrem Anwendungscode verknüpft und entspricht nicht dem, was die Zwölf-Faktoren-App unter „Konfiguration“ versteht. Allerdings kommt es in der Softwareentwicklung immer auf die jeweilige Situation an, nicht wahr?
In den nächsten beiden Abschnitten ändern Sie diese Zeile, um zwei Beispiele für die Konfiguration auf Anwendungsebene zu implementieren.
Beispiel 1
In diesem Beispiel legen Sie die maximale Größe eines Anfragetexts fest, den der Dienst messenger akzeptiert. Diese Größenbeschränkung wird durch das Argument limit der Funktion express.json festgelegt, wie in der Express-API-Dokumentation beschrieben. Hier fügen Sie das Argument limit zur Konfiguration der oben beschriebenen JSON-Middleware des Express-Frameworks hinzu.
- Öffnen Sie in Ihrem bevorzugten Texteditor die Datei „app/index.mjs“ und ersetzen Sie Folgendes:
app.use(express.json())- mit:
app.use(express.json({ limit: "20b" }));- Wechseln Sie im App-Terminal (das Sie unter „Einrichtung“ verwendet haben) in das Verzeichnis „app“ und starten Sie den Dienst „messenger“:
cd app npm install node index.mjs
messenger_service listening on port 4000- Starten Sie eine zweite, separate Terminalsitzung (die in den folgenden Anweisungen als „Client-Terminal“ bezeichnet wird) und senden Sie eine
POST-Anfrage an den Dienst messenger. Die Fehlermeldung zeigt an, dass die Anfrage erfolgreich verarbeitet wurde, da der Anfragekörper unter dem in Schritt 1 festgelegten Limit von 20 Byte lag, der Inhalt der JSON-Nutzlast jedoch fehlerhaft ist:
curl -d '{ "text": "hello" }' -H "Content-Type: application/json" -X POST http://localhost:4000/conversations...
{ "error": "Conversation must have 2 unique users" }- Senden Sie einen etwas längeren Nachrichtentext (wieder im Client-Terminal). Die Ausgabe ist deutlich umfangreicher als in Schritt 3 und enthält unter anderem eine Fehlermeldung, die darauf hinweist, dass der Anfragetext diesmal größer als 20 Byte ist:
curl -d '{ "text": "hello, world" }' -H "Content-Type: application/json" -X POST http://localhost:4000/conversations...
\”PayloadTooLargeError: request entity too large"Beispiel 2
In diesem Beispiel wird convict verwendet, eine Bibliothek, mit der Sie ein vollständiges Konfigurationsschema in einer einzigen Datei definieren können. Außerdem veranschaulicht es zwei Leitlinien aus Faktor 3 der Zwölf-Faktoren-App:
- Konfiguration in Umgebungsvariablen speichern – Sie ändern die App so, dass die maximale Größe des Nachrichtentexts über eine Umgebungsvariable (
JSON_BODY_LIMIT) festgelegt wird, anstatt sie im App-Code fest zu codieren. - Definieren Sie Ihre Servicekonfiguration eindeutig – Dies ist eine Anpassung von Faktor 3 für Microservices. Falls Ihnen dieses Konzept nicht vertraut ist, empfehlen wir Ihnen, sich einen Moment Zeit zu nehmen und den folgenden Beitrag zu lesen: „Best Practices für die Konfiguration von Microservices-Apps“ in unserem Blog.
Das Beispiel richtet außerdem einige grundlegende Strukturen ein, die Sie in Aufgabe 2 nutzen werden: Das Bereitstellungsskript messenger, das Sie in dieser Aufgabe erstellen, legt die Umgebungsvariable JSON_BODY_LIMIT fest, die Sie hier in den Anwendungscode einfügen. Dies veranschaulicht eine in einem Bereitstellungsskript festgelegte Konfiguration.
- Öffnen Sie die
convict-Konfigurationsdatei app/config/config.mjs und fügen Sie nach dem SchlüsselamqpportFolgendes als neuen Schlüssel hinzu:
jsonBodyLimit: { doc: `The max size (with unit included) that will be parsed by the
JSON middleware. Unit parsing is done by the
https://www.npmjs.com/package/bytes library.
ex: "100kb"`,
format: String,
default: null,
env: "JSON_BODY_LIMIT",
},- Die Bibliothek
convictübernimmt das Parsen der UmgebungsvariableJSON_BODY_LIMIT, wenn Sie damit in Schritt 3 unten die maximale Body-Größe über die Befehlszeile festlegen:- Ruft den Wert aus der richtigen Umgebungsvariable ab
- Überprüft den Typ der Variablen (
String) - Ermöglicht den Zugriff darauf in der Anwendung über den Schlüssel „
jsonBodyLimit“
- In app/index.mjs ersetzen:
app.use(express.json({ limit: "20b" }));- mit
app.use(express.json({ limit: config.get("jsonBodyLimit") }));- Drücken Sie im App-Terminal (in dem Sie den Dienst messenger in Schritt 2 von Beispiel 1 gestartet haben)
Ctrl+c, um den Dienst zu beenden. Starten Sie ihn anschließend erneut und legen Sie dabei mit der UmgebungsvariablenJSON_BODY_LIMITdie maximale Größe des Bodys auf 27 Byte fest:
^cJSON_BODY_LIMIT=27b node index.mjs- Dies ist ein Beispiel dafür, wie Sie die Konfigurationsmethode ändern können, wenn dies für Ihren Anwendungsfall sinnvoll ist: Statt einen Wert (in diesem Fall eine Größenbeschränkung) im Anwendungscode fest zu codieren, legen Sie ihn nun – wie von der Zwölf-Faktoren-App empfohlen – über eine Umgebungsvariable fest. Wie oben erwähnt, wird in Aufgabe 2 die Verwendung der Umgebungsvariable
JSON_BODY_LIMITzu einem Beispiel für den zweiten Konfigurationsort, wenn Sie die Umgebungsvariable über das Bereitstellungsskript des Dienstes messenger festlegen, anstatt sie in der Befehlszeile anzugeben. - Wiederholen Sie im Client-Terminal den Befehl „
curl“ aus Schritt 4 von Beispiel 1 (mit dem größeren Anfragetext). Da Sie die Größenbeschränkung nun auf 27 Byte erhöht haben, überschreitet der Anfragetext die Beschränkung nicht mehr. Sie erhalten daher eine Fehlermeldung, die darauf hinweist, dass die Anfrage verarbeitet wurde, der Inhalt der JSON-Nutzlast jedoch fehlerhaft ist:
curl -d '{ "text": "hello, world" }' -H "Content-Type: application/json" -X POST http://localhost:4000/conversations{ "error": "Conversation must have 2 unique users" }- Sie können das Client-Terminal schließen, wenn Sie möchten. Alle weiteren Befehle in diesem Tutorial führen Sie im App-Terminal aus.
- Drücken Sie im App-Terminal
Ctrl+c, um den messenger-Dienst zu beenden (Sie haben den Dienst oben in Schritt 3 in diesem Terminal beendet und neu gestartet).
^c- Stoppen Sie die messenger-database. Sie können die angezeigte Fehlermeldung bedenkenlos ignorieren, da das Netzwerk weiterhin von den im Repository platform definierten Infrastrukturelementen verwendet wird. Führen Sie diesen Befehl im Stammverzeichnis des Repositorys messenger aus.
docker compose down
...failed to remove network mm_2023....Aufgabe 2: Bereitstellungsskripte für einen Dienst erstellen
„Die Konfiguration sollte strikt vom Code getrennt sein (wie könnte sie sonst zwischen Bereitstellungen variieren?)“ – Aus Faktor 3 der Zwölf-Faktoren-App
Auf den ersten Blick könnte man dies als Aufforderung verstehen, Konfigurationen nicht in die Versionsverwaltung einzuchecken. In dieser Aufgabe implementieren Sie ein gängiges Muster für Microservices-Umgebungen, das scheinbar gegen diese Regel verstößt, sie in Wirklichkeit jedoch einhält und zugleich wertvolle Prozessverbesserungen ermöglicht, die für Microservices-Umgebungen von entscheidender Bedeutung sind.
In dieser Aufgabe erstellen Sie Bereitstellungsskripte, die die Funktionalität von Infrastructure-as-Code und Bereitstellungsmanifesten nachbilden, mit denen ein Microservice konfiguriert wird. Anschließend ändern Sie die Skripte, sodass sie externe Konfigurationsquellen verwenden, legen ein Geheimnis fest und führen die Skripte aus, um Dienste und deren Infrastruktur bereitzustellen.
Sie erstellen die Bereitstellungsskripte in einem neu angelegten infrastructure-Verzeichnis im messenger-Repository. Ein Verzeichnis namens infrastructure (oder eine Variante dieses Namens) ist ein gängiges Muster in modernen Microservice-Architekturen und dient beispielsweise zum Speichern folgender Elemente:
- Infrastructure as Code (zum Beispiel Terraform, AWS CloudFormation, Google Cloud Deployment Manager, und Azure Resource Manager)
- Konfiguration für das Container-Orchestrierungssystem (zum Beispiel Helm-Charts und Kubernetes-Manifeste)
- Alle weiteren Dateien, die mit der Bereitstellung von Anwendungen zusammenhängen
Zu den Vorteilen dieses Musters gehören:
- Dadurch wird die Verantwortung für die Bereitstellung des Dienstes und der dienstspezifischen Infrastruktur (z. B. Datenbanken) dem Team übertragen, das für den Dienst zuständig ist.
- Das Team kann sicherstellen, dass Änderungen an all diesen Elementen den eigenen Entwicklungsprozess durchlaufen (Code-Review, CI usw.).
- Das Team kann problemlos Änderungen an der Bereitstellung des Dienstes und der unterstützenden Infrastruktur vornehmen, ohne darauf angewiesen zu sein, dass externe Teams die Arbeiten für das Team übernehmen.
Wie bereits erwähnt, soll das Tutorial nicht zeigen, wie ein reales System eingerichtet wird. Auch die Skripte, die Sie in dieser Aufgabe bereitstellen, entsprechen keinem echten Produktionssystem. Vielmehr veranschaulichen sie einige grundlegende Konzepte und Probleme, die bei der Bereitstellung von Infrastruktur für Microservices durch werkzeugspezifische Konfiguration gelöst werden. Gleichzeitig sind die Skripte so abstrahiert, dass möglichst wenige konkrete Tools vorausgesetzt werden.
Erste Bereitstellungsskripte erstellen
- Erstellen Sie im App-Terminal ein Verzeichnis namens infrastructure im Stammverzeichnis des messenger-Repositorys und erstellen Sie darin Dateien für die Bereitstellungsskripte des messenger-Dienstes und der messenger-database. Je nach Umgebung müssen Sie den
chmod-Befehlen möglicherweisesudovoranstellen:
mkdir infrastructurecd infrastructure touch messenger-deploy.sh chmod +x messenger-deploy.sh touch messenger-db-deploy.sh chmod +x messenger-db-deploy.sh- Öffnen Sie in Ihrem bevorzugten Texteditor die Datei messenger-deploy.sh und fügen Sie Folgendes hinzu, um ein erstes Bereitstellungsskript für den Dienst messenger zu erstellen:
#!/bin/bashset -e
JSON_BODY_LIMIT=20b
docker run \
--rm \
-e JSON_BODY_LIMIT="${JSON_BODY_LIMIT}" \
messengerDieses Skript ist zu diesem Zeitpunkt noch nicht vollständig, veranschaulicht aber einige Konzepte:
- Es weist Umgebungsvariablen einen Wert zu, indem diese Konfiguration direkt in das Bereitstellungsskript aufgenommen wird.
- Dabei wird das
-e-Flag des Befehlsdockerrunverwendet, um zur Laufzeit Umgebungsvariablen in den Container einzufügen.
Es mag überflüssig erscheinen, die Werte von Umgebungsvariablen auf diese Weise festzulegen. Doch so können Sie – unabhängig davon, wie komplex dieses Bereitstellungsskript wird – mit einem kurzen Blick an den Anfang des Skripts erkennen, wie die Konfigurationsdaten für die Bereitstellung bereitgestellt werden.
Obwohl ein echtes Bereitstellungsskript den Befehl docker run möglicherweise nicht explizit aufruft, soll dieses Beispielskript außerdem die grundlegenden Probleme veranschaulichen, die etwa durch ein Kubernetes-Manifest gelöst werden. Bei Verwendung eines Container-Orchestrierungssystems wie Kubernetes startet eine Bereitstellung einen Container, dem die aus Ihren Kubernetes-Konfigurationsdateien abgeleitete Anwendungskonfiguration zur Verfügung gestellt wird. Daher können wir diese Beispiel-Bereitstellungsdatei als Minimalversion eines Bereitstellungsskripts betrachten, das dieselbe Funktion erfüllt wie Framework-spezifische Bereitstellungsdateien, beispielsweise Kubernetes-Manifeste.
In einer realen Entwicklungsumgebung würden Sie diese Datei möglicherweise in die Versionsverwaltung einchecken und einem Code-Review unterziehen. Dadurch erhält der Rest Ihres Teams die Möglichkeit, Ihre Einstellungen zu kommentieren, was wiederum dazu beiträgt, Vorfälle zu vermeiden, bei denen falsch konfigurierte Werte zu unerwartetem Verhalten führen. In diesem Screenshot weist beispielsweise ein Teammitglied zu Recht darauf hin, dass eine Begrenzung eingehender JSON-Anfragekörper auf 20 Byte (festgelegt mit JSON_BODY_LIMIT) zu niedrig ist.

Bereitstellungsskripte so anpassen, dass sie Konfigurationswerte aus externen Quellen abrufen
In diesem Teil der Aufgabe richten Sie den dritten Speicherort für die Konfiguration eines Microservices ein: eine externe Quelle, die zum Zeitpunkt der Bereitstellung abgefragt wird. Werte dynamisch zu registrieren und sie zum Zeitpunkt der Bereitstellung aus einer externen Quelle abzurufen, ist wesentlich besser, als Werte fest im Code zu hinterlegen, da diese ständig aktualisiert werden müssen und Fehler verursachen können. Weitere Informationen finden Sie im Beitrag „Best Practices für die Konfiguration von Microservices-Anwendungen“ in unserem Blog.
Zu diesem Zeitpunkt werden im Hintergrund zwei Infrastrukturkomponenten ausgeführt, die die vom Dienst messenger benötigten Hilfsdienste bereitstellen:
- RabbitMQ, das in einer realen Bereitstellung dem Plattformteam untersteht (gestartet in Schritt 3 von „Einrichtung“)
- Die messenger-database, die in einer realen Bereitstellung von Ihrem Team verwaltet wird (gestartet in Schritt 4 von „Einrichtung“)
Das convict-Schema für den messenger-Dienst in app/config/config.mjs definiert die erforderlichen Umgebungsvariablen für diese externen Konfigurationselemente. In diesem Abschnitt richten Sie diese beiden Komponenten für die Bereitstellung der Konfiguration ein, indem Sie die Werte der Variablen an einem zentral zugänglichen Ort festlegen, sodass sie beim Bereitstellen des messenger-Dienstes abgefragt werden können.
Die erforderlichen Verbindungsinformationen für RabbitMQ und die messenger-database werden im Consul Key/Value (KV) Store hinterlegt, einem zentralen Speicherort, auf den alle Services bei ihrer Bereitstellung zugreifen können. Der Consul KV Store ist zwar kein üblicher Ort für diese Art von Daten, wird in diesem Tutorial jedoch der Einfachheit halber verwendet.
- Ersetzen Sie den Inhalt von infrastructure/messenger-deploy.sh (erstellt in Schritt 2 des vorherigen Abschnitts) durch Folgendes:
#!/bin/bashset -e
# This configuration requires a new commit to change
NODE_ENV=production
PORT=4000
JSON_BODY_LIMIT=100kb
# Postgres database configuration by pulling information from
# the system
POSTGRES_USER=$(curl -X GET http://localhost:8500/v1/kv/messenger-db-application-user?raw=true)
PGPORT=$(curl -X GET http://localhost:8500/v1/kv/messenger-db-port?raw=true)
PGHOST=$(curl -X GET http://localhost:8500/v1/kv/messenger-db-host?raw=true)
# RabbitMQ configuration by pulling from the system
AMQPHOST=$(curl -X GET http://localhost:8500/v1/kv/amqp-host?raw=true)
AMQPPORT=$(curl -X GET http://localhost:8500/v1/kv/amqp-port?raw=true)
docker run \
--rm \
-e NODE_ENV="${NODE_ENV}" \
-e PORT="${PORT}" \
-e JSON_BODY_LIMIT="${JSON_BODY_LIMIT}" \
-e PGUSER="${POSTGRES_USER}" \
-e PGPORT="${PGPORT}" \
-e PGHOST="${PGHOST}" \
-e AMQPPORT="${AMQPPORT}" \
-e AMQPHOST="${AMQPHOST}" \
messenger- Dieses Skript veranschaulicht zwei Arten der Konfiguration:
- Direkt im Bereitstellungsskript angegebene Konfiguration – Sie legt die Bereitstellungsumgebung (
NODE_ENV) und den Port (PORT) fest und ändertJSON_BODY_LIMITauf 100 KB, einen realistischeren Wert als 20 Byte. - Aus externen Quellen abgerufene Konfiguration – Ruft die Werte der Umgebungsvariablen
POSTGRES_USER,PGPORT,PGHOST,AMQPHOSTundAMQPPORTaus dem Consul-KV-Speicher ab. Die Werte der Umgebungsvariablen legen Sie in den folgenden zwei Schritten im Consul-KV-Speicher fest.
- Direkt im Bereitstellungsskript angegebene Konfiguration – Sie legt die Bereitstellungsumgebung (
- Öffnen Sie messenger-db-deploy.sh und fügen Sie Folgendes hinzu, um ein initiales Bereitstellungsskript für die messenger-database zu erstellen:
#!/bin/bashset -e
PORT=5432
POSTGRES_USER=postgres
docker run \
-d \
--rm \
--name messenger-db \
-v db-data:/var/lib/postgresql/data/pgdata \
-e POSTGRES_USER="${POSTGRES_USER}" \
-e POSTGRES_PASSWORD="${POSTGRES_PASSWORD}" \
-e PGPORT="${PORT}" \
-e PGDATA=/var/lib/postgresql/data/pgdata \
--network mm_2023 \
postgres:15.1
# Register details about the database with Consul
curl -X PUT http://localhost:8500/v1/kv/messenger-db-port \
-H "Content-Type: application/json" \
-d "${PORT}"
curl -X PUT http://localhost:8500/v1/kv/messenger-db-host \
-H "Content-Type: application/json" \
-d 'messenger-db' # This matches the "--name" flag above
# (the hostname)
curl -X PUT http://localhost:8500/v1/kv/messenger-db-application-user \
-H "Content-Type: application/json" \
-d "${POSTGRES_USER}"- Neben der Definition von Konfigurationsdaten, die vom messenger-Dienst zum Zeitpunkt der Bereitstellung abgefragt werden können, veranschaulicht das Skript dieselben beiden Konzepte wie das ursprüngliche Skript für den messenger-Dienst aus Erste Bereitstellungsskripte erstellen):
- Bestimmte Konfigurationseinstellungen werden direkt im Bereitstellungsskript angegeben. In diesem Fall werden damit der PostgreSQL-Datenbank der zu verwendende Port und der Benutzername des Standardbenutzers mitgeteilt.
- Docker wird mit dem Schalter „
-e“ ausgeführt, um zur Laufzeit Umgebungsvariablen in den Container einzufügen. Außerdem wird der Name des laufenden Containers auf „messenger-db“ festgelegt. Dieser wird zum Hostnamen der Datenbank in dem Docker-Netzwerk, das Sie beim Starten des Dienstes „platform“ in Schritt 2 von „Einrichtung“ erstellt haben.
- In einer realen Bereitstellung übernimmt normalerweise das Plattformteam (oder ein vergleichbares Team) die Bereitstellung und Wartung eines Dienstes wie RabbitMQ im platform-Repository, so wie Sie es für messenger-database im messenger-Repository tun. Das Plattformteam stellt anschließend sicher, dass Dienste, die von dieser Infrastruktur abhängen, deren Standort ermitteln können. Legen Sie für dieses Tutorial die RabbitMQ-Werte selbst fest:
curl -X PUT --silent --output /dev/null --show-error --fail \ -H "Content-Type: application/json" \ -d "rabbitmq" \ http://localhost:8500/v1/kv/amqp-host curl -X PUT --silent --output /dev/null --show-error --fail \ -H "Content-Type: application/json" \ -d "5672" \ http://localhost:8500/v1/kv/amqp-port- (Sie fragen sich vielleicht, warum
amqpzur Definition von RabbitMQ-Variablen verwendet wird – das liegt daran, dass AMQP das von RabbitMQ verwendete Protokoll ist.)
Ein Secret in den Bereitstellungsskripten festlegen
In den Bereitstellungsskripten für den Dienst messenger fehlt nur noch eine (entscheidende) Information – das Passwort für die messenger-database!
Hinweis: Die Verwaltung von Geheimnissen steht nicht im Mittelpunkt dieses Tutorials. Der Einfachheit halber wird das Geheimnis daher in den Bereitstellungsdateien definiert. Tun Sie dies niemals in einer realen Umgebung – weder in der Entwicklungs-, Test- noch in der Produktionsumgebung –, da dadurch ein enormes Sicherheitsrisiko entsteht.
Weitere Informationen zur ordnungsgemäßen Verwaltung von Secrets finden Sie in Teil 2, Grundlagen der Verwaltung von Secrets in Microservices des Microservices March 2023. (Spoiler: Ein Tool zur Verwaltung von Secrets ist die einzige wirklich sichere Methode zum Speichern von Secrets.)
- Ersetzen Sie den Inhalt von infrastructure/messenger-db-deploy.sh durch Folgendes, um das Passwort-Secret für die messenger-database im Consul-KV-Store zu speichern:
#!/bin/bashset -e
PORT=5432
POSTGRES_USER=postgres
# NOTE: Never do this in a real-world deployment. Store passwords
# only in an encrypted secrets store.
POSTGRES_PASSWORD=postgres
docker run \
--rm \
--name messenger-db-primary \
-d \
-v db-data:/var/lib/postgresql/data/pgdata \
-e POSTGRES_USER="${POSTGRES_USER}" \
-e POSTGRES_PASSWORD="${POSTGRES_PASSWORD}" \
-e PGPORT="${PORT}" \
-e PGDATA=/var/lib/postgresql/data/pgdata \
--network mm_2023 \
postgres:15.1
echo "Register key messenger-db-port\n"
curl -X PUT --silent --output /dev/null --show-error --fail http://localhost:8500/v1/kv/messenger-db-port \
-H "Content-Type: application/json" \
-d "${PORT}"
echo "Register key messenger-db-host\n"
curl -X PUT --silent --output /dev/null --show-error --fail http://localhost:8500/v1/kv/messenger-db-host \
-H "Content-Type: application/json" \
-d 'messenger-db-primary' # This matches the "--name" flag above
# which for our setup means the hostname
echo "Register key messenger-db-application-user\n"
curl -X PUT --silent --output /dev/null --show-error --fail http://localhost:8500/v1/kv/messenger-db-application-user \
-H "Content-Type: application/json" \
-d "${POSTGRES_USER}"
echo "Register key messenger-db-password-never-do-this\n"
curl -X PUT --silent --output /dev/null --show-error --fail http://localhost:8500/v1/kv/messenger-db-password-never-do-this \
-H "Content-Type: application/json" \
-d "${POSTGRES_PASSWORD}"
printf "\nDone registering postgres details with Consul\n"- Ersetzen Sie den Inhalt von infrastructure/messenger-deploy.sh durch Folgendes, um das Passwort-Secret für messenger-database aus dem Consul-KV-Store abzurufen:
#!/bin/bashset -e
# This configuration requires a new commit to change
NODE_ENV=production
PORT=4000
JSON_BODY_LIMIT=100kb
# Postgres database configuration by pulling information from
# the system
POSTGRES_USER=$(curl -X GET http://localhost:8500/v1/kv/messenger-db-application-user?raw=true)
PGPORT=$(curl -X GET http://localhost:8500/v1/kv/messenger-db-port?raw=true)
PGHOST=$(curl -X GET http://localhost:8500/v1/kv/messenger-db-host?raw=true)
# NOTE: Never do this in a real-world deployment. Store passwords
# only in an encrypted secrets store.
PGPASSWORD=$(curl -X GET http://localhost:8500/v1/kv/messenger-db-password-never-do-this?raw=true)
# RabbitMQ configuration by pulling from the system
AMQPHOST=$(curl -X GET http://localhost:8500/v1/kv/amqp-host?raw=true)
AMQPPORT=$(curl -X GET http://localhost:8500/v1/kv/amqp-port?raw=true)
docker run \
--rm \
-d \
-e NODE_ENV="${NODE_ENV}" \
-e PORT="${PORT}" \
-e JSON_BODY_LIMIT="${JSON_BODY_LIMIT}" \
-e PGUSER="${POSTGRES_USER}" \
-e PGPORT="${PGPORT}" \
-e PGHOST="${PGHOST}" \
-e PGPASSWORD="${PGPASSWORD}" \
-e AMQPPORT="${AMQPPORT}" \
-e AMQPHOST="${AMQPHOST}" \
--network mm_2023 \
messengerBereitstellungsskripte ausführen
- Wechseln Sie in das Verzeichnis app im Repository messenger und erstellen Sie das Docker-Image für den Dienst messenger:
cd ../appdocker build -t messenger .- Überprüfen Sie, dass nur die Container ausgeführt werden, die zum Dienst „platform“ gehören:
docker ps --format '{{.Names}}'consul-server
consul-client
rabbitmq- Wechseln Sie in das Stammverzeichnis des messenger-Repositorys und stellen Sie messenger-database sowie den Dienst messenger bereit:
cd .../infrastructure/messenger-db-deploy.sh ./infrastructure/messenger-deploy.sh- Das Skript messenger-db-deploy.sh startet die messenger-database und registriert die entsprechenden Informationen im System (in diesem Fall im Consul-KV-Speicher). Das Skript messenger-deploy.sh startet anschließend die Anwendung und ruft die von messenger-db-deploy.sh registrierte Konfiguration aus dem System ab (wiederum aus dem Consul-KV-Speicher). Hinweis: Wenn ein Container nicht gestartet werden kann, entfernen Sie im Bereitstellungsskript den zweiten Parameter des Befehls
dockerrun(die Zeile-d\) und führen Sie das Skript erneut aus. Der Container wird dann im Vordergrund gestartet, sodass seine Protokolle im Terminal angezeigt werden und möglicherweise Aufschluss über das Problem geben. Nachdem Sie das Problem behoben haben, fügen Sie die Zeile-d\wieder ein, damit der eigentliche Container im Hintergrund ausgeführt wird. - Senden Sie eine einfache Anfrage zur Integritätsprüfung an die Anwendung, um zu überprüfen, ob die Bereitstellung erfolgreich war:
curl localhost:4000/healthcurl: (7) Failed to connect to localhost port 4000 after 11 ms: Connection refused- Hoppla, fehlgeschlagen! Wie sich herausstellt, fehlt noch eine entscheidende Konfiguration, und der Dienst messenger ist für das restliche System nicht erreichbar. Er läuft zwar problemlos im Netzwerk mm_2023, aber auf dieses Netzwerk kann nur innerhalb von Docker zugegriffen werden.
- Stoppen Sie den laufenden Container, um im nächsten Schritt ein neues Image zu erstellen:
docker rm $(docker stop $(docker ps -a -q --filter ancestor=messenger --format="{{.ID}}"))Herausforderung 3: Einen Dienst für die Außenwelt zugänglich machen
In einer Produktionsumgebung stellen Sie Dienste in der Regel nicht direkt bereit. Stattdessen folgen Sie einem gängigen Microservices-Muster und schalten Ihrem Hauptdienst einen Reverse-Proxy-Dienst vor.
In dieser Aufgabe machen Sie den Dienst messenger für die Außenwelt zugänglich, indem Sie die Diensterkennung einrichten: die Registrierung neuer Dienstinformationen und die dynamische Aktualisierung dieser Informationen, wenn andere Dienste darauf zugreifen. Dazu verwenden Sie die folgenden Technologien:
- Consul, eine dynamische Dienstregistrierung, und Consul Template, ein Tool zur dynamischen Aktualisierung einer Datei auf Grundlage von Consul-Daten
- NGINX Open Source als Reverse-Proxy und Load-Balancer, der einen zentralen Einstiegspunkt für Ihren Messenger-Dienst bereitstellt, der aus mehreren einzelnen, in Containern ausgeführten Instanzen der Anwendung besteht
Weitere Informationen zur Serviceerkennung finden Sie unter „Einen Service als Konfiguration verfügbar machen“ im Beitrag „Best Practices für die Konfiguration von Microservices-Anwendungen“ in unserem Blog.
Consul einrichten
Die Datei app/consul/index.mjs im messenger-Repository enthält den gesamten Code, der erforderlich ist, um den messenger-Dienst beim Start bei Consul zu registrieren und ihn beim ordnungsgemäßen Herunterfahren abzumelden. Sie stellt eine Funktion namens register bereit, die jeden neu bereitgestellten Dienst in der Dienstregistrierung von Consul registriert.
- Öffnen Sie in Ihrem bevorzugten Texteditor die Datei app/index.mjs und fügen Sie nach den anderen
import-Anweisungen das folgende Codefragment ein, um die Funktionregisteraus app/consul/index.mjs zu importieren:
import { register as registerConsul } from "./consul/index.mjs";- Ändern Sie dann wie gezeigt den Abschnitt
SERVERSTARTam Ende des Skripts, sodassregisterConsul()nach dem Start der Anwendung aufgerufen wird:
/* ================= SERVER START
================== */
app.listen(port, async () => {
console.log(`messenger_service listening on port ${port}`);
registerConsul();
});
export default app;- Öffnen Sie das
convict-Schema in app/config/config.mjs und fügen Sie nach dem SchlüsseljsonBodyLimit, den Sie in Schritt 1 von Beispiel 2 hinzugefügt haben, die folgenden Konfigurationswerte ein.
consulServiceName: { doc: "The name by which the service is registered in Consul. If not specified, the service is not registered",
format: "*",
default: null,
env: "CONSUL_SERVICE_NAME",
},
consulHost: {
doc: "The host where the Consul client runs",
format: String,
default: "consul-client",
env: "CONSUL_HOST",
},
consulPort: {
doc: "The port for the Consul client",
format: "port",
default: 8500,
env: "CONSUL_PORT",
},- Dadurch werden der Name, unter dem ein neuer Dienst registriert wird, sowie der Hostname und der Port für den Consul-Client festgelegt. Im nächsten Schritt passen Sie das Bereitstellungsskript für den messenger-Dienst an, um diese neuen Informationen zur Consul-Verbindung und Dienstregistrierung einzubeziehen.
- Öffnen Sie infrastructure/messenger-deploy.sh und ersetzen Sie den Inhalt durch Folgendes, um die im vorherigen Schritt festgelegten Consul-Verbindungs- und Dienstregistrierungsinformationen in die Konfiguration des Dienstes messenger aufzunehmen:
#!/bin/bashset -e
# This configuration requires a new commit to change
NODE_ENV=production
PORT=4000
JSON_BODY_LIMIT=100kb
CONSUL_SERVICE_NAME="messenger"
# Consul host and port are included in each host since we
# cannot query Consul until we know them
CONSUL_HOST="${CONSUL_HOST}"
CONSUL_PORT="${CONSUL_PORT}"
# Postgres database configuration by pulling information from
# the system
POSTGRES_USER=$(curl -X GET "http://localhost:8500/v1/kv/messenger-db-application-user?raw=true")
PGPORT=$(curl -X GET "http://localhost:8500/v1/kv/messenger-db-port?raw=true")
PGHOST=$(curl -X GET "http://localhost:8500/v1/kv/messenger-db-host?raw=true")
# NOTE: Never do this in a real-world deployment. Store passwords
# only in an encrypted secrets store.
PGPASSWORD=$(curl -X GET "http://localhost:8500/v1/kv/messenger-db-password-never-do-this?raw=true")
# RabbitMQ configuration by pulling from the system
AMQPHOST=$(curl -X GET "http://localhost:8500/v1/kv/amqp-host?raw=true")
AMQPPORT=$(curl -X GET "http://localhost:8500/v1/kv/amqp-port?raw=true")
docker run \
--rm \
-d \
-e NODE_ENV="${NODE_ENV}" \
-e PORT="${PORT}" \
-e JSON_BODY_LIMIT="${JSON_BODY_LIMIT}" \
-e PGUSER="${POSTGRES_USER}" \
-e PGPORT="${PGPORT}" \
-e PGHOST="${PGHOST}" \
-e PGPASSWORD="${PGPASSWORD}" \
-e AMQPPORT="${AMQPPORT}" \
-e AMQPHOST="${AMQPHOST}" \
-e CONSUL_HOST="${CONSUL_HOST}" \
-e CONSUL_PORT="${CONSUL_PORT}" \
-e CONSUL_SERVICE_NAME="${CONSUL_SERVICE_NAME}" \
--network mm_2023 \
messenger- Die wichtigsten Punkte sind: Hinweis: In einer realen Bereitstellung ist dies ein Beispiel für eine Konfiguration, die zwischen den Teams abgestimmt werden muss. Das für Consul zuständige Team muss die Umgebungsvariablen
CONSUL_HOSTundCONSUL_PORTin allen Umgebungen bereitstellen, da ein Dienst Consul ohne diese Verbindungsinformationen nicht abfragen kann.- Die Umgebungsvariable „
CONSUL_SERVICE_NAME“ teilt der Dienstinstanz messenger mit, unter welchem Namen sie sich bei Consul registrieren soll. - Die Umgebungsvariablen
CONSUL_HOSTundCONSUL_PORTsind für den Consul-Client bestimmt, der an dem Ort ausgeführt wird, an dem das Bereitstellungsskript läuft.
- Die Umgebungsvariable „
- Wechseln Sie im App-Terminal in das Verzeichnis „app“, beenden Sie alle laufenden Instanzen des Dienstes „messenger“ und erstellen Sie das Docker-Image neu, um den neuen Code für die Dienstregistrierung zu integrieren:
cd appdocker rm $(docker stop $(docker ps -a -q --filter ancestor=messenger --format="{{.ID}}")) docker build -t messenger .- Rufen Sie http://localhost:8500 in einem Browser auf, um die Consul-Benutzeroberfläche in Aktion zu sehen (auch wenn dort noch nichts Interessantes passiert).
- Führen Sie im Stammverzeichnis des messenger-Repositorys das Bereitstellungsskript aus, um eine Instanz des messenger-Dienstes zu starten:
CONSUL_HOST=consul-client CONSUL_PORT=8500 ./infrastructure/messenger-deploy.sh- Klicken Sie in der Consul-Benutzeroberfläche im Browser in der Kopfzeile auf „Services“, um zu überprüfen, ob genau ein „messenger“-Dienst ausgeführt wird.

- Führen Sie das Bereitstellungsskript noch einige Male aus, um weitere Instanzen des Dienstes messenger zu starten. Überprüfen Sie in der Consul-Benutzeroberfläche, ob sie ausgeführt werden.
CONSUL_HOST=consul-client CONSUL_PORT=8500 ./infrastructure/messenger-deploy.shNGINX einrichten
Im nächsten Schritt wird NGINX Open Source als Reverse-Proxy und Load Balancer hinzugefügt, um eingehenden Datenverkehr an alle laufenden messenger-Instanzen weiterzuleiten.
- Wechseln Sie im App-Terminal in das Stammverzeichnis des Repositorys „messenger“ und erstellen Sie ein Verzeichnis namens „load-balancer“ sowie drei Dateien:
mkdir load-balancercd load-balancer touch nginx.ctmpl touch consul-template-config.hcl touch Dockerfile- Die Dockerfile definiert den Container, in dem NGINX und Consul Template ausgeführt werden. Consul Template verwendet die beiden anderen Dateien, um die NGINX-Upstreams dynamisch zu aktualisieren, wenn sich der messenger-Dienst in der Dienstregistrierung ändert (Dienstinstanzen werden gestartet oder beendet).
- Öffnen Sie die in Schritt 1 erstellte Datei „nginx.ctmpl“ und fügen Sie das folgende NGINX-Konfigurationsfragment hinzu, das Consul Template verwendet, um die NGINX-Upstream-Gruppe dynamisch zu aktualisieren:
upstream messenger_service { {{- range service "messenger" }}
server {{ .Address }}:{{ .Port }};
{{- end }}
}
server {
listen 8085;
server_name localhost;
location / {
proxy_pass http://messenger_service;
add_header Upstream-Host $upstream_addr;
}
}- Dieses Snippet fügt die IP-Adresse und Portnummer jeder bei Consul registrierten Instanz des Dienstes messenger zur NGINX-Upstream-Gruppe messenger_service hinzu. NGINX leitet eingehende Anfragen an die dynamisch definierte Gruppe von Upstream-Dienstinstanzen weiter.
- Öffnen Sie die in Schritt 1 erstellte Datei „consul-template-config.hcl“ und fügen Sie die folgende Konfiguration hinzu:
consul { address = "consul-client:8500"
retry {
enabled = true
attempts = 12
backoff = "250ms"
}
}
template {
source = "/usr/templates/nginx.ctmpl"
destination = "/etc/nginx/conf.d/default.conf"
perms = 0600
command = "if [ -e /var/run/nginx.pid ]; then nginx -s reload; else nginx; fi"
}- Diese Konfiguration für Consul Template weist das Tool an, das
source-Template (das im vorherigen Schritt erstellte NGINX-Konfigurationsfragment) neu zu rendern, am angegebenendestinationabzulegen und schließlich den angegebenencommandauszuführen (der NGINX anweist, seine Konfiguration neu zu laden). In der Praxis bedeutet dies, dass jedes Mal eine neue default.conf-Datei erstellt wird, wenn eine Serviceinstanz in Consul registriert, aktualisiert oder abgemeldet wird. NGINX lädt seine Konfiguration anschließend ohne Ausfallzeit neu. Dadurch wird sichergestellt, dass NGINX über eine aktuelle, fehlerfreie Gruppe von Servern (messenger-Serviceinstanzen) verfügt, an die es Datenverkehr weiterleiten kann. - Öffnen Sie die in Schritt 1 erstellte Datei Dockerfile und fügen Sie den folgenden Inhalt hinzu, mit dem der NGINX-Dienst erstellt wird. (Für dieses Tutorial müssen Sie das Dockerfile nicht verstehen; der Code ist jedoch zu Ihrer Unterstützung direkt mit Kommentaren versehen.)
FROM nginx:1.23.1
ARG CONSUL_TEMPLATE_VERSION=0.30.0
# Set an environment variable for the location of the Consul
# cluster. By default, it tries to resolve to consul-client:8500
# which is the behavior if Consul is running as a container in the
# same host and linked to this NGINX container (with the alias
# consul, of course). But this environment variable can also be
# overridden as the container starts if we want to resolve to
# another address.
ENV CONSUL_URL consul-client:8500
# Download the specified version of Consul template
ADD https://releases.hashicorp.com/consul-template/${CONSUL_TEMPLATE_VERSION}/consul-template_${CONSUL_TEMPLATE_VERSION}_linux_amd64.zip /tmp
RUN apt-get update \
&& apt-get install -y --no-install-recommends dumb-init unzip \
&& unzip /tmp/consul-template_${CONSUL_TEMPLATE_VERSION}_linux_amd64.zip -d /usr/local/bin \
&& rm -rf /tmp/consul-template_${CONSUL_TEMPLATE_VERSION}_linux_amd64.zip
COPY consul-template-config.hcl ./consul-template-config.hcl
COPY nginx.ctmpl /usr/templates/nginx.ctmpl
EXPOSE 8085
STOPSIGNAL SIGQUIT
CMD ["dumb-init", "consul-template", "-config=consul-template-config.hcl"]- Erstellen Sie ein Docker-Image:
docker build -t messenger-lb .- Wechseln Sie in das Stammverzeichnis des Verzeichnisses messenger und erstellen Sie eine Datei namens messenger-load-balancer-deploy.sh als Bereitstellungsdatei für den NGINX-Dienst (genau wie bei den übrigen Diensten, die Sie im Laufe des Tutorials bereitgestellt haben). Je nach Umgebung müssen Sie dem Befehl
chmodmöglicherweisesudovoranstellen:
cd .. touch infrastructure/messenger-load-balancer-deploy.sh chmod +x infrastructure/messenger-load-balancer-deploy.sh- Öffnen Sie „messenger-load-balancer-deploy.sh“ und fügen Sie den folgenden Inhalt hinzu:
#!/bin/bashset -e
# Consul host and port are included in each host since we
# cannot query Consul until we know them
CONSUL_HOST="${CONSUL_HOST}"
CONSUL_PORT="${CONSUL_PORT}"
docker run \
--rm \
-d \
--name messenger-lb \
-e CONSUL_URL="${CONSUL_HOST}:${CONSUL_PORT}" \
-p 8085:8085 \
--network mm_2023 \
messenger-lb- Nachdem Sie nun alles eingerichtet haben, stellen Sie den NGINX-Dienst bereit:
CONSUL_HOST=consul-client CONSUL_PORT=8500 ./infrastructure/messenger-load-balancer-deploy.sh- Prüfen Sie, ob Sie von extern auf den messenger-Dienst zugreifen können:
curl -X GET http://localhost:8085/health
OK- Es funktioniert! NGINX verteilt die Last jetzt auf alle erstellten Instanzen des Dienstes messenger. Das erkennen Sie daran, dass der Header
X-Forwarded-Fordieselben IP-Adressen des Dienstes messenger anzeigt wie die Consul-Benutzeroberfläche in Schritt 8 des vorherigen Abschnitts.
Aufgabe 4: Migrieren einer Datenbank mithilfe eines Dienstes als Job-Runner
Große Anwendungen verwenden häufig „Job Runner“ mit kleinen Worker-Prozessen, die einmalige Aufgaben wie das Ändern von Daten ausführen können (Beispiele hierfür sind Sidekiq und Celery). Diese Tools benötigen häufig zusätzliche unterstützende Infrastruktur wie Redis oder RabbitMQ. In diesem Fall verwenden Sie den Dienst messenger selbst als „Job Runner“, um einmalige Aufgaben auszuführen. Das ist sinnvoll, da er bereits sehr klein ist, uneingeschränkt mit der Datenbank und anderen Infrastrukturkomponenten interagieren kann, von denen er abhängig ist, und vollständig getrennt von der Anwendung ausgeführt wird, die den Datenverkehr verarbeitet.
Dieses Vorgehen bietet drei Vorteile:
- Der Job-Runner (einschließlich der von ihm ausgeführten Skripte) durchläuft genau dieselben Prüfungen und denselben Review-Prozess wie der Produktionsdienst.
- Konfigurationswerte wie etwa Datenbankbenutzer lassen sich problemlos ändern, um die Produktionsbereitstellung sicherer zu gestalten. Beispielsweise können Sie den Produktionsdienst mit einem Benutzer mit geringen Berechtigungen ausführen, der nur in vorhandene Tabellen schreiben und diese abfragen kann. Sie können eine andere Dienstinstanz so konfigurieren, dass sie Änderungen an der Datenbankstruktur mit einem höher privilegierten Benutzer vornimmt, der Tabellen erstellen und entfernen kann.
- Einige Teams führen Jobs auf Instanzen aus, die gleichzeitig auch den Produktivdatenverkehr des Dienstes verarbeiten. Das ist riskant, da Probleme mit dem Job die anderen Funktionen beeinträchtigen können, die von der Anwendung im Container ausgeführt werden. Genau solche Situationen zu vermeiden, ist doch überhaupt der Grund, warum wir auf Microservices setzen, oder?
In dieser Aufgabe untersuchen Sie, wie ein Artefakt durch die Änderung einiger Datenbankkonfigurationswerte für eine neue Rolle angepasst werden kann. Anschließend migrieren Sie die messenger-Datenbank, damit sie die neuen Werte verwendet, und testen ihre Leistung.
Messenger-Datenbank migrieren
Für eine reale Produktionsbereitstellung könnten Sie zwei separate Benutzer mit unterschiedlichen Berechtigungen erstellen: einen „Anwendungsbenutzer“ und einen „Migrationsbenutzer“. Der Einfachheit halber verwenden Sie in diesem Beispiel den Standardbenutzer als Anwendungsbenutzer und erstellen einen Migrationsbenutzer mit Superuser-Rechten. In einer realen Situation lohnt es sich, genauer zu prüfen, welche spezifischen Mindestberechtigungen die einzelnen Benutzer je nach ihrer Rolle benötigen.
- Erstellen Sie im App-Terminal einen neuen PostgreSQL-Benutzer mit Superuser-Berechtigungen:
echo "CREATE USER messenger_migrator WITH SUPERUSER PASSWORD 'migrator_password';" | docker exec -i messenger-db-primary psql -U postgres- Öffnen Sie das Datenbank-Bereitstellungsskript (infrastructure/messenger-db-deploy.sh) und ersetzen Sie dessen Inhalt, um die Anmeldedaten des neuen Benutzers hinzuzufügen. Hinweis: An dieser Stelle sei noch einmal ausdrücklich darauf hingewiesen: Bei einer realen Bereitstellung dürfen Sie Geheimnisse wie Datenbank-Anmeldedaten NIEMALS in einem Bereitstellungsskript oder an einem anderen Ort als in einem Tool zur Verwaltung von Geheimnissen speichern. Weitere Informationen finden Sie unter Einheit 2: Grundlagen der Geheimnisverwaltung für Microservices aus Microservices March 2023.
#!/bin/bash
set -e
PORT=5432
POSTGRES_USER=postgres
# NOTE: Never do this in a real-world deployment. Store passwords
# only in an encrypted secrets store.
# Because we’re focusing on other concepts in this tutorial, we
# set the password this way here for convenience.
POSTGRES_PASSWORD=postgres
# Migration user
POSTGRES_MIGRATOR_USER=messenger_migrator
# NOTE: As above, never do this in a real deployment.
POSTGRES_MIGRATOR_PASSWORD=migrator_password
docker run \
--rm \
--name messenger-db-primary \
-d \
-v db-data:/var/lib/postgresql/data/pgdata \
-e POSTGRES_USER="${POSTGRES_USER}" \
-e POSTGRES_PASSWORD="${POSTGRES_PASSWORD}" \
-e PGPORT="${PORT}" \
-e PGDATA=/var/lib/postgresql/data/pgdata \
--network mm_2023 \
postgres:15.1
echo "Register key messenger-db-port\n"
curl -X PUT --silent --output /dev/null --show-error --fail http://localhost:8500/v1/kv/messenger-db-port \
-H "Content-Type: application/json" \
-d "${PORT}"
echo "Register key messenger-db-host\n"
curl -X PUT --silent --output /dev/null --show-error --fail http://localhost:8500/v1/kv/messenger-db-host \
-H "Content-Type: application/json" \
-d 'messenger-db-primary' # This matches the "--name" flag above
# which for our setup means the hostname
echo "Register key messenger-db-application-user\n"
curl -X PUT --silent --output /dev/null --show-error --fail http://localhost:8500/v1/kv/messenger-db-application-user \
-H "Content-Type: application/json" \
-d "${POSTGRES_USER}"
curl -X PUT --silent --output /dev/null --show-error --fail http://localhost:8500/v1/kv/messenger-db-password-never-do-this \
-H "Content-Type: application/json" \
-d "${POSTGRES_PASSWORD}"
echo "Register key messenger-db-application-user\n"
curl -X PUT --silent --output /dev/null --show-error --fail http://localhost:8500/v1/kv/messenger-db-migrator-user \
-H "Content-Type: application/json" \
-d "${POSTGRES_MIGRATOR_USER}"
curl -X PUT --silent --output /dev/null --show-error --fail http://localhost:8500/v1/kv/messenger-db-migrator-password-never-do-this \
-H "Content-Type: application/json" \
-d "${POSTGRES_MIGRATOR_PASSWORD}"
printf "\nDone registering postgres details with Consul\n"- Durch diese Änderung wird lediglich der Migrationsbenutzer zu den Benutzern hinzugefügt, die nach der Bereitstellung der Datenbank in Consul hinterlegt werden.
- Erstellen Sie eine neue Datei im Verzeichnis infrastructure mit dem Namen messenger-db-migrator-deploy.sh (möglicherweise müssen Sie dem Befehl
chmodauch hiersudovoranstellen):
touch infrastructure/messenger-db-migrator-deploy.shchmod +x infrastructure/messenger-db-migrator-deploy.sh- Öffnen Sie „messenger-db-migrator-deploy.sh“ und fügen Sie Folgendes hinzu:
#!/bin/bashset -e
# This configuration requires a new commit to change
NODE_ENV=production
PORT=4000
JSON_BODY_LIMIT=100kb
CONSUL_SERVICE_NAME="messenger-migrator"
# Consul host and port are included in each host since we
# cannot query Consul until we know them
CONSUL_HOST="${CONSUL_HOST}"
CONSUL_PORT="${CONSUL_PORT}"
# Get the migrator user name and password
POSTGRES_USER=$(curl -X GET "http://localhost:8500/v1/kv/messenger-db-migrator-user?raw=true")
PGPORT=$(curl -X GET "http://localhost:8500/v1/kv/messenger-db-port?raw=true")
PGHOST=$(curl -X GET http://localhost:8500/v1/kv/messenger-db-host?raw=true)
# NOTE: Never do this in a real-world deployment. Store passwords
# only in an encrypted secrets store.
PGPASSWORD=$(curl -X GET "http://localhost:8500/v1/kv/messenger-db-migrator-password-never-do-this?raw=true")
# RabbitMQ configuration by pulling from the system
AMQPHOST=$(curl -X GET "http://localhost:8500/v1/kv/amqp-host?raw=true")
AMQPPORT=$(curl -X GET "http://localhost:8500/v1/kv/amqp-port?raw=true")
docker run \--rm \
-d \
--name messenger-migrator \
-e NODE_ENV="${NODE_ENV}" \
-e PORT="${PORT}" \
-e JSON_BODY_LIMIT="${JSON_BODY_LIMIT}" \
-e PGUSER="${POSTGRES_USER}" \
-e PGPORT="${PGPORT}" \
-e PGHOST="${PGHOST}" \
-e PGPASSWORD="${PGPASSWORD}" \
-e AMQPPORT="${AMQPPORT}" \
-e AMQPHOST="${AMQPHOST}" \
-e CONSUL_HOST="${CONSUL_HOST}" \
-e CONSUL_PORT="${CONSUL_PORT}" \
-e CONSUL_SERVICE_NAME="${CONSUL_SERVICE_NAME}" \
--network mm_2023 \
messenger- Dieses Skript ähnelt stark dem Skript infrastructure/messenger-deploy.sh in seiner endgültigen Form, das Sie in Schritt 3 von „Consul einrichten“ erstellt haben. Der Hauptunterschied besteht darin, dass
CONSUL_SERVICE_NAMEaufmessenger-migratorstatt aufmessengergesetzt ist undPGUSERdem oben in Schritt 1 erstellten „migrator“-Superuser entspricht. Es ist wichtig, dassCONSUL_SERVICE_NAMEaufmessenger-migratorgesetzt ist. Wäre der Wert aufmessengergesetzt, würde NGINX diesen Dienst automatisch in die Rotation zur Verarbeitung von API-Aufrufen aufnehmen, obwohl er keinerlei Datenverkehr verarbeiten soll. Das Skript stellt eine kurzlebige Instanz in der Rolle des Migrators bereit. Dadurch wird verhindert, dass sich Probleme bei der Migration auf die Verarbeitung des Datenverkehrs durch die Instanzen des Hauptdienstes messenger auswirken. - Stellen Sie die PostgreSQL-Datenbank erneut bereit. Da Sie in diesem Tutorial
bash-Skripte verwenden, müssen Sie den Datenbankdienst beenden und neu starten. In einer Produktionsanwendung führen Sie stattdessen in der Regel einfach ein Infrastructure-as-Code-Skript aus, um nur die geänderten Elemente hinzuzufügen.
docker stop messenger-db-primaryCONSUL_HOST=consul-client CONSUL_PORT=8500 ./infrastructure/messenger-db-deploy.sh- Stellen Sie den PostgreSQL-Datenbankmigrationsdienst bereit:
CONSUL_HOST=consul-client CONSUL_PORT=8500 ./infrastructure/messenger-db-migrator-deploy.sh- Überprüfen Sie, ob die Instanz wie erwartet ausgeführt wird:
docker ps --format "{{.Names}}"
...
messenger-migrator- Sie können außerdem in der Consul-Benutzeroberfläche überprüfen, ob sich der Datenbankmigrationsdienst ordnungsgemäß unter dem Namen messenger-migrator bei Consul registriert hat (auch hier erfolgt keine Registrierung unter dem Namen messenger, da der Dienst keinen Datenverkehr verarbeitet):

- Nun zum letzten Schritt – führen Sie die Datenbankmigrationsskripte aus! Diese Skripte ähneln zwar keinen echten Datenbankmigrationsskripten, verwenden aber den Dienst messenger-migrator, um datenbankspezifische Skripte auszuführen. Sobald die Datenbank migriert wurde, beenden Sie den Dienst messenger-migrator:
docker exec -i -e PGDATABASE=postgres -e CREATE_DB_NAME=messenger messenger-migrator node scripts/create-db.mjsdocker exec -i messenger-migrator node scripts/create-schema.mjs docker exec -i messenger-migrator node scripts/create-seed-data.mjs docker stop messenger-migratorDen messenger-Dienst in Aktion testen
Nachdem Sie die messenger-Datenbank nun in ihr endgültiges Format migriert haben, können Sie den messenger-Dienst endlich in Aktion erleben! Führen Sie dazu einige einfache curl-Abfragen an den NGINX-Dienst aus (Sie haben NGINX unter „NGINX einrichten“ als Einstiegspunkt des Systems konfiguriert).
Einige der folgenden Befehle verwenden die Bibliothek „jq“ zur Formatierung der JSON-Ausgabe. Sie können sie bei Bedarf installieren oder aus dem Befehl weglassen.
- Erstellen Sie eine Konversation:
curl -d '{"participant_ids": [1, 2]}' -H "Content-Type: application/json" -X POST 'http://localhost:8085/conversations'{
"conversation": { "id": "1", "inserted_at": "YYYY-MM-DDT06:41:59.000Z" }
}- Senden Sie eine Nachricht von einem Benutzer mit der ID 1 an die Unterhaltung:
curl -d '{"content": "This is the first message"}' -H "User-Id: 1" -H "Content-Type: application/json" -X POST 'http://localhost:8085/conversations/1/messages' | jq{
"message": {
"id": "1",
"content": "This is the first message",
"index": 1,
"user_id": 1,
"username": "James Blanderphone",
"conversation_id": 1,
"inserted_at": "YYYY-MM-DDT06:42:15.000Z"
}
}- Antworten Sie mit einer Nachricht von einem anderen Benutzer (mit der ID 2):
curl -d '{"content": "This is the second message"}' -H "User-Id: 2" -H "Content-Type: application/json" -X POST 'http://localhost:8085/conversations/1/messages' | jq{
"message": {
"id": "2",
"content": "This is the second message",
"index": 2,
"user_id": 2,
"username": "Normalavian Ropetoter",
"conversation_id": 1,
"inserted_at": "YYYY-MM-DDT06:42:25.000Z"
}
}- Nachrichten abrufen:
curl -X GET 'http://localhost:8085/conversations/1/messages' | jq{
"messages": [
{
"id": "1",
"content": "This is the first message",
"user_id": "1",
"channel_id": "1",
"index": "1",
"inserted_at": "YYYY-MM-DDT06:42:15.000Z",
"username": "James Blanderphone"
},
{
"id": "2",
"content": "This is the second message",
"user_id": "2",
"channel_id": "1",
"index": "2",
"inserted_at": "YYYY-MM-DDT06:42:25.000Z",
"username": "Normalavian Ropetoter"
}
]
}Aufräumen
Im Laufe dieses Tutorials haben Sie eine beträchtliche Anzahl von Containern und Images erstellt! Mit den folgenden Befehlen können Sie alle Docker-Container und -Images entfernen, die Sie nicht behalten möchten.
- So entfernen Sie alle laufenden Docker-Container:
docker rm $(docker stop $(docker ps -a -q --filter ancestor=messenger --format="{{.ID}}"))docker rm $(docker stop messenger-db-primary) docker rm $(docker stop messenger-lb)- So entfernen Sie die Plattform-Dienste:
# From the platform repositorydocker compose down- So entfernen Sie alle im Tutorial verwendeten Docker-Images:
docker rmi messengerdocker rmi messenger-lb docker rmi postgres:15.1 docker rmi hashicorp/consul:1.14.4 docker rmi rabbitmq:3.11.4-management-alpineNächste Schritte
Vielleicht denken Sie jetzt: „Das scheint ziemlich viel Aufwand zu sein, nur um etwas Einfaches einzurichten“ – und damit hätten Sie recht! Der Umstieg auf eine auf Microservices ausgerichtete Architektur erfordert große Sorgfalt bei der Strukturierung und Konfiguration der Services. Trotz all der Komplexität haben Sie bereits beachtliche Fortschritte erzielt:
- Sie haben eine auf Microservices ausgerichtete Konfiguration eingerichtet, die für andere Teams leicht verständlich ist.
- Sie richten das Microservices-System so ein, dass es sowohl hinsichtlich der Skalierung als auch der Nutzung der verschiedenen beteiligten Services eine gewisse Flexibilität bietet.
Wenn Sie Ihr Wissen über Microservices vertiefen möchten, sehen Sie sich den Microservices March 2023 an. Einheit 2, Grundlagen der Verwaltung von Geheimnissen in Microservices, bietet einen ausführlichen und zugleich leicht verständlichen Überblick über die Verwaltung von Geheimnissen in Microservice-Umgebungen.

Über den Autor
Ähnliche Blogbeiträge
Die Zukunft von NGINX: Zurück zu unseren Open-Source-Wurzeln
Beim NGINX Sprint 2022 besinnen wir uns auf unsere Open-Source-Wurzeln. Erfahren Sie mehr über die Initiativen, die wir im kommenden Jahr verfolgen, und teilen Sie uns mit, was Sie sich wünschen.

Ein Leitfaden zur Auswahl eines Ingress-Controllers, Teil 1: Ermitteln Sie Ihre Anforderungen
Ein Ingress-Controller kann eines der leistungsstärksten Tools in Ihrem Kubernetes-Stack sein. Erfahren Sie, wie Sie Ihre Ingress-Anforderungen ermitteln, um die beste Option auszuwählen.
Bereitstellung des NGINX Ingress Controllers auf Amazon EKS: So haben wir getestet
Wir haben kürzlich den Lösungsleitfaden zum NGINX Ingress Controller um Empfehlungen zur Dimensionierung für Amazon Elastic Kubernetes Service (EKS) ergänzt. In diesem Blogbeitrag erläutern wir, wie wir diese Empfehlungen erarbeitet haben, und stellen Ihnen alle Informationen zur Verfügung, die Sie benötigen, um selbst ähnliche Tests durchzuführen.

Protokollierung des Datenverkehrs zwischen NGINX und Upstream-Servern bei CDN77
Bei der Fehlersuche bei Datenverkehrsproblemen zwischen NGINX und Upstream-Servern konnte CDN77 die benötigten Daten nur aus dem Fehlerprotokoll mit aktivierter Debug-Ausgabe beziehen, was die Leistung beeinträchtigt und riesige Dateien erzeugt. Daher entwickelte das Team ein neues benutzerdefiniertes Modul, das ausschließlich die benötigten Daten protokolliert.
Ankündigung von NGINX Plus R16
NGINX Plus R16 ist eine einheitliche, elastische Ingress- und Egress-Schicht für Anwendungen, die die Funktionen eines Load Balancers, API-Gateways und einer WAF vereint und neue Features wie clusterfähige Ratenbegrenzung, Schlüssel-Wert-Speicher, „Random with Two Choices“, verbessertes UDP-Load-Balancing und vieles mehr bietet.
5 Gründe für den Umstieg auf softwarebasiertes Load Balancing
Entdecken Sie fünf Gründe für den Wechsel zu einem softwarebasierten Load Balancer wie NGINX Plus, darunter Kosteneinsparungen, die Einführung von DevOps und eine einheitliche Lösung für alle Umgebungen.
