|
| 1 | +# JavaHttpClient – Projektdokumentation |
| 2 | + |
| 3 | +## Zweck |
| 4 | + |
| 5 | +Diagnose-Tool für HTTP-Konnektivitätsprobleme in Kubernetes-Clustern mit Istio Service Mesh. |
| 6 | +Läuft als Pod im Cluster und ermöglicht: |
| 7 | +- HTTP-Requests über einen Java-HTTP-Client an beliebige Ziel-URLs zu senden |
| 8 | +- Istio-Sidecar (Envoy) Konfiguration und Fehler-Metriken auszuwerten |
| 9 | +- VirtualService/DestinationRule/ServiceEntry-Konfiguration mit einer Ziel-URL zu korrelieren |
| 10 | +- TLS-Zertifikatsketten und mTLS/SPIFFE-Identitäten zu inspizieren |
| 11 | +- Redirect-Chains, Protokollversionen und DNS-Auflösungen sichtbar zu machen |
| 12 | + |
| 13 | +--- |
| 14 | + |
| 15 | +## Tech Stack |
| 16 | + |
| 17 | +| Schicht | Technologie | |
| 18 | +|---------|-------------| |
| 19 | +| Backend | Spring Boot 4.0.3, Java 25, Jetty | |
| 20 | +| HTTP-Client | `java.net.http.HttpClient` (JDK built-in) | |
| 21 | +| K8s-Integration | Official Kubernetes Java Client v25.0.0 | |
| 22 | +| Frontend | Vanilla JS, Bootstrap 5.3, Bootstrap Icons | |
| 23 | +| API-Doku | SpringDoc OpenAPI 3.0.1 (`/swagger-ui.html`, `/api-docs`) | |
| 24 | +| Deployment | Docker (AOT-Build), Helm, Istio Gateway | |
| 25 | + |
| 26 | +--- |
| 27 | + |
| 28 | +## Architektur |
| 29 | + |
| 30 | +``` |
| 31 | +Browser |
| 32 | + │ |
| 33 | + ├─ POST /client → HttpClientController → ClientService |
| 34 | + │ ├─ HTTP/2-Client (NEVER redirect) |
| 35 | + │ ├─ Manuelle Redirect-Chain |
| 36 | + │ ├─ DNS-Auflösung (InetAddress) |
| 37 | + │ └─ Response-Header: |
| 38 | + │ X-Protocol-Version |
| 39 | + │ X-Redirect-Chain |
| 40 | + │ X-Resolved-IP |
| 41 | + │ |
| 42 | + ├─ GET /api/k8s/context → DiagnosticController → K8sDiagnosticService |
| 43 | + ├─ GET /api/k8s/status → ↑ |
| 44 | + ├─ GET /api/k8s/istio/{type} → ↑ |
| 45 | + ├─ GET /api/k8s/istio/full-report → ↑ (Envoy Admin API: /stats, /clusters, /config_dump) |
| 46 | + ├─ GET /api/k8s/correlate → ↑ (VS/DR/SE-Matching gegen URL) |
| 47 | + └─ GET /api/k8s/tls → DiagnosticController → TlsInspectorService (SSLSocket-Probe) |
| 48 | +``` |
| 49 | + |
| 50 | +--- |
| 51 | + |
| 52 | +## Backend – Klassen |
| 53 | + |
| 54 | +### `ClientService` |
| 55 | +Sendet HTTP-Requests über `java.net.http.HttpClient`. |
| 56 | + |
| 57 | +- **HTTP/2** aktiv (`Version.HTTP_2`), fällt automatisch auf HTTP/1.1 zurück (ALPN) |
| 58 | +- **`Redirect.NEVER`** – Redirects werden manuell verfolgt (bis zu 10 Hops) |
| 59 | + - 307/308: Methode + Body beibehalten |
| 60 | + - 301/302/303: → GET, kein Body |
| 61 | + - Folge-Requests erhalten **keine** originalen Browser-Header (verhindert Auth-Token-Leak) |
| 62 | +- **DNS-Auflösung** vor dem Request: `InetAddress.getAllByName()` → alle A/AAAA-Records |
| 63 | +- **Response-Header** die zurückgegeben werden: |
| 64 | + - `X-Protocol-Version`: `HTTP/2` oder `HTTP/1.1` |
| 65 | + - `X-Redirect-Chain`: JSON-Array der Zwischenschritte `[{from, status, to, proto}]` |
| 66 | + - `X-Resolved-IP`: kommagetrennte IPs |
| 67 | + |
| 68 | +Gefilterte Request-Header (werden nie weitergeleitet): |
| 69 | +`host`, `content-length`, `connection`, `accept-encoding`, `upgrade` |
| 70 | + |
| 71 | +Fehlermeldungen (502): |
| 72 | +| Exception | Meldung | |
| 73 | +|-----------|---------| |
| 74 | +| `UnknownHostException` | DNS Fehler: Host nicht gefunden | |
| 75 | +| `ConnectException` | Verbindung abgelehnt: Port zu / Pod läuft nicht | |
| 76 | +| `HttpConnectTimeoutException` | Timeout: NetworkPolicy Blockade? | |
| 77 | +| `IllegalArgumentException` (URI) | Ungültige URL | |
| 78 | + |
| 79 | +--- |
| 80 | + |
| 81 | +### `K8sDiagnosticService` |
| 82 | + |
| 83 | +**Envoy Admin API** (Standard: `http://127.0.0.1:15000`, konfigurierbar via `ENVOY_ADMIN_URL`): |
| 84 | +- `/config_dump` → vollständige Envoy-Konfiguration |
| 85 | +- `/clusters` → aktive Upstream-Cluster |
| 86 | +- `/stats` → **alle** Metriken (kein serverseitiger Filter), nur Werte ≥ 0 (non-zero) |
| 87 | + |
| 88 | +**Istio-Sidecar-Erkennung**: Socket-Test auf `127.0.0.1:15021` |
| 89 | + |
| 90 | +**Unterstützte Istio-Ressourcentypen** (API-Versionen v1 → v1beta1 → v1alpha3 Fallback): |
| 91 | +`virtualservices`, `destinationrules`, `gateways`, `serviceentries`, `sidecars`, |
| 92 | +`envoyfilters`, `peerauthentications`, `requestauthentications`, `authorizationpolicies` |
| 93 | + |
| 94 | +**Gefilterte Metriken**: `cluster.xds-grpc.*` wird aus `activeErrorMetrics` entfernt |
| 95 | +(interner Istio Control-Plane-Kanal, kein App-Traffic) |
| 96 | + |
| 97 | +**`diagnoseMetrics()`** – automatische Problemerkennung aus Envoy-Stats: |
| 98 | + |
| 99 | +| Pattern | Severity | Diagnose | |
| 100 | +|---------|----------|----------| |
| 101 | +| `upstream_cx_connect_fail` | KRITISCH | Verbindung abgelehnt (Connection refused) | |
| 102 | +| `upstream_rq_pending_overflow` | KRITISCH | Circuit Breaker / Pool-Overflow | |
| 103 | +| `upstream_cx_none_healthy` | KRITISCH | Keine gesunden Endpoints | |
| 104 | +| `upstream_rq_timeout` | WARNUNG | Request Timeouts (NetworkPolicy?) | |
| 105 | +| `upstream_cx_connect_timeout` | WARNUNG | Connection Timeout | |
| 106 | +| `upstream_rq_5xx` | WARNUNG | 5xx Fehler vom Upstream | |
| 107 | +| `upstream_rq_retry_limit_exceeded` | INFO | Retry-Limit überschritten | |
| 108 | +| `upstream_cx_destroy_remote_with_active_rq` | INFO | Verbindung mit aktiven Requests abgebrochen | |
| 109 | + |
| 110 | +**`correlateUrl(url, namespace)`** – URL-Korrelation gegen Istio-Ressourcen: |
| 111 | + |
| 112 | +*Host-Matching-Logik* (VS-Host vs. URL-Hostname): |
| 113 | +- Exakter Match |
| 114 | +- Wildcard: `*.namespace` matcht `foo.namespace` |
| 115 | +- Short-Name: `my-svc` matcht `my-svc.ns.svc.cluster.local` |
| 116 | +- Prefix: `my-svc.ns` matcht `my-svc.ns.svc.cluster.local` |
| 117 | + |
| 118 | +*VirtualService*: prüft HTTP-Routen auf Pfad-Match (`exact` / `prefix` / `regex`) |
| 119 | +→ gibt matched Routes inkl. Destinations, Timeout, Retries, Fault-Injection zurück |
| 120 | + |
| 121 | +*DestinationRule*: prüft Host-Match |
| 122 | +→ gibt TrafficPolicy (connectionPool, outlierDetection, loadBalancer) und Subsets zurück |
| 123 | + |
| 124 | +*ServiceEntry*: prüft Host-Match + Port-Match gegen `spec.ports` |
| 125 | +→ gibt location, resolution, endpoints, subjectAltNames zurück |
| 126 | +→ Warnung wenn angeforderter Port nicht in `spec.ports` definiert |
| 127 | + |
| 128 | +--- |
| 129 | + |
| 130 | +### `TlsInspectorService` |
| 131 | + |
| 132 | +Separater `SSLSocket`-basierter TLS-Probe (unabhängig vom Haupt-Request): |
| 133 | +- Eigener `SSLContext` mit `X509ExtendedTrustManager` der **alles akzeptiert** (auch self-signed, expired) → Chain immer sichtbar |
| 134 | +- SNI korrekt gesetzt via `SSLParameters` |
| 135 | +- Gibt zurück: |
| 136 | + - `tlsVersion`: `TLSv1.3` / `TLSv1.2` |
| 137 | + - `cipherSuite` |
| 138 | + - `isMtls`: `true` wenn Leaf-Cert eine `URI:spiffe://`-SAN hat |
| 139 | + - `spiffeId`: extrahierte SPIFFE-URI (Istio-Workload-Identität) |
| 140 | + - `chain`: Array pro Zertifikat mit subject, issuer, serial, validFrom, validTo, daysUntilExpiry, expired, subjectAltNames |
| 141 | +- Wird nur für `https://`-URLs aufgerufen |
| 142 | + |
| 143 | +**Wichtig**: Separate Verbindung – mögliche marginale Abweichung zu vom HttpClient genutzter Verbindung bei sehr kurzlebigen DNS-Einträgen. |
| 144 | + |
| 145 | +--- |
| 146 | + |
| 147 | +## API-Endpoints |
| 148 | + |
| 149 | +| Method | Path | Beschreibung | |
| 150 | +|--------|------|-------------| |
| 151 | +| `POST` | `/client` | HTTP-Request weiterleiten | |
| 152 | +| `GET` | `/api/k8s/context` | Pod-Name, Namespace, Istio-Status | |
| 153 | +| `GET` | `/api/k8s/status` | K8s-Client-Initialisierungsstatus | |
| 154 | +| `GET` | `/api/k8s/istio/full-report` | Envoy Config + Fehler-Metriken + Diagnosen | |
| 155 | +| `GET` | `/api/k8s/istio/{type}?namespace=` | Istio-Ressourcen eines Typs | |
| 156 | +| `GET` | `/api/k8s/correlate?url=&namespace=` | URL gegen VS/DR/SE korrelieren | |
| 157 | +| `GET` | `/api/k8s/tls?url=` | TLS-Zertifikatskette inspizieren | |
| 158 | + |
| 159 | +--- |
| 160 | + |
| 161 | +## Frontend – Tabs & Features |
| 162 | + |
| 163 | +### Hauptbereich (linke Spalte) |
| 164 | +- HTTP-Method-Dropdown + URL-Feld |
| 165 | +- Dynamische Custom-Header (Key/Value, add/remove) |
| 166 | +- Body-Textarea (JSON) |
| 167 | +- Option: Browser-Header kopieren |
| 168 | +- Buttons: **K8s/Istio Diagnose** | **Send Request** |
| 169 | + |
| 170 | +**Response-Bereich** nach jedem Request: |
| 171 | +- `HTTP {status}`-Badge (grün/rot) |
| 172 | +- `HTTP/2` / `HTTP/1.1`-Badge (blau/grau) – tatsächlich verwendetes Protokoll |
| 173 | +- Resolved-IP-Badge (alle aufgelösten IPs) |
| 174 | +- Dauer in ms |
| 175 | +- **Redirect-Chain**: aufklappbare Schritte `URL → 301 HTTP/1.1 → URL → 302 HTTP/2 → ...` |
| 176 | +- **TLS-Panel** (nur HTTPS): TLS-Version, Cipher Suite, mTLS/SPIFFE-Badge, aufklappbare Cert-Cards pro Zertifikat (leaf/intermediate/root) mit Ablauf-Countdown |
| 177 | + |
| 178 | +### Historie (rechte Spalte) |
| 179 | +- Bis zu 50 Einträge in `localStorage` |
| 180 | +- Grüner/roter Rand je nach Status |
| 181 | +- Klick stellt Formular wieder her |
| 182 | +- Export als JSON |
| 183 | + |
| 184 | +### Istio-Panel (nach Diagnose-Klick) |
| 185 | + |
| 186 | +**Tab A – Config & Erreichbarkeit** |
| 187 | +- Envoy Config JSON (aufklappbar) |
| 188 | +- Cluster-Suche mit Zähler |
| 189 | +- Aktive Endpoints als scrollbares Konsolen-Output |
| 190 | + |
| 191 | +**Tab B – Aktive Fehler-Metriken** |
| 192 | +- Ziel-URL-Korrelation: hebt Metriken hervor die den Hostnamen der URL enthalten |
| 193 | +- Diagnose-Karten (KRITISCH/WARNUNG/INFO) mit Beschreibung + Empfehlung + betroffene Metriken |
| 194 | +- Vollständige Metriken-Tabelle (alle Werte inkl. 0) |
| 195 | + |
| 196 | +**Tab C – Pod Kontext & Istio** |
| 197 | +- Navbar-Badges: Pod-Name, Namespace, Istio ON/OFF |
| 198 | +- Kontext-Tabelle: podName, namespace, istioSidecar |
| 199 | +- Istio Sidecar Details (clusterSummary, networkStats) |
| 200 | +- **URL-Korrelations-Report** (wenn URL eingegeben): |
| 201 | + - `Match gefunden` (grün) oder `Kein Match` (gelb) |
| 202 | + - VirtualServices: pro Route ob Pfad-Match + Destinations + Timeout/Retries/FaultInjection |
| 203 | + - DestinationRules: TrafficPolicy-Badges + Subsets mit Labels |
| 204 | + - ServiceEntries: location, resolution, Ports, Port-Match-Warnung |
| 205 | +- Istio-Ressourcen-Liste (alle 9 Typen): |
| 206 | + - Gematchte Ressourcen: grüner Rand + `URL-Match`-Badge + automatisch aufgeklappt |
| 207 | + - Nicht gematchte: zugeklappt |
| 208 | +- K8s-Client-Status |
| 209 | + |
| 210 | +--- |
| 211 | + |
| 212 | +## Konfiguration |
| 213 | + |
| 214 | +| Variable | Default | Beschreibung | |
| 215 | +|----------|---------|-------------| |
| 216 | +| `ENVOY_ADMIN_URL` | `http://127.0.0.1:15000` | Envoy Admin API Adresse | |
| 217 | +| `KUBERNETES_NAMESPACE` | `default` | Fallback wenn Namespace-Datei nicht lesbar | |
| 218 | + |
| 219 | +Namespace wird primär aus `/var/run/secrets/kubernetes.io/serviceaccount/namespace` gelesen. |
| 220 | + |
| 221 | +--- |
| 222 | + |
| 223 | +## Deployment |
| 224 | + |
| 225 | +``` |
| 226 | +Namespace: clients |
| 227 | +Service: ClusterIP :8080 |
| 228 | +Istio: Gateway in istio-ingress |
| 229 | +TLS: cert-manager ClusterIssuer |
| 230 | +Hosts: javahttpclient.tp.lan / javahttpclient.gmk.lan |
| 231 | +``` |
| 232 | + |
| 233 | +**Docker-Builds:** |
| 234 | +- `Dockerfile25` – Java 25 mit JRE |
| 235 | +- `Dockerfile25Jlink` – Java 25 mit JLink Custom Image (~295MB vs ~510MB) |
| 236 | +- AOT aktiv: `spring-boot:process-aot` im Build, `-Dspring.aot.enabled=true` zur Laufzeit |
| 237 | + |
| 238 | +--- |
| 239 | + |
| 240 | +## Bekannte Einschränkungen |
| 241 | + |
| 242 | +- **TLS-Inspektion**: Separate Verbindung – bei extrem kurzlebigen DNS-Einträgen kann die angezeigte IP marginal abweichen |
| 243 | +- **mTLS via Istio**: Wenn Traffic durch Envoy-Sidecar (iptables intercept) geht, handelt Envoy das TLS – Java sieht dann ggf. plain HTTP intern. TLS-Panel zeigt in dem Fall das Cert des Envoy-Proxys (SPIFFE-Cert), nicht des Zielservices direkt. |
| 244 | +- **HTTP/2 h2c**: Cleartext HTTP/2 funktioniert nur wenn der Zielserver h2c-Upgrade unterstützt; sonst automatischer Fallback auf HTTP/1.1 |
| 245 | +- **Redirect Auth-Header**: Browser-Header werden nur beim ersten Hop mitgeschickt (Schutz vor Token-Leak bei Cross-Domain-Redirects) |
| 246 | +- **Envoy-Stats**: `upstream_cx_connect_fail` in den Diagnosen greift nur wenn der Traffic durch den Envoy-Sidecar proxied wird |
| 247 | +- **K8s-API**: ServiceAccount muss RBAC-Rechte auf Istio Custom Resources haben (get/list) |
0 commit comments