Прим. перев.: эта статья, написанная 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 передается в качестве параметра флага --controllers kube-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'а на некий узел запускает следующую цепочку событий:



Справка: Архитектура CRI-плагинов Containerd.

Взаимодействие среды запуска контейнеров и плагинов 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.

$ cat /etc/cni/net.d/10-flannel.conflist
{
  "name": "cni0",
  "plugins": [
    {
      "type": "flannel",
      "delegate": {
         "ipMasq": false,
        "hairpinMode": true,
        "isDefaultGateway": true
      }
    }
  ]
}

CNI-плагин Flannel работает совместно с Flanneld. Во время запуска Flanneld извлекает podCIDR и другие связанные с сетью подробности из API-сервера и сохраняет их в файл /run/flannel/subnet.env.

FLANNEL_NETWORK=10.244.0.0/16 
FLANNEL_SUBNET=10.244.0.1/24
FLANNEL_MTU=1450 
FLANNEL_IPMASQ=false

CNI-плагин Flannel использует данные из /run/flannel/subnet.env для настройки и вызова CNI-плагина моста (bridge).

CNI-плагин Bridge


Этот плагин вызывается со следующей конфигурацией:

{
  "name": "cni0",
  "type": "bridge",
  "mtu": 1450,
  "ipMasq": false,
  "isGateway": true,
  "ipam": {
    "type": "host-local",
    "subnet": "10.244.0.0/24"
  }
}

При первом вызове он создает Linux-мост с «name»: «cni0», что указывается в конфиге. Затем для каждого pod'а создается пара veth. Один ее конец подключается к сетевому пространству имен контейнера, другой входит в Linux-мост в сети хоста. CNI-плагин Bridge подключает все контейнеры хоста к Linux-мосту в сети хоста.

Закончив с настройкой пары veth, плагин Bridge вызывает локальный для хоста (host-local) CNI-плагин IPAM. Тип IPAM-плагина можно настроить в конфиге CNI, который плагин CRI использует для вызова CNI-плагина Flannel.

Локальные для хоста IPAM-плагины CNI


Bridge CNI вызывает хост-локальный IPAM-плагин CNI со следующей конфигурацией:

{
  "name": "cni0",
  "ipam": {
    "type": "host-local",
    "subnet": "10.244.0.0/24",
    "dataDir": "/var/lib/cni/networks"
  }
}

Host-local IPAM-плагин (IP Address Management — управление IP-адресами) возвращает IP-адрес для контейнера из подсети и сохраняет выделенный IP на хосте в директории, указанной в разделе dataDir/var/lib/cni/networks/<network-name=cni0>/<ip>. В этом файле содержится ID контейнера, которому присвоен данный IP-адрес.

При вызове host-local IPAM-плагина он возвращает следующие данные:

{
  "ip4": {
    "ip": "10.244.4.2",
    "gateway": "10.244.4.3"
  },
  "dns": {}
}

Резюме


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 или по адресу hello@ronaknathani.com. Не стесняйтесь обращаться, если захотите обсудить аспекты этой статьи или что-нибудь другое. Я с удовольствием пообщаюсь с вами!

Ссылки


Контейнеры и сеть



Как работает Flannel



CRI и CNI



P.S. от переводчика


Читайте также в нашем блоге: