Кратко
kubectl
- Валидирует запрос — проверяет что он понимает что это за запрос.
- Составляет http запрос к apiserver в соответствии с вычисленным типом ресурса — под, деплоймент и прочие разные штуки kube’а.
- Договаривается с apiserver об используемой версии api.
- Аутентифицируется в apiserver — по сертификатам, токену или паролю.
kube-apiserver
- Аутентифицирует клиента разрешенными = указанными при старте способами.
- Авторизует запрос — что вам его можно выполнять. По цепочке авторизаторов — по файлам политик и проч.
- Контроль допуска — проверяет что вообще такого типа запросы на этом кластере разрешены — лимиты, квоты и т.п.
- Формирует соответствующий запросу ресурс — runtime представления заказанного пода, деплоймента и т.п.
- Сохраняет его в etcd через “поставщик хранилища”, но в неинициализированном виде — не ставится галка.
- Поставщик хранилища его сохраняет и get’ает, чтобы проверить успешное сохранение.
- Apiserver отправляет http ответ.
- Поставщик хранилища вызывает post-hooks.
Инициализаторы
Если у вновь записанного типа ресурса есть какие-то предварительные шаги, то на его создание зарегистрированы соответствующие инициалайзеры, как например, ижектеры проксей для пода и т.п.
- Эти инициализаторы записываются в pending список этому ресурсу.
- И начиная с первого выполняются по одному — они подписаны на объекты и если видят себя в голове списка, то отрабатывают.
- Пустой список означает, что ресурс initialized. И он становится доступен контроллерам.
Контроллеры
- Каждый из них считывает состояние каких-то etcd нод, за которые он отвечает. Например, Deployment controller видит, что появился новый деплоймент nginx’ов и его ответственность — создать нужные ReplicaSet’ы. Создать в смысле записать в etcd в нужном виде.
- Когда записаны новые ReplicaSet’ы, то в свою очередь ReplicaSet controller видит, что его понимание о том какие поды существуют не соответствует тому какие ReplicaSet’ы указаны в etcd. И он начинает создавать нужные pod’ы согласно правилам, например, соблюдая ReplicaSet burst count — количество подов создаваемых за раз.
- Контроллеры узнают об изменении в хранилище через Informer’ы — специальный механизм листенеров над etcd.
Шедулер
- Когда все контроллеры отработали и завели все нужные поды, поды будут тем не менее находиться в Pending состоянии, т.к. они еще не были назначены на конкретные ноды.
- Шедулер работает примерно так же как другие контроллеры — он слушает все события про поды и отфильтровывает поды, у которых NodeName пустой и пытается найти им подходящие Ноды.
- Шедулинг происходит так:
- Прежде всего проверяется цепочка предикатов, которые отфильтровывают неподходящие ноды. Например, если в PodSpec’е указаны CPU ресурс, а на какой-то ноде столько нет.
- Дальше выполняются функции приоритета, по которым оставшиеся ноды ранжируются. Например, выше ставится нода с большим количеством свободных ресурсов. Когда нода выбрана, шедулер создает Binding запрос к apiserver’у — который запишет имя ноды в NodeName PodSpec’и и установит PodScheduled статус в True .
kubelet
- Это компонент запущенный на всех нодах Кубернетес кластера и занимающийся среди прочего жизненным циклом подов на этой ноде — он транслирует все необходимое из абстрации “Под” в реальные контейнеры, volum’ы и т.п.
- kubelet так же как контроллеры и шедулер получает статус всех подов назначеных его ноде от apiserver’а и проверяет, что всё это соответсвует его локальной действительности.
- А если что-то не соответствует, то он пытается отсинхронизовать действительность под желаемые аписервером требования. Вот что он делает:
- Создает объект PodStatus, в котором отслеживается изменения его состояния, такие как Pending, Running, Succeeded, Failed. Например, определив какие контейнеры надо запустить, кублет понимает, что они еще не созданы, и поэтому Pod находится в Pending состоянии.
- Затем этот PodStatus отдается статус-менеджеру Пода, который ассинхронно будет записывать обновления в apiserver.
- Запускаются секьюрити хендлеры, которые, например, проверяют профили Apparmor. Поды не прошедшие эти хендлеры навсегда остаются в состоянии Pending.
- Если был установлен флаг cgroups-per-qos, то kubelet создает cgroup’ы для пода и ставит туда ограничения ресурсов.
- Кублет создает дата-директории для пода — /var/run/kubelet/pods/
для самого пода, /volumes для вольюмов и /plugins для плагинов. - Volume manager будет ожидать соответствующих PodSpec’е вольюмов.
- Кублет получит все соответвующие поду секреты от аписервера, чтобы позже их заинжектить в контейнер.
- Container runtime запускает контейнер:
ContainerRuntimeInterface
- CRI это абстракция между kubelet’ом и, например, docker’ом, которая нужна, чтобы кубернетес мог работать не только поверх docker’а, но и поверх rkt или вообще вирутальных машин.
- kubelet запускает вызов RunPodSandbox, где “sandbox” это CRI термин обозначающий под. Т.е. это может быть как группа контейнеров, так и виртуалка или что-то подобное.
- В случае docker’а создание sandbox’а соответствует созданию запаузенного (paused) контейнера — родительского контейнера для пода, через который все контейнеры пода будут шарить ресурсы — Linux неймспейсы (IPC, network, PID).
ContainerNetworkInterface
- CNI это тоже интерфейс абстракция, которая позволяет подключать различные провайдеры организации сетей.
- Если у Pod’а был сконфигурирован интерфейс типа bridge:
- Плагин bridge создаст локальный линуксовый сетевой мост на ноде.
- Затем вторая пара от veth будет заинжектена внутрь paused родительского контейнера, т.е. в его network namespace.
- CNI присвоит этому интерфейсу IP и назначит роутинг. Делает это он через ipam (address manager) плагин, который, например, создает host-local конфигурацию.
- CNI получив информацию о DNS сервере от kublet’а, пропишет ее в resolve.conf контейнеру.
Межнодовое сетевое взаимодействие
Обычно оно организовывается с помощью overlay сети, например, это может быть layer-3 IPv4 межнодовая сеть, которая энкапсулирует ip пакеты в UDP реальной сети.
Запуск контейнеров
После того как sandbox подготовлен, kubelet запускает уже реальные контейнеры Пода — сначала запускаются init контейнеры, если такие есть в спеке Пода, и только потом основные контейнеры.
- Pull’ится соответствующий образ.
- CRI создает контейнер внутри родительского paused контейнера.
- Если нужно, контейнер регистрируется в CPU manager’е — ему приписывается набор ЦПУ, на которых он будет работать.
- Контейнера запускается!
- Запускаются post-start хуки.