DevOps

Kubernetes: архитектура

Откуда взялся Kubernetes

2014 год. Google запускает 2 миллиарда контейнеров в неделю через внутреннюю систему Borg. За пределами Google ничего подобного нет. Команда инженеров пишет Kubernetes как открытую версию Borg и отдаёт миру. К 2019 году он становится де-факто операционной системой облака - Netflix, Airbnb, GitLab, Spotify деплоят миллионы контейнеров в день через тот же инструмент.

Kubernetes демократизировал инфраструктуру уровня Google. Стартап из 5 человек получил доступ к тем же паттернам оркестрации, что и компания с 50 000 инженеров.

Control Plane: мозг кластера

**2014 год.** Google открывает исходный код Kubernetes. Внутри компании работал Borg - система, которая запускала 2 миллиарда контейнеров в неделю. Инженеры Google каждый день деплоили поиск, Maps, YouTube через одну и ту же инфраструктуру. За пределами Google ничего подобного не существовало. За 5 лет Kubernetes стал де-факто операционной системой облака - не метафора, а буквальное описание роли.

Kubernetes делится на две части: control plane знает, что должно быть; data plane делает, чтобы это было. Control plane - это четыре компонента на master-узлах, которые никогда не запускают пользовательские контейнеры. Они только принимают решения.

API Server

**kube-apiserver** - единственный компонент, с которым разговаривают все остальные. `kubectl apply`, dashboard, CI/CD, сами компоненты кластера - всё идёт через REST API сервер. Он валидирует запросы, применяет RBAC, сохраняет объекты в etcd и транслирует события подписчикам. Никакие два компонента не общаются друг с другом напрямую - только через API server. Это осознанное архитектурное решение: один канал коммуникации, одно место для аудита.

etcd

Единственный stateful компонент control plane. Распределённый key-value стор на Raft консенсусе хранит всё состояние кластера: какие Pod-ы должны быть запущены, где они запущены, какие ConfigMap-ы и Secret-ы существуют. Если etcd умрёт и бэкап не сделан - кластер становится read-only. Kubernetes без etcd не знает ничего о себе. Для HA-кластера обычно 3 или 5 узлов etcd (нечётное для кворума).

Scheduler

**kube-scheduler** смотрит на Pod-ы в состоянии `Pending` (нет назначенного узла) и решает, на какой node их поставить. Алгоритм в две фазы: фильтрация (убрать узлы, которые не могут принять Pod - нет ресурсов, taints, affinity не совпадает) и scoring (ранжировать оставшиеся по десяткам метрик). Scheduler не запускает контейнеры - он только записывает решение в API server. Дальше kubelet на выбранном узле видит новый Pod и запускает его.

Controller Manager

**kube-controller-manager** - это десятки контроллеров в одном процессе. ReplicaSet-контроллер следит: если нужно 3 реплики, а запущено 2 - создать ещё одну. Если 4 - убить лишнюю. Node-контроллер помечает узлы как `NotReady`, если они перестали отправлять heartbeat. Endpoint-контроллер обновляет список IP за каждым Service. Каждый контроллер - бесконечный цикл reconciliation: смотришь на desired state, сравниваешь с actual state, приводишь к соответствию.

Паттерн reconciliation loop - основа всего Kubernetes. Желаемое состояние декларируется в etcd. Контроллеры непрерывно сближают реальность с желаемым. Это называется level-triggered (не edge-triggered): контроллер реагирует на состояние, не на событие. Если пропустил событие - ничего страшного, при следующей проверке состояние всё равно будет исправлено.

Control plane компонент kube-scheduler записывает своё решение в:

Data Plane: руки кластера

Worker-узлы не принимают решений. Они исполняют. На каждом работает три компонента: kubelet запускает контейнеры, kube-proxy настраивает сеть, CNI-плагин обеспечивает связность. Mastodon, Twitter, GitLab - всё это в итоге упирается в такие узлы на чьих-то облачных серверах.

kubelet

Главный агент на worker-узле. kubelet регистрирует узел в кластере, затем следит через API server за Pod-ами, которые назначены его узлу. Когда появляется новый Pod - kubelet говорит container runtime (containerd, CRI-O) запустить нужные контейнеры. Когда Pod должен умереть - kubelet отправляет SIGTERM, ждёт `terminationGracePeriodSeconds`, потом SIGKILL. Каждые 10 секунд kubelet отправляет heartbeat в API server. Если heartbeat пропал - node-controller через 40 секунд помечает узел `NotReady`, через 5 минут эвакуирует Pod-ы.

kube-proxy

Service в Kubernetes - это виртуальный IP, который балансирует трафик между Pod-ами. kube-proxy реализует эту абстракцию. На каждом узле он следит за изменениями Service и Endpoint объектов, затем программирует iptables или IPVS правила. Когда приходит пакет на ClusterIP:port - iptables/IPVS перехватывает и случайно пересылает на один из Pod IP. kube-proxy сам трафик не проксирует (вопреки названию) - только управляет правилами ядра.

CNI-плагин

Container Network Interface - стандарт, который Kubernetes вызывает при создании Pod. CNI-плагин (Calico, Flannel, Cilium, Weave) получает namespace нового контейнера и должен: создать network interface, назначить IP, настроить маршрутизацию так, чтобы любой Pod мог достучаться до любого другого Pod по IP без NAT. Это фундаментальный принцип сети в Kubernetes: flat network. Cilium использует eBPF вместо iptables - быстрее на порядок при тысячах правил.

Kubernetes требует: каждый Pod получает уникальный IP, Pod-ы на разных узлах общаются напрямую (без NAT), агенты на узле видят Pod-ы с их реальными IP. CNI-плагин - это имплементация этих требований. Выбор плагина влияет на производительность и функциональность (NetworkPolicy поддерживает не каждый).

Какой компонент отвечает за то, что трафик на ClusterIP:80 попадёт в один из Pod-ов?

Pod lifecycle и Deployment vs StatefulSet

Pod - минимальная единица деплоя в Kubernetes. Не контейнер - Pod. Один Pod может содержать несколько контейнеров, которые разделяют network namespace (общий IP, порты) и могут разделять volumes. Sidecar-паттерн (Envoy proxy рядом с приложением в Service Mesh) работает именно так: два контейнера в одном Pod на одном localhost.

Жизненный цикл Pod

Pod проходит через состояния: `Pending` (ждёт scheduler или image pull), `Running` (хотя бы один контейнер работает), `Succeeded` (все контейнеры завершились с кодом 0 - типично для Job), `Failed` (контейнер завершился с ненулевым кодом), `Unknown` (kubelet не отвечает). `CrashLoopBackOff` - не состояние, а статус: контейнер падает, kubelet рестартует с экспоненциальной задержкой (10s, 20s, 40s... до 5 минут).

Pod-ы - ephemeral. Kubernetes не лечит Pod-ы: если Pod умер, он удалён навсегда. Рестартует контейнер внутри Pod (если `restartPolicy` позволяет), но не сам Pod. Именно поэтому Pod-ы не создают напрямую - используют Deployment или StatefulSet, которые пересоздают Pod при падении.

Deployment

Deployment управляет ReplicaSet, который управляет набором идентичных Pod-ов. Идентичных - ключевое слово. Каждый Pod в Deployment взаимозаменяем: нет имени, нет постоянного storage, нет идентичности. Rolling update создаёт новый ReplicaSet с новым образом и постепенно переносит трафик. Rollback - переключение на предыдущий ReplicaSet. Для stateless приложений (web servers, API) - идеальный примитив.

StatefulSet

StatefulSet решает проблему, которую Deployment не умеет: постоянная идентичность Pod-а. Pod-ы получают предсказуемые имена: `postgres-0`, `postgres-1`, `postgres-2`. При пересоздании `postgres-0` получит то же имя и тот же PersistentVolume. Это позволяет запускать кластерные базы данных (Cassandra, MongoDB, PostgreSQL Patroni) где каждый узел знает своих соседей. Запуск и удаление Pod-ов идут по порядку (0, 1, 2... и 2, 1, 0 для удаления) - нельзя поднять `postgres-1` до `postgres-0`.

Когда выбирать что: Deployment - для stateless. StatefulSet - для баз данных, очередей, Kafka, Elasticsearch. DaemonSet - когда нужно по одному Pod на каждый узел (агент мониторинга, log-шиппер, CNI-плагин). Job/CronJob - разовые или периодические задачи.

Kubernetes рестартует упавший Pod

Kubernetes рестартует контейнер внутри Pod. Если Pod удалён - Deployment/StatefulSet создаёт новый Pod (с новым именем для Deployment). Сам Pod не восстанавливается.

Pod - ephemeral объект. Рестарт контейнера (restartPolicy) и пересоздание Pod (controller) - разные механизмы. CrashLoopBackOff означает что контейнер рестартуется, но Pod живёт.

Cassandra нужна постоянная идентичность узлов (каждый узел знает своих соседей по имени) и свой PersistentVolume. Какой объект Kubernetes использовать?

Ключевые идеи

  • **Control plane** (API server + etcd + scheduler + controller manager) принимает решения. **Data plane** (kubelet + kube-proxy + CNI) исполняет. Никакой прямой коммуникации - только через API server.
  • **Reconciliation loop** - сердце K8s: контроллеры непрерывно сравнивают desired state (в etcd) с actual state и устраняют расхождение. Missed events не критичны - следующий цикл исправит.
  • **Pod** - ephemeral. **Deployment** - для stateless, взаимозаменяемые реплики. **StatefulSet** - для баз данных, предсказуемые имена и постоянные PVC.

Связанные темы

Kubernetes - это оркестрация контейнеров, которая строится на сетевых примитивах и ведёт к более сложным паттернам:

  • Docker Compose — Предшественник: оркестрация на одном хосте перед K8s
  • K8s: продвинутые паттерны — HPA, PDB, Helm, Operator pattern - следующий уровень
  • Service Mesh — Istio и Linkerd добавляют mTLS и observability поверх K8s
  • Сети для DevOps — CNI и kube-proxy строятся на iptables и network namespaces Linux

Вопросы для размышления

  • etcd - единственный stateful компонент control plane. Что произойдёт с уже запущенными Pod-ами если etcd упадёт?
  • Reconciliation loop реагирует на состояние, не на события. В чём преимущество этого подхода при сетевых сбоях?
  • StatefulSet даёт предсказуемые имена Pod-ам (postgres-0, 1, 2). Как база данных использует это для настройки репликации?

Связанные уроки

  • devops-05 — Docker Compose - предыдущий уровень оркестрации перед K8s
  • devops-07 — Продвинутые паттерны K8s строятся поверх этой архитектуры
  • devops-08 — Service Mesh (Istio, Linkerd) работает поверх K8s data plane
  • ds-05-replication — StatefulSet решает те же проблемы что и репликация БД
  • devops-03 — CNI и kube-proxy строятся на сетевых примитивах Linux
  • dist-09-raft
  • net-48-kubernetes-networking
Kubernetes: архитектура

0

1

Войти