Hoe behoud je het bron-IP van verzoeken na load balancing in een K8s-cluster
Categories:
Inleiding
Applicatie-implementatie is niet altijd simpelweg installeren en uitvoeren, soms moet je ook rekening houden met netwerkproblemen. Dit artikel legt uit hoe je in een K8s-cluster ervoor zorgt dat services het bron-IP van verzoeken kunnen verkrijgen.
Applicaties die diensten leveren zijn afhankelijk van invoerinformation, als die invoer niet afhankelijk is van de vijf-tupel (bron-IP, bronpoort, doel-IP, doelpoort, protocol), dan heeft de dienst een lage netwerkkoppeling en hoeft het zich geen zorgen te maken over netwerkdetails.
Daarom is het voor de meeste mensen niet nodig om dit artikel te lezen. Als je geïnteresseerd bent in netwerken, of je horizon wilt verbreden, kun je doorgaan met lezen om meer service-scenario’s te begrijpen.
Dit artikel is gebaseerd op K8s v1.29.4. In het artikel worden pod en endpoint soms door elkaar gebruikt; in dit scenario kun je ze als equivalent beschouwen.
Als er fouten zijn, welkom om te corrigeren, ik zal het kịp tijdig aanpassen.
Waarom gaat de bron-IP-informatie verloren?
Laten we eerst verduidelijken wat het bron-IP is. Wanneer A een verzoek stuurt naar B, en B stuurt het verzoek door naar C, ziet C in het IP-protocol het bron-IP van B, maar in dit artikel beschouwen we het IP van A als het bron-IP.
Er zijn voornamelijk twee soorten gedragingen die leiden tot verlies van broninformatie:
- Netwerkadresvertaling (NAT), met als doel het besparen van publieke IPv4-adressen, load balancing, enz. Dit zorgt ervoor dat de server het bron-IP van het NAT-apparaat ziet, in plaats van het echte bron-IP.
- Proxy, reverse proxy (RP, Reverse Proxy) en load balancer (LB, Load Balancer) behoren allemaal tot deze categorie, hieronder samengevat als proxyserver. Deze proxyservices sturen verzoeken door naar backend-services, maar vervangen het bron-IP door hun eigen IP.
- NAT komt neer op IP-ruimte ruilen voor poortruimte. IPv4-adressen zijn beperkt, één IP-adres kan 65535 poorten mappen. In de meeste gevallen zijn deze poorten niet volledig gebruikt, dus kunnen meerdere subnet-IP’s één publiek IP delen en services onderscheiden op poortniveau. De gebruiksvorm is:
public IP:public port -> private IP_1:private port. Meer informatie vind je in Netwerkadresvertaling - Proxyservices zijn bedoeld om te verbergen of bloot te stellen. Proxyservices sturen verzoeken door naar backend-services en vervangen het bron-IP door hun eigen IP, om het echte IP van de backend-service te verbergen en de veiligheid te beschermen. De gebruiksvorm is:
client IP -> proxy IP -> server IP. Meer informatie vind je in Proxy
NAT en proxyservers zijn zeer gebruikelijk, de meeste services kunnen het bron-IP van verzoeken niet verkrijgen.
Dit zijn de twee meest voorkomende manieren om het bron-IP te wijzigen; suggesties voor anderen zijn welkom.
Hoe behoud je het bron-IP?
Hier is een voorbeeld van een HTTP-verzoek:
| Veld | Lengte (bytes) | Bitverschuiving | Beschrijving |
|---|---|---|---|
| IP-kop | |||
Bron-IP | 4 | 0-31 | IP-adres van de verzender |
| Doel-IP | 4 | 32-63 | IP-adres van de ontvanger |
| TCP-kop | |||
| Bronpoort | 2 | 0-15 | Verzendpoortnummer |
| Doelpoort | 2 | 16-31 | Ontvangpoortnummer |
| Sequencenummer | 4 | 32-63 | Gebruikt om de byte-stroom te identificeren die door de verzender is verzonden |
| Bevestigingsnummer | 4 | 64-95 | Als de ACK-vlag is ingesteld, het volgende verwachte sequencenummer |
| Gegevensverschuiving | 4 | 96-103 | Aantal bytes van de TCP-kop tot het begin van de gegevens |
| Gereserveerd | 4 | 104-111 | Gereserveerd veld, niet gebruikt, ingesteld op 0 |
| Vlagbits | 2 | 112-127 | Verschillende controle-vlaggen, zoals SYN, ACK, FIN, enz. |
| Venstergrootte | 2 | 128-143 | Hoeveelheid gegevens die de ontvanger kan ontvangen |
| Controlesom | 2 | 144-159 | Gebruikt om te detecteren of gegevens fouten hebben tijdens transmissie |
| Dringendheidspointer | 2 | 160-175 | Positie van dringende gegevens die de verzender wil dat de ontvanger snel verwerkt |
| Opties | Variabel | 176-… | Kan tijdstempels, maximale segmentlengte, enz. bevatten |
| HTTP-kop | |||
| Verzoekregel | Variabel | …-… | Bevat verzoekmethode, URI en HTTP-versie |
Kopvelden | Variabel | …-… | Bevat verschillende kopvelden, zoals Host, User-Agent, enz. |
| Lege regel | 2 | …-… | Gebruikt om kop en body te scheiden |
| Body | Variabel | …-… | Optionele verzoek- of responsbody |
Uit de bovenstaande HTTP-verzoekstructuur blijkt dat TCP-opties, verzoekregel, kopvelden en body variabel zijn. De TCP-optiesruimte is beperkt en wordt meestal niet gebruikt om bron-IP door te geven. De verzoekregel draagt vaste informatie die niet kan worden uitgebreid. De HTTP-body kan na versleuteling niet worden gewijzigd. Alleen de HTTP-kopvelden zijn geschikt om uit te breiden voor het doorgeven van bron-IP.
In de HTTP-header kan het veld X-REAL-IP worden toegevoegd om het bron-IP door te geven. Deze operatie wordt meestal uitgevoerd op de proxyserver, waarna de proxyserver het verzoek doorstuurt naar de backend-service, die het bron-IP via dit veld kan verkrijgen.
Let op: zorg ervoor dat de proxyserver vóór het NAT-apparaat staat, zodat het het echte bron-IP van het verzoek kan verkrijgen. In Alibaba Cloud-producten zien we de Load Balancer als een aparte categorie; de positie in het netwerk verschilt van gewone applicatieservers.
K8S-bedieningshandleiding
Gebruik het whoami-project als voorbeeld voor de implementatie.
Deployment maken
Maak eerst de service:
apiVersion: apps/v1
kind: Deployment
metadata:
name: whoami-deployment
spec:
replicas: 3
selector:
matchLabels:
app: whoami
template:
metadata:
labels:
app: whoami
spec:
containers:
- name: whoami
image: docker.io/traefik/whoami:latest
ports:
- containerPort: 8080
Deze stap creëert een Deployment met 3 Pods, elk met een container die de whoami-service draait.
Service maken
Je kunt een NodePort- of LoadBalancer-type service maken voor externe toegang, of een ClusterIP-type service voor alleen interne clustertoegang, en dan een Ingress-service toevoegen om externe toegang bloot te stellen.
NodePort kan zowel via NodeIP:NodePort als via Ingress-service worden benaderd, handig voor testen. In deze sectie gebruiken we de NodePort-service.
apiVersion: v1
kind: Service
metadata:
name: whoami-service
spec:
type: NodePort
selector:
app: whoami
ports:
- protocol: TCP
port: 80
targetPort: 8080
nodePort: 30002
Na het maken van de service, toegang met curl whoami.example.com:30002, en je ziet dat het geretourneerde IP het NodeIP is, niet het bron-IP van het verzoek.
Let op, dit is niet het juiste client-IP; het zijn interne IP’s van het cluster. Dit is wat er gebeurt:
- De client stuurt een datapakket naar node2:nodePort
- node2 vervangt het bron-IP-adres van het datapakket door zijn eigen IP-adres (SNAT)
- node2 vervangt het doel-IP op het datapakket door het Pod-IP
- Het datapakket wordt gerouteerd naar node1 en dan naar het endpoint
- De reactie van de Pod wordt gerouteerd terug naar node2
- De reactie van de Pod wordt teruggestuurd naar de client
Weergegeven in een diagram:

externalTrafficPolicy: Local configureren
Om dit te voorkomen, heeft Kubernetes een functie om het client-bron-IP te behouden. Als je service.spec.externalTrafficPolicy instelt op Local, zal kube-proxy verzoeken alleen proxyen naar lokale endpoints en verkeer niet doorsturen naar andere nodes.
apiVersion: v1
kind: Service
metadata:
name: whoami-service
spec:
type: NodePort
externalTrafficPolicy: Local
selector:
app: whoami
ports:
- protocol: TCP
port: 80
targetPort: 8080
nodePort: 30002
Test met curl whoami.example.com:30002. Wanneer whoami.example.com is gemapt naar IP’s van meerdere nodes in het cluster, is er een zekere kans dat het niet toegankelijk is. Je moet bevestigen dat de DNS-record alleen de IP bevat van de node waar het endpoint (pod) zich bevindt.
Deze configuratie heeft een kost: het verlies van load balancing binnen het cluster. Clients krijgen alleen een reactie als ze de node bezoeken waar het endpoint is geïmplementeerd.

Wanneer de client Node 2 benadert, is er geen reactie.
Ingress maken
De meeste services worden aan gebruikers aangeboden via http/https. De vorm https://ip:port voelt vreemd voor gebruikers. Meestal wordt Ingress gebruikt om de eerder gemaakte NodePort-service te load balancen naar poort 80/443 onder een domein.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: whoami-ingress
namespace: default
spec:
ingressClassName: external-lb-default
rules:
- host: whoami.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: whoami-service
port:
number: 80
Na toepassing, test met curl whoami.example.com, en je ziet dat ClientIP altijd het Pod-IP is van de Ingress Controller op de node waar het endpoint zich bevindt.
root@client:~# curl whoami.example.com
...
RemoteAddr: 10.42.1.10:56482
...
root@worker:~# kubectl get -n ingress-nginx pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
ingress-nginx-controller-c8f499cfc-xdrg7 1/1 Running 0 3d2h 10.42.1.10 k3s-agent-1 <none> <none>
Het gebruik van Ingress als reverse proxy voor de NodePort-service betekent dat er twee service-lagen voor het endpoint zijn. Het onderstaande diagram toont het verschil.
graph LR
A[Client] -->|whoami.example.com:80| B(Ingress)
B -->|10.43.38.129:32123| C[Service]
C -->|10.42.1.1:8080| D[Endpoint]graph LR
A[Client] -->|whoami.example.com:30001| B(Service)
B -->|10.42.1.1:8080| C[Endpoint]In pad 1 bereikt extern verkeer naar Ingress eerst het endpoint Ingress Controller, en dan het endpoint whoami.
De Ingress Controller is in wezen een LoadBalancer-service.
kubectl -n ingress-nginx get svc
NAMESPACE NAME CLASS HOSTS ADDRESS PORTS AGE
default echoip-ingress nginx ip.example.com 172.16.0.57,2408:4005:3de:8500:4da1:169e:dc47:1707 80 18h
default whoami-ingress nginx whoami.example.com 172.16.0.57,2408:4005:3de:8500:4da1:169e:dc47:1707 80 16h
Daarom kun je het eerder genoemde externalTrafficPolicy instellen in de Ingress Controller om het bron-IP te behouden.
Tegelijkertijd moet je use-forwarded-headers in de configmap van ingress-nginx-controller instellen op true, zodat de Ingress Controller de velden X-Forwarded-For of X-REAL-IP kan herkennen.
apiVersion: v1
data:
allow-snippet-annotations: "false"
compute-full-forwarded-for: "true"
use-forwarded-headers: "true"
enable-real-ip: "true"
forwarded-for-header: "X-Real-IP" # X-Real-IP or X-Forwarded-For
kind: ConfigMap
metadata:
labels:
app.kubernetes.io/component: controller
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
app.kubernetes.io/version: 1.10.1
name: ingress-nginx-controller
namespace: ingress-nginx
Het verschil tussen NodePort-service en ingress-nginx-controller-service ligt voornamelijk hierin dat de backend van NodePort meestal niet op elke node is geïmplementeerd, terwijl de backend van ingress-nginx-controller meestal op elke naar buiten gerichte node is geïmplementeerd.
In tegenstelling tot het instellen van externalTrafficPolicy in de NodePort-service, wat leidt tot geen reactie op verzoeken over nodes heen, kan Ingress eerst de HEADER instellen en dan proxy-forwarden, waardoor zowel bron-IP behouden als load balancing mogelijk is.
Samenvatting
- Adresvertaling (NAT), proxy (Proxy), reverse proxy (Reverse Proxy) en load balancing (Load Balance) leiden tot verlies van bron-IP.
- Om verlies van bron-IP te voorkomen, kan de proxyserver bij doorsturen het echte IP instellen in het HTTP-kopveld
X-REAL-IPen doorgeven via de proxyservice. Bij meerdere proxy-lagen kun je het veldX-Forwarded-Forgebruiken, dat een lijst van IP’s registreert van bron-IP en proxy-pad in de vorm van een stack. - Instellen van
externalTrafficPolicy: Localvoor NodePort-services in het cluster behoudt het bron-IP, maar verliest load balancing-capaciteit. - ingress-nginx-controller, geïmplementeerd als daemonset op alle loadbalancer-rolnodes, behoudt het bron-IP met
externalTrafficPolicy: Localen behoudt ook load balancing-capaciteit.