Dein Microservice startet lokal nicht
Ein Microservice, der auf deiner Maschine nicht hochkommt, ist fast nie ein Fehler in deinem Code. Es ist eines von sechs Dingen, und die meiste verlorene Zeit geht dafür drauf, nicht zu wissen, welches der sechs. Diese Seite ordnet sie nach Häufigkeit und gibt die Prüfung, die jede in unter einer Minute ausschließt.
Die Reihenfolge zählt: schließe von außen nach innen aus
Die Versuchung ist, den Code zu öffnen. Das ist fast immer das Letzte, was man ansehen sollte, denn derselbe Commit startet in der gemeinsamen Umgebung. Was sich geändert hat, ist nicht der Service: es ist, was ihn umgibt.
| # | Ursache | Was du sehen wirst | Prüfung |
|---|---|---|---|
| 1 | Port schon belegt | address already in use, EADDRINUSE, oder der Prozess stirbt sofort |
lsof -i :8080 |
| 2 | Infrastruktur unten | connection refused Richtung 5432, 9092 oder 6379 |
docker ps |
| 3 | Zu früh gestartet | Mal startet er, mal nicht, mit demselben Code | Starte ihn von Hand neu; geht es jetzt, war es die Reihenfolge |
| 4 | Unvollständige Konfiguration | Scheitert beim Bauen eines Beans oder beim Lesen einer Property | Vergleiche die Variablen mit denen der gemeinsamen Umgebung |
| 5 | Datenbank ohne Schema | relation … does not exist, oder scheiternde Migrationen |
Verbinde dich von Hand und liste die Tabellen |
| 6 | Runtime-Version | UnsupportedClassVersionError, Syntax, die nicht kompiliert |
java -version, node -v |
1. Der Port ist schon belegt
Die häufigste Ursache und die, die sich am schnellsten ausschließen lässt. Alle Projekte derselben Technologie kommen mit demselben Standardport: 8080 auf der JVM, 3000 bei Node, 5432 bei Postgres. Es reicht, zwei Dinge offen zu haben.
# macOS und Linux
lsof -i :8080
# Windows
netstat -ano | findstr :8080
Taucht etwas auf, hast du den Schuldigen. Unangenehm ist nicht, ihn zu killen: unangenehm ist, dass es morgen mit einem anderen Port wieder passiert, und dass der Service, der ihn aufruft, weiter auf den alten zeigt.
2. Die Infrastruktur läuft nicht (oder ist nicht bereit)
docker ps und schau, ob Kafka, Redis und Postgres da sind. Aber Vorsicht mit
der Falle: „läuft“ ist nicht „bereit“. Ein Container hat ab der ersten
Sekunde eine PID, und das Postgres darin kann zwanzig weitere brauchen, um Verbindungen
anzunehmen. Startet dein Service in dieser Lücke, sieht er ein
connection refused und stirbt.
Genau das löst das depends_on von Compose nicht: es wartet auf den
Container, nicht auf den Service. Darum braucht es einen healthcheck und eine
service_healthy-Bedingung, und darum startet dieselbe Compose-Datei auf einer
schnellen Maschine und auf einer langsamen nicht.
3. Er startete vor dem, was er aufruft
Das verräterische Symptom: mal startet er und mal nicht, mit demselben Code. Das ist ein Race, kein Fehler. Dein Service registriert sich oder fordert beim Start etwas an, und wer es ihm geben soll, hat noch nicht geantwortet.
Ihn von Hand noch einmal zu starten bestätigt es in zehn Sekunden: geht es jetzt, war es nicht der Code.
4. Ihm fehlt Konfiguration, die ihm die Cloud gibt
In der gemeinsamen Umgebung kommen diese Variablen vom Deployment. Auf deiner Maschine setzt du sie, und das Problem ist nicht, sie zu setzen: es ist, zu wissen, welche. Die Liste lebt üblicherweise in einer Deployment-Datei, die nicht in deinem Repository liegt.
Man erkennt den Fehler daran, dass der Service beim Bauen stirbt, nicht beim Ausliefern: ein Bean, das nicht erzeugt werden kann, eine fehlende Pflicht-Property, ein Client, der seine Basis-URL nicht findet.
5. Die Datenbank existiert, ist aber leer
Container oben, Verbindung korrekt, und trotzdem relation "orders" does not
exist. Die Migrationen fehlen, oder sie liefen gegen eine andere Datenbank.
Die Regel, die man dabei besser nicht bricht: starte keine Migrationen gegen die
gemeinsame Umgebung. Ein versehentlich gegen die gemeinsame Datenbank abgefeuertes
flyway migrate ist ein verlorener Nachmittag für das ganze Team, nicht nur
für dich.
6. Die Runtime-Version ist nicht die, die das Projekt erwartet
Zuletzt in der Liste, weil am seltensten, aber am irreführendsten, wenn es passiert: die
Meldung spricht von Klassen oder Syntax und sieht nach einem Codeproblem aus. Wenn das
Projekt seine Version deklariert (.sdkmanrc, .nvmrc, die
Maven-Property), vergleiche sie mit deiner.
Und wenn er startet, fängt das eigentliche Problem an
Die sechs Ursachen oben betreffen einen Service. Wenn drei starten und
miteinander sprechen müssen, taucht die auf, die auf keiner Liste steht: wohin
jeder ruft. Die richtige URL hängt davon ab, wo der Aufrufer läuft —ein nativer
Prozess erreicht einen Container über localhost, ein Container einen anderen
über den Servicenamen, und ein Container, der etwas Natives aufruft, braucht den
speziellen Hostnamen—, also hat dieselbe Abhängigkeit drei URLs, je nachdem, wie jedes
Ende läuft.
Das ist es, was am Ende von Hand in der Konfiguration eines Repositories steht, das nicht deines ist, mit der gedanklichen Notiz, es nicht zu committen.
Wie Aseptic es aus dem Weg räumt
Aseptic errät nicht, warum dein Code scheitert, und diese Seite verkauft es auch nicht so. Was es tut, ist, dass fünf der sechs Ursachen nicht mehr auftreten:
- Kollidierende Ports werden automatisch remappt, und der neue Wert ist der, den die Aufrufer bekommen — niemand muss informiert werden.
- Die Infrastruktur startet die App und wartet darauf, dass sie gesund ist, nicht darauf, dass sie eine PID hat.
- Das Szenario startet in Abhängigkeitsreihenfolge und wartet auf die Health jedes einzelnen, bevor es weitergeht.
- Die Konfiguration wird beim Start injiziert als
-D-Properties oder Umgebungsvariablen, es gibt also keine Datei zu ändern und nichts, woran man beim Zurücknehmen denken müsste. - Die URLs zwischen den Services werden umgeschrieben, mit der richtigen Perspektive je nachdem, wo jedes Ende läuft.
Die sechste —die Runtime-Version— bleibt deine, und das ist richtig so: es ist dein Projekt, das deklariert, womit es gebaut wird.
Wenn dir das täglich passiert, ist der Ausgangspunkt was eine lokale Microservice-Umgebung braucht, und von dort nur den Teil starten, der dich interessiert.
Häufige Fragen
Warum startet mein Microservice in der gemeinsamen Umgebung und nicht auf meiner Maschine?
Fast immer, weil in der gemeinsamen Umgebung jemand die vier Dinge für dich gelöst hat, die du lokal selbst löst: die Infrastruktur, die Startreihenfolge, die URLs zwischen den Services und die Daten. Der Service hat sich nicht geändert; was ihn umgibt, schon.
Das Log sagt „connection refused“, aber der Container läuft. Warum?
Weil „läuft“ nicht „bereit“ ist. Ein Container hat ab der ersten Sekunde eine PID, und der Service darin kann zwanzig weitere brauchen, um Verbindungen anzunehmen. Das depends_on von Compose wartet auf den Container, nicht auf die Health.
Woran erkenne ich, ob es am Port liegt?
Schau nach, wer ihn hält: lsof -i :8080 auf macOS oder Linux, netstat -ano | findstr :8080 auf Windows. Wenn etwas auftaucht, liegt der Fehler nicht in deinem Code.
Lohnt es sich, das zu beheben, oder lebt man mit der LOCAL_SETUP.md?
Das hängt davon ab, wie oft am Tag du dafür zahlst. Ein Dokument mit vierzig Schritten ist billig zu schreiben und teuer zu pflegen: es scheitert auf jeder Maschine anders und niemand aktualisiert es, wenn sich eine Option ändert.