Operations

Сетевая модель Kubernetes

С Kubernetes достаточно просто начинать работу. Но если заглянуть внутрь, мы увидим сложную систему с множеством «подвижных» компонентов, функционирование и взаимодействие которых необходимо понимать, если вы хотите быть готовы к возможным сбоям. Одной из наиболее сложных и, возможно, наиболее критичных составляющих Kubernetes является сеть.

В основе сетевого устройства Kubernetes заложен важный архитектурный принцип — «У каждого пода свой уникальный IP адрес».

IP адрес пода делится между всеми его контейнерами и является доступным (маршрутизируемым) для всех остальных подов. Замечали когда-нибудь на своих узлах работающие pause-контейнеры? Их ещё называют «контейнерами-песочницами» (sandbox containers), потому что их работа заключается в резервировании и удержании сетевого пространства имён (netns), используемого всеми контейнерами пода. Благодаря этому IP адрес пода не меняется даже в тех случаях, когда контейнер умирает и вместо него создаётся новый. Большим достоинством такой модели — IP для каждого пода (IP-per-pod) — является отсутствие коллизий IP адресов/портов на нижележащем хосте. А нам не нужно беспокоиться о том, какие порты используют приложения.

Поэтому единственное требование Kubernetes — все IP адреса подов должны быть доступны/маршрутизируемы из остальных подов вне зависимости от того, на каком узле они расположены.

Взаимодействие внутри узлов (intra-node)

Первый шаг — удостовериться, что поды одного узла способны общаться между собой. Затем эта идея расширяется до взаимодействия между узлами, с интернетом и т.п.

На каждом узле Kubernetes, которым в данном случае является Linux-машина, существует корневое сетевое пространство имён — root netns. Основной сетевой интерфейс — eth0 — находится в этом root netns:

Аналогичным образом у каждого пода есть свой netns с виртуальным интерфейсом Ethernet, связывающим их с root netns. По сути это виртуальный линк с одним концом в root netns и другим — в netns пода.

Конец на стороне пода назван eth0, потому что под не знает о нижележащем хосте и думает, что у него своя корневая сетевая конфигурация. Другой конец назван как-нибудь вроде vethxxx. Вы можете увидеть все эти интерфейсы на своём узле Kubernetes, воспользовавшись командой ifconfig или ip a.

Таково устройство всех подов на узле. Для того, чтобы поды могли общаться друг с другом, используется Ethernet-мост Linux — cbr0. Docker использует похожий мост под названием docker0.

Вывести список мостов можно командой:

$ brctl show

Путь пакета из pod1 в pod2:

  • Он через eth0 покидает netns, принадлежащий pod1, и попадает в root netns через vethxxx.
  • Попадает в cbr0, который выдаёт ему точку назначения с помощью ARP-запроса, спрашивающего: «У кого такой IP-адрес?».
  • Интерфейс vethyyy отвечает, что у него нужный IP — так мост узнаёт, куда переслать пакет.
  • Пакет достигает vethyyy и, проходя виртуальный линк, попадает в netns, принадлежащий pod2.

Так контейнеры одного узла общаются между собой. Очевидно, есть и другие способы взаимодействия, но этот, пожалуй, самый простой его же использует Docker.

Взаимодействие между узлами (inter-node)

Как упоминалось выше, поды также должны быть доступны из всех узлов. И для Kubernetes вовсе не принципиально, как это реализовано. Посему можно использовать L2 (ARP между узлами), L3 (IP-маршрутизация между узлами — аналогично таблицам роутинга у облачных провайдеров) и оверлейные сети (overlay networks). Каждому узлу назначается уникальный блок CIDR (диапазон IP-адресов) для IP адресов, выдаваемых подам, так что у каждого пода свой уникальный IP, не конфликтующий с подами других узлов.

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

Рассмотрим пример с двумя узлами, аналогичный тому, что был выше. У каждого узла есть различные сетевые пространства имён, сетевые интерфейсы и мост.

Пошаговый разбор пути пакета из pod1 на pod4 (который расположен на другом узле):

  • Он через eth0 покидает netns, принадлежащий pod1, и попадает в root netns через vethxxx.
  • Попадает в cbr0, который делает ARP-запрос в поисках точки назначения.
  • Из cbr0 переходит в основной сетевой интерфейс eth0, поскольку ни у кого на этом узле нет IP-адреса, соответствующего pod4.
  • Покидает машину node1, оставаясь в сетевом проводе со значениями src=pod1 и dst=pod4.
  • В таблице маршрутизации настроен роутинг для блоков CIDR каждого узла — согласно ей, пакет отправляет на узел, блок CIDR которого содержит IP-адрес pod4.
  • Пакет прибывает на основной сетевой интерфейс узла node2 — eth0. Теперь, хотя pod4 и не является IP-адресом eth0, пакет перенаправляется на cbr0, поскольку на узлах включён IP forwarding. Таблица маршрутизации узла просматривается на наличие маршрутов, соответствующих IP-адресу pod4. В ней обнаруживается cbr0 как точка назначения для блока CIDR этого узла. Посмотреть таблицу маршрутизации узла можно с помощью команды route -n — она покажет маршрут для cbr0 вроде такого:
  • Мост забирает пакет, делает ARP-запрос и выясняет, что IP принадлежит vethyyy.
  • Пакет проходит через виртуальный линк и попадает в pod4.

Оверлейные сети (overlay network)

Оверлейные сети не требуются по умолчанию, однако они полезны в некоторых ситуациях. Например, когда нам не хватает пространства IP-адресов или сеть не может управлять дополнительными маршрутами. Или когда мы хотим получить дополнительные возможности управления, предоставляемые оверлеями.

Частый случай — наличие ограничения на количество маршрутов, поддерживаемых в таблицах роутинга облачного провайдера.

Например, для таблицы маршрутизации в AWS заявлена поддержка до 50 маршрутов без влияния на производительность сети. Если нам потребуется более 50 узлов Kubernetes, таблицы маршрутизации AWS перестанет хватать. В таких случаях поможет оверлейная сеть.

Оверлейная сеть инкапсулирует пакеты, проходящие по сети между узлами. Возможно, вы не захотите её использовать из-за того, что инкапсуляция/декапсуляция всех пакетов добавляет небольшую задержку и сложность. Зачастую это не нужно, но стоит учитывать, принимая решение об их использовании.

Чтобы понять, как ходит трафик в оверлейной сети (overlay network), рассмотрим пример с flannel (Open Source-проектом от CoreOS):

Здесь мы видим конфигурацию, аналогичную предыдущей, однако в ней появилось новое виртуальное Ethernet-устройство под названием flannel0 — оно находится в корневом пространстве имён (root netns). Это реализация Virtual Extensible LAN (VXLAN), которая для Linux — просто ещё один сетевой интерфейс.

Прохождение пакета из pod1 в pod4 (он находится на другом узле) выглядит примерно так:

  • Пакет через eth0 покидает netns, принадлежащий pod1, и оказывается в root netns на vethxxx.
  • Проходит до cbr0, который делает ARP-запрос для обнаружения точки назначения.
  • Поскольку ни у кого на этом узле нет IP-адреса, соответствующего pod4, мост отправляет пакет в flannel0 — таблица маршрутизации узла настроена на использованиее flannel0 в качестве цели для сетевого диапазона пода.
  • Демон flanneld взаимодействует с Kubernetes apiserver или нижележащим etcd, откуда получает все IP-адреса подов и сведения о том, на каких узлах они расположены. Таким образом, flannel создаёт соответствующие сопоставления (в пользовательском пространстве) для IP-адресов подов и IP-адресов узлов. flannel0 берёт пакет и заворачивает его в UDP-пакет с дополнительными заголовками, изменяющими IP-адреса источника и получателя на соответствующие узлы, отправляет его на специальный порт vxlan (обычно 8472):Хотя сопоставления находятся в пользовательском пространстве, реальная инкапсуляция и прохождение данных происходит в пространстве ядра, так что это достаточно быстро.
  • Инкапсулированный пакет отправляется через eth0, поскольку он отвечает за роутинг трафика узла.
  • Пакет покидает узел с IP-адресами узлов в качестве источника и назначения.
  • Таблица роутинга облачного провайдера уже знает, как маршрутизировать трафик между узлами, поэтому пакет отправляется к узлу-получателю — node2.
  • Пакет прибывает на eth0 узла node2. Поскольку в качестве порта используется специальный vxlan — ядро отправляет пакет на flannel0.
  • flannel0 декапсулирует пакет и переносит его обратно в root netns. Пакет покидает узел с IP-адресами узлов в качестве источника и назначения. Дальнейший путь совпадает с тем, что был в случае обычной (не оверлейной) сети.
  • Поскольку включён IP forwarding, ядро отправляет пакет на cbr0 согласно таблице маршрутизации.
  • Мост забирает пакет, делает ARP-запрос и выясняет, что нужный IP-адрес принадлежит vethyyy.
  • Пакет проходит через виртуальный линк и попадает в pod4.

У различных реализаций могут быть незначительные отличия, но в целом именно так работают оверлейные сети (overlay networks) в Kubernetes.

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