Mikrodienstarchitekturen erfordern eine zuverlässige Kommunikation zwischen Diensten, den Schutz externer API-Anfragen und Lastverteilung. Proxy-Server lösen diese Aufgaben, indem sie als Vermittler zwischen Diensten, externen APIs und Clients fungieren. In diesem Leitfaden werden wir untersuchen, wie man Proxys richtig in die Mikrodienstinfrastruktur integriert, welche Proxy-Typen für verschiedene Szenarien verwendet werden sollten und wie man eine sichere Kommunikation einrichtet.
Die Rolle von Proxys in der Mikrodienstarchitektur
In der Mikrodienstarchitektur übernehmen Proxy-Server mehrere kritische Funktionen, die sich von der traditionellen Verwendung von Proxys zur Anonymisierung oder Umgehung von Sperren unterscheiden. Hier werden Proxys zu einem unverzichtbaren Bestandteil der Infrastruktur, die eine zuverlässige und sichere Kommunikation zwischen den Systemkomponenten gewährleistet.
Die Hauptrollen von Proxys in Mikrodiensten:
- API-Gateway — einheitlicher Einstiegspunkt für alle Client-Anfragen, der diese an die entsprechenden Mikrodienste weiterleitet und die interne Architektur des Systems verbirgt
- Sidecar-Proxy — ein Proxy-Container, der neben jedem Dienst arbeitet (Service Mesh-Muster) und den gesamten eingehenden und ausgehenden Verkehr abfängt
- Reverse Proxy — Lastverteilung zwischen mehreren Instanzen eines Dienstes, Gewährleistung der Ausfallsicherheit
- Forward Proxy — Kontrolle und Schutz von ausgehenden Anfragen an externe APIs, Verbergen interner IP-Adressen der Infrastruktur
- Sicherheitsproxy — SSL/TLS-Terminierung, Authentifizierung, Autorisierung, Schutz vor DDoS- und anderen Angriffen
Proxys ermöglichen die Implementierung wichtiger Architektur-Muster: Circuit Breaker (automatische Abschaltung nicht funktionierender Dienste), Retry-Logik (erneute Versuche bei Fehlern), Rate Limiting (Begrenzung der Anfragefrequenz), Request/Response-Transformation (Datenformatumwandlung). All dies macht das System widerstandsfähiger gegen Ausfälle und vereinfacht das Management einer komplexen verteilten Infrastruktur.
Wichtig: In der Mikrodienstarchitektur arbeiten Proxys auf zwei Ebenen — als externes Gateway für Clients (API-Gateway) und als interne Proxys zwischen Diensten (Service Mesh). Beide Ebenen sind für die Sicherheit und Zuverlässigkeit des Systems von entscheidender Bedeutung.
Proxy-Typen für verschiedene Nutzungsszenarien
Die Wahl des Proxy-Typs hängt von der spezifischen Aufgabe in der Mikrodienstarchitektur ab. Verschiedene Szenarien erfordern unterschiedliche Eigenschaften: Geschwindigkeit, Zuverlässigkeit, Anonymität oder geografische Verteilung.
| Szenario | Proxy-Typ | Warum |
|---|---|---|
| Interne Kommunikation zwischen Diensten | HTTP/HTTPS-Proxys (Envoy, NGINX) | Maximale Geschwindigkeit, geringe Latenz, Unterstützung für HTTP/2 |
| Anfragen an externe APIs mit Limits | Residential Proxys | Umgehung von Rate Limits, echte IPs von Nutzern, geringes Risiko von Sperrungen |
| Datenparsing für Analysen | Datacenter Proxys | Hohe Geschwindigkeit, niedrige Kosten, geeignet für Massenanfragen |
| Arbeiten mit mobilen APIs | Mobile Proxys | Simulation echter mobiler Nutzer, Zugang zu mobile-only APIs |
| Lastverteilung | Reverse Proxy (HAProxy, NGINX) | Verteilung des Verkehrs, Health Checks, automatisches Umschalten bei Ausfällen |
| Geografisch verteiltes System | Residential Proxys mit Geo-Targeting | Zugang zu regionalen APIs, Einhaltung der Anforderungen an die Datenlokalisierung |
Für die interne Kommunikation zwischen Mikrodiensten werden normalerweise spezialisierte Proxy-Lösungen wie Envoy Proxy oder NGINX verwendet, die für geringe Latenz und hohe Durchsatzraten optimiert sind. Sie unterstützen moderne Protokolle (HTTP/2, gRPC) und integrieren sich in Service Mesh-Systeme.
Bei der Arbeit mit externen APIs hängt die Wahl von den Anforderungen des jeweiligen Dienstes ab. Wenn APIs strenge Rate Limits haben oder Anfragen von IPs aus Rechenzentren blockieren, sind Residential Proxys erforderlich. Für die Massenbeschaffung von Daten, bei denen Geschwindigkeit wichtiger ist als Anonymität, sind Datacenter Proxys geeignet. Mobile Proxys sind notwendig, wenn mit APIs gearbeitet wird, die den Gerätetyp überprüfen oder mobile IP-Adressen benötigen.
Proxy als API-Gateway: Schutz und Routing
Ein API-Gateway ist ein spezialisierter Proxy-Server, der als einheitlicher Einstiegspunkt für alle Client-Anfragen an das Mikrodienstsystem fungiert. Anstatt dass Clients direkt mit Dutzenden von verschiedenen Diensten kommunizieren, senden sie alle Anfragen an eine Adresse des API-Gateways, das sie an die entsprechenden Dienste weiterleitet.
Die Hauptfunktionen eines API-Gateways:
- Routing von Anfragen — Bestimmung, welcher Mikrodienst die Anfrage basierend auf URL, Headern oder anderen Parametern verarbeiten soll
- Authentifizierung und Autorisierung — Überprüfung von Tokens (JWT, OAuth), Verwaltung des Zugriffs auf verschiedene Dienste
- Rate Limiting — Begrenzung der Anzahl der Anfragen von einem Client zum Schutz vor Überlastung und DDoS
- Aggregation von Antworten — Zusammenführung von Daten aus mehreren Diensten in einer Antwort an den Client
- Protokollumwandlung — Konvertierung von REST in gRPC, HTTP/1.1 in HTTP/2
- Caching — Speicherung häufig angeforderter Daten zur Reduzierung der Last auf den Diensten
- Protokollierung und Überwachung — zentrale Sammlung von Metriken und Protokollen aller Anfragen
Beliebte Lösungen für API-Gateways: Kong, Tyk, AWS API Gateway, Azure API Management, NGINX Plus, Traefik. Die Wahl hängt von der Größe des Systems, den Leistungsanforderungen und der verwendeten Cloud-Plattform ab.
// Beispielkonfiguration von NGINX als API-Gateway
upstream auth_service {
server auth:8001;
}
upstream user_service {
server user:8002;
}
upstream order_service {
server order:8003;
}
server {
listen 80;
server_name api.example.com;
# Begrenzung der Anfragefrequenz
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
location /api/auth/ {
limit_req zone=api_limit burst=20;
proxy_pass http://auth_service/;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /api/users/ {
# Überprüfung des Tokens vor dem Proxieren
auth_request /auth/verify;
proxy_pass http://user_service/;
}
location /api/orders/ {
auth_request /auth/verify;
proxy_pass http://order_service/;
}
# Interner Endpoint zur Überprüfung von Tokens
location = /auth/verify {
internal;
proxy_pass http://auth_service/verify;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
}
}
Das API-Gateway verbirgt die interne Architektur des Systems vor externen Clients. Clients wissen nicht, wie viele Mikrodienste existieren und wie sie interagieren — sie sehen nur eine einheitliche API. Dies vereinfacht das Versionieren, ermöglicht Änderungen an der internen Struktur ohne Auswirkungen auf die Clients und verbessert die Sicherheit, da interne Dienste nicht direkt aus dem Internet zugänglich sind.
Integration mit Service Mesh (Istio, Linkerd)
Ein Service Mesh ist eine Infrastruktur-Schicht, die die Kommunikation zwischen Mikrodiensten über Proxy-Server verwaltet, die neben jedem Dienst bereitgestellt werden (Sidecar-Muster). Im Gegensatz zum API-Gateway, das nur externen Verkehr verarbeitet, kontrolliert das Service Mesh den gesamten internen Verkehr zwischen den Diensten.
Die beliebtesten Service Mesh-Lösungen sind Istio (verwendet Envoy Proxy als Sidecar) und Linkerd (verwendet einen eigenen leichten Proxy). Sie implementieren automatisch einen Proxy-Container neben jedem Pod in Kubernetes und fangen den gesamten eingehenden und ausgehenden Verkehr ab.
Funktionen des Service Mesh über Proxys:
- Mutual TLS (mTLS) — automatische Verschlüsselung des gesamten Verkehrs zwischen Diensten mit gegenseitiger Authentifizierung
- Traffic Management — Verwaltung des Routings, Canary-Deployments, A/B-Tests
- Observability — automatisierte Erfassung von Metriken, Traces und Protokollen ohne Änderung des Dienstcodes
- Resilience — Circuit Breaking, Retry-Logik, Timeout-Management, Fehlerinjektion für Tests
- Service Discovery — automatisches Entdecken von Diensten und Lastverteilung
# Beispielkonfiguration von Istio VirtualService für Routing
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: user-service
spec:
hosts:
- user-service
http:
- match:
- headers:
version:
exact: "v2"
route:
- destination:
host: user-service
subset: v2
- route:
- destination:
host: user-service
subset: v1
weight: 90
- destination:
host: user-service
subset: v2
weight: 10 # Canary-Deployment: 10% des Verkehrs auf v2
---
# Circuit Breaker zum Schutz vor kaskadierenden Ausfällen
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: user-service
spec:
host: user-service
trafficPolicy:
connectionPool:
tcp:
maxConnections: 100
http:
http1MaxPendingRequests: 50
maxRequestsPerConnection: 2
outlierDetection:
consecutiveErrors: 5
interval: 30s
baseEjectionTime: 30s
maxEjectionPercent: 50
Das Service Mesh löst das Problem des "verteilten Monolithen" — wenn die Logik der Interaktion zwischen Diensten (Retry, Timeout, Circuit Breaking) im Code jedes Dienstes dupliziert wird. Stattdessen wird diese gesamte Logik in die Proxy-Schicht ausgelagert, was den Code der Dienste vereinfacht und ein einheitliches Verhalten des gesamten Systems gewährleistet.
Ein wichtiges Vorteil ist die vollständige Transparenz des Verkehrs. Jede Anfrage zwischen Diensten läuft über den Proxy, der Metriken protokolliert: Antwortzeiten, Fehlercodes, Payload-Größe. Diese Daten werden automatisch an Überwachungssysteme (Prometheus, Grafana) und Tracing-Systeme (Jaeger, Zipkin) gesendet, wodurch ein vollständiges Bild der Funktionsweise des verteilten Systems entsteht, ohne dass eine Instrumentierung im Code jedes Dienstes erforderlich ist.
Schutz von Anfragen an externe APIs über Proxys
Mikrodienste interagieren häufig mit externen APIs: Zahlungssystemen, Geolokalisierungsdiensten, sozialen Netzwerken, Datenanbietern. Direkte Anfragen an externe APIs schaffen mehrere Probleme: Offenlegung interner IP-Adressen der Infrastruktur, Risiko von Sperrungen bei Überschreitung von Rate Limits, fehlende Kontrolle über den ausgehenden Verkehr.
Die Verwendung von Proxys für ausgehende Anfragen löst diese Probleme und fügt zusätzliche Möglichkeiten hinzu:
- Verbergen der Infrastruktur — externe APIs sehen die IP-Adressen der Proxys, nicht die Ihrer Server
- Umgehung von Rate Limits — Rotation von IP-Adressen zur Verteilung von Anfragen
- Geografische Verteilung — Zugang zu regionalen APIs über Proxys in den benötigten Ländern
- Zentralisierte Verwaltung — einheitlicher Kontrollpunkt für alle ausgehenden Anfragen
- Caching von Antworten — Reduzierung der Anzahl der Anfragen an teure APIs
- Überwachung und Protokollierung — Nachverfolgung aller Anfragen an externe Dienste
// Python: Proxy für Anfragen an externe APIs einrichten
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
class ExternalAPIClient:
def __init__(self, proxy_url, proxy_rotation=False):
self.session = requests.Session()
# Proxy einrichten
self.proxies = {
'http': proxy_url,
'https': proxy_url
}
# Retry-Logik für Robustheit
retry_strategy = Retry(
total=3,
backoff_factor=1,
status_forcelist=[429, 500, 502, 503, 504],
allowed_methods=["GET", "POST"]
)
adapter = HTTPAdapter(max_retries=retry_strategy)
self.session.mount("http://", adapter)
self.session.mount("https://", adapter)
def call_payment_api(self, data):
"""Anfrage an die Zahlungs-API über Proxy"""
try:
response = self.session.post(
'https://api.payment-provider.com/charge',
json=data,
proxies=self.proxies,
timeout=10,
headers={'User-Agent': 'MyService/1.0'}
)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
# Fehler protokollieren
print(f"Zahlungs-API-Fehler: {e}")
raise
# Verwendung mit einem Proxy-Pool zur Rotation
class ProxyPool:
def __init__(self, proxy_list):
self.proxies = proxy_list
self.current = 0
def get_next(self):
proxy = self.proxies[self.current]
self.current = (self.current + 1) % len(self.proxies)
return proxy
# Initialisierung
proxy_pool = ProxyPool([
'http://user:pass@proxy1.example.com:8080',
'http://user:pass@proxy2.example.com:8080',
'http://user:pass@proxy3.example.com:8080'
])
# Für jede Anfrage den nächsten Proxy verwenden
client = ExternalAPIClient(proxy_pool.get_next())
Für die Arbeit mit externen APIs, die strenge Einschränkungen haben oder Anfragen von IPs aus Rechenzentren blockieren, sind Residential Proxys unerlässlich. Sie bieten echte IP-Adressen von Haushaltsnutzern, was das Risiko von Sperrungen verringert und das Umgehen geografischer Einschränkungen ermöglicht.
// Node.js: Proxy für externe APIs mit automatischer Rotation
const axios = require('axios');
const HttpsProxyAgent = require('https-proxy-agent');
class ExternalAPIService {
constructor(proxyList) {
this.proxyList = proxyList;
this.currentProxyIndex = 0;
this.requestCounts = new Map(); // Zähler für Anfragen zur Rate Limiting
}
getNextProxy() {
const proxy = this.proxyList[this.currentProxyIndex];
this.currentProxyIndex = (this.currentProxyIndex + 1) % this.proxyList.length;
return proxy;
}
async callAPI(endpoint, data, options = {}) {
const proxyUrl = this.getNextProxy();
const agent = new HttpsProxyAgent(proxyUrl);
// Rate Limiting: nicht mehr als 100 Anfragen pro Minute an den Proxy
const proxyKey = proxyUrl;
const now = Date.now();
const count = this.requestCounts.get(proxyKey) || { count: 0, resetTime: now + 60000 };
if (count.count >= 100 && now < count.resetTime) {
// Wechseln zum nächsten Proxy
return this.callAPI(endpoint, data, options);
}
try {
const response = await axios({
method: options.method || 'POST',
url: endpoint,
data: data,
httpsAgent: agent,
timeout: options.timeout || 10000,
headers: {
'User-Agent': 'Mozilla/5.0 (compatible; MyService/1.0)',
...options.headers
}
});
// Zähler aktualisieren
if (now >= count.resetTime) {
this.requestCounts.set(proxyKey, { count: 1, resetTime: now + 60000 });
} else {
count.count++;
}
return response.data;
} catch (error) {
if (error.response?.status === 429) {
// Rate Limit überschritten - wechseln zu einem anderen Proxy
console.log(`Rate Limit bei ${proxyUrl}, Proxy wechseln`);
return this.callAPI(endpoint, data, options);
}
throw error;
}
}
}
// Verwendung
const apiService = new ExternalAPIService([
'http://user:pass@proxy1.example.com:8080',
'http://user:pass@proxy2.example.com:8080'
]);
module.exports = apiService;
Lastverteilung und Ausfallsicherheit
Proxy-Server spielen eine Schlüsselrolle bei der Gewährleistung der hohen Verfügbarkeit eines Mikrodienstsystems durch Lastverteilung und automatisches Umschalten bei Ausfällen. Wenn mehrere Instanzen eines Dienstes (für horizontale Skalierung) ausgeführt werden, verteilt der Proxy die Anfragen gleichmäßig zwischen ihnen.
Die Hauptalgorithmen zur Lastverteilung:
- Round Robin — abwechselnde Weiterleitung von Anfragen an jeden Server in der Liste, einfach und effektiv für homogene Server
- Least Connections — Weiterleitung der Anfrage an den Server mit der geringsten Anzahl aktiver Verbindungen, geeignet für langwierige Anfragen
- IP Hash — Bindung des Clients an einen bestimmten Server basierend auf seiner IP, sorgt für sticky sessions
- Weighted Round Robin — Verteilung unter Berücksichtigung der Serverleistung (leistungsstärkere Server erhalten mehr Anfragen)
- Random — zufällige Auswahl eines Servers, geeignet für stateless Dienste
# HAProxy-Konfiguration für Lastverteilung mit Health Checks
global
maxconn 4096
log stdout format raw local0
defaults
mode http
timeout connect 5s
timeout client 50s
timeout server 50s
option httplog
frontend api_frontend
bind *:80
default_backend api_servers
backend api_servers
balance roundrobin
# Health Check: Überprüfung von /health alle 2 Sekunden
option httpchk GET /health
http-check expect status 200
# Retry-Logik
retries 3
option redispatch
# Server mit Gewichten (server3 ist doppelt so leistungsstark)
server server1 10.0.1.10:8080 check weight 1 maxconn 500
server server2 10.0.1.11:8080 check weight 1 maxconn 500
server server3 10.0.1.12:8080 check weight 2 maxconn 1000
# Backup-Server (wird nur verwendet, wenn die Hauptserver nicht verfügbar sind)
server backup1 10.0.2.10:8080 check backup
Health Checks sind eine kritische Funktion für die Ausfallsicherheit. Der Proxy überprüft regelmäßig die Verfügbarkeit jedes Servers (normalerweise über den HTTP-Endpunkt /health oder /ready) und schließt automatisch nicht funktionierende Server aus dem Lastverteilungspool aus. Wenn ein Server wiederhergestellt wird und auf Health Checks reagiert, wird er automatisch wieder in den Pool aufgenommen.
Strategien zur Ausfallsicherheit über Proxys:
- Aktive Health Checks — der Proxy fragt aktiv die Server ab, um deren Verfügbarkeit zu überprüfen
- Passive Health Checks — der Proxy überwacht reale Anfragen und schließt Server bei Fehlerhäufungen aus
- Circuit Breaker — vorübergehende Abschaltung eines problematischen Dienstes zur Verhinderung kaskadierender Ausfälle
- Graceful Degradation — Umschaltung auf einen vereinfachten Betriebsmodus oder auf zwischengespeicherte Daten bei Ausfällen
- Failover zu Backup — automatisches Umschalten auf Backup-Server oder -Regionen
// Python: Implementierung eines Circuit Breakers für Proxys zu externen Diensten
from datetime import datetime, timedelta
from enum import Enum
class CircuitState(Enum):
CLOSED = "closed" # Normale Funktion
OPEN = "open" # Dienst nicht verfügbar, Anfragen werden blockiert
HALF_OPEN = "half_open" # Testmodus nach Wiederherstellung
class CircuitBreaker:
def __init__(self, failure_threshold=5, timeout=60, success_threshold=2):
self.failure_threshold = failure_threshold # Fehler bis zur Öffnung
self.timeout = timeout # Sekunden bis zum Wiederherstellungsversuch
self.success_threshold = success_threshold # Erfolge zum Schließen
self.state = CircuitState.CLOSED
self.failures = 0
self.successes = 0
self.last_failure_time = None
def call(self, func, *args, **kwargs):
if self.state == CircuitState.OPEN:
if datetime.now() - self.last_failure_time > timedelta(seconds=self.timeout):
self.state = CircuitState.HALF_OPEN
print("Circuit breaker: Wechsel zu HALF_OPEN")
else:
raise Exception("Circuit breaker OPEN: Dienst nicht verfügbar")
try:
result = func(*args, **kwargs)
self._on_success()
return result
except Exception as e:
self._on_failure()
raise e
def _on_success(self):
self.failures = 0
if self.state == CircuitState.HALF_OPEN:
self.successes += 1
if self.successes >= self.success_threshold:
self.state = CircuitState.CLOSED
self.successes = 0
print("Circuit breaker: Wiederherstellung, Wechsel zu CLOSED")
def _on_failure(self):
self.failures += 1
self.last_failure_time = datetime.now()
if self.failures >= self.failure_threshold:
self.state = CircuitState.OPEN
print(f"Circuit breaker OPEN: {self.failures} Fehler hintereinander")
# Verwendung
breaker = CircuitBreaker(failure_threshold=3, timeout=30)
def call_external_service():
# Ihr Code für die Anfrage an die externe API über den Proxy
pass
try:
result = breaker.call(call_external_service)
except Exception as e:
# Fallback-Logik: Cache, Standardwerte usw.
print(f"Dienst nicht verfügbar: {e}")
Sicherheit der Kommunikation zwischen Diensten
In der Mikrodienstarchitektur bieten Proxy-Server mehrere Sicherheitsebenen: Verschlüsselung des Verkehrs, Authentifizierung von Diensten, Schutz vor Angriffen und Isolierung von Netzwerksegmenten. Ohne die richtige Sicherheitskonfiguration kann der interne Verkehr zwischen Diensten abgefangen oder gefälscht werden.
Schlüsselaspekte der Sicherheit über Proxys:
- Mutual TLS (mTLS) — beidseitige Authentifizierung, bei der sowohl der Client als auch der Server die Zertifikate des jeweils anderen überprüfen. Service Mesh konfiguriert automatisch mTLS zwischen allen Diensten
- TLS-Terminierung — der Proxy entschlüsselt den HTTPS-Verkehr an der Grenze, überprüft ihn und leitet ihn über einen sicheren Kanal an die Dienste weiter
- JWT-Validierung — Überprüfung von Zugriffstokens auf Proxy-Ebene, bevor die Anfrage den Dienst erreicht
- IP-Whitelisting — Zugang zu Diensten nur von autorisierten IP-Adressen
- DDoS-Schutz — Rate Limiting, Verbindungsgrenzen, Schutz vor SYN Flood auf Proxy-Ebene
- WAF (Web Application Firewall) — Filterung bösartiger Anfragen, Schutz vor SQL-Injection, XSS
# NGINX-Konfiguration mit SSL/TLS und Sicherheit
server {
listen 443 ssl http2;
server_name api.internal.example.com;
# SSL-Zertifikate
ssl_certificate /etc/nginx/certs/api.crt;
ssl_certificate_key /etc/nginx/certs/api.key;
# Moderne Protokolle und Cipher
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# Client-Zertifikat für mTLS
ssl_client_certificate /etc/nginx/certs/ca.crt;
ssl_verify_client on;
# Sichere Header
add_header Strict-Transport-Security "max-age=31536000" always;
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
# Rate Limiting
limit_req_zone $binary_remote_addr zone=api:10m rate=100r/s;
limit_req zone=api burst=200 nodelay;
# Verbindungsbegrenzung
limit_conn_zone $binary_remote_addr zone=addr:10m;
limit_conn addr 10;
# IP-Whitelisting
allow 10.0.0.0/8; # Internes Netzwerk
allow 172.16.0.0/12; # VPC
deny all;
location / {
# Überprüfung des JWT-Tokens
auth_jwt "Eingeschränkte API";
auth_jwt_key_file /etc/nginx/jwt_key.json;
proxy_pass http://backend_service;
# Übertragung von Informationen über das Client-Zertifikat
proxy_set_header X-Client-DN $ssl_client_s_dn;
proxy_set_header X-Client-Verify $ssl_client_verify;
}
}
Service Mesh vereinfacht die Sicherheitskonfiguration erheblich, indem es automatisch Zertifikate für mTLS generiert und rotiert, Zugriffsrichtlinien anwendet und den gesamten Verkehr zwischen den Diensten verschlüsselt. Beispielsweise kann in Istio eine Richtlinie festgelegt werden, dass der Dienst "payment" Anfragen nur vom Dienst "order" akzeptieren kann, und dies wird automatisch auf Proxy-Ebene ohne Änderung des Dienstcodes angewendet.
Wichtig für die Produktion: Verwenden Sie immer mTLS für die interne Kommunikation zwischen Diensten, selbst wenn sie sich im selben privaten Netzwerk befinden. Dies schützt vor Man-in-the-Middle-Angriffen und gewährleistet die Authentifizierung auf Diensteebene und nicht nur auf Netzwerkebene.
Überwachung und Protokollierung des Proxy-Verkehrs
Proxy-Server bieten eine einzigartige Möglichkeit zur zentralisierten Überwachung des gesamten Verkehrs in einem Mikrodienstsystem. Da der gesamte Verkehr über den Proxy läuft (sowohl extern über das API-Gateway als auch intern über das Service Mesh), erhalten Sie vollständige Sichtbarkeit über die Funktionsweise des Systems, ohne dass eine Instrumentierung jedes Dienstes erforderlich ist.
Schlüsselmesswerte für die Überwachung auf Proxy-Ebene:
- Latenz — Zeit für die Verarbeitung einer Anfrage in jeder Phase: Proxy, Dienst, externe APIs
- Durchsatz — Anzahl der Anfragen pro Sekunde, Datenvolumen
- Fehlerquote — Prozentsatz der Fehler (4xx, 5xx), Fehlerarten, problematische Endpunkte
- Verbindungsmetriken — Anzahl aktiver Verbindungen, Nutzung des Verbindungs-Pools
- Circuit Breaker-Zustand — Zustand der Circuit Breaker für jeden Dienst
- SSL/TLS-Metriken — Status der Zertifikate, Protokollversionen, Handshake-Fehler
# NGINX-Konfiguration zum Exportieren von Metriken an Prometheus
server {
listen 9113;
location /metrics {
stub_status;
access_log off;
allow 10.0.0.0/8; # Nur für den Prometheus-Server
deny all;
}
}
# Protokollierung im JSON-Format für strukturiertes Logging
log_format json_combined escape=json
'{'
'"time_local":"$time_local",'
'"remote_addr":"$remote_addr",'
'"request":"$request",'
'"status": "$status",'
'"body_bytes_sent":"$body_bytes_sent",'
'"request_time":"$request_time",'
'"upstream_response_time":"$upstream_response_time",'
'"upstream_addr":"$upstream_addr",'
'"http_referrer":"$http_referer",'
'"http_user_agent":"$http_user_agent",'
'"http_x_forwarded_for":"$http_x_forwarded_for"'
'}';
access_log /var/log/nginx/access.log json_combined;
Distributed Tracing ist eine der leistungsstärksten Überwachungsmöglichkeiten über Proxys. Jede Anfrage erhält eine eindeutige Trace-ID, die der Proxy in die Header einfügt und an die nachfolgenden Dienste weitergibt. Tracing-Systeme (Jaeger, Zipkin) sammeln Informationen von allen Proxys und erstellen den vollständigen Pfad der Anfrage durch das System, wobei angezeigt wird, wie viel Zeit sie in jedem Dienst verbracht hat.
// Node.js: Hinzufügen von Tracing in das Proxy-Middleware
const express = require('express');
const { v4: uuidv4 } = require('uuid');
const axios = require('axios');
const app = express();
// Middleware zum Hinzufügen der Trace-ID
app.use((req, res, next) => {
// Trace-ID aus dem Header abrufen oder neu erstellen
const traceId = req.headers['x-trace-id'] || uuidv4();
const spanId = uuidv4();
// In die Header für die Weitergabe einfügen
req.traceId = traceId;
req.spanId = spanId;
res.setHeader('x-trace-id', traceId);
// Beginn der Verarbeitung protokollieren
const startTime = Date.now();
res.on('finish', () => {
const duration = Date.now() - startTime;
// Strukturiertes Log für die Analyse
console.log(JSON.stringify({
timestamp: new Date().toISOString(),
traceId: traceId,
spanId: spanId,
method: req.method,
path: req.path,
status: res.statusCode,
duration: duration,
userAgent: req.headers['user-agent'],
ip: req.ip
}));
});
next();
});
// Proxy-Endpoint mit Übertragung der Tracing-Header
app.all('/api/*', async (req, res) => {
const targetService = determineTargetService(req.path);
try {
const response = await axios({
method: req.method,
url: `http://${targetService}${req.path}`,
data: req.body,
headers: {
...req.headers,
'x-trace-id': req.traceId,
'x-parent-span-id': req.spanId,
'x-span-id': uuidv4() // Neuer Span für die nachgelagerte Anfrage
}
});
res.status(response.status).json(response.data);
} catch (error) {
console.error(JSON.stringify({
traceId: req.traceId,
error: error.message,
service: targetService
}));
res.status(500).json({ error: 'Dienst nicht verfügbar' });
}
});
function determineTargetService(path) {
if (path.startsWith('/api/users')) return 'user-service:8080';
if (path.startsWith('/api/orders')) return 'order-service:8080';
return 'default-service:8080';
}
app.listen(3000);
Alarme basierend auf den Proxy-Metriken ermöglichen eine schnelle Problemerkennung. Beispielsweise können Alarme eingerichtet werden für: plötzlichen Anstieg der Latenz (möglicherweise degradiert einer der Dienste), Anstieg der Fehlerquote über einen Schwellenwert (Probleme mit dem Code oder Abhängigkeiten), Änderung der Verkehrsströme (möglicher DDoS-Angriff oder Viruslast).
Beispiele für Implementierungen in Python und Node.js
Lassen Sie uns praktische Beispiele zur Integration von Proxys in Mikrodienste in Python und Node.js für verschiedene Szenarien betrachten: interne Kommunikation, Arbeit mit externen APIs, Lastverteilung.
Python: Dienst mit Proxy für externe APIs
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import httpx
import asyncio
from typing import List, Optional
import logging
app = FastAPI()
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class ProxyConfig(BaseModel):
url: str
max_requests_per_minute: int = 60
class ProxyPool:
def __init__(self, proxies: List[ProxyConfig]):
self.proxies = proxies
self.current_index = 0
self.request_counts = {p.url: 0 for p in proxies}
self.reset_time = asyncio.get_event_loop().time() + 60
async def get_next_proxy(self) -> str:
# Zurücksetzen der Zähler jede Minute
current_time = asyncio.get_event_loop().time()
if current_time >= self.reset_time:
self.request_counts = {p.url: 0 for p in self.proxies}
self.reset_time = current_time + 60
# Finden Sie einen Proxy mit verfügbaren Anfragen
for _ in range(len(self.proxies)):
proxy = self.proxies[self.current_index]
self.current_index = (self.current_index + 1) % len(self.proxies)
if self.request_counts[proxy.url] < proxy.max_requests_per_minute:
self.request_counts[proxy.url] += 1
return proxy.url
# Alle Proxys haben ihr Limit erreicht
raise HTTPException(status_code=429, detail="Alle Proxys sind rate-limitiert")
# Initialisierung des Proxy-Pools
proxy_pool = ProxyPool([
ProxyConfig(url="http://user:pass@proxy1.example.com:8080", max_requests_per_minute=100),
ProxyConfig(url="http://user:pass@proxy2.example.com:8080", max_requests_per_minute=100),
ProxyConfig(url="http://user:pass@proxy3.example.com:8080", max_requests_per_minute=100)
])
class ExternalAPIClient:
def __init__(self, proxy_pool: ProxyPool):
self.proxy_pool = proxy_pool
async def fetch_data(self, endpoint: str, params: dict = None) -> dict:
proxy_url = await self.proxy_pool.get_next_proxy()
async with httpx.AsyncClient(proxies={"http://": proxy_url, "https://": proxy_url}) as client:
try:
response = await client.get(
endpoint,
params=params,
timeout=10.0,
headers={"User-Agent": "MyMicroservice/1.0"}
)
response.raise_for_status()
logger.info(f"Erfolgreich von {endpoint} über {proxy_url} abgerufen")
return response.json()
except httpx.HTTPStatusError as e:
logger.error(f"HTTP-Fehler {e.response.status_code} von {endpoint}")
raise HTTPException(status_code=e.response.status_code, detail=str(e))
except httpx.RequestError as e:
logger.error(f"Anfragefehler an {endpoint}: {e}")
raise HTTPException(status_code=503, detail="Externe API nicht verfügbar")
api_client = ExternalAPIClient(proxy_pool)
@app.get("/data/{resource_id}")
async def get_external_data(resource_id: str):
"""Endpoint, der Daten aus einer externen API über einen Proxy abruft"""
external_endpoint = f"https://api.external-service.com/v1/resources/{resource_id}"
try:
data = await api_client.fetch_data(external_endpoint)
return {"status": "success", "data": data}
except HTTPException:
raise
except Exception as e:
logger.error(f"Unerwarteter Fehler: {e}")
raise HTTPException(status_code=500, detail="Interner Serverfehler")
@app.get("/health")
async def health_check():
return {"status": "healthy", "service": "external-api-proxy"}
# Start: uvicorn main:app --host 0.0.0.0 --port 8000
Node.js: API-Gateway mit Lastverteilung
const express = require('express');
const axios = require('axios');
const rateLimit = require('express-rate-limit');
const app = express();
app.use(express.json());
// Konfiguration der Mikrodienste
const services = {
users: [
{ url: 'http://user-service-1:8001', healthy: true, activeConnections: 0 },
{ url: 'http://user-service-2:8001', healthy: true, activeConnections: 0 },
// Weitere Dienste hier hinzufügen
],
orders: [
{ url: 'http://order-service-1:8002', healthy: true, activeConnections: 0 },
{ url: 'http://order-service-2:8002', healthy: true, activeConnections: 0 },
// Weitere Dienste hier hinzufügen
]
};
// Middleware zur Lastverteilung
app.use((req, res, next) => {
const serviceType = req.path.startsWith('/api/users') ? 'users' : 'orders';
const service = selectService(services[serviceType]);
if (service) {
req.serviceUrl = service.url;
next();
} else {
res.status(503).send('Alle Dienste sind derzeit nicht verfügbar');
}
});
// Auswahl eines gesunden Dienstes
function selectService(serviceList) {
const healthyServices = serviceList.filter(service => service.healthy);
if (healthyServices.length === 0) return null;
// Hier können Sie einen Algorithmus zur Lastverteilung implementieren
return healthyServices[0]; // Beispiel: immer den ersten gesunden Dienst verwenden
}
// Proxy-Endpoint
app.all('/api/*', async (req, res) => {
try {
const response = await axios({
method: req.method,
url: `${req.serviceUrl}${req.path}`,
data: req.body,
headers: {
...req.headers,
'X-Forwarded-For': req.ip
}
});
res.status(response.status).json(response.data);
} catch (error) {
console.error(`Fehler bei der Anfrage an ${req.serviceUrl}: ${error.message}`);
res.status(500).send('Dienst nicht verfügbar');
}
});
app.listen(3000, () => {
console.log('API-Gateway läuft auf Port 3000');
});