Jak zachować oryginalny IP źródła żądania po równoważeniu obciążenia w klastrze K8s
Categories:
Wstęp
Wdrożenie aplikacji nie zawsze polega na prostym instalowaniu i uruchamianiu, czasami trzeba też rozważyć problemy sieciowe. Niniejszy artykuł opisuje, jak w klastrze k8s sprawić, aby usługa mogła uzyskać źródłowy IP żądania.
Aplikacje świadczące usługi zazwyczaj zależą od informacji wejściowych. Jeśli informacje wejściowe nie zależą od pięciokrotki (źródłowy IP, źródłowy port, docelowy IP, docelowy port, protokół), to taka usługa ma niskie powiązanie z siecią i nie musi przejmować się szczegółami sieciowymi.
Dlatego dla większości osób nie ma potrzeby czytania tego artykułu. Jeśli interesujesz się sieciami lub chcesz poszerzyć horyzonty, możesz kontynuować czytanie, aby dowiedzieć się więcej o scenariuszach usług.
Artykuł oparty na k8s v1.29.4. W niektórych opisach mieszam pojęcia pod i endpoint – w kontekście tego artykułu można je traktować jako równoważne.
Jeśli zauważysz błędy, chętnie przyjmę poprawki, poprawię je niezwłocznie.
Dlaczego informacje o źródłowym IP są tracone?
Najpierw wyjaśnijmy, czym jest źródłowy IP. Gdy A wysyła żądanie do B, a B przekazuje je do C, to chociaż C widzi źródłowy IP protokołu IP jako IP B, w tym artykule IP A traktujemy jako źródłowy IP.
Głównie dwie klasy zachowań powodują utratę informacji o źródle:
- Translacja adresów sieciowych (NAT), w celu oszczędzania publicznych IPv4, równoważenia obciążenia itp. Powoduje, że serwer widzi źródłowy IP jako IP urządzenia NAT, a nie prawdziwy źródłowy IP.
- Proxy, odwrotne proxy (RP, Reverse Proxy) i równoważenie obciążenia (LB, Load Balancer) należą do tej klasy, poniżej określane zbiorczo jako serwery proxy. Te serwery proxy przekazują żądania do usług backendowych, ale zastępują źródłowy IP swoim własnym IP.
- NAT to w skrócie wymiana przestrzeni portów na przestrzeń IP. Adresy IPv4 są ograniczone, jeden adres IP może mapować 65535 portów. W większości przypadków te porty nie są w pełni wykorzystane, więc wiele podsieci IP może współdzielić jeden publiczny IP, rozróżniając usługi po portach. Forma użycia:
publiczny IP:publiczny port -> prywatny IP_1:prywatny port. Więcej informacji znajdziesz w Translacja adresów sieciowych. - Serwery proxy służą do ukrywania lub eksponowania. Przekazują żądania do usług backendowych, jednocześnie zastępując źródłowy IP swoim własnym IP, co ukrywa prawdziwy IP usług backendowych i chroni ich bezpieczeństwo. Forma użycia:
IP klienta -> IP proxy -> IP serwera. Więcej informacji znajdziesz w Proxy.
NAT i serwery proxy są bardzo powszechne, większość usług nie może uzyskać źródłowego IP żądania.
To dwie powszechne metody modyfikacji źródłowego IP. Inne sugestie mile widziane.
Jak zachować źródłowy IP?
Oto przykład żądania HTTP:
| Pole | Długość (bajty) | Przesunięcie bitowe | Opis |
|---|---|---|---|
| Nagłówek IP | |||
Źródłowy IP | 4 | 0-31 | Adres IP nadawcy |
| Docelowy IP | 4 | 32-63 | Adres IP odbiorcy |
| Nagłówek TCP | |||
| Źródłowy port | 2 | 0-15 | Numer portu nadawcy |
| Docelowy port | 2 | 16-31 | Numer portu odbiorcy |
| Numer sekwencyjny | 4 | 32-63 | Służy do identyfikacji strumienia bajtów danych wysyłanych przez nadawcę |
| Numer potwierdzenia | 4 | 64-95 | Jeśli ustawiona flaga ACK, to następny oczekiwany numer sekwencyjny |
| Przesunięcie danych | 4 | 96-103 | Liczba bajtów od początku nagłówka TCP do danych |
| Zarezerwowane | 4 | 104-111 | Pole zarezerwowane, nieużywane, ustawione na 0 |
| Flagi | 2 | 112-127 | Różne flagi kontrolne, takie jak SYN, ACK, FIN itp. |
| Rozmiar okna | 2 | 128-143 | Ilość danych, jaką odbiorca może przyjąć |
| Suma kontrolna | 2 | 144-159 | Służy do wykrywania błędów w transmisji danych |
| Wskaźnik pilny | 2 | 160-175 | Pozycja pilnych danych, które nadawca chce, aby odbiorca przetworzył jak najszybciej |
| Opcje | Zmienne | 176-… | Może zawierać znaczniki czasu, maksymalny rozmiar segmentu itp. |
| Nagłówek HTTP | |||
| Wiersz żądania | Zmienne | …-… | Zawiera metodę żądania, URI i wersję HTTP |
Pola nagłówka | Zmienne | …-… | Zawiera różne pola nagłówka, takie jak Host, User-Agent itp. |
| Pusta linia | 2 | …-… | Służy do oddzielenia nagłówka od ciała |
| Ciało | Zmienne | …-… | Opcjonalne ciało żądania lub odpowiedzi |
Przeglądając powyższą strukturę żądania HTTP, widać, że opcje TCP, wiersz żądania, pola nagłówka, ciało są zmienne. Przestrzeń opcji TCP jest ograniczona i generalnie nie służy do przekazywania źródłowego IP. Wiersz żądania ma stałą informację i nie można go rozszerzać. Ciało HTTP po zaszyfrowaniu nie można modyfikować. Tylko pola nagłówka HTTP nadają się do rozszerzenia w celu przekazania źródłowego IP.
W nagłówku HTTP można dodać pole X-REAL-IP, aby przekazać źródłowy IP. Ta operacja jest zazwyczaj wykonywana na serwerze proxy, a następnie serwer proxy przekazuje żądanie do usługi backendowej, która może uzyskać źródłowy IP z tego pola.
Uwaga: Należy zapewnić, że serwer proxy znajduje się przed urządzeniem NAT, aby uzyskać prawdziwy źródłowy IP żądania whoami. W produktach Alibaba Cloud widzimy kategorię towaru Load Balancer, której pozycja w sieci różni się od zwykłych serwerów aplikacji.
Instrukcja操作 K8S
Przykład wdrożenia z projektem whoami.
Tworzenie Deployment
Najpierw utwórz usługę:
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
To utworzy Deployment zawierający 3 Pody, każdy pod zawiera jeden kontener uruchamiający usługę whoami.
Tworzenie Service
Możesz utworzyć usługę typu NodePort lub LoadBalancer dla dostępu zewnętrznego lub usługę typu ClusterIP tylko dla dostępu wewnętrznego w klastrze, a następnie dodać usługę Ingress do ekspozycji dostępu zewnętrznego.
NodePort można uzyskać zarówno przez NodeIP:NodePort, jak i przez usługę Ingress, co ułatwia testowanie. W tej sekcji używamy usługi NodePort.
apiVersion: v1
kind: Service
metadata:
name: whoami-service
spec:
type: NodePort
selector:
app: whoami
ports:
- protocol: TCP
port: 80
targetPort: 8080
nodePort: 30002
Po utworzeniu usługi, dostęp curl whoami.example.com:30002 pokaże IP jako NodeIP, a nie źródłowy IP żądania whoami.
Uwaga: To nie jest poprawny IP klienta, to wewnętrzne IP klastra. Oto co się dzieje:
- Klient wysyła pakiet do node2:nodePort
- node2 zastępuje źródłowy IP pakietu swoim własnym adresem IP (SNAT)
- node2 zastępuje docelowy IP pakietu IP Pod
- Pakiet jest routowany do node1, potem do endpointu
- Odpowiedź Pod jest routowana z powrotem do node2
- Odpowiedź Pod jest wysyłana z powrotem do klienta
Ilustracja graficzna:

Konfiguracja externalTrafficPolicy: Local
Aby uniknąć tej sytuacji, Kubernetes ma funkcję zachowującą źródłowy IP klienta. Jeśli ustawisz service.spec.externalTrafficPolicy na Local, kube-proxy będzie proxyować żądania tylko do lokalnych endpointów, bez przekazywania ruchu do innych węzłów.
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
Testuj za pomocą curl whoami.example.com:30002. Gdy whoami.example.com mapuje się na IP wielu węzłów klastra, istnieje pewna szansa na brak dostępu. Upewnij się, że rekord DNS zawiera tylko IP węzła z endpointem (podem).
Ta konfiguracja ma swoją cenę: traci się zdolność równoważenia obciążenia w klastrze. Klient uzyska odpowiedź tylko po dostępie do węzła z wdrożonym endpointem.

Gdy klient uzyska dostęp do Node 2, nie będzie odpowiedzi.
Tworzenie Ingress
Większość usług oferowanych użytkownikom używa http/https, forma https://ip:port może być obca dla użytkowników. Zazwyczaj używa się Ingress, aby załadować usługę NodePort utworzoną powyżej na port 80/443 domeny.
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
Po zastosowaniu, testuj dostęp curl whoami.example.com. ClientIP zawsze będzie IP Poda Ingress Controller na węźle endpointu.
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>
Użycie Ingress jako odwrotnego proxy dla usługi NodePort oznacza dwie warstwy service przed endpointem. Poniższy rysunek pokazuje różnicę.
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]W ścieżce 1, przy dostępie zewnętrznym do Ingress, pierwszym endpointem jest Ingress Controller, potem endpoint whoami.
Ingress Controller to w istocie usługa LoadBalancer,
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
Dlatego można ustawić wspomniane wcześniej externalTrafficPolicy w Ingress Controller, aby zachować źródłowy IP.
Jednocześnie należy ustawić use-forwarded-headers na true w configmap ingress-nginx-controller, aby Ingress Controller mógł rozpoznać pola X-Forwarded-For lub X-REAL-IP.
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
Różnica między usługą NodePort a ingress-nginx-controller polega na tym, że backend NodePort zazwyczaj nie jest wdrażany na każdym węźle, podczas gdy backend ingress-nginx-controller zazwyczaj jest wdrażany na każdym węźle eksponowanym na zewnątrz.
W przeciwieństwie do usługi NodePort, gdzie ustawienie externalTrafficPolicy powoduje brak odpowiedzi dla żądań między węzłami, Ingress może najpierw ustawić nagłówek, a potem przekazać żądanie, realizując zarówno zachowanie źródłowego IP, jak i równoważenie obciążenia.
Podsumowanie
- Translacja adresów (NAT), Proxy, odwrotne proxy (Reverse Proxy), równoważenie obciążenia (Load Balance) powodują utratę źródłowego IP.
- Aby zapobiec utracie źródłowego IP, serwer proxy może ustawić prawdziwy IP w polu nagłówka HTTP
X-REAL-IPpodczas przekazywania. Przy wielu warstwach proxy używa się polaX-Forwarded-For, które w formie stosu rejestruje źródłowy IP i listę IP ścieżki proxy. - Ustawienie
externalTrafficPolicy: Localw usłudze NodePort klastra zachowuje źródłowy IP, ale traci zdolność równoważenia obciążenia. - ingress-nginx-controller wdrożony w formie daemonset na wszystkich węzłach roli loadbalancer, z ustawieniem
externalTrafficPolicy: Local, zachowuje źródłowy IP i zdolność równoważenia obciążenia.