Calico Kubernetes the hard way v2 How packet goes from pod
Calico: как пакет выходит из POD
Это заметка про Calico, где рассматривается небольшая часть пути пакета - как пакет покидает POD.
Она появилась по-тому что сеть сделана в Calico не совсем классическим способом, и используются механизмы которые редко можно встретить за пределами сетей провайдеров.
Упрощенная схема
На схеме не показаны интерфейсы которые не описаны в этом документе, только те что касаются пересылки пакетов из POD в хостовую систему
+----------------------------------------+ | Host: | | +---------------------+ | | | POD | | | | | | | | 10.244.235.132/32 | | | +---eth0--------------+ | | | | | | | | calieede18e00ae | | На этом интерфейсе НЕТ ip адреса | +----------------------------------------+
"Странности" внутри PODa
Внутри POD
таблица маршрутизации выглядит достаточно необычно:
route -n
Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 0.0.0.0 169.254.1.1 0.0.0.0 UG 0 0 0 eth0 169.254.1.1 0.0.0.0 255.255.255.255 UH 0 0 0 eth0
При этом на первый взгляд выглядит так как-будто работать такая схема не должна
Адрес 169.254.1.1 это вообще Link-Local address
На интерфейсе установлен адрес с маской /32, в качестве шлюза указан адрес доступный непосредственно через интерфейс
ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1450 inet 10.244.235.132 netmask 255.255.255.255 broadcast 0.0.0.0 inet6 fe80::68a6:d0ff:fe94:1d10 prefixlen 64 scopeid 0x20<link> ether 6a:a6:d0:94:1d:10 txqueuelen 0 (Ethernet) RX packets 5 bytes 446 (446.0 B) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 17 bytes 1286 (1.2 KB) TX errors 0 dropped 1 overruns 0 carrier 0 collisions 0 lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536 inet 127.0.0.1 netmask 255.0.0.0 inet6 ::1 prefixlen 128 scopeid 0x10<host> loop txqueuelen 1000 (Local Loopback) RX packets 0 bytes 0 (0.0 B) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 0 bytes 0 (0.0 B) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
Сам интерфейс внутри POD
представляет из себя обычный для контейнеров veth
, второй конец которой находится за пределами сетевого пространства имен POD
,
для того что бы найти второй конец виртуального линка можно определить peer_ifindex
ethtool -S eth0 NIC statistics: peer_ifindex: 254 rx_queue_0_xdp_packets: 0 rx_queue_0_xdp_bytes: 0 rx_queue_0_xdp_drops: 0
В примере выше peer_ifindex: 254
это и есть индекс искомого интерфейса (на хосте, за пределами POD), который можно увидеть командой ip link show
ip -d link show
254 это и есть найденный peer_ifindex
... 254: calieede18e00ae@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP mode DEFAULT group default link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff link-netnsid 13 promiscuity 0 minmtu 68 maxmtu 65535 veth addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535 RX: bytes packets errors dropped overrun mcast 237561215 1243131 0 0 0 0 TX: bytes packets errors dropped carrier collsns 236237144 1273779 0 0 0 0
Как это работает (отправка пакета из POD
)
Для того что бы POD
мог отправить пакет, он проделывает следующие шаги:
- просматривает таблицу маршрутизации
- так как в таблице всего одна запись, шлюз, то все пакеты будут отправляться на шлюз
- для того что бы отправить пакет на шлюз необходимо знать МАК-адрес шлюза, соответственно необходима запись в arp-таблице
- для ее создания будет создан arp-запрос
Но, так как (со стороны хоста, за пределами контейнера) на ответной части интерфейса eth0, интерфейсе calieede18e00ae
нет никакого ip адреса, возникает вопрос.
Откуда берется arp-ответ?
Linux proxy_arp
Короткий ответ на вопрос - это "proxy arp
".
Для того что бы в первом приближении понять что механизм proxy_arp
включен, можно проверить sysctl
:
sysctl -a | grep calieede18e00ae | grep -v ipv6
Вывод сокращен:
... net.ipv4.conf.calieede18e00ae.proxy_arp = 1 net.ipv4.conf.calieede18e00ae.proxy_arp_pvlan = 0 net.ipv4.neigh.calieede18e00ae.proxy_delay = 0 ...
Из вывода видно (net.ipv4.conf.calieede18e00ae.proxy_arp = 1
), что для интерфейса механизм proxy_arp
включен (значение ключа установлено в 1)
Далее рассмотрим его работу более подробно.
Proxy ARP: 2 варианта
В целом в Linux есть 2 реализации proxy_arp, которые работают немного по разному
proxy_arp
, который используется Calicoproxy_arp_pvlan
, который реализует несколько другую логику ответов на arp-запросы (о нем ниже)
proxy_arp
Описание в документации достаточно скудное:
proxy_arp - BOOLEAN Do proxy arp. proxy_arp for the interface will be enabled if at least one of conf/{all,interface}/proxy_arp is set to TRUE, it will be disabled otherwise
Информации не много. Из других источников, удалось выяснить, что при включении этой опции ядро будет отвечать на arp-запросы только если они требуют переадресации.
Попробую пояснить на примерах.
Пример
Допустим есть интерфейс
eth0
на котором включен proxy_arp
и на этом интерфейсе настроен IP адрес 192.168.0.1/24
.
Согласно маске /24
, это означает что все адреса от 192.168.0.0
до 192.168.0.255
доступны непосредственно через этот интерфейс.
Ядро будет отвечать МАК-адресом интерфейса eth0
на все ARP-запросы, полученные на интерфейсе eth0
,
если IP aдрес в запросе, для которого запрашивается МАК адрес
доступен НЕ через этот интерфейс, другими словами любой адрес в ARP, Request who-has
не из диапазона 192.168.0.0-192.168.0.255
ARP, Request who-has 10.20.30.40
- будет обработан и отправлен ответ с мак-адресом интерфейсаeth0
,так как адрес в запросе10.20.30.40
не из диапазона192.168.0.0-192.168.0.255
(при условии что существует маршрут к10.20.30.40
, в том числе даже если этоdefault
ARP, Request who-has 192.168.0.100
- будет проигнорирован так как адрес192.168.0.100
из192.168.0.0-192.168.0.255
Это поведение вполне можно считать логичным - по-умолчанию ядро предполагает, что хост с адресом 192.168.0.100
, так как он находится в сети доступной через eth0
, сам может ответить и "помогать" с таким запросом необходимости нет.
В нашем случае с Calico
этот механизм вполне работает, и вот почему
- Ядро получает arp-запрос
ARP, Request who-has 169.254.1.1
на интерфейсеcalieede18e00ae
- Адрес
169.254.1.1
с точки зрения ядра доступен через маршрут по-умолчанию (а не через интерфейсcalieede18e00ae
) - Условия соблюдены, значит ответ на запрос будет отправлен.
Отмечу отдельно, что в такой конфигурации можно было бы обойтись даже без шлюза, указав со стороны POD маршрут по-умолчанию в интерфейс eth0
Это несомненно работает, но влечет за собой перерасход памяти, так как вместо одной записи для шлюза в таблицу arp будут попадать записи для каждого IP адреса с которым POD будет устанавливать соединение
proxy_arp_pvlan
Второй доступный в Linux механизм это proxy_arp_pvlan
Мне при первом прочтении документация показалась совершенно недостаточной
proxy_arp_pvlan - BOOLEAN Private VLAN proxy arp. Basically allow proxy arp replies back to the same interface (from which the ARP request/solicitation was received). This is done to support (ethernet) switch features, like RFC 3069, where the individual ports are NOT allowed to communicate with each other, but they are allowed to talk to the upstream router. As described in RFC 3069, it is possible to allow these hosts to communicate through the upstream router by proxy_arp'ing. Don't need to be used together with proxy_arp. This technology is known by different names: In RFC 3069 it is called VLAN Aggregation. Cisco and Allied Telesyn call it Private VLAN. Hewlett-Packard call it Source-Port filtering or port-isolation. Ericsson call it MAC-Forced Forwarding (RFC Draft).
Тут суть проблемы в том, что существуют сети, которые хотя и выглядят внешне как ethernet таковыми являются не в полной мере
- Радио (wifi) - представим себе ситуацию, что есть сеть из трех устройств, и максимальное расстояние на которых они могут взаимодействовать это 100м. Сеть состоит из точки доступа (базовой станции) и 2 клиентов
|((((((((((( )))))) | (((((( ))))))) | | /|\ | +---+-------+ +--------------+ +---+-------+ | Client 1 | | Base Station | | Client 2 | +-----------+ +--------------+ +-----------+ 192.168.0.2/24 192.168.0.1/24 192.168.0.3/24 0M -----------------------100M ----------------200M
Из грубой схемы видно, что хотя каждый из клиентов сможет передавать данные на базовую станцию и получать от нее ответы, связаться друг с другом напрямую клиенты не могут из-за того что находятся далеко друг от друга.
при этом типичная ситуация что все клиенты находятся в одном диапазоне адресов. Таким образом возникает проблема передачи данных от Client1 к Client 2:
-
- Прямая связь невозможна из за расстояния
- База даже при включенном
proxy_arp
будет "думать" что клиенты должны и без нее знать маки друг-друга
- Сети с изоляцией портов. Эта технология позволяет специально запретить всякое прохождение пакетов между портами коммутатора, указанными как клиентские и разрешить прохождение только от порта роутера к клиенту и от клиента к порту роутера. У разных производителей оборудования технология может называться по-разному.
Для решения этих проблем и был добавлен механизм proxy_arp_pvlan
, который разрешает ответы proxy_arp даже если они получены с того же интерфейса где ожидаемо находится адрес, который запрашивался
В документации написано Basically allow proxy arp replies back to the same interface (from which the ARP request/solicitation was received)., что меня сбивает с толку и наводит на мысли о том что есть случаи когда ответ отправляется не с того интерфейса, с которого был запрос. Это НЕ ТАК. Речь идет о том интерфейсе, через который доступен адрес, чей мак запрашивается.
- В случае если к базовой станции из примера выше приходит запрос
ARP, Request who-has 192.168.0.3
от клиента 1, базовая станция сделает следующие шаги- Проверит через какой интерфейс получен запрос (для простоты считаем что это wifi0)
- Проверит через какой интерфейс доступен запрошенный адрес 192.168.0.3, это тоже wifi0
В зависимости от настройки proxy_arp_pvlan
будет принято решение
- если
proxy_arp_pvlan = 1
ответ будет отправлен, так как базовая станция знает что не каждый клиент может получить мак другого клиента - если
proxy_arp_pvlan = 0
запрос будет проигнорирован, так как базовая станция думает что клиенты могут сами обменяться информацией о мак-адресах
При этом настройка proxy_arp_pvlan
никак не влияет на работу proxy_arp
- для запросов адресов которые доступны через другие интерфейсы, отличные от того через который поступил запрос, будет обработан или игнорироваться в зависимости от значения proxy_arp
Для Cisco
ip proxy-arp
- аналогproxy_arp
ip local-proxy-arp
- аналогproxy_arp_pvlan
Подведение итога
Для закрепления еще раз повторю путь пакета из POD
до хост-системы
- Внутри POD3 POD хочет отправить пакет
- Внутри POD3 POD просматривает свою таблицу маршрутизации и находит что у него единственный маршрут через шлюз
- Внутри POD3 Шлюз доступен через интерфейс eth0
- Внутри POD3 формируется и отправляется ARP запрос
- На ноде Arp запрос получен на стороне ноды, и обработан за счет механизма proxy_arp
- Внутри POD3 POD получает ответ на свой запрос, добавляет запись в свою arp таблицу
- Внутри POD3 POD отправляет пакет, где в МАК-адрес получателя это МАК-адрес интерфейса на стороне ноды, изученный их ARP-ответа
- На ноде Нода получает пакет, и так как ма-адрес получателя это ее МАК то пакет не отбрасывается а обрабатывается далее согласно правилам маршрутизации/файрволла
Ссылки
- https://qiita.com/ukinau/items/cb25588fb0c276a009dc ( на японском, переводил хромом, более-менее понятно)