Das Grundbild: Umschläge in Umschlägen
Jede Nachricht im Netz ist verschachtelt wie ein Brief in mehreren Umschlägen. Innen der eigentliche Inhalt, etwa ein HTTP-Request. Jeder Layer legt einen Umschlag mit seiner eigenen Adresse darum.
Vier Umschläge, vier Adressarten
Die Layer auf einen Blick
| Layer | Bild | Adresse | Typisches Gerät |
|---|---|---|---|
| L1 | Straße, LKW | – (nur Bits) | Kabel, NIC, Transceiver |
| L2 | Hausflur | MAC-Adresse | Switch, Bridge, OVS |
| L3 | Postadresse | IP-Adresse | Router |
| L4 | Wohnungsnummer + Versandart | Port, TCP/UDP | L4-Loadbalancer, kube-proxy |
| L7 | Inhalt des Briefs | Host, Pfad, Header | Reverse Proxy, Ingress-Controller, Envoy |
OSI-Modell gegen Praxis
Das OSI-Modell hat sieben Layer. Im Alltag zählen vier: L2, L3, L4 und L7. L5 (Sitzung) und L6 (Darstellung) existieren im TCP/IP-Stack nicht als eigene Schichten – ihre Aufgaben stecken in der Anwendung oder in TLS. Wenn im Kubernetes-Kontext von „L7“ die Rede ist, ist praktisch alles oberhalb von TCP/UDP gemeint.
Die Layer einzeln
Was jeder Layer tut, wo er im Cluster auftaucht und mit welchem Befehl man ihn prüft.
L1 · Bitübertragung – Kabel und Link-Status
Kabel, Glasfaser, Netzwerkkarte, Link-Status. Hier gibt es keine Adressen, nur Signale.
Im Cluster: selten ein Thema, außer auf Bare Metal. Typische Fälle sind ein Link, der down ist, falsche Geschwindigkeit oder falsches Duplex, defekte Transceiver oder SR-IOV-Virtual-Functions, die nicht hochkommen.
ip link show
ethtool ens1f0
L2 · Sicherung – Ethernet und MAC
Zustellung innerhalb eines Segments (einer Broadcast-Domäne, eines VLANs). Ein Switch lernt, hinter welchem Port welche MAC-Adresse sitzt. ARP übersetzt „ich suche IP 10.0.0.5“ in „die MAC dazu ist aa:bb:cc:…“. Außerhalb des Segments ist L2 blind – dort übernimmt L3.
Im Cluster:
- Jeder Pod hängt über ein veth-Paar an einer Bridge oder an Open vSwitch auf dem Node – reines L2.
- MetalLB im L2-Modus: genau ein Node antwortet auf ARP-Anfragen für die Service-VIP. Fällt er aus, übernimmt ein anderer und verschickt ein Gratuitous ARP. Das ist Failover, kein Loadbalancing über Nodes.
- Multus mit
macvlanoderbridgehängt Pods direkt in ein physisches L2-Netz. - VLANs, Bonding und Link-Aggregation spielen ebenfalls hier.
ip neigh # ARP-Tabelle: welche IP hat welche MAC?
arping -I ens1f0 10.0.0.50 # antwortet jemand auf die VIP?
L3 · Vermittlung – IP über Netzgrenzen
Zustellung über Netzgrenzen hinweg. Ein Router schaut nur auf die Ziel-IP und entscheidet anhand seiner Routing-Tabelle. Den Inhalt sieht er nicht.
Im Cluster:
- Jede Pod-IP und jede Node-IP ist L3. Das CNI (OVN-Kubernetes, Calico, Cilium, Flannel) macht Pod-IPs auf anderen Nodes erreichbar – per Overlay oder per direktem Routing bzw. BGP.
- EgressIP schreibt die Quell-IP ausgehender Pakete auf eine feste Adresse um (SNAT).
- MetalLB im BGP-Modus kündigt die VIP als Route bei den Upstream-Routern an. Mit ECMP ist echtes Verteilen über mehrere Nodes möglich.
ipBlockin einer NetworkPolicy filtert nach IP-Bereichen.
ip route get 10.131.0.22 # welchen Weg nimmt das Paket?
ping 10.131.0.22
tracepath 10.131.0.22 # Weg und Path-MTU
L4 · Transport – TCP, UDP, Ports
Auf L4 kommen Port und Versandart dazu. TCP ist ein Einschreiben mit Rückschein: Handshake, garantierte Reihenfolge, erneute Übertragung bei Verlust. UDP ist eine Postkarte: schnell, ohne Garantie.
Im Cluster:
- Ein Service ist im Kern eine L4-Regel:
port,targetPort,protocol: TCP | UDP | SCTP. - kube-proxy (iptables, IPVS oder nftables) bzw. OVN-Kubernetes übersetzen
ClusterIP:Portper DNAT aufPod-IP:targetPort. - NodePort und LoadBalancer sind L4 – sie verteilen Verbindungen, keine Requests.
- conntrack merkt sich jede Verbindung, damit Antwortpakete zurückübersetzt werden.
portsin einer NetworkPolicy filtert auf L4.
nc -zv 10.131.0.22 8080 # Port erreichbar?
ss -tlnp # wer lauscht wo?
conntrack -L -d 172.30.14.7 # aktive NAT-Einträge zur ClusterIP
L5 / L6 · Sitzung und Darstellung – und wo TLS hingehört
Diese Layer spielen in der Praxis keine eigenständige Rolle. TLS wird oft hier einsortiert. Pragmatisch liegt TLS zwischen L4 und L7: Es läuft über TCP und verschlüsselt alles, was L7 ist.
L7 · Anwendung – Host, Pfad, Header
Hier liegt der Inhalt: HTTP-Methode, Host, Pfad, Header, Cookies, gRPC-Methode, DNS-Anfragen.
Im Cluster:
- Ingress, OpenShift Routes (edge/reencrypt), Gateway API HTTPRoute und GRPCRoute
- Service Mesh (Envoy-Sidecars oder Waypoint-Proxies)
- CoreDNS – DNS ist ein L7-Protokoll, das L3-Adressen liefert
httpGet- undgrpc-Probes
curl -v https://app.example.de/api/health
dig +short my-svc.my-ns.svc.cluster.local
Was ein Gerät sieht, bestimmt, was es kann
Die wichtigste Konsequenz des Umschlag-Modells: Ein Gerät kann nur nach dem entscheiden, was es liest. Daraus folgen die meisten Architekturentscheidungen.
Wie weit welches Gerät liest
Drei Konsequenzen
- Ein L4-Loadbalancer kann nicht nach
/apigegen/webrouten, keine Header setzen und keine Cookies für Sticky Sessions auswerten. Er öffnet den Umschlag mit dem HTTP-Inhalt nie. - Ein L7-Proxy kann all das, muss dafür aber TLS terminieren. Er braucht Zertifikat und Schlüssel und baut eine eigene, neue Verbindung zum Backend auf.
- SNI ist der Sonderfall dazwischen: Der Hostname steht unverschlüsselt im TLS-ClientHello. Ein Proxy kann danach routen, ohne TLS aufzubrechen – so funktionieren OpenShift-Passthrough-Routes und die Gateway-API-TLSRoute.
Der Weg eines Requests durch den Cluster
Von außen herein passieren mehrere Stationen, und jede entscheidet auf ihrem eigenen Layer.
Von außen in den Cluster
| Station | Layer | Was dort entschieden wird |
|---|---|---|
| Externer LB | L4 (L2/L3 für die VIP) | welcher Node bekommt die TCP-Verbindung |
| Router / Ingress-Controller | L7 | welche Route zu Host und Pfad passt, welcher Backend-Pod |
| Service | – | liefert die Liste der Endpoints |
| CNI / Overlay | L3 (+ L2 lokal) | wie das Paket zum Node des Ziel-Pods kommt |
| Pod | L4 → L7 | App lauscht auf dem Port und verarbeitet den Request |
sessionAffinity oder kube-proxy-Regeln an dieser Stelle nicht.Innerhalb des Clusters: Pod zu Service
Pod A ClusterIP Pod B
10.128.2.15 ──TCP──▸ 172.30.14.7:80 ──DNAT──▸ 10.131.0.22:8080
(existiert nur als Regel)
Die ClusterIP ist virtuell. Es gibt kein Interface mit dieser Adresse. kube-proxy bzw. OVN schreiben beim ersten Paket Ziel-IP und Ziel-Port um (DNAT, also L3 + L4) und merken sich das in conntrack.
Overlay: der Umschlag im Umschlag
Spricht Pod A auf Node 1 mit Pod B auf Node 2, kennt das physische Netz die Pod-IPs nicht. Ein Overlay packt deshalb den kompletten Ethernet-Frame des Pods in ein UDP-Paket zwischen den Nodes – Geneve bei OVN-Kubernetes, VXLAN bei Flannel, Calico und Cilium.
Kapselung im Detail
Das Netzwerk zwischen den Nodes sieht nur Node-IPs und UDP-Port 6081 (Geneve) bzw. 4789 (VXLAN). Diese Ports müssen zwischen allen Nodes offen sein.
Die MTU-Falle
Die äußeren Header kosten Platz. Bei OVN-Kubernetes sind das 100 Byte: Bei einer Host-MTU von 1500 bleibt für Pods eine MTU von 1400. Ist irgendwo auf dem Weg die MTU kleiner als angenommen und wird ICMP „Fragmentation needed“ geblockt, entsteht ein klassisches Fehlerbild:
ping, SSH und kleine API-Calls funktionieren- große Uploads, große Responses oder TLS-Handshakes mit langen Zertifikatsketten hängen einfach
# Pod-MTU 1400: 1400 - 20 (IP) - 8 (ICMP) = 1372 Byte Nutzlast
ping -M do -s 1372 10.131.0.22 # muss gehen
ping -M do -s 1373 10.131.0.22 # muss mit "message too long" scheitern
tracepath 10.131.0.22 # zeigt die Path-MTU pro Hop
Kubernetes-Bausteine nach Layer
Die Zuordnungstabelle ist der Kern des Themas. Alles danach erklärt einzelne Zeilen daraus.
| Baustein | Layer | Entscheidet anhand |
|---|---|---|
| veth, Bridge, OVS-Port | L2 | MAC |
| MetalLB L2-Modus | L2 | ARP für die VIP |
| Multus macvlan/bridge | L2 | MAC im physischen Netz |
| CNI-Routing, Overlay | L3 | Pod-/Node-IP |
| EgressIP | L3 | Quell-IP (SNAT) |
| MetalLB BGP-Modus | L3 | Routen-Announcement |
| NetworkPolicy | L3 + L4 | Pod-Labels → IPs, ipBlock, Port |
| Service (ClusterIP, NodePort, LoadBalancer) | L4 | IP + Port, DNAT |
sessionAffinity: ClientIP | L3 | Quell-IP |
| Route passthrough, Gateway TLSRoute | L4 + SNI | Hostname im ClientHello |
| Route edge/reencrypt, Ingress, HTTPRoute | L7 | Host, Pfad, Header |
| Service Mesh (Sidecar, Waypoint) | L7 | Methode, Pfad, Header, Identität |
| CoreDNS | L7 | DNS-Name |
Services und kube-proxy (L4)
Ein Service verteilt Verbindungen, nicht Requests. Die Auswahl des Backends passiert beim Verbindungsaufbau, danach bleibt die Verbindung per conntrack am selben Pod. Für kurze HTTP/1.1-Verbindungen reicht das; für langlebige wird es zum Problem (Abschnitt 6).
Ein Service ohne passende Endpoints – falscher Selector, keine Ready-Pods – liefert meist ein Connection refused, kein Timeout:
kubectl get endpointslices -l kubernetes.io/service-name=my-svc
OpenShift Routes: edge, reencrypt, passthrough
| Termination | Router arbeitet auf | Zertifikat am Router | Pod bekommt |
|---|---|---|---|
edge | L7 | ja | HTTP im Klartext |
reencrypt | L7 | ja, plus CA für das Backend | neue TLS-Verbindung vom Router |
passthrough | L4 + SNI | nein | Original-TLS vom Client |
Bei passthrough entfallen alle L7-Funktionen: kein Pfad-Routing, kein X-Forwarded-For, keine Header-Manipulation. Dafür bleibt die Ende-zu-Ende-Verschlüsselung erhalten, und die App verwaltet ihr Zertifikat selbst.
Gateway API: der Layer steht im Ressourcennamen
| Route-Typ | Layer | Entscheidet anhand |
|---|---|---|
HTTPRoute | L7 | Host, Pfad, Header, Methode, Query |
GRPCRoute | L7 | gRPC-Service und -Methode |
TLSRoute | L4 + SNI | Hostname aus dem ClientHello |
TCPRoute / UDPRoute | L4 | Listener-Port |
TLSRoute, TCPRoute und UDPRoute waren lange nur im Experimental-Channel.NetworkPolicy (L3 + L4) – und wo sie aufhört
Eine NetworkPolicy beantwortet genau zwei Fragen: Wer (L3: Pods, Namespaces, IP-Blöcke) darf auf welchen Port (L4)?
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-to-api
spec:
podSelector:
matchLabels:
app: api
policyTypes: [Ingress]
ingress:
- from:
- podSelector:
matchLabels:
app: frontend # L3: wer – Labels werden zu Pod-IPs aufgelöst
ports:
- protocol: TCP
port: 8080 # L4: welcher Port
Eine Regel wie „GET /api/orders erlaubt, DELETE verboten“ ist damit nicht ausdrückbar. Dafür braucht es L7-Policies, etwa CiliumNetworkPolicy mit HTTP-Regeln oder eine Istio AuthorizationPolicy.
Service Mesh: Sidecar gegen Ambient
Ein klassisches Sidecar-Mesh schickt jede Verbindung durch einen Envoy-Proxy im Pod und arbeitet damit durchgehend auf L7.
Istio Ambient trennt die Layer explizit:
- ztunnel (einer pro Node) arbeitet auf L4: mTLS, Autorisierung nach Identität und Port, Metriken pro Verbindung.
- Waypoint-Proxy (optional, pro Namespace oder Service) arbeitet auf L7: Retries, Header-Routing, L7-Policies, Metriken pro Request.
Die Frage „brauche ich L7?“ entscheidet damit direkt über Ressourcenverbrauch und Latenz.
Probes – auch Health-Checks haben einen Layer
| Probe | Layer | Prüft |
|---|---|---|
tcpSocket | L4 | Port nimmt Verbindungen an |
httpGet | L7 | HTTP-Status 200–399 |
grpc | L7 | gRPC Health Checking Protocol |
exec | – | Exit-Code eines Kommandos im Container |
tcpSocket-Probe bleibt grün, solange der Port offen ist – auch wenn die Anwendung dahinter längst hängt. httpGet auf einen echten Health-Endpoint sagt mehr aus.L4- gegen L7-Loadbalancing
Der Unterschied in einem Satz: L4 verteilt Verbindungen, L7 verteilt Requests. Daraus folgt alles Weitere.
| L4 | L7 | |
|---|---|---|
| Verteilt | Verbindungen | Requests |
| Sieht | IP, Port | Host, Pfad, Header, Cookies |
| TLS | bleibt zu (oder SNI) | wird terminiert |
| Zertifikat nötig | nein | ja |
| Client-IP beim Backend | bleibt erhalten oder geht per SNAT verloren | steht in X-Forwarded-For |
| Overhead | gering | höher (Parsing, neue Verbindung) |
| Beispiele | Service, NodePort, MetalLB, F5 im L4-Modus | Router edge, Ingress-NGINX, Envoy, HTTPRoute |
Das gRPC-Problem
gRPC nutzt HTTP/2 und hält eine TCP-Verbindung lange offen, über die tausende Requests laufen. Ein L4-Loadbalancer verteilt aber nur die Verbindung:
Client-Pod ──TCP-Verbindung──▸ Service (L4) ──zugeordnet──▸ Pod A
Request 1 ────────────────────────────────▸ Pod A
Request 2 ────────────────────────────────▸ Pod A
Request 3 … 10.000 ──────────────────────▸ Pod A
Pod B ← bekommt nichts
Lösungen:
- Headless Service (
clusterIP: None) plus clientseitiges Loadbalancing: DNS liefert alle Pod-IPs, der gRPC-Client verteilt selbst (round_robin). - L7-Proxy dazwischen (Envoy, Mesh, GRPCRoute), der pro Request verteilt.
- Serverseitig
MaxConnectionAgesetzen, damit Clients regelmäßig neu verbinden.
Wo bleibt die Client-IP?
- L4 mit
externalTrafficPolicy: Cluster(Default): Der Node leitet eventuell an einen Pod auf einem anderen Node weiter und macht dabei SNAT – die Client-IP geht verloren. - L4 mit
externalTrafficPolicy: Local: Traffic geht nur an Pods auf dem empfangenden Node. Die Client-IP bleibt erhalten, die Verteilung kann ungleichmäßig werden. - L7-Proxy: Der Proxy baut eine neue Verbindung auf. Das Backend sieht die Proxy-IP, die Client-IP steht in
X-Forwarded-For. - L4-LB vor L7-Router: Das PROXY-Protokoll reicht die Client-IP als Präfix der TCP-Verbindung durch – LB und Ingress-Controller müssen es beide aktiviert haben.
Fehlersuche von unten nach oben
Die Layer liefern eine feste Reihenfolge. Jeder Test schließt einen Layer aus oder ein – der erste Test, der scheitert, zeigt, wo das Problem sitzt.
Der Entscheidungsbaum
refused heißt, das Ziel wurde erreicht, aber dort lauscht nichts. timeout heißt, etwas unterwegs verwirft die Pakete – meist eine Policy oder Firewall.Befehle pro Layer
| Layer | Frage | Befehl |
|---|---|---|
| L1 | Link da? | ip link show, ethtool <nic> |
| L2 | Nachbar per MAC bekannt? | ip neigh, arping -I <nic> <ip> |
| L3 | Weg zur IP? MTU? | ip route get <ip>, ping, tracepath <ip> |
| L4 | Port offen? Wer lauscht? | nc -zv <ip> <port>, ss -tlnp, conntrack -L |
| TLS | Zertifikat, SNI | openssl s_client -connect <host>:443 -servername <host> |
| L7 | Antwort korrekt? | curl -v, dig, grpcurl |
Werkzeuge im Cluster
# Debug-Container mit Netzwerk-Tools in den Network-Namespace eines Pods hängen
kubectl debug -it <pod> --image=nicolaka/netshoot --target=<container>
# Auf dem Node (OpenShift): Debug-Pod starten, dann Host-Dateisystem nutzen
oc debug node/<node>
chroot /host
tcpdump -i any -nn host 10.131.0.22 and port 8080
# Router oder Gateway testen, ohne DNS: Host-Header und SNI erzwingen
curl -v --resolve app.example.de:443:<router-ip> https://app.example.de/
# Klassiker auf L4: lauscht die App nur auf localhost?
kubectl exec <pod> -- ss -tlnp
# 127.0.0.1:8080 → von außen nicht erreichbar; 0.0.0.0:8080 oder *:8080 → ok
Grauzonen und Spickzettel
Manches passt nicht sauber in einen Layer. Das Modell ist eine Denkhilfe, keine Naturgesetzgebung.
Die Grauzonen
- ARP verbindet L3 und L2: Es fragt nach einer IP und antwortet mit einer MAC.
- TLS läuft über L4 und verschlüsselt L7 – meist als „zwischen L4 und L7“ eingeordnet.
- SNI-Routing liest ein kleines Stück aus dem TLS-Handshake, ohne zu entschlüsseln. Manche nennen das scherzhaft „L4,5“.
- DNS ist ein L7-Protokoll, dessen Ergebnis L3-Adressen sind. DNS-Probleme sehen deshalb oft wie Netzwerkprobleme aus.
- NetworkPolicy kombiniert L3 (wer) und L4 (welcher Port).
Übersetzungshilfe für den Flurfunk
| Jemand sagt … | … und meint |
|---|---|
| „Das ist ein L2-Problem.“ | ARP, MAC, VLAN, gleiches Segment. Oft VIP-Failover oder falsches VLAN. |
| „Das ist L3-Routing.“ | Routen, Subnetze, Overlay, MTU. Ping auf Pod-IPs ist der erste Test. |
| „Der LB ist nur L4.“ | Kein Pfad-Routing, keine Header, TLS bleibt zu, Client-IP eventuell weg. |
| „Mach das auf L7.“ | Proxy muss TLS terminieren, kann dann nach Host, Pfad und Header entscheiden. |
| „Passthrough.“ | L4 + SNI, Zertifikat liegt in der App, keine L7-Features am Router. |
| „Dafür brauchst du eine L7-Policy.“ | NetworkPolicy reicht nicht, weil nach HTTP-Methode oder Pfad gefiltert werden soll. |
Zum Mitnehmen
- Grundregel„Passiert auf Layer X“ heißt: Die Entscheidung fällt nur anhand der Daten bis Layer X.
- Debug-Reihenfolge
pingtestet L3,nc -zvtestet L4,curltestet L7. Der erste Test, der scheitert, zeigt den Layer. - Reihenfolge der Layer (7 → 1)Alle Deutschen Studenten Trinken Verschiedene Sorten Bier.