Calico Kubernetes the hard way v2 How packet goes from pod
Calico: как пакет выходит из POD
Это заметка про Calico, где рассматривается небольшая часть пути пакета - как пакет покидает POD.
Она появилась по-тому что сеть сделана в Calico не совсем классическим способом, и используются механизмы которые редко можно встретить за пределами сетей провайдеров.
"Странности" внутри 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. Ядро будет отвечать МАК-адресом этого интерфейса на все запросы если адрес в запросе доступен НЕ через этот интерфейс
ARP, Request who-has 10.20.30.40
- будет обработан и отправлен ответ с мак-адресом интерфейса eth0 (при условии что существует маршрут к10.20.30.40
, в том числе даже если этоdefault
ARP, Request who-has 192.168.0.100
- будет проигнорирован
Это поведение вполне можно считать логичным - по-умолчанию ядро предполагает, что хост с адресом 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
будет "думать" что клиенты должны и без нее знать маки друг-друга
- Сети с изоляцией портов. Эта технология позволяет специально запретить всякое прохождение пакетов между портами коммутатора, указанными как клиентские и разрешить прохождение только от порта роутера к клиенту и от клиента к порту роутера. У разных производителей оборудования технология может называться по-разному