Hoe behoud je het bron-IP van verzoeken na load balancing in een K8s-cluster

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:

  1. 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.
  2. 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.

Beperkingen van toegangspaden

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-IP en doorgeven via de proxyservice. Bij meerdere proxy-lagen kun je het veld X-Forwarded-For gebruiken, dat een lijst van IP’s registreert van bron-IP en proxy-pad in de vorm van een stack.
  • Instellen van externalTrafficPolicy: Local voor 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: Local en behoudt ook load balancing-capaciteit.

Referenties