Прим. перев.: эта статья, написанная SRE-инженером из LinkedIn, в деталях рассказывает о той «внутренней магии» в Kubernetes — точнее, взаимодействии CRI, CNI и kube-apiserver, — что происходит, когда очередному pod’у требуется назначить IP-адрес.
Одно из базовых требований сетевой модели Kubernetes состоит в том, что у каждого pod’а должен быть свой собственный IP-адрес и любой другой pod в кластере должен иметь возможность связаться с ним по этому адресу. Есть множество сетевых «провайдеров» (Flannel, Calico, Canal и т.п.), которые помогают реализовать данную сетевую модель.
Когда я только начинал работать с Kubernetes, мне было не совсем ясно, как именно pod’ы получают свои IP-адреса. Даже с пониманием, как функционируют отдельные компоненты, было сложно представить их совместную работу. Например, я знал, для чего нужны плагины CNI, но не представлял, как именно они вызываются. Поэтому решил написать эту статью, чтобы поделиться знаниями о различных сетевых компонентах и их совместной работе в кластере Kubernetes, которые и позволяют каждому pod’у получить свой уникальный IP-адрес.
Существуют различные способы организации сетевого взаимодействия в Kubernetes — аналогично тому, как и различные варианты исполняемых сред (runtime) для контейнеров. В этой публикации будет использоваться Flannel для организации сети в кластере, а в качестве исполняемой среды — Containerd. Также я исхожу из предположения, что вы знаете, как устроено сетевое взаимодействие между контейнерами, поэтому лишь вкратце затрону его, исключительно для контекста.
Некоторые базовые понятия
Контейнеры и сеть: краткий обзор
В интернете достаточно отличных публикаций, объясняющих, как контейнеры связываются друг с другом по сети. Поэтому проведу лишь общий обзор основных понятий и ограничусь одним подходом, подразумевающим создание Linux-моста и инкапсуляцию пакетов. Подробности опущены, поскольку сама тема сетевого взаимодействия контейнеров заслуживает отдельной статьи. Ссылки на некоторые особенно содержательные и познавательные публикации будут приведены ниже.
Контейнеры на одном хосте
Один из способов организации связи по IP-адресам между контейнерами, работающими на одном и том же хосте, предполагает создание Linux-моста. Для этого в Kubernetes (и Docker) создаются виртуальные устройства veth (virtual ethernet). Один конец veth-устройства подключается к сетевому пространству имен контейнера, другой — к Linux-мосту в сети хоста.
У всех контейнеров на одном хосте один из концов veth подключен к мосту, через который они могут связываться друг с другом по IP-адресам. У Linux-моста также имеется IP-адрес, и он выступает в качестве шлюза для исходящего (egress) трафика из pod’ов, предназначенного для других узлов.
Контейнеры на разных хостах
Инкапсуляция пакетов — один из способов, позволяющий контейнерам на разных узлах связываться друг с другом по IP-адресам. Во Flannel за эту возможность отвечает технология vxlan, которая «упаковывает» исходный пакет в пакет UDP и затем отправляет его по назначению.
В кластере Kubernetes Flannel создает устройство vxlan и соответствующим образом дополняет таблицу маршрутов на каждом из узлов. Каждый пакет, предназначенный для контейнера на другом хосте, проходит через устройство vxlan и инкапсулируется в пакет UDP. В пункте назначения вложенный пакет извлекается и перенаправляется на нужный pod.
Примечание: Это лишь один из способов организации сетевого взаимодействия между контейнерами.
Что такое CRI?
CRI (Container Runtime Interface) — это плагин, позволяющий kubelet’у использовать разные исполняемые среды контейнеров. API CRI встроен в различные исполняемые среды, поэтому пользователи могут выбирать runtime по своему усмотрению.
Что такое CNI?
Проект CNI представляет собой спецификацию для организации универсального сетевого решения для Linux-контейнеров. Кроме того, он включает в себя плагины, отвечающие за различные функции при настройке сети pod’а. Плагин CNI — это исполняемый файл, соответствующи спецификации (некоторые плагины мы обсудим ниже).
Выделение подсетей узлам для назначения IP-адресов pod’ам
Поскольку каждый pod кластера должен иметь IP-адрес, важно убедиться в том, чтобы этот адрес был уникальным. Это достигается путем выделения каждому узлу уникальной подсети, из которой затем pod’ам на этом узле назначаются IP-адреса.
Контроллер IPAM узла
Когда nodeipam передается в качестве параметра флага --controllerskube-controller-manager’а, он каждому узлу выделяет отдельную подсеть (podCIDR) из CIDR кластера (т.е. диапазона IP-адресов для сети кластера). Поскольку эти podCIDR’ы не пересекаются, становится возможным каждому pod’у выделить уникальный IP-адрес.
Узлу Kubernetes присваивается podCIDR в момент его начальной регистрации в кластере. Чтобы изменить podCIDR у узлов, необходимо их дерегистрировать и затем повторно зарегистрировать, в промежутке внеся соответствующие изменения в конфигурацию управляющего слоя Kubernetes. Вывести podCIDR узла можно с помощью следующей команды:
$ kubectl get no <nodeName> -o json | jq '.spec.podCIDR'
10.244.0.0/24
Kubelet, среда запуска контейнера и плагины CNI: как это все работает
Планирование pod’а на узел связано с выполнением множества подготовительных действий. В этом разделе я сконцентрируюсь только на тех из них, которые непосредственно связаны с настройкой сети pod’а.
Планирование pod’а на некий узел запускает следующую цепочку событий:
Взаимодействие среды запуска контейнеров и плагинов CNI
У каждого сетевого провайдера имеется свой плагин CNI. Runtime контейнера запускает его, чтобы сконфигурировать сеть для pod’a в процессе его запуска. В случае containerd запуском CNI-плагина занимается плагин Containerd CRI.
При этом у каждого провайдера есть свой агент. Он устанавливается во все узлы Kubernetes и отвечает за сетевую настройку pod’ов. Этот агент идет либо в комплекте с конфигом CNI, либо самостоятельно создает его на узле. Конфиг помогает CRI-плагину установить, какой плагин CNI вызывать.
Местонахождение конфига CNI можно настроить; по умолчанию он лежит в /etc/cni/net.d/<config-file>. Администраторы кластера также отвечают за установку плагинов CNI на каждый узел кластера. Их местонахождение также настраивается; директория по умолчанию — /opt/cni/bin.
При использовании containerd пути для конфига и бинарников плагина можно задать в разделе [plugins.«io.containerd.grpc.v1.cri».cni] в файле конфигурации containerd.
Поскольку мы используем Flannel в качестве сетевого провайдера, давайте немного поговорим о его настройке:
Flanneld (демон Flannel’а) обычно устанавливается в кластер как DaemonSet с install-cni в качестве init-контейнера.
Install-cni создает файл конфигурации CNI (/etc/cni/net.d/10-flannel.conflist) на каждом узле.
Flanneld создает устройство vxlan, извлекает сетевые метаданные с API-сервера и следит за обновлениями pod’ов. По мере их создания он распространяет маршруты для всех pod’ов по всему кластеру.
Эти маршруты и позволяют pod’ам связываться друг с другом по IP-адресам.
Для получения более подробной информации о работе Flannel рекомендую воспользоваться ссылками в конце статьи.
Вот схема взаимодействия между плагином Containerd CRI и плагинами CNI:
Как видно выше, kubelet вызывает плагин Containerd CRI, чтобы создать pod, а тот уже вызывает плагин CNI для настройки сети pod’а. При этом CNI-плагин сетевого провайдера вызывает другие базовые плагины CNI для настройки различных аспектов сети.
Взаимодействие между CNI-плагинами
Существуют различные плагины CNI, задача которых — помочь настроить сетевое взаимодействие между контейнерами на хосте. В этой статье речь пойдет о трех из них.
CNI-плагин Flannel
При использовании Flannel в качестве сетевого провайдера компонент Containerd CRI вызывает CNI-плагин Flannel, используя конфигурационный файл CNI /etc/cni/net.d/10-flannel.conflist.
CNI-плагин Flannel работает совместно с Flanneld. Во время запуска Flanneld извлекает podCIDR и другие связанные с сетью подробности из API-сервера и сохраняет их в файл /run/flannel/subnet.env.
При первом вызове он создает Linux-мост с «name»: «cni0», что указывается в конфиге. Затем для каждого pod’а создается пара veth. Один ее конец подключается к сетевому пространству имен контейнера, другой входит в Linux-мост в сети хоста. CNI-плагин Bridge подключает все контейнеры хоста к Linux-мосту в сети хоста.
Закончив с настройкой пары veth, плагин Bridge вызывает локальный для хоста (host-local) CNI-плагин IPAM. Тип IPAM-плагина можно настроить в конфиге CNI, который плагин CRI использует для вызова CNI-плагина Flannel.
Host-local IPAM-плагин (IPAddress Management — управление IP-адресами) возвращает IP-адрес для контейнера из подсети и сохраняет выделенный IP на хосте в директории, указанной в разделе dataDir — /var/lib/cni/networks/<network-name=cni0>/<ip>. В этом файле содержится ID контейнера, которому присвоен данный IP-адрес.
При вызове host-local IPAM-плагина он возвращает следующие данные:
Kube-controller-manager каждому узлу присваивает podCIDR. Pod’ы каждого узла получают IP-адреса из пространства адресов в выделенном диапазоне podCIDR. Поскольку podCIDR’ы узлов не пересекаются, все pod’ы получают уникальные IP-адреса.
Администратор кластера Kubernetes настраивает и устанавливает kubelet, среду запуска контейнеров, агента сетевого провайдера и копирует плагины CNI на каждый узел. Во время старта агент сетевого провайдера генерирует конфиг CNI. Когда pod планируется на узел, kubelet вызывает CRI-плагин для его создания. Далее, если используется containerd, плагин Containerd CRI вызывает CNI-плагин, указанный в конфиге CNI, для настройки сети pod’а. В результате pod получает IP-адрес.
Мне потребовалось некоторое время, чтобы разобраться во всех тонкостях и нюансах всех этих взаимодействий. Надеюсь, полученный опыт поможет и вам лучше понять, как работает Kubernetes. Если я в чем-то ошибаюсь, пожалуйста, свяжитесь со мной в Twitter или по адресу [email protected]. Не стесняйтесь обращаться, если захотите обсудить аспекты этой статьи или что-нибудь другое. Я с удовольствием пообщаюсь с вами!