[Перевод] Как работают Kubernetes Services: управление трафиком с помощью iptables

Примечание переводчика: статья является переводом оригинального материала Марка Бетца (Mark Betz). В ней рассматриваются ключевые аспекты работы Kubernetes Services (далее — сервисы) и то, какое участие в этом принимает iptables. Этот перевод поможет русскоязычным читателям лучше разобраться в сложных концепциях Kubernetes и эффективно применять их на практике. Статья особенно актуальна для DevOps/DevSecOps-инженеров и администраторов кластеров Kubernetes.

fe23639ba21ebac0e172806919f82a88.png

TL; DR — iptables могут сломать вам мозг (надеюсь, это достаточно хорошее предупреждение для того, о чём пойдёт речь ниже…).

Если вы хоть раз сталкивались с Kubernetes, вам точно приходилось развёртывать сервисы или взаимодействовать с ними. Но как же они работают? В этой публикации мы об этом и поговорим. А о сервисах Kubernetes в целом и о том, зачем они нужны, читайте в этой статье (перевод статьи на Хабре).

Окружение

Краткое описание окружения:

  • Кластер RKE2 с версией 1.28.4+rk2r1 на борту

  • Calico CNI 3.26.3 с использованием оверлея VxLAN

  • Все узлы работают под управлением Ubuntu 22.04

Запускаем рабочую нагрузку

Прежде чем начать разбираться в том, как работает сервис, нам понадобятся рабочая нагрузка и сам сервис. Для этого воспользуемся веб-приложением Podinfo:

# При необходимости добавьте репозиторий Podinfo
$ helm repo add podinfo https://stefanprodan.github.io/podinfo
"podinfo" already exists with the same configuration, skipping

# Обновите все репозитории Helm, чтобы убедиться, что будет использоваться последняя версия Podinfo
$ helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "podinfo" chart repository
...Successfully got an update from the "haproxytech" chart repository
Update Complete. ⎈Happy Helming!⎈

# Разверните podinfo в целевой кластер
$ helm upgrade -i --install --wait frontend --namespace podinfo \
--set replicaCount=2 \
--set backend=http://backend-podinfo:9898/echo \
podinfo/podinfo
Release "frontend" does not exist. Installing it now.
NAME: frontend
LAST DEPLOYED: Wed Mar 13 15:41:29 2024
NAMESPACE: podinfo
STATUS: deployed
REVISION: 1
NOTES:
1. URL-адрес приложения можно получить, выполнив следующие команды:
  echo "Visit http://127.0.0.1:8080 to use your application"
  kubectl -n podinfo port-forward deploy/frontend-podinfo 8080:9898

Посмотрим, куда было запланировано наше приложение:

Вывод команды kubectl get po -n podinfo -o wide показывает, что поды запланированы на узлы k8s-node04 и k8s-node06

Вывод команды kubectl get po -n podinfo -o wide показывает, что поды запланированы на узлы k8s-node04 и k8s-node06

А теперь посмотрим, какой IP-адрес был присвоен созданному сервису:

Результат команды kubectl get svc -n podinfo показывает единственный сервис с адресом 10.43.156.98

Результат команды kubectl get svc -n podinfo показывает единственный сервис с адресом 10.43.156.98

Kube-proxy и iptables

Тут необходимо сделать небольшое отступление и объяснить, почему мы говорим об iptables. Как отмечает Марк в своей статье о сервисах, iptables — это программа в пространстве пользователя, которая выступает фронтендом для netfilter. Это механизм обработки пакетов в слое данных, который, помимо прочего, умеет перенаправлять трафик в другое место.

В большинстве дистрибутивов Kubernetes kube-proxy по умолчанию использует iptables. Список других программ, которые поддерживает kube-proxy, можно найти здесь. Эта статья актуальна только для инсталляций на базе iptables.

Трейсинг iptables

Команды ниже выполняются на «пустом» узле (то есть на узле, на котором нет запланированных Podinfo-подов). Включим трейсинг в iptables для трафика, который попадает на порт 9898 — порт сервиса Podinfo.

$ iptables -t raw -A PREROUTING -p tcp --dport 9898 -j TRACE
$ iptables -t raw -A OUTPUT -p tcp --dport 9898 -j TRACE

Обратите внимание, что, поскольку фильтр выше перехватывает только порт назначения 9898, обратный трафик от сервиса Podinfo перехватываться не будет.

Команда ниже запускается с правами root и работает в отдельной сессии терминала:

$ xtables-monitor --trace

Вернемся к первой сессии терминала и сделаем curl-запрос на сервис Podinfo:

$ curl http://10.43.156.98:9898
{
 "hostname": "frontend-podinfo-7854c7cd4c-jvdx5",
 "version": "6.6.0",
 "revision": "357009a86331a987811fefc11be1350058da33fc",
 "color": "#34577c",
 "logo": "https://raw.githubusercontent.com/stefanprodan/podinfo/gh-pages/cuddle_clap.gif",
 "message": "greetings from podinfo v6.6.0",
 "goos": "linux",
 "goarch": "amd64",
 "runtime": "go1.21.7",
 "num_goroutine": "8",
 "num_cpu": "8"
}

Прежде чем продолжать, нужно отключить трейсинг пакетов, поскольку на загруженных системах может собираться слишком много данных:

$ iptables -t raw -D PREROUTING -p tcp --dport 9898 -j TRACE
$ iptables -t raw -D OUTPUT -p tcp --dport 9898 -j TRACE

Если вернуться к терминалу, на котором была запущена команда xtables-monitor, увидим здоровенный вывод. Сначала пара слов о столбцах в этом бардаке:

PACKET: 2 0427ffe5 OUT=enp3s0 SRC=192.168.1.88 DST=10.43.156.98 LEN=60 TOS=0x0 TTL=64 ID=45471DF SPORT=49058 DPORT=9898 SYN
 TRACE: 2 0427ffe5 raw:OUTPUT:rule:0x18:CONTINUE  -4 -t raw -A OUTPUT -p tcp -m tcp --dport 9898 -j TRACE
 TRACE: 2 0427ffe5 raw:OUTPUT:return:
 TRACE: 2 0427ffe5 raw:OUTPUT:policy:ACCEPT
 TRACE: 2 0427ffe5 mangle:OUTPUT:return:
 TRACE: 2 0427ffe5 mangle:OUTPUT:policy:ACCEPT
PACKET: 2 0427ffe5 OUT=enp3s0 SRC=192.168.1.88 DST=10.43.156.98 LEN=60 TOS=0x0 TTL=64 ID=45471DF SPORT=49058 DPORT=9898 SYN
 TRACE: 2 0427ffe5 nat:OUTPUT:rule:0xb9:JUMP:cali-OUTPUT  -4 -t nat -A OUTPUT -m comment --comment "cali:tVnHkvAo15HuiPy0" -j cali-OUTPUT
 TRACE: 2 0427ffe5 nat:cali-OUTPUT:rule:0xb8:JUMP:cali-fip-dnat  -4 -t nat -A cali-OUTPUT -m comment --comment "cali:GBTAv2p5CwevEyJm" -j cali-fip-dnat
 TRACE: 2 0427ffe5 nat:cali-fip-dnat:return:
 TRACE: 2 0427ffe5 nat:cali-OUTPUT:return:
 TRACE: 2 0427ffe5 nat:OUTPUT:rule:0x9:JUMP:KUBE-SERVICES  -4 -t nat -A OUTPUT -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
 TRACE: 2 0427ffe5 nat:KUBE-SERVICES:rule:0x4aaa:JUMP:KUBE-SVC-Y4T5L63IYP3YFEBS  -4 -t nat -A KUBE-SERVICES -d 10.43.156.98/32 -p tcp -m comment --comment "podinfo/frontend-podinfo:http cluster IP" -j KUBE-SVC-Y4T5L63IYP3YFEBS
 TRACE: 2 0427ffe5 nat:KUBE-SVC-Y4T5L63IYP3YFEBS:rule:0x471b:JUMP:KUBE-MARK-MASQ  -4 -t nat -A KUBE-SVC-Y4T5L63IYP3YFEBS ! -s 10.42.0.0/16 -d 10.43.156.98/32 -p tcp -m comment --comment "podinfo/frontend-podinfo:http cluster IP" -j KUBE-MARK-MASQ
 TRACE: 2 0427ffe5 nat:KUBE-MARK-MASQ:rule:0x4aa4:CONTINUE  -4 -t nat -A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000
 TRACE: 2 0427ffe5 nat:KUBE-MARK-MASQ:return:
 TRACE: 2 0427ffe5 nat:KUBE-SVC-Y4T5L63IYP3YFEBS:rule:0x471d:JUMP:KUBE-SEP-OYNVDBAPYJ623W6H  -4 -t nat -A KUBE-SVC-Y4T5L63IYP3YFEBS -m comment --comment "podinfo/frontend-podinfo:http -> 10.42.54.194:9898" -j KUBE-SEP-OYNVDBAPYJ623W6H
 TRACE: 2 0427ffe5 nat:KUBE-SEP-OYNVDBAPYJ623W6H:rule:0x4721:ACCEPT  -4 -t nat -A KUBE-SEP-OYNVDBAPYJ623W6H -p tcp -m comment --comment "podinfo/frontend-podinfo:http" -m tcp -j DNAT --to-destination 10.42.54.194:9898
PACKET: 2 0427ffe5 OUT=enp3s0 SRC=192.168.1.88 DST=10.42.54.194 LEN=60 TOS=0x0 TTL=64 ID=45471DF SPORT=49058 DPORT=9898 SYN MARK=0x4000
 TRACE: 2 0427ffe5 filter:OUTPUT:rule:0x91:JUMP:cali-OUTPUT  -4 -t filter -A OUTPUT -m comment --comment "cali:tVnHkvAo15HuiPy0" -j cali-OUTPUT
 TRACE: 2 0427ffe5 filter:cali-OUTPUT:rule:0x8a:CONTINUE  -4 -t filter -A cali-OUTPUT -m comment --comment "cali:iC1pSPgbvgQzkUk_" -j MARK --set-xmark 0x0/0xf0000
 TRACE: 2 0427ffe5 filter:cali-OUTPUT:return:
 TRACE: 2 0427ffe5 filter:OUTPUT:rule:0x17:JUMP:KUBE-PROXY-FIREWALL  -4 -t filter -A OUTPUT -m conntrack --ctstate NEW -m comment --comment "kubernetes load balancer firewall" -j KUBE-PROXY-FIREWALL
 TRACE: 2 0427ffe5 filter:KUBE-PROXY-FIREWALL:return:
 TRACE: 2 0427ffe5 filter:OUTPUT:rule:0x12:JUMP:KUBE-SERVICES  -4 -t filter -A OUTPUT -m conntrack --ctstate NEW -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
 TRACE: 2 0427ffe5 filter:KUBE-SERVICES:return:
 TRACE: 2 0427ffe5 filter:OUTPUT:rule:0x6:JUMP:KUBE-FIREWALL  -4 -t filter -A OUTPUT -j KUBE-FIREWALL
 TRACE: 2 0427ffe5 filter:KUBE-FIREWALL:return:
 TRACE: 2 0427ffe5 filter:OUTPUT:return:
 TRACE: 2 0427ffe5 filter:OUTPUT:policy:ACCEPT
PACKET: 2 0427ffe5 OUT=vxlan.calico SRC=192.168.1.88 DST=10.42.54.194 LEN=60 TOS=0x0 TTL=64 ID=45471DF SPORT=49058 DPORT=9898 SYN MARK=0x4000
 TRACE: 2 0427ffe5 mangle:POSTROUTING:rule:0x15:JUMP:cali-POSTROUTING  -4 -t mangle -A POSTROUTING -m comment --comment "cali:O3lYWMrLQYEMJtB5" -j cali-POSTROUTING
 TRACE: 2 0427ffe5 mangle:cali-POSTROUTING:rule:0x12:CONTINUE  -4 -t mangle -A cali-POSTROUTING -m comment --comment "cali:nnqPh8lh2VOogSzX" -j MARK --set-xmark 0x0/0xf0000
 TRACE: 2 0427ffe5 mangle:cali-POSTROUTING:rule:0x13:JUMP:cali-to-host-endpoint  -4 -t mangle -A cali-POSTROUTING -m comment --comment "cali:nquN8Jw8Tz72pcBW" -m conntrack --ctstate DNAT -j cali-to-host-endpoint
 TRACE: 2 0427ffe5 mangle:cali-to-host-endpoint:return:
 TRACE: 2 0427ffe5 mangle:cali-POSTROUTING:return:
 TRACE: 2 0427ffe5 mangle:POSTROUTING:return:
 TRACE: 2 0427ffe5 mangle:POSTROUTING:policy:ACCEPT
PACKET: 2 0427ffe5 OUT=vxlan.calico SRC=192.168.1.88 DST=10.42.54.194 LEN=60 TOS=0x0 TTL=64 ID=45471DF SPORT=49058 DPORT=9898 SYN MARK=0x4000
 TRACE: 2 0427ffe5 nat:POSTROUTING:rule:0xba:JUMP:cali-POSTROUTING  -4 -t nat -A POSTROUTING -m comment --comment "cali:O3lYWMrLQYEMJtB5" -j cali-POSTROUTING
 TRACE: 2 0427ffe5 nat:cali-POSTROUTING:rule:0xb5:JUMP:cali-fip-snat  -4 -t nat -A cali-POSTROUTING -m comment --comment "cali:Z-c7XtVd2Bq7s_hA" -j cali-fip-snat
 TRACE: 2 0427ffe5 nat:cali-fip-snat:return:
 TRACE: 2 0427ffe5 nat:cali-POSTROUTING:rule:0xb6:JUMP:cali-nat-outgoing  -4 -t nat -A cali-POSTROUTING -m comment --comment "cali:nYKhEzDlr11Jccal" -j cali-nat-outgoing
 TRACE: 2 0427ffe5 nat:cali-nat-outgoing:return:
 TRACE: 2 0427ffe5 nat:cali-POSTROUTING:rule:0xb7:ACCEPT  -4 -t nat -A cali-POSTROUTING -o vxlan.calico -m comment --comment "cali:e9dnSgSVNmIcpVhP" -m addrtype ! --src-type LOCAL --limit-iface-out -m addrtype --src-type LOCAL -j MASQUERADE --random-fully
  • PACKET: — указывает на начало нового события типа «пакет». В строке содержится информация об обрабатываемом пакете, его источнике, пункте назначения, длине и других сведениях на уровне пакета. Строки PACKET: также содержат исходящий интерфейс, основанный на текущем IP-адресе получателя пакета.

  • TRACE: — строки трейса, в которых прослеживается путь пакета через различные цепочки и правила iptables. Каждая строка TRACE: представляет собой шаг в обработке пакета, показывая, по какому правилу или цепочке пакет оценивался на тот или иной момент.

  • Номер протокола — та самая двойка (2) после TRACE: или PACKET:. В нашем случае представляет AF_INET (трафик IPv4).

  • Идентификатор пакета — значение 0427ffe5 в приведённом выше выводе служит уникальным идентификатором пакета, позволяющим соотнести с ним трейсы.

  • Таблица: Цепочка —для TRACE-строк следующий столбец указывает на таблицу и цепочку iptables, которая обрабатывает пакет. Например, raw:OUTPUT относится к таблице raw и цепочке OUTPUT. Чтобы вывести конкретную цепочку iptables, выполните sudo iptables -t raw -L OUTPUT -n --line-numbers.

Еще один важный момент — понимание того, как netfilter обрабатывает пакеты. Для пакета, исходящего из системы, порядок следующий:

Более подробную информацию можно найти здесь в разделе 6–2. Приведённая ниже схема отлично показывает порядок обработки пакетов:

Схема обработки пакетов в netfilter. Источник

Если быть точнее, дальше мы сосредоточимся на этом участке схемы:

Увеличенная часть схемы — обработка исходящих пакетов на сетевом уровне

Увеличенная часть схемы — обработка исходящих пакетов на сетевом уровне

Теперь рассмотрим, как обрабатывается пакет 0427ff35.

Таблица raw

Цель таблицы raw — дать пакетам возможность обойти функцию отслеживания соединений (conntrack) в iptables. В данном потоке эта функциональность не используется, поэтому таблица проходится довольно быстро:

PACKET: 2 0427ffe5 OUT=enp3s0 SRC=192.168.1.88 DST=10.43.156.98 LEN=60 \
TOS=0x0 TTL=64 ID=45471DF SPORT=49058 DPORT=9898 SYN

TRACE: 2 0427ffe5 raw:OUTPUT:rule:0x18:CONTINUE  -4 -t raw -A OUTPUT \
-p tcp -m tcp --dport 9898 -j TRACE

TRACE: 2 0427ffe5 raw:OUTPUT:return:

TRACE: 2 0427ffe5 raw:OUTPUT:policy:ACCEPT

Первая строка — информационная. Она показывает, что пакет получен и начинается его обработка. Обратите внимание, что SRC=192.168.1.88 — IP-адрес узла, инициировавшего curl-запрос.

Вторая строка — это TRACE, который ранее мы перехватывали с помощью xtables-monitor (iptables -t raw -A PREROUTING -p tcp --dport 9898 -J TRACE).

Третья и четвёртая строки показывают, что конечным результатом обработки в таблице raw стала цель ACCEPT. Она означает, что пакет передаётся в следующую таблицу. Напомним, что таблицы и цепочки можно просмотреть с помощью соответствующих команд iptables.

$ iptables -t raw -L OUTPUT -n --line-numbers
Chain OUTPUT (policy ACCEPT)
num  target     prot opt source               destination         
1    cali-OUTPUT  all  --  0.0.0.0/0            0.0.0.0/0            /* cali:tVnHkvAo15HuiPy0 */

$ iptables -t raw -L cali-OUTPUT -n --line-numbers
Chain cali-OUTPUT (1 references)
num  target     prot opt source               destination         
1    MARK       all  --  0.0.0.0/0            0.0.0.0/0            /* cali:njdnLwYeGqBJyMxW */ MARK and 0xfff0ffff
2    cali-to-host-endpoint  all  --  0.0.0.0/0            0.0.0.0/0            /* cali:rz86uTUcEZAfFsh7 */
3    ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0            /* cali:pN0F5zD0b8yf9W1Z */ mark match 0x10000/0x10000

$ iptables -t raw -L cali-to-host-endpoint -n --line-numbers
Chain cali-to-host-endpoint (1 references)
num  target     prot opt source               destination

Вот схема, соответствующая приведённому выше выводу:

Схема прохождения пакета через raw в mangle

Схема прохождения пакета через raw в mangle

Первое правило в цепочке cali-OUTPUT — MARK. Неясно, почему оно не отобразилось в выводе xtables-monitor. Рабочая версия состоит в том, что оно никак не повлияло на пакет и потому не было включено в вывод. MARK — это нетерминирующая цель, то есть обработка в текущей цепочке продолжается, даже если MARK-правило срабатывает.

Состояние пакета

Состояние пакета после цепочки raw:OUTPUT:

IP-адрес отправителя: 192.168.1.88

Порт отправителя: 49058

IP-адрес получателя: 10.43.156.98

Порт получателя: 9898

Таблица mangle — OUTPUT

Как следует из названия, таблица mangle используется для модификации пакета определённым образом. Пример — изменение полей TTL или ToS/DSCP в заголовке IPv4. Как и в случае с raw:OUTPUT, обработка здесь происходит быстро. Пакет добирается до цели ACCEPT и переходит к следующей таблице.

TRACE: 2 0427ffe5 mangle:OUTPUT:return:

TRACE: 2 0427ffe5 mangle:OUTPUT:policy:ACCEPT

Состояние пакета

Состояние пакета после цепочки mangle:OUTPUT:

IP-адрес отправителя: 192.168.1.88

Порт отправителя: 49058

IP-адрес получателя: 10.43.156.98

Порт получателя: 9898

Выходной интерфейс: enp3s0

Метка: нет

Таблица NAT — OUTPUT

В таблице nat:OUTPUT пакетам гораздо «веселее»:

00: PACKET: 2 0427ffe5 OUT=enp3s0 SRC=192.168.1.88 DST=10.43.156.98 LEN=60 \
TOS=0x0 TTL=64 ID=45471DF SPORT=49058 DPORT=9898 SYN

01: TRACE: 2 0427ffe5 nat:OUTPUT:rule:0xb9:JUMP:cali-OUTPUT  -4 -t nat \
-A OUTPUT -m comment --comment "cali:tVnHkvAo15HuiPy0" -j cali-OUTPUT

02: TRACE: 2 0427ffe5 nat:cali-OUTPUT:rule:0xb8:JUMP:cali-fip-dnat  -4 \
-t nat -A cali-OUTPUT -m comment --comment "cali:GBTAv2p5CwevEyJm" \
-j cali-fip-dnat

03: TRACE: 2 0427ffe5 nat:cali-fip-dnat:return:

04: TRACE: 2 0427ffe5 nat:cali-OUTPUT:return:

05: TRACE: 2 0427ffe5 nat:OUTPUT:rule:0x9:JUMP:KUBE-SERVICES  -4 \
-t nat -A OUTPUT -m comment --comment "kubernetes service portals" \
-j KUBE-SERVICES

06: TRACE: 2 0427ffe5 nat:KUBE-SERVICES:rule:0x4aaa:JUMP:KUBE-SVC-Y4T5L63IYP3YFEBS  \
-4 -t nat -A KUBE-SERVICES -d 10.43.156.98/32 -p tcp -m comment \
--comment "podinfo/frontend-podinfo:http cluster IP" \
-j KUBE-SVC-Y4T5L63IYP3YFEBS

07: TRACE: 2 0427ffe5 nat:KUBE-SVC-Y4T5L63IYP3YFEBS:rule:0x471b:JUMP:KUBE-MARK-MASQ \
-4 -t nat -A KUBE-SVC-Y4T5L63IYP3YFEBS ! -s 10.42.0.0/16 -d 10.43.156.98/32 \
-p tcp -m comment --comment "podinfo/frontend-podinfo:http cluster IP" \
-j KUBE-MARK-MASQ

08: TRACE: 2 0427ffe5 nat:KUBE-MARK-MASQ:rule:0x4aa4:CONTINUE  \
-4 -t nat -A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000

09: TRACE: 2 0427ffe5 nat:KUBE-MARK-MASQ:return:

10: TRACE: 2 0427ffe5 nat:KUBE-SVC-Y4T5L63IYP3YFEBS:rule:0x471d:JUMP:KUBE-SEP-OYNVDBAPYJ623W6H  \
-4 -t nat -A KUBE-SVC-Y4T5L63IYP3YFEBS -m comment \
--comment "podinfo/frontend-podinfo:http -> 10.42.54.194:9898" \
-j KUBE-SEP-OYNVDBAPYJ623W6H

11: TRACE: 2 0427ffe5 nat:KUBE-SEP-OYNVDBAPYJ623W6H:rule:0x4721:ACCEPT  \
-4 -t nat -A KUBE-SEP-OYNVDBAPYJ623W6H -p tcp -m comment \
--comment "podinfo/frontend-podinfo:http" -m tcp \
-j DNAT --to-destination 10.42.54.194:9898

Первые пять строк показывают, как пакет проходит через nat:OUTPUT и попадает в nat:cali-OUTPUT, далее в nat:cali-fip-dnat, а затем возвращается в nat:OUTPUT и передаётся в следующее правило в этой цепочке — nat:KUBE-SERVICES.

Обработка в цепочке nat:KUBE-SERVICES начинается с пятой строки, и далее пакет оказывается в nat:KUBE-SVC-Y4T5L63IYP3YFEBS по IP-адресу сервиса (10.43.156.98).

Восьмая строка перекидывает его в цепочку nat:KUBE-MARK-MASQ, поскольку адрес отправителя пакета не принадлежит подсети 10.42.0.0/16 (то есть отправителем не является другой под). Далее он помечается значением 0x4000. Эта метка носит локальный характер и не сохраняется при передаче на другой сервер/хоп. Она потребуется позже, при обработке пакета. В строке 10 пакет возвращается в цепочку nat:KUBE-SVC-Y4T5L63IYP3YFEBS.

В строке 11 происходит балансировка нагрузки внутри kube-proxy в режиме iptables. Ее логику можно посмотреть, выведя цепочку iptables:

$ iptables -t nat -L KUBE-SVC-Y4T5L63IYP3YFEBS -n --line-numbers
Chain KUBE-SVC-Y4T5L63IYP3YFEBS (1 references)
num  target     prot opt source               destination         
1    KUBE-MARK-MASQ  tcp  -- !10.42.0.0/16         10.43.156.98         
/* podinfo/frontend-podinfo:http cluster IP */

2    KUBE-SEP-6G2GMHWEBXQ5W3DV  all  --  0.0.0.0/0            0.0.0.0/0     
/* podinfo/frontend-podinfo:http -> 10.42.217.66:9898 */ 
statistic mode random probability 0.50000000000

3    KUBE-SEP-OYNVDBAPYJ623W6H  all  --  0.0.0.0/0            0.0.0.0/0            
/* podinfo/frontend-podinfo:http -> 10.42.54.194:9898 */

Обратите внимание на statistic mode random probability 0.50000000000. Это правило будет срабатывать в 50% случаев и перекидывать пакет в цепочку nat:KUBE-SEP-6G2GMHWEBXQ5W3DV. В остальных случаях он будет попадать в nat:KUBE-SEP-OYNVDBAPYJ623W6H. Эти SEP-цепочки выполняют одну и ту же функцию: выполняют DNAT, меняя IP-адрес получателя с адреса сервиса 10.43.156.98 на адрес одного из двух подов, развёрнутых для сервиса Podinfo. Цепочка, которая заканчивается на W3DV, ведёт в под с IP-адресом 10.42.217.66, а цепочка, заканчивающаяся на 3W6h, — в под с IP-адресом 10.43.54.194. Конкретно этот пакет попал в под 10.43.54.194, поэтому в строке 11 происходит переход от цепочки SVC к SEP (Service Endpoint) для пода 10.43.54.194. Наконец, строка 12 показывает, что DNAT действительно произошёл и IP-адрес получателя теперь равен 10.42.54.194:

Схема прохождения пакета из NAT в filter

Схема прохождения пакета из NAT в filter

Состояние пакета

Состояние пакета после цепочки nat:OUTPUT:

IP-адрес отправителя: 192.168.1.88

Порт отправителя: 49058

IP-адрес получателя: 10.42.54.194

Порт получателя: 9898

Выходной интерфейс: enp3s0

Метка: 0×4000/0×4000

Таблица filter — OUTPUT

Таблица filter не даёт трафику покинуть хост. Учитывая это, существенных изменений пакета не ожидается.

00: PACKET: 2 0427ffe5 OUT=enp3s0 SRC=192.168.1.88 DST=10.42.54.194 \
LEN=60 TOS=0x0 TTL=64 ID=45471DF SPORT=49058 DPORT=9898 SYN MARK=0x4000

01: TRACE: 2 0427ffe5 filter:OUTPUT:rule:0x91:JUMP:cali-OUTPUT  -4 \
-t filter -A OUTPUT -m comment --comment "cali:tVnHkvAo15HuiPy0" \
-j cali-OUTPUT

02: TRACE: 2 0427ffe5 filter:cali-OUTPUT:rule:0x8a:CONTINUE  -4 \
-t filter -A cali-OUTPUT -m comment --comment "cali:iC1pSPgbvgQzkUk_" \
-j MARK --set-xmark 0x0/0xf0000

03: TRACE: 2 0427ffe5 filter:cali-OUTPUT:return:

04: TRACE: 2 0427ffe5 filter:OUTPUT:rule:0x17:JUMP:KUBE-PROXY-FIREWALL  -4 \
-t filter -A OUTPUT -m conntrack --ctstate NEW -m comment \
--comment "kubernetes load balancer firewall" -j KUBE-PROXY-FIREWALL

05: TRACE: 2 0427ffe5 filter:KUBE-PROXY-FIREWALL:return:

06: TRACE: 2 0427ffe5 filter:OUTPUT:rule:0x12:JUMP:KUBE-SERVICES  -4 \
-t filter -A OUTPUT -m conntrack --ctstate NEW -m comment \
--comment "kubernetes service portals" -j KUBE-SERVICES

07: TRACE: 2 0427ffe5 filter:KUBE-SERVICES:return:

08: TRACE: 2 0427ffe5 filter:OUTPUT:rule:0x6:JUMP:KUBE-FIREWALL  -4 \
-t filter -A OUTPUT -j KUBE-FIREWALL

09: TRACE: 2 0427ffe5 filter:KUBE-FIREWALL:return:

10: TRACE: 2 0427ffe5 filter:OUTPUT:return:

11: TRACE: 2 0427ffe5 filter:OUTPUT:policy:ACCEPT

Первая запись TRACE говорит о том, что пакет из цепочки filter:OUTPUT перешёл в цепочку filter:cali-OUTPUT. Цепочка filter:cali-OUTPUT меняет метку пакета и затем передаёт обработку обратно в цепочку filter:OUTPUT в третьей строке (обратите внимание, что метка пакета на самом деле не меняется из-за того, как работает побитовая AND между 0xf000 и 0x4000). Далее пакет проходит через различные Kubernetes-цепочки, которые в конечном итоге не вносят никаких изменений и пакет попадает в ACCEPT.

Но есть тут и кое-что полезное. Посмотрите на строки, в которых есть -m conntrack -ctstate NEW. Поскольку это пакет TCP SYN (первый в трёхстороннем рукопожатии), он подлежит более тщательной проверке. Вышеупомянутые строки служат для обновления таблицы соединений, используемой iptables. Любопытно, что в полном выводе xtable-monitor --trace трейсы последующих пакетов содержат гораздо меньше строк.

Состояние пакета

Состояние пакета после цепочки nat:OUTPUT:

IP-адрес отправителя: 192.168.1.88

Порт отправителя: 49058

IP-адрес получателя: 10.42.54.194

Порт получателя: 9898

Выходной интерфейс: enp3s0

Метка: 0×4000/0×4000

Таблица mangle — POSTROUTING

Опять же, цель таблиц mangle — модифицировать пакеты (преимущественно поля TTL и ToS/DSCP в заголовках). Учитывая это, серьёзных изменений здесь также не предвидится.

00: PACKET: 2 0427ffe5 OUT=vxlan.calico SRC=192.168.1.88 DST=10.42.54.194 \
LEN=60 TOS=0x0 TTL=64 ID=45471DF SPORT=49058 DPORT=9898 SYN MARK=0x4000

01: TRACE: 2 0427ffe5 mangle:POSTROUTING:rule:0x15:JUMP:cali-POSTROUTING  -4 \
-t mangle -A POSTROUTING -m comment --comment "cali:O3lYWMrLQYEMJtB5" \
-j cali-POSTROUTING

02: TRACE: 2 0427ffe5 mangle:cali-POSTROUTING:rule:0x12:CONTINUE  -4 \
-t mangle -A cali-POSTROUTING -m comment --comment "cali:nnqPh8lh2VOogSzX" \
-j MARK --set-xmark 0x0/0xf0000

03: TRACE: 2 0427ffe5 mangle:cali-POSTROUTING:rule:0x13:JUMP:cali-to-host-endpoint  \
-4 -t mangle -A cali-POSTROUTING -m comment --comment "cali:nquN8Jw8Tz72pcBW" \
-m conntrack --ctstate DNAT -j cali-to-host-endpoint

04: TRACE: 2 0427ffe5 mangle:cali-to-host-endpoint:return:

05: TRACE: 2 0427ffe5 mangle:cali-POSTROUTING:return:

06: TRACE: 2 0427ffe5 mangle:POSTROUTING:return:

07: TRACE: 2 0427ffe5 mangle:POSTROUTING:policy:ACCEPT

Ещё одна попытка обновить метку происходит в строке 02. Как и в прошлый раз, она остаётся прежней (0x40000).

Состояние пакета

Состояние пакета после цепочки nat:OUTPUT:

IP-адрес отправителя: 192.168.1.88

Порт отправителя: 49058

IP-адрес получателя: 10.42.54.194

Порт получателя: 9898

Выходной интерфейс: vxlan.calico

Метка: 0×4000/0×4000

Более заметное изменение состояния пакета косвенно связано с модификациями, произведёнными iptables. IP-адрес получателя пакета изменился в цепочке nat:OUTPUT с адреса сервиса 10.43.156.98 на адрес пода 10.42.54.194. Далее был проведён поиск по таблице маршрутизации по адресу пода, и исходящим интерфейсом оказался vxlan.calico (что и подтверждает вывод ниже):

$ ip route get 10.42.54.194
10.42.54.194 via 10.42.54.193 dev vxlan.calico src 10.42.135.130 uid 0
    cache

Таким образом, оверлей VxLAN доставит пакет от узла, на котором был инициирован запрос, к узлу, на котором работает под, а заголовки а-ля «состояние пакета» будут обработаны узлом-получателем после удаления VxLAN-инкапсуляции. Вся VxLAN-операции происходят за пределами iptables.

Таблица NAT — POSTROUTING

Таблица NAT отвечает за любые NAT-изменения отправителя и получателя пакета. Поскольку получатель уже был изменён, никаких дополнительных действий не требуется:

00: PACKET: 2 0427ffe5 OUT=vxlan.calico SRC=192.168.1.88 DST=10.42.54.194 LEN=60 TOS=0x0 TTL=64 ID=45471DF SPORT=49058 DPORT=9898 SYN MARK=0x4000

01: TRACE: 2 0427ffe5 nat:POSTROUTING:rule:0xba:JUMP:cali-POSTROUTING  -4 \
-t nat -A POSTROUTING -m comment --comment "cali:O3lYWMrLQYEMJtB5" \
-j cali-POSTROUTING

02: TRACE: 2 0427ffe5 nat:cali-POSTROUTING:rule:0xb5:JUMP:cali-fip-snat  -4 \
-t nat -A cali-POSTROUTING -m comment --comment "cali:Z-c7XtVd2Bq7s_hA" \
-j cali-fip-snat

03: TRACE: 2 0427ffe5 nat:cali-fip-snat:return:

04: TRACE: 2 0427ffe5 nat:cali-POSTROUTING:rule:0xb6:JUMP:cali-nat-outgoing  \
-4 -t nat -A cali-POSTROUTING -m comment --comment "cali:nYKhEzDlr11Jccal" \
-j cali-nat-outgoing

05: TRACE: 2 0427ffe5 nat:cali-nat-outgoing:return:

06: TRACE: 2 0427ffe5 nat:cali-POSTROUTING:rule:0xb7:ACCEPT  -4 -t nat \
-A cali-POSTROUTING -o vxlan.calico -m comment \
--comment "cali:e9dnSgSVNmIcpVhP" -m addrtype ! --src-type LOCAL \
--limit-iface-out -m addrtype --src-type LOCAL -j MASQUERADE --random-fully

Закругляемся

Честно говоря, я в шоке, что вы дочитали до этого места. Что ж, спасибо. Надеюсь, что вы вынесли из этой статьи что-то, кроме головной боли и презрения к iptables.

P. S.

Читайте также в нашем блоге:

© Habrahabr.ru