K8s Q A Architecture
Архитектура? Какая еще архитектура
Тут общее описание - что делает каждый компонент (и иногда - зачем) Требуется более подробное описание
Kubelet
Основные функции Kubelet:
- Контейнеризация. Kubelet отвечает за запуск, остановку и управление контейнерами на узле в соответствии с указаниями из API-сервера Kubernetes.
- Он следит за состоянием подов и управляет контейнерами, чтобы поддерживать желаемое состояние, определённое в объектах ReplicaSet, Deployment и других контроллерах развёртывания.
- Взаимодействие с API-сервером. Kubelet взаимодействует с API-сервером Kubernetes для получения информации о планирующихся к деплою подах, а также для отчёта о состоянии и доступности узла. Это позволяет API-серверу отслеживать состояние кластера и координировать работу Kubelet на разных узлах.
- Мониторинг ресурсов. Kubelet мониторит ресурсы узла, такие, как использование CPU, память, дисковое пространство и т. д. Он передаёт эти метрики в API-сервер, чтобы система планирования знала, на каком узле размещать новые поды.
- Обработка Health Checks. Kubelet проверяет здоровье контейнеров, выполняя заранее определённые Health Checks. Если контейнер не отвечает на Health Check или его состояние становится некорректным, то Kubelet может перезапустить контейнер, чтобы восстановить его работоспособность.
- Работа с Volume. Kubelet обрабатывает монтирование Volume (томов) в контейнеры. Он управляет связыванием томов, определённых в манифестах подов, с соответствующими файловыми системами или другими хранилищами узла.
- Работа с сетью. Kubelet работает с CNI-плагином (Container Network Interface), чтобы обеспечить сетевую изоляцию и обмен данными между контейнерами в различных подах.
Kubernetes API (kube-apiserver)
Упрощённо стандартный workflow выглядит так:
- Мы делаем запрос в API ->
- Запрос проходит валидацию, ряд проверок и сохраняется в ETCD — key-value-хранилище ->
- Другие сервисы, например, тот же Kubelet, стучатся в API и получают список задач из этого хранилища.
Сразу возникает вопрос: а зачем вообще нужен этот API, если сервисы могли бы напрямую ходить в ETCD? У подхода с API есть несколько ключевых преимуществ:
- Валидация. API-сервер Kubernetes выполняет валидацию данных, которые приходят от клиентов, прежде чем они будут записаны в ETCD. Это включает проверку синтаксиса, правильности значений и других ограничений, определённых в спецификации API.
- Безопасность. Обращение к ETCD через API-сервер позволяет контролировать доступ к данным и обеспечивать безопасность. API-сервер выполняет аутентификацию и авторизацию запросов от компонентов кластера, чтобы предотвратить несанкционированный доступ к данным ETCD.
Этапы обработки API-запроса
Кроме того, в API есть ещё один очень важный компонент, о котором мы поговорим чуть позже.
- Admission Controllers — расширения API-сервера, которые позволяют внедрять дополнительные проверки и манипуляции перед тем, как запросы будут приняты или отклонены.
API при всей его критичности не хранит никаких данных.
Он реализует только логику и занимается исключительно обработкой запросов от пользователей и компонентов Kubernetes.
Именно поэтому этот компонент не кластеризуется. У вас может быть одна нода API, может быть несколько.
Конфликт между ними невозможен, так как за консистентное хранение состояния отвечают ETCD-бэкенд и Raft-протокол.
Raft-протоколе писать может только лидер. Если лидеру станет плохо, то его быстро пометят как уставшего и выберут нового. API может запросить любой узел ETCD и попросить внести какие-то изменения в конфигурацию. Если ближайшая нода окажется read-only, то она просто проксирует этот запрос к лидеру, и тот внесёт нужные изменения.
Admission Controller
API выступает в качестве важной прослойки между компонентами и распределённым бэкендом в ETCD. Его функциональность можно существенно дополнить ещё одной прослойкой — Admission Controller.
Admission Controller — это расширение API-сервера Kubernetes, которое позволяет выполнять дополнительные проверки, модификации и манипуляции с объектами Kubernetes перед тем, как они будут созданы, изменены или удалены в кластере.
Он предоставляет механизм для внедрения пользовательской логики на этапе входящего запроса к API-серверу, что обеспечивает дополнительные уровни безопасности и валидации.
Admission Controllers могут быть запущены как встроенные (built-in) или пользовательские (custom).
Встроенные Admission Controllers уже включены в API-сервер, и их можно просто включать при необходимости. Вот некоторые типовые встроенные варианты:
- NamespaceLifecycle. Управляет жизненным циклом пространств имён, позволяя разрешить или запретить их создание и удаление.
- ResourceQuota. Контролирует количество ресурсов (CPU, память) и объектов (поды, сервисы) в пространствах имён.
- SecurityContextDeny. Запрещает создание объектов без установленных Security Context, что повышает безопасность.
- NamespaceExists. Проверяет существование пространства имён, прежде чем разрешить создание объектов в нём.
Admission Webhook — ещё один очень полезный компонент, который может дополнительно улучшить безопасность, проверяя допустимость тех или иных API-запросов перед их выполнением.
Он представляет собой HTTP-сервер, который используется в Admission Controller для выполнения дополнительных проверок и манипуляций с объектами Kubernetes перед их созданием, изменением или удалением.
Например, мы хотим что-то задеплоить, но при этом у нас обязательно нужно описывать Network Policy,
чтобы ограничить возможности пода взаимодействовать с другими компонентами.
Но мы, негодяи, взяли и отправили запрос без этого описания.
Admission Controller увидит несоответствие и вернёт отказ через Admission Webhook в формате AdmissionReview API Kubernetes. Этот ответ содержит информацию о том, разрешён запрос или нет, а также может включать в себя какие-то дополнительные данные.
Controller Manager
Ещё один критически важный компонент. У нас есть «стадо приложений» — тот самый cattle.
В нашем манифесте мы декларативно описали, что нам нужно, чтобы численность «стада» была ровно 100 голов.
Соответственно, нужен какой-то пастух-администратор, который будет это самое стадо постоянно пересчитывать, при необходимости отстреливая избыточных бурёнок и пополняя недостающих.
Наше целевое состояние было пропущено через API и сохранено в ETCD, который гарантирует консистентность данных по всему кластеру. Controller Manager будет периодически тыкать в API и запрашивать из ETCD целевое состояние системы, сравнивать с текущим и при необходимости его корректировать.
Вот некоторые типы контроллеров, за которыми следит Controller Manager:
- ReplicaSet Controller. Контролирует количество запущенных реплик подов для объектов ReplicaSet. Он поддерживает желаемое количество реплик и может запускать или останавливать поды, чтобы поддерживать этот желаемый уровень.
- Deployment Controller. Управляет развёртываниями (Deployments) и обеспечивает желаемое количество реплик, включая возможность обновления версий приложений.
- StatefulSet Controller. Обрабатывает StatefulSet, предоставляющий уникальные идентификаторы для подов в режиме управления состоянием.
- DaemonSet Controller. Управляет DaemonSet, который обеспечивает запуск по одной реплике пода на каждом узле кластера.
- Job Controller. Управляет заданиями (Jobs), обеспечивая выполнение задачи и отслеживание статуса выполнения.
- Service Controller. Следит за объектами Service и обновляет Endpoints для сервисов, чтобы обеспечить правильную маршрутизацию трафика к подам.
- Endpoint Controller. Обновляет Endpoints для сервисов на основе изменений в Pod и Service.
- Namespace Controller. Обрабатывает пространства имён (Namespaces), создавая и удаляя их по запросу.
Ещё один важный момент. По аналогии с инстансами API мы можем запустить сколько угодно Controller Manager для отказоустойчивости. Но в отличие от API активным может быть только один Controller Manager. Например, два инстанста работают активно одновременно и увидели новый deployment. Тут же кинулись создавать Replica Set и получили его задвоение, хотя в основе был консистентный и единый ETCD. Именно поэтому Controller Manager тоже использует механизм голосования и выбирает лидера, чтобы этого избежать. Именно лидер уже и принимает нужные решения по поддержанию «численности стада». Все остальные будут ждать в резерве для отказоустойчивости.
Scheduler
Scheduler (планировщик) в Kubernetes — это компонент системы управления кластером, который отвечает за размещение подов на доступных узлах в кластере. Он отвечает за то, чтобы кластер не перекосило от нагрузки, а все инстансы запускались максимально равномерно.
Scheduler распределяет поды по узлам кластера так, чтобы нагрузка была равномерно распределена.
Он учитывает доступные ресурсы на каждом узле, такие, как CPU, память и дисковое пространство, чтобы предотвратить перегрузку или неэффективное использование ресурсов.
При необходимости он будет отвечать за автоматическое масштабирование на основе текущей нагрузки. Если кластер перегружен, то планировщик может поднять дополнительные поды для того, чтобы сгладить пиковую нагрузку. Кроме того, Scheduler учитывает требования к георезервированию и предотвращает размещение нескольких реплик одного пода на одном узле.
Это обеспечивает высокую доступность, так как при сбое узла другие реплики подов продолжают работать на других нодах.
Результатом работы Scheduler является скоринг. То есть он находит оптимальное место для размещения ресурса и делает пометку в ETCD через API. Потом в API стучится Kubelet, считывает информацию и видит, что ему нужно запустить вон тот под на этом узле.
Инструмент крайне гибкий, и его можно настроить под свои нужды как угодно. Если у вас пока нет понимания, чего вы от него хотите, то разумным будет оставить те самые sane defaults «из коробки».
Для упрощения настройки могу порекомендовать такую штуку, которая называется Kube Scheduler simulator. В ней можно погонять в GUI различные сценарии и подобрать более оптимальные варианты.
Kubernetes не прибит гвоздями к Docker
Если я сейчас покажу вам свой домашний Kubernetes-кластер, то вы внезапно не увидите там Docker. Потыкаем в утилиту lsns для точности и получим кучу namespace, которые принадлежат кубернетовским сервисам, но самого Docker нет.
Основная идея в том, что Kubernetes — это конструктор. Причём он изначально создавался как максимально слабосвязанный и модульный, чтобы была возможность заменять один его компонент другим. Docker по-прежнему — один из наиболее популярных рантаймов, но в некоторых дистрибутивах он вытесняется другими альтернативами:
- Containerd. Containerd — это индустриальный стандарт контейнерного рантайма, который был инициирован из проекта Docker. Со временем Docker реорганизовал свою архитектуру, и большая часть функциональности, ранее включённая в Docker, была перенесена в Containerd.
- Container Runtime Interface (CRI) с runc. Container Runtime Interface (CRI) — это спецификация, предоставляющая стандартизированный интерфейс между Kubernetes и контейнерным рантаймом. runc — это инструмент, реализующий стандарт OCI (Open Container Initiative) для запуска контейнеров. Многие Kubernetes-дистрибутивы используют CRI и runc вместо Docker.
- Containerd с CRI. Containerd также поддерживает CRI-интерфейс, что делает его совместимым с Kubernetes. Kubernetes может взаимодействовать с Containerd через CRI, и это даёт возможность использовать Containerd в качестве контейнерного рантайма вместо Docker.
- CRI-O. CRI-O — это реализация CRI, которая предоставляет простой и легковесный контейнерный рантайм, оптимизированный для использования в Kubernetes. Он предоставляет только функциональность, необходимую для работы с Kubernetes, и является альтернативой Docker для развёртывания контейнеров.
В итоге Docker всё чаще остаётся как удобный инструмент для запуска и подготовки образов на локалхосте и в CI-системах, а в кластере эти образы крутит уже совместимый рантайм, который умеет с ними работать.
Сетевая часть
Есть много различных инструментов, которые обеспечивают overlay-сеть, но один из самых распространённых — Flannel.
Этот инструмент деплоит маленький бинарник flanneld на каждом хосте.
Этот бинарник отвечает за корректное выделение подсетей на каждом хосте из большего, заранее определённого пространства адресов. Flannel, как и другие компоненты, использует API, чтобы хранить данные о выделении адресов в ETCD, связывая всю инфраструктуру в одну Layer3-сеть.
Низкоуровневый дизайн сетевой подсистемы
По сути, под капотом нашего overlay работают те же самые классические Linux-инструменты.
Используя пакет iproute2, создаётся виртуальное сетевое окружение с необходимой конфигурацией.
Интерфейс Virtual Ethernet в контейнерном сетевом пространстве подключается к виртуальному bridge (виртуальный коммутатор) в хостовой машине. Виртуальный bridge объединяет все Virtual Ethernet-адаптеры, что позволяет контейнерам на одной ноде взаимодействовать друг с другом, как будто они взаимодействуют через коммутатор.
Netfilter + iptables
Но нам мало обеспечить связность подов между собой. Нам надо ещё опубликовать наше приложение и сделать его глобально доступным. Доступ во внешний мир идёт с помощью старых классических механизмов на базе модуля ядра Netfilter. Именно он обеспечивает функциональность firewall в Linux. Тот же iptables — это имплементация интерфейса, взаимодействующего с этим модулем и настраивающего сеть.
Что умеет firewall? Firewall, кроме того, чтобы кому-то прикрыть доступ, зарезать пакеты, умеет эти пакеты немножко модифицировать, менять заголовки. На базе этого механизма и работает NAT. Когда на внешний адрес вылетает какой-то пакет из внутренней сети, то по дороге происходит подмена, и для внешнего мира он вылетел уже не от имени контейнера, а от адреса Kubernetes-ноды.
Полноценный же сетевой доступ обеспечивается за счёт отдельной абстрации — Service.
Kube-proxy
Service предоставляет стабильный виртуальный IP-адрес и DNS-имя для доступа к приложению.
Это позволяет клиентам взаимодействовать с приложением. Причём неважно, на какой ноде оно запущено.
Service распределяет трафик между подами, работающими в составе одного сервиса. Это обеспечивает балансировку нагрузки между подами, что позволяет распределить запросы равномерно и предотвратить перегрузку определённых подов.
Кроме того, этот компонент автоматически обнаруживает появление новых подов и соответствующим образом обновляет свою конфигурацию. Это обеспечивает отказоустойчивость и непрерывную доступность приложения при масштабировании или перезапуске подов.
Задачу по проксированию сетевого трафика, фильтрации и настройке iptables выполняет ещё одна прослойка между Service и Pods — Kube-proxy.
Механизм eBPF в сетевой подсистеме
У Netfilter есть одна важная проблема. Он создавался в древние времена, когда целый интернет-провайдер мог иметь аплинк в 100 мегабит. Сейчас, когда уже и 40 гигабит между серверами не такая уж редкость, Netfilter в чистом виде перестаёт справляться. Для того чтобы обойти эту проблему, внедрили механизм eBPF (extended Berkeley Packet Filter) — это технология, которая позволяет встраивать и исполнять пользовательский код (байт-код) в ядре Linux. Этот механизм позволяет более гибко и оптимально обрабатывать сетевые запросы, чтобы обеспечивать нужный уровень быстродействия для типовых задач в Kubernetes.