Використання Source IP

Використання Source IP

Застосунки, що працюють у кластері Kubernetes, виявляють одне одного та взаємодіють між собою й із зовнішнім світом через абстракцію Service. У цьому документі пояснюється, що відбувається з Source IP пакетів, надісланих на різні типи Service, і як ви можете змінити цю поведінку відповідно до ваших потреб.

Перш ніж ви розпочнете

Терміни

У цьому документі використовуються такі терміни:

NAT
Трансляція мережевих адрес
Source NAT
Заміна Source IP в пакеті; у цьому документі це зазвичай означає заміну на IP-адресу вузла.
Destination NAT
Заміна Destination IP в пакеті; у цьому документі це зазвичай означає заміну на IP-адресу Pod
VIP
Віртуальна IP-адреса, така як та, що призначається кожному Service у Kubernetes
kube-proxy
Мережевий демон, який керує віртуальними IP Service на кожному вузлі

Необхідні умови

Вам треба мати кластер Kubernetes, а також інструмент командного рядка kubectl має бути налаштований для роботи з вашим кластером. Рекомендується виконувати ці настанови у кластері, що має щонайменше два вузли, які не виконують роль вузлів управління. Якщо у вас немає кластера, ви можете створити його, за допомогою minikube або використовувати одну з цих пісочниць:

У прикладах використовується невеликий вебсервер nginx, який повертає Source IP отриманих запитів через HTTP-заголовок. Ви можете створити його так:

kubectl create deployment source-ip-app --image=registry.k8s.io/echoserver:1.10

Результат:

deployment.apps/source-ip-app created

Цілі

  • Відкрити доступ до простого застосунку через різні типи Service
  • Зрозуміти, як кожен тип Service обробляє Source IP NAT
  • Зрозуміти компроміси, повʼязані зі збереженням Source IP

Source IP для Service з Type=ClusterIP

Пакети, надіслані на ClusterIP зсередини кластера, ніколи не обробляються Source NAT, якщо kube-proxy працює в режимі iptables (типовий режим). Режим kube-proxy можна дізнатися, запитавши http://localhost:10249/proxyMode на вузлі, де працює kube-proxy.

kubectl get nodes

Результат схожий на цей:

NAME                           STATUS     ROLES    AGE     VERSION
kubernetes-node-6jst   Ready      <none>   2h      v1.13.0
kubernetes-node-cx31   Ready      <none>   2h      v1.13.0
kubernetes-node-jj1t   Ready      <none>   2h      v1.13.0

Отримайте режим проксі на одному з вузлів (kube-proxy слухає порт 10249):

# Запустіть це в оболонці на вузлі, який хочете запитати.
curl http://localhost:10249/proxyMode

Результат:

iptables

Ви можете протестувати збереження Source IP, створивши Service для застосунку source IP:

kubectl expose deployment source-ip-app --name=clusterip --port=80 --target-port=8080

Результат:

service/clusterip exposed
kubectl get svc clusterip

Результат схожий на:

NAME         TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)   AGE
clusterip    ClusterIP   10.0.170.92   <none>        80/TCP    51s

А також звернувшись до ClusterIP з Podʼа в тому самому кластері:

kubectl run busybox -it --image=busybox:1.28 --restart=Never --rm

Результат схожий на цей:

Waiting for pod default/busybox to be running, status is Pending, pod ready: false
If you don't see a command prompt, try pressing enter.

Потім ви можете виконати команду всередині цього Podʼа:

# Запустіть це всередині терміналу "kubectl run"
ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
3: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1460 qdisc noqueue
    link/ether 0a:58:0a:f4:03:08 brd ff:ff:ff:ff:ff:ff
    inet 10.244.3.8/24 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::188a:84ff:feb0:26a5/64 scope link
       valid_lft forever preferred_lft forever

…потім використайте wget, щоб надіслати запит до локального вебсервера

# Замініть "10.0.170.92" на IPv4-адресу Service з назвою "clusterip"
wget -qO - 10.0.170.92
CLIENT VALUES:
client_address=10.244.3.8
command=GET
...

client_address завжди є IP-адресою Podʼа-клієнта, незалежно від того, чи перебувають Pod-клієнт і Pod-сервер на одному вузлі, чи на різних вузлах.

Source IP для Service з Type=NodePort

Пакети, надіслані на Service з Type=NodePort, типово обробляються Source NAT. Ви можете протестувати це, створивши Service NodePort:

kubectl expose deployment source-ip-app --name=nodeport --port=80 --target-port=8080 --type=NodePort

Результат:

service/nodeport exposed
NODEPORT=$(kubectl get -o jsonpath="{.spec.ports[0].nodePort}" services nodeport)
NODES=$(kubectl get nodes -o jsonpath='{ $.items[*].status.addresses[?(@.type=="InternalIP")].address }')

Якщо ви працюєте у хмарного провайдера, можливо, вам доведеться відкрити правило брандмауера для nodes:nodeport, зазначеного вище. Тепер ви можете спробувати звернутися до Service ззовні кластера через виділений вище порт вузла.

for node in $NODES; do curl -s $node:$NODEPORT | grep -i client_address; done

Результат схожий на:

client_address=10.180.1.1
client_address=10.240.0.5
client_address=10.240.0.3

Зверніть увагу, що це неправильні IP клієнтів, це внутрішні IP кластера. Ось що відбувається:

  • Клієнт надсилає пакет до node2:nodePort
  • node2 замінює Source IP-адресу (SNAT) у пакеті своєю власною IP-адресою
  • node2 замінює Destination IP-адресу пакета на IP Podʼа
  • пакет спрямовується до node 1, а потім до точки доступу
  • відповідь Podʼа спрямовується назад до node2
  • відповідь Podʼа надсилається клієнту

Візуально:

схема 01 source IP для nodeport

Схема. Source IP Type=NodePort з використанням SNAT

Щоб уникнути цього, у Kubernetes є можливість зберегти Source IP клієнта. Якщо ви встановите для service.spec.externalTrafficPolicy значення Local, kube-proxy спрямовує запити лише до локальних точок доступу і не пересилає трафік до інших вузлів. Цей підхід зберігає початкову Source IP-адресу. Якщо локальних точок доступу немає, пакети, надіслані до вузла, відкидаються, тому ви можете покладатися на правильну Source IP-адресу в будь-яких правилах обробки пакетів, які ви можете застосувати до пакета, що проходить до точки доступу.

Встановіть поле service.spec.externalTrafficPolicy так:

kubectl patch svc nodeport -p '{"spec":{"externalTrafficPolicy":"Local"}}'

Вивід:

service/nodeport patched

Повторіть тест:

for node in $NODES; do curl --connect-timeout 1 -s $node:$NODEPORT | grep -i client_address; done

Вивід подібний до:

client_address=198.51.100.79

Зауважте, що ви отримали лише одну відповідь із правильною IP-адресою клієнта від одного вузла, на якому працює Pod точки доступу.

Ось що відбувається:

  • клієнт надсилає пакет на node2:nodePort, який не має точок доступу
  • пакет відкидається
  • клієнт надсилає пакет на node1:nodePort, який має точки доступу
  • node1 направляє пакет до точки доступу з правильною Source IP-адресою

Візуально:

схема 02 source IP для nodeport

Схема. Source IP Type=NodePort зберігає Source IP-адресу клієнта

Source IP для Service з Type=LoadBalancer

Пакети, надіслані на Service з Type=LoadBalancer, типово обробляються Source NAT, оскільки всі вузли Kubernetes, доступні для планування, у стані Ready можуть отримувати трафік від балансувальника навантаження. Тому якщо пакети надходять на вузол без точки доступу, система через проксі спрямовує їх на вузол з точкою доступу, замінюючи Source IP пакета на IP-адресу вузла (як описано в попередньому розділі).

Ви можете перевірити це, відкривши доступ до застосунку source-ip-app через балансувальник навантаження:

kubectl expose deployment source-ip-app --name=loadbalancer --port=80 --target-port=8080 --type=LoadBalancer

Вивід:

service/loadbalancer exposed

Виведіть IP-адреси Service:

kubectl get svc loadbalancer

Вивід подібний до цього:

NAME           TYPE           CLUSTER-IP    EXTERNAL-IP       PORT(S)   AGE
loadbalancer   LoadBalancer   10.0.65.118   203.0.113.140     80/TCP    5m

Потім надішліть запит на зовнішню IP-адресу цього Service:

curl 203.0.113.140

Вивід подібний до цього:

CLIENT VALUES:
client_address=10.240.0.5
...

Однак якщо ви працюєте в Google Kubernetes Engine/GCE, встановлення того самого поля service.spec.externalTrafficPolicy у Local змушує вузли без точок доступу Service вилучати себе зі списку вузлів, які отримують трафік від балансувальника навантаження, навмисно не проходячи перевірки стану.

Візуально:

Source IP з externalTrafficPolicy

Ви можете перевірити це, встановивши анотацію:

kubectl patch svc loadbalancer -p '{"spec":{"externalTrafficPolicy":"Local"}}'

Ви одразу побачите поле service.spec.healthCheckNodePort, виділене Kubernetes:

kubectl get svc loadbalancer -o yaml | grep -i healthCheckNodePort

Вивід подібний до цього:

  healthCheckNodePort: 32122

Поле service.spec.healthCheckNodePort вказує на порт на кожному вузлі, на якому надається перевірка стану за адресою /healthz. Ви можете перевірити це:

kubectl get pod -o wide -l app=source-ip-app

Вивід подібний до цього:

NAME                            READY     STATUS    RESTARTS   AGE       IP             NODE
source-ip-app-826191075-qehz4   1/1       Running   0          20h       10.180.1.136   kubernetes-node-6jst

Використайте curl, щоб запитати /healthz на різних вузлах:

# Виконайте це локально на вузлі, який ви вибрали
curl localhost:32122/healthz
1 Service Endpoints found

На іншому вузлі ви можете отримати інший результат:

# Виконайте це локально на вузлі, який ви вибрали
curl localhost:32122/healthz
No Service Endpoints Found

Контролер, який працює на панелі управління, відповідає за виділення хмарного балансувальника навантаження. Той самий контролер також створює HTTP-перевірки стану, що вказують на цей порт/шлях на кожному вузлі. Зачекайте приблизно 10 секунд, поки 2 вузли без точок доступу не пройдуть перевірки стану, а потім за допомогою curl запитайте IPv4-адресу балансувальника навантаження:

curl 203.0.113.140

Вивід подібний до цього:

CLIENT VALUES:
client_address=198.51.100.79
...

Міжплатформна підтримка

Лише деякі хмарні провайдери підтримують збереження Source IP-адреси через Service з Type=LoadBalancer. Ваш хмарний провайдер може виконати запит на балансувальник навантаження кількома способами:

  1. З проксі, що завершує зʼєднання клієнта та відкриває нове зʼєднання з вашими вузлами/точками доступу. У таких випадках Source IP-адреса завжди буде адресою хмарного балансувальника, а не адресою клієнта.

  2. За допомогою пересилача пакетів (packet forwarder), завдяки чому запити від клієнта, надіслані на VIP балансувальника, потрапляють на вузол із Source IP-адресою клієнта, а не проміжного проксі.

Балансувальники навантаження першої категорії повинні використовувати узгоджений між балансувальником навантаження та бекендом протокол для передачі справжньої IP-адреси клієнта, як-от заголовки HTTP Forwarded або X-FORWARDED-FOR, або протокол proxy. Балансувальники навантаження другої категорії можуть використовувати описану вище можливість, створюючи HTTP-перевірку стану, що вказує на порт, збережений у полі service.spec.healthCheckNodePort Service.

Очищення

Видаліть Service:

kubectl delete svc -l app=source-ip-app

Видаліть Deployment, ReplicaSet та Pod:

kubectl delete deployment source-ip-app

Що далі

Змінено November 03, 2026 at 9:28 AM PST: [uk] Add tutorials/services/source-ip.md (6dac08a291)