1. Kubernetes. Архитектура
От циклов согласования etcd до механики List-Watch в Go
Когда распределенная система вырастает из масштабов одиночных серверов с Docker Compose до сотен узлов и тысяч микросервисов, перед инженерами встают вызовы совершенно иного порядка:
- Что делать, если физический сервер внезапно сгорает в дата-центре?
- Как обновить 100 запущенных реплик Go-приложения с нулевым временем простоя (Zero-Downtime Rolling Update)?
- Как автоматически балансировать входящий трафик и динамически масштабировать сервисы при утреннем пике нагрузки?
Ответом облачной индустрии стал Kubernetes (K8s) — де-факто промышленный стандарт оркестрации контейнеризированных приложений.
Для многих разработчиков Kubernetes выглядит непостижимым «черным ящиком», пугающим сотнями строк конфигурационных YAML-файлов. Однако Kubernetes целиком написан на языке Go. Вся его внутренняя архитектура является образцовым воплощением классической философии Go: конкурентность, декларативность, реактивная обработка событий и общение компонентов через очереди вместо блокировок. Глубокое понимание устройства Control Plane и Worker-нод — обязательный рубеж для перехода на уровень Senior и Staff/Lead инженера.
1. Философия Kubernetes: Декларативное состояние и циклы согласования
Ключевой сдвиг ментальной парадигмы при переходе к K8s — отказ от императивного управления в пользу декларативного подхода (Declarative State).
В традиционных скриптах развертывания (Bash, Ansible) мы отдаем императивные команды: «Скачай образ, останови старый процесс, запусти новый на порту 8080». Если на промежуточном шаге моргнет сеть, скрипт упадет в непредсказуемом состоянии.
В Kubernetes вы не приказываете кластеру, что именно делать. Вы декларируете, каким должен быть желаемый финальный результат (Desired State):
«В кластере должно работать ровно 3 живые реплики микросервиса
auth-serviceс лимитом памяти 512 МБ».
Оркестратор непрерывно сравнивает реальное текущее состояние системы (Current State) с желаемым (Desired State). Если нода сгорает и одна реплика погибает, K8s фиксирует расхождение (Current: 2, Desired: 3) и самостоятельно планирует запуск недостающего контейнера на другом физическом сервере.
Этот непрерывный процесс называется циклом согласования (Reconciliation Loop):
flowchart LR
classDef process fill:#1e293b,stroke:#38bdf8,stroke-width:1px,color:#f8fafc
classDef success fill:#064e3b,stroke:#34d399,stroke-width:1px,color:#f8fafc
classDef error fill:#7f1d1d,stroke:#f87171,stroke-width:1px,color:#f8fafc
classDef warning fill:#78350f,stroke:#fbbf24,stroke-width:1px,color:#f8fafc
classDef entry fill:#1e3a5f,stroke:#38bdf8,stroke-width:2px,color:#f8fafc
classDef aux fill:#334155,stroke:#64748b,stroke-width:1px,color:#f8fafc
classDef system fill:#312e81,stroke:#a78bfa,stroke-width:1px,color:#f8fafc
classDef data fill:#132d29,stroke:#2dd4bf,stroke-width:1px,color:#f8fafc
classDef network fill:#881337,stroke:#fb7185,stroke-width:1px,color:#f8fafc
classDef accent fill:#1e1b4b,stroke:#818cf8,stroke-width:1px,color:#f8fafc
A["Desired State (Манифест YAML)"]:::process --> C{"Reconciliation Loop"}
B["Current State (Реальность кластера)"] --> C
C -->|"Расхождение обнаружено"| D["Управляющее действие: запуск/удаление"]
D --> B
2. Control Plane: Мозг и командный пункт кластера
Слой управления (Control Plane, исторически называемый Master) отвечает за принятие глобальных решений в кластере: планирование подов, реакцию на сбои и хранение конфигураций. В продакшене все компоненты Control Plane резервируются на 3 или 5 независимых серверах.
flowchart TD
classDef process fill:#1e293b,stroke:#38bdf8,stroke-width:1px,color:#f8fafc
classDef success fill:#064e3b,stroke:#34d399,stroke-width:1px,color:#f8fafc
classDef error fill:#7f1d1d,stroke:#f87171,stroke-width:1px,color:#f8fafc
classDef warning fill:#78350f,stroke:#fbbf24,stroke-width:1px,color:#f8fafc
classDef entry fill:#1e3a5f,stroke:#38bdf8,stroke-width:2px,color:#f8fafc
classDef aux fill:#334155,stroke:#64748b,stroke-width:1px,color:#f8fafc
classDef system fill:#312e81,stroke:#a78bfa,stroke-width:1px,color:#f8fafc
classDef data fill:#132d29,stroke:#2dd4bf,stroke-width:1px,color:#f8fafc
classDef network fill:#881337,stroke:#fb7185,stroke-width:1px,color:#f8fafc
classDef accent fill:#1e1b4b,stroke:#818cf8,stroke-width:1px,color:#f8fafc
User["Разработчик - kubectl apply"]:::network -->|"HTTP POST YAML (mTLS)"| APIServer["kube-apiserver (REST API на Go)"]
APIServer -->|"Сохранение состояния (Raft)"| Etcd[("etcd (Key-Value Store)")]
APIServer -->|"Событие: Новый Pod без ноды"| Scheduler["kube-scheduler"]
Scheduler -->|"Назначение ноды (NodeBinding)"| APIServer
APIServer -->|"Reconciliation Loops"| KCM["kube-controller-manager"]
APIServer -->|"Событие: Pod назначен на ноду X"| Kubelet["Kubelet (на Worker Node X)"]
Kubelet -->|"Запуск через CRI"| Runtime["Container Runtime (containerd)"]
1. kube-apiserver (Единая точка входа)
Центральный нервный узел всей системы. Это высокопроизводительный HTTP/REST сервер на Go.
- Единственный компонент кластера, который имеет право напрямую читать и писать в базу данных
etcd. - Все остальные участники (включая утилиту
kubectl, планировщик, контроллеры и агенты на нодах) взаимодействуют строго с API Server по защищенному протоколу HTTPS с двусторонней аутентификацией (mTLS). - Выполняет валидацию манифестов (Schema Validation), аутентификацию (JWT, токены, сертификаты), авторизацию (RBAC) и запуск мутирующих и валидирующих хуков (Admission Webhooks).
2. etcd (Память кластера)
Высоконадежное распределенное key-value хранилище, написанное на Go. Оно реализует строгий алгоритм консенсуса Raft, обеспечивающий устойчивость к разделению сети (Network Partitions). В etcd хранится абсолютно всё состояние кластера: описания подов, сервисов, секреты, роли доступа и текущие сетевые эндпоинты.
3. kube-scheduler (Планировщик)
Интеллектуальный диспетчер, написанный на Go. Он непрерывно мониторит API Server в поисках вновь созданных Подов, у которых поле nodeName пустое (статус Pending).
Планировщик анализирует ресурсы:
- Фильтрация (Predicates): Отсекает ноды, на которых недостаточно свободной памяти или CPU (
requests), либо нарушены правилаnodeSelector,taints/tolerationsи ограничения affinity/anti-affinity. - Скоринг (Priorities): Ранжирует подходящие ноды по специальной оценочной шкале (например, равномерность распределения реплик одного сервиса по разным стойкам и зонам доступности).
- Биндинг (Binding): Планировщик не запускает контейнер сам! Он лишь отправляет в API Server команду связать Под с выбранной нодой.
4. kube-controller-manager (Менеджер контроллеров)
Монолитный Go-бинарник, объединяющий десятки параллельных циклов согласования (Reconciliation Loops):
- Deployment Controller: Следит за обновлениями образов и организует стратегии выкатки (Rolling Updates).
- ReplicaSet Controller: Следит за тем, чтобы количество живых реплик пода строго соответствовало спецификации
replicas. - Node Controller: Реагирует на потерю связи с серверами: если нода не отвечает более 5 минут, инициирует аварийное выселение (Eviction) всех запущенных на ней подов.
3. Worker Nodes: Мускулы кластера
Воркер-ноды — это физические серверы или виртуальные машины, на которых непосредственно исполняется полезная нагрузка — ваши Go-микросервисы.
1. kubelet (Хостовый агент)
Главный агент Kubernetes на каждом сервере, написанный на Go. Kubelet отвечает за реальную жизнь подов на конкретной ноде:
- Отслеживает в API Server поды, назначенные на его ноду.
- Общается со средой исполнения контейнеров через интерфейс CRI (Container Runtime Interface) по протоколу gRPC.
- Монтирует тома и пробрасывает секреты из
ConfigMapиSecret. - Регулярно запускает проверки жизнеспособности подов (Liveness, Readiness и Startup Probes), перезапуская зависшие контейнеры.
2. kube-proxy (Сетевой диспетчер)
Сетевой компонент, обеспечивающий абстракцию Kubernetes Service. Он отвечает за то, чтобы запросы на стабильный виртуальный IP-адрес сервиса (ClusterIP) равномерно балансировались между эфемерными IP-адресами реальных подов.
3. Container Runtime (Среда исполнения)
Низкоуровневый программный комплекс, управляющий контейнерами на ноде. С версии Kubernetes 1.24 поддержка монолитного Docker Engine была официально исключена (Dockershim removed). Стандартом де-факто стал легковесный рантайм containerd (или CRI-O), который взаимодействует с Kubelet через строгий интерфейс OCI.
4. Механика синхронизации: Паттерн List-Watch и Informers в Go
Каким образом тысячи распределенных агентов (Kubelet, Scheduler, Controllers) мгновенно узнают об изменениях манифестов в kube-apiserver, не убивая базу данных etcd постоянным поллингом?
Kubernetes решает эту задачу с помощью изящного архитектурного паттерна List-Watch:
- Фаза
LIST(Инициализация): При холодном старте компонент отправляет обычный HTTP GET-запрос к API Server:GET /api/v1/pods. Он получает полный моментальный снимок (Snapshot) всех объектов и фиксирует текущую версию ресурса (resourceVersion). - Фаза
WATCH(Подписка на поток дельт): Компонент открывает долгоживущее HTTP-соединение с параметром?watch=true&resourceVersion=12345. API Server удерживает это TCP-соединение и потоково передает компактные события об изменениях (ADDED, MODIFIED, DELETED) по мере их появления вetcd.
sequenceDiagram
autonumber
participant Client as Kubelet / Controller (client-go)
participant API as kube-apiserver
participant Etcd as etcd (Raft)
Client->>API: 1. LIST /api/v1/pods (Холодный старт)
API->>Etcd: Чтение снапшота
Etcd-->>API: Срез объектов (resourceVersion=100)
API-->>Client: Снапшот объектов
Note over Client: Кэширование в local Thread-safe Store
Client->>API: 2. WATCH /api/v1/pods?resourceVersion=100
Note over API,Client: Постоянное HTTP/2 соединение
Etcd-->>API: Изменение: Pod A перешел в Running (ver=101)
API-->>Client: Событие MODIFIED (Pod A)
Note over Client: Вызов пользовательских Event Handlers
В официальной клиентской библиотеке k8s.io/client-go эта концепция упакована в абстракцию SharedInformer:
- Информер держит локальный потокобезопасный кэш объектов в памяти (Indexer / Thread-safe Store).
- Бизнес-логика контроллера читает данные только из локального кэша, вообще не создавая нагрузки на API Server.
- При поступлении событий информер складывает ключи изменившихся объектов в очередь с дедупликацией (WorkQueue), из которой рабочие горутины вызывают циклы согласования.
Итог
- Декларативное управление: Kubernetes непрерывно приводит Current State системы к Desired State с помощью Reconciliation Loops.
- Control Plane:
kube-apiserverвыступает единственным фасадом кetcd, планировщикkube-schedulerнаходит оптимальные ноды, аkube-controller-managerуправляет жизненным циклом ресурсов. - Требования к etcd: Распределенное хранилище консенсуса критически чувствительно к задержкам дискового ввода-вывода (
fsync()) и требует скоростных NVMe SSD. - IPVS в kube-proxy: Масштабирование сервисов требует перехода с последовательных правил
iptables($O(N)$) на хэш-таблицы ядраIPVS($O(1)$). - Механика List-Watch: Реактивная подписка на события через
client-goInformers минимизирует нагрузку на API Server и обеспечивает мгновенную реакцию на изменения. - Автономия Data Plane: Выход из строя Control Plane не нарушает работу уже запущенных сервисов.
Архитектура кластера задает базовый каркас оркестрации. Но повседневная работа разработчика сосредоточена вокруг базовых примитивов рабочей нагрузки: Подов, Деплойментов и Сервисов. В следующей статье мы разберем их анатомию и жизненный цикл: 2. Pod, Deployment, Service.