Reverse-Proxy: schöne Hostnamen statt localhost:port
Zielgruppe: DevOps / jede:r, der/die sich
localhost:8180und Co. nicht merken will. Phase 12. Setztenterprise-workspace/shared/enterprise-infrastructurevoraus (liegt bewusst außerhalb dieses Repos, siehe dort das README für den vollen Kontext).
Das Problem
Jeder lokale Dienst dieses Projekts lief bisher nur unter localhost:<port> - Keycloak
localhost:8180, Grafana localhost:3000, Jenkins localhost:8091, und so weiter (volle
Tabelle in docker-compose-guide.md). Nachteile:
- Man muss sich Portnummern merken statt sprechender Namen.
- Läuft parallel ein zweites Lernprojekt mit eigener Infrastruktur, kollidieren Ports (bereits dokumentiert: docker-compose-guide.md, "Portkonflikte mit anderen lokal laufenden Projekten").
- Jenkins müsste jedes Projekt einzeln mitbringen - unnötig, RAM- und Boot-Zeit-teuer.
Die Lösung: Traefik + ein gemeinsames externes Container-Netzwerk
Traefik ist ein Reverse-Proxy, der Container über Labels
automatisch erkennt und routet (via Podmans Docker-kompatible API - der Traefik-Provider heißt
weiterhin "docker") - kein manuelles Nachpflegen einer separaten Proxy-Konfigurationsdatei bei
jedem neuen Dienst, wie es z. B. bei klassischem nginx nötig wäre.
Traefik selbst läuft zentral in enterprise-infrastructure (außerhalb dieses
Repos, projektübergreifend), dieses Projekt "meldet" seine Dienste nur per Label an.
Wie die Verbindung zustande kommt
┌─────────────────────────────┐ ┌──────────────────────────────────┐
│ enterprise-workspace/shared/enterprise-infrastructure/ │ │ library-enterprise-platform/ │
│ │ │ infra/docker-compose.yml │
│ ┌────────────┐ │ proxy │ ┌────────────┐ │
│ │ Traefik │◄─────────────┼────────┼──┤ keycloak │ (+ 9 weitere) │
│ │ Port 80 │ (externes, │ Netz │ └────────────┘ │
│ └────────────┘ gemeinsames │ │ │ │
│ ┌────────────┐ Netzwerk) │ │ library-net (unveraendert, │
│ │ Jenkins │ │ │ Container erreichen sich intern │
│ └────────────┘ │ │ weiter ueber Container-Namen) │
└─────────────────────────────┘ └──────────────────────────────────┘
Zwei Zutaten in infra/docker-compose.yml:
1) Das externe Netzwerk referenzieren (shared/enterprise-infrastructure/docker-compose.yml erzeugt es, dieses
Projekt bindet es nur ein):
networks:
library-net:
driver: bridge
proxy:
external: true
name: proxy
2) Pro Dienst mit Weboberfläche: dem proxy-Netzwerk zusätzlich beitreten (NICHT als Ersatz
für library-net - additives Prinzip, wie schon beim dritten Kafka-Listener in Phase 11) und
Traefik-Labels setzen. Beispiel keycloak:
keycloak:
# ... unveraendert ...
networks: [library-net, proxy]
labels:
- "traefik.enable=true"
- "traefik.http.routers.library-keycloak.rule=Host(`keycloak.library.localhost`)"
- "traefik.http.services.library-keycloak.loadbalancer.server.port=8080"
- "traefik.docker.network=proxy"
Wichtig: die bestehenden ports:-Mappings bleiben unverändert bestehen.
localhost:8180 funktioniert also weiterhin genau wie vorher - der Hostname ist ein
zusätzlicher, gleichwertiger Zugriffsweg, kein Ersatz. Das library--Präfix in Router-/
Service-Namen und im Hostnamen selbst ist nötig, weil Traefik keine Projektgrenzen kennt: ein
zweites angeschlossenes Projekt mit einem eigenen "keycloak"-Dienst würde sonst Namen
überschreiben (siehe shared/enterprise-infrastructure/README.md
für das allgemeine Muster). Das traefik.docker.network=proxy-Label ist nötig, sobald ein
Container an ZWEI Netzwerken hängt - ohne es könnte Traefik das falsche wählen. (Der Label-Key
heißt auch unter Podman traefik.docker.network - "docker" ist der Provider-Name, nicht die
Laufzeit.)
Hostnamen-Übersicht
| Dienst | Neuer Hostname | Weiterhin auch unter |
|---|---|---|
| Keycloak | http://keycloak.library.localhost |
localhost:8180 |
| pgAdmin | http://pgadmin.library.localhost |
localhost:5050 |
| Kafka UI | http://kafka-ui.library.localhost |
localhost:8090 |
| RabbitMQ (Management) | http://rabbitmq.library.localhost |
localhost:15673 |
| Prometheus | http://prometheus.library.localhost |
localhost:9090 |
| Grafana | http://grafana.library.localhost |
localhost:3000 |
| Zipkin | http://zipkin.library.localhost |
localhost:9411 |
| Eureka-Dashboard (discovery-server) | http://discovery.library.localhost |
localhost:8761 |
| Config-Server (REST-API) | http://config.library.localhost |
localhost:8888 |
| api-gateway (Anwendung) | http://api.library.localhost |
localhost:8080 |
| Jenkins (projektübergreifend, siehe unten) | http://jenkins.localhost |
- (kein Host-Port mehr, siehe jenkins-pipeline.md) |
| Traefik-Dashboard | http://traefik.localhost |
- |
Die sechs Fachservices (catalog-service, member-service, ...) bekommen bewusst KEINE eigenen
Hostnamen - sie werden im normalen Betrieb nicht direkt aufgerufen, sondern ausschließlich über
api-gateway (api.library.localhost).
Warum .localhost und kein Eintrag in der hosts-Datei nötig ist
.localhost ist laut RFC 6761 eine reservierte
Top-Level-Domain, die IMMER auf die lokale Maschine (127.0.0.1) zeigen MUSS. Chrome, Firefox
und Edge lösen *.localhost-Namen deshalb automatisch auf - kein Eintrag in
C:\Windows\System32\drivers\etc\hosts nötig, funktioniert direkt im Browser. Nur für
Kommandozeilen-Werkzeuge außerhalb des Browsers (z. B. curl in einem Skript), die diese
Auflösung nicht automatisch beherrschen, bräuchte es zusätzlich einen manuellen hosts-Eintrag
(Administratorrechte nötig) - für den normalen Browser-Zugriff auf diese Dienste nicht
erforderlich.
Alternativen (kurz - Details siehe tech-stack.md)
Zwei unabhängige Entscheidungen stecken in dieser Lösung, jede hätte auch anders ausfallen können:
- Traefik als Reverse-Proxy - Alternativen und Abwägung (nginx-proxy, Caddy) stehen in tech-stack.md (Zeile "Traefik"), bewusst dort und nicht hier dupliziert - das ist die Stelle im Projekt, an der JEDE Technologie-Entscheidung mit ihren Alternativen gesammelt ist.
.localhostals TLD (siehe oben) - Alternative wäre eine frei wählbare TLD wie.localoder.testMIT manuellemhosts-Datei-Eintrag statt automatischer Browser-Auflösung. Vorteil der Alternative: funktioniert auch für Nicht-Browser-Werkzeuge ohne Zusatzschritt. Nachteil: Administratorrechte nötig, ein manueller Schritt pro Hostname statt "funktioniert sofort" - für dieses Setup deshalb bewusst nicht gewählt.
Voraussetzung: Enterprise Infrastructure läuft
cd ../../shared/enterprise-infrastructure # relativ zu diesem Projekt, siehe Repo-Layout in lernpfad.md
podman compose up -d
Läuft unabhängig von diesem Projekt im Hintergrund weiter (siehe
shared/enterprise-infrastructure/README.md). Ohne laufende Enterprise Infrastructure
funktionieren die .localhost-Hostnamen nicht (Traefik selbst läuft dort) - die klassischen
localhost:<port>-Zugriffe sind davon unberührt.
Troubleshooting
- Hostname löst gar nicht auf / "Diese Website ist nicht erreichbar": läuft
Enterprise Infrastructure (
podman compose psdort,traefik-Container mussUpsein)? Läuft die Infrastruktur dieses Projekts (podman compose psininfra/)? - Traefik antwortet, aber mit "404 page not found": Label-Tippfehler (Backtick-Zeichen in
Host(\...`)vergessen, Router-Name kollidiert mit einem anderen Projekt) - im Traefik-Dashboard (http://traefik.localhost`) unter "HTTP > Routers" nachsehen, ob der erwartete Router überhaupt auftaucht. Ein 404 kann aber auch schlicht die legitime Antwort der Anwendung SELBST auf den Wurzelpfad/sein (z. B.config.library.localhost- Config-Server hat keine Route für/, aber/catalog-service/defaultantwortet mit 200) - im Zweifel einen echten Endpunkt statt nur/testen. - Hostname löst auf, aber "Connection refused"/"Bad Gateway": der Ziel-Container ist nicht im
proxy-Netzwerk (podman network inspect proxyprüfen) oder dastraefik.docker.network- Label fehlt/ist falsch gesetzt. - Router taucht im Traefik-Dashboard gar nicht erst auf, obwohl die Labels stimmen (bei der
Verifikation dieser Phase konkret bei Jenkins beobachtet): der Container wurde ohne
Netzwerk-Anbindung erzeugt (
podman inspect <container> --format '{{.NetworkSettings.Networks}}'zeigt{}stattproxy) - meist, weil ein vorherigerpodman compose upmitten im Image-Bau abgebrochen wurde. Abhilfe:podman compose up -d --force-recreate <service>. - Ein Dienst wird "unhealthy", nachdem er dem
proxy-Netzwerk beigetreten ist (bei der Verifikation dieser Phase konkret bei Zipkin beobachtet): manche Container-Images bringen einen eigenen Healthcheck mit, der den eigenen Hostnamen auflöst, um sich selbst anzusprechen - sobald der Container an ZWEI Netzwerken hängt, liefert diese Auflösung ZWEI IP-Adressen zurück, und einfache Werkzeuge wiewgetscheitern daran (wget: bad address '<ip1> <ip2>:<port>'). Fix: im eigenendocker-compose.ymleinenhealthcheck:-Block ergänzen, der explizit127.0.0.1statt eines Hostnamens verwendet (siehe derzipkin-Service ininfra/docker-compose.ymlfür das konkrete Beispiel). podman compose upininfra/schlägt mit einem Netzwerk-Fehler fehl: dasproxy-Netz muss existieren - entweder Enterprise Infrastructure VOR diesem Projekt starten oder einmaligpodman network create proxy(siehe "Voraussetzung" oben).
Behobener, unabhängiger Bug: pgAdmin startete nicht mehr
Bei der Verifikation dieser Phase fiel auf, dass library-pgadmin bereits VOR dieser Änderung
nicht mehr startete ('admin@library-platform.local' does not appear to be a valid email address) - eine neuere dpage/pgadmin4:8-Patch-Version validiert die
PGADMIN_DEFAULT_EMAIL-Umgebungsvariable strenger und lehnt die .local-Endung ab. Betraf NUR
pgAdmin selbst, nicht die hier beschriebene Traefik-Integration (der pgadmin.library.localhost-
Router war korrekt konfiguriert, hatte aber keinen laufenden Container zum Routen) - im ersten
Durchgang dieser Phase bewusst nicht mitgefixt (unabhängiges Problem), inzwischen behoben:
PGADMIN_DEFAULT_EMAIL auf admin@library-platform.com geändert (besteht dieselbe Validierung),
siehe docker-compose-guide.md.
Bewusst nicht umgesetzt (Vertiefungsaufgaben)
Siehe shared/enterprise-infrastructure/README.md, Abschnitt "Grenzen dieses Ansatzes" -
insbesondere: kein HTTPS (nur Port 80), Jenkins' vorinstallierte Tool-Versionen sind auf DIESES
Projekt zugeschnitten (ein zweites angeschlossenes Projekt mit anderen Versionsanforderungen
bräuchte dedizierte Container-Build-Agents statt eines fest bestückten Jenkins-Images).