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 будет устанавливать соединение