2026 г.

Kubernetes: идея и устройство

Проект ЦИТадель

Kubernetes вводит много сущностей — Pod, Deployment, Service, Ingress, оператор, — но значительная часть их поведения строится вокруг общего принципа: пользователь сохраняет в API желаемое состояние, а независимые циклы согласования приближают к нему наблюдаемое состояние. Этого принципа недостаточно для описания всей системы: планировщик выбирает узлы, kubelet управляет процессами на них, сеть и хранилища реализуют отдельные плагины. Однако согласование объясняет, почему компоненты могут восстанавливаться после пропущенных изменений и почему объектами Kubernetes обычно управляют декларативно. Статья продолжает материал о контейнерах и связывает устройство Kubernetes с репликацией, отказами и консенсусом из раздела о распределённых системах.

1. Центральная идея: желаемое состояние и согласование

Императивный сценарий задаёт последовательность действий: остановить старые процессы, запустить новые, изменить балансировщик. После сбоя в середине сценария системе приходится определять, какие шаги успели выполниться. В декларативной модели объект хранит spec с желаемым состоянием, а контроллер повторяет цикл: наблюдение → сравнение → ограниченное действие → новая проверка. Если ReplicaSet требует три Pod, а существует два, его контроллер создаёт недостающий объект через API; другие компоненты назначают ему узел и запускают контейнеры.

События и watch-потоки ускоряют реакцию, но не должны быть единственным источником истины: соединение может оборваться, контроллер — перезапуститься, а его локальный кэш — устареть. Повторное чтение состояния и идемпотентные действия позволяют продолжить работу после восстановления связи. Это даёт сходимость, но не гарантирует мгновенного или безусловного восстановления. Контроллеру нужны доступный API, ресурсы и корректная декларация; неисправность приложения, потерянные данные или исчерпанная ёмкость сами по себе не исчезают.

2. Анатомия: из чего собран кластер

Управляющий слой (control plane):

  • etcd — согласованное хранилище типа «ключ — значение», которое Kubernetes использует как основное хранилище данных API. Там находятся спецификации объектов и записанное компонентами состояние, но фактические процессы, пакеты и внешние диски продолжают жить на узлах и в подключённых системах. etcd использует Raft и требует резервных копий. Для отказоустойчивости обычно разворачивают нечётное число именно членов etcd; узлы управляющего слоя и члены etcd могут размещаться по-разному.
  • API-сервер предоставляет Kubernetes API и является поддерживаемым путём к данным etcd для остальных компонентов. Он выполняет аутентификацию, авторизацию, контроль допуска (admission) и валидацию, а также поддерживает watch — поток изменений объектов. Несколько экземпляров API-сервера можно разместить за балансировщиком.
  • Планировщик наблюдает новые Pod без назначенного узла, отбрасывает неподходящие узлы, оценивает оставшиеся по ресурсам, ограничениям совместного и раздельного размещения, топологии и другим правилам, затем записывает привязку через API.
  • Менеджер контроллеров запускает многие логически независимые контроллеры в одном исполняемом файле: например, контроллеры Node, Job и EndpointSlice. Контроллер может наблюдать один тип объектов, а создавать или изменять другой.

Узлы (рабочие машины):

  • kubelet получает спецификации Pod, назначенных узлу, и через CRI просит исполнитель контейнеров создать окружение Pod (sandbox) и его контейнеры. Распространены containerd и CRI-O; настроенный обработчик может запускать runc, gVisor, Kata или другой OCI runtime. Kubelet также монтирует тома, выполняет пробы и сообщает статусы Pod и узла.
  • kube-proxy или эквивалентный сетевой компонент реализует виртуальные адреса Service и пересылку к конечным Pod. Kube-proxy программирует правила ядра, но сетевой плагин может полностью заменить его собственной плоскостью данных.

Основная координация управляющих компонентов происходит через объекты API. Планировщик не запускает контейнер на узле: он записывает привязку Pod, а kubelet наблюдает назначенные ему объекты. Это уменьшает связанность и позволяет возобновлять циклы после перезапуска. Но утверждать, что прямых соединений нет, нельзя: например, API-сервер обращается к kubelet для exec, журналов и перенаправления портов, а контроллеры облака и хранилища взаимодействуют с внешними API.

3. Под: почему не просто контейнер

Единица планирования в Kubernetes — не отдельный контейнер, а Pod: один или несколько контейнеров, всегда назначаемых одному узлу. Они разделяют сетевое пространство и IP-адрес, могут обращаться друг к другу через localhost и подключать общие тома. В Linux исполнитель обычно создаёт инфраструктурный sandbox-контейнер, который удерживает общие пространства имён; это распространённая реализация CRI, а не сущность пользовательского API. Несколько контейнеров помещают в один Pod, когда у них общий жизненный цикл и тесное локальное взаимодействие: например, приложение и sidecar. Для независимых сервисов создают разные Pod.

Pod получает UID и назначается узлу только один раз. Kubelet может перезапускать отказавший контейнер внутри того же Pod в соответствии с restartPolicy, но сам Pod на другой узел не переносится. При отказе узла или удалении контроллер рабочей нагрузки создаёт замену с новым UID; Pod, созданный напрямую, такого контроллера не имеет. Имя Pod у StatefulSet может сохраниться, а IP обычно меняется, поэтому идентичность проверяют по UID, а доступ строят через Service или другую устойчивую абстракцию.

4. Контроллеры рабочих нагрузок

Долгоживущие Pod обычно создают не напрямую, а через контроллер рабочей нагрузки:

  • Deployment управляет ReplicaSet, а ReplicaSet поддерживает заданное число Pod одного шаблона. Изменение spec.template создаёт новый ReplicaSet. Стратегия RollingUpdate масштабирует старый и новый наборы с учётом maxSurge, maxUnavailable и готовности Pod; поэтому обновление не сводится к строгой последовательности «плюс один, минус один». Откат восстанавливает предыдущий шаблон.
  • StatefulSet нужен нагрузкам со стабильными порядковыми именами и, вместе с headless Service, устойчивой сетевой идентичностью; volumeClaimTemplates создаёт отдельные PersistentVolumeClaim. По умолчанию создание, масштабирование и обновление упорядочены, но политика может разрешать параллельность. StatefulSet не реализует протокол репликации СУБД, выбор лидера, резервное копирование и проверку восстановления.
  • DaemonSet поддерживает Pod на каждом подходящем узле с учётом селекторов и ограничений. Job добивается заданного числа успешных завершений, а CronJob создаёт Job по расписанию; повторы и параллелизм задаются политиками объекта.

5. Сервисы и сеть

Сетевая модель требует, чтобы каждый Pod получил собственный IP и мог обращаться к IP других Pod без преобразования адресов со стороны хоста. Маршрутизацию и туннели реализует сетевой плагин, обычно через CNI; поддерживающий NetworkPolicy плагин может намеренно запретить часть связей. IP конкретного Pod не следует считать долговечным.

Service задаёт устойчивую точку доступа к меняющемуся набору конечных Pod. Для Service с селектором контроллер создаёт EndpointSlice, в которых отражает подходящие адреса и их готовность. Обычный ClusterIP имеет виртуальный адрес и DNS-имя; headless Service обходится без виртуального IP, а Service без селектора может указывать на внешние адреса. Пересылку реализует kube-proxy или плоскость данных сетевого плагина, поэтому сводить её только к iptables или IPVS нельзя.

Для внешнего HTTP(S)-трафика применяют Ingress с отдельно установленным Ingress-контроллером либо Gateway API, который проект Kubernetes рекомендует для новых возможностей; API Ingress остаётся стабильным, но заморожен. Другие протоколы часто публикуют через Service типов NodePort или LoadBalancer. Ресурс сам по себе не создаёт прокси: нужен контроллер или интеграция облака, способные исполнить декларацию.

Readiness-проба определяет, готов ли контейнер принимать трафик: неготовый endpoint помечается соответствующим образом в EndpointSlice и обычно исключается из обычной балансировки. Liveness-проба после порога ошибок заставляет kubelet перезапустить контейнер, а не весь Pod. Startup-проба откладывает остальные проверки для медленно запускающегося приложения. Readiness не стоит транзитивно связывать с доступностью всех зависимостей: краткий отказ общей базы может одновременно вывести из обслуживания все реплики приложения.

6. Ресурсы: декларации встречают cgroups

Для CPU и памяти контейнеру задают requests и limits. Планировщик суммирует requests и использует их при размещении; при наличии свободных ресурсов контейнер может потреблять больше своего request. CPU limit ядро применяет через ограничение времени выполнения. Memory limit действует реактивно: при достижении предела ядро пытается освободить память и может завершить процесс по OOM. Kubelet передаёт параметры исполнителю, а тот на Linux обычно настраивает cgroups.

Класс QoS определяется для Pod целиком. Guaranteed требует, чтобы у каждого обычного и init-контейнера requests совпадали с limits одновременно для CPU и памяти. BestEffort не задаёт ни тех ни других; остальные варианты относятся к Burstable. При нехватке ресурсов kubelet учитывает не только QoS, но и Priority, превышение requests и величину потребления. Универсального правила requests = limits нет: равенство упрощает резервирование, но CPU limit может вызвать ненужный throttling, а слишком низкий memory limit — OOM. Значения выбирают по измерениям и пересматривают при изменении нагрузки.

7. Расширяемость: платформа для платформ

CRD (CustomResourceDefinition) добавляет в API новый тип объектов, а внешний контроллер может согласовывать их состояние. Не всякая пара «CRD + контроллер» является оператором: шаблон оператора (operator pattern) переносит в программу предметные действия, которые иначе выполнял бы оператор-человек. Например, качественный оператор PostgreSQL может управлять конфигурацией репликации, переключением роли, обновлением и резервными копиями. Его наличие не доказывает корректность этих процедур — реализацию и восстановление всё равно проверяют.

Модель API и контроллеров делает Kubernetes основой для внутренних платформ, но не превращает его в готовую PaaS. Для законченного пользовательского пути нужны политики доступа, сборка и доставка образов, секреты, наблюдаемость, сетевые и дисковые плагины. Helm и аналогичные инструменты помогают параметризовать и устанавливать наборы объектов, но пакетирование не заменяет контроллер и не обеспечивает жизненный цикл приложения само по себе.

8. Когда Kubernetes не надо

Цена Kubernetes находится не только в YAML. Управляющий слой — распределённая система с сертификатами, резервными копиями etcd, обновлениями и ограниченным окном совместимости версий; сеть, DNS, ingress и хранилища добавляют собственные компоненты и отказы. Команде нужны наблюдаемость, управление доступом, проверка восстановления и готовность разбирать проблемы сразу в нескольких слоях.

Единого порога по числу сервисов или сотрудников нет. Kubernetes оправдан, когда его планирование, стандартизованный API, изоляция команд, автоматические обновления и экосистема расширений решают измеримые задачи лучше более простого варианта. Для небольшой системы могут быть достаточны systemd и Docker Compose на нескольких ВМ, PaaS или сервис вида «контейнер в URL». Управляемый Kubernetes снимает часть работы с управляющим слоем, но не отвечает за манифесты приложения, права, расходы, сетевые политики и данные; не каждое предложение автоматически обновляет рабочие узлы. Выбор должен перечислять приобретаемые свойства и стоимость их эксплуатации.

9. Практикум: наблюдаем согласование

Используйте одноразовый локальный кластер kind или minikube и проверьте текущий контекст командой kubectl config current-context. Не выполняйте опыт в рабочем кластере. Создайте namespace citadel, а следующий манифест сохраните как web.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
  namespace: citadel
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
  template:
    metadata:
      labels:
        app: web
      annotations:
        citadel/revision: "1"
    spec:
      containers:
      - name: nginx
        image: nginx:1.28-alpine
        ports:
        - name: http
          containerPort: 80
        readinessProbe:
          httpGet:
            path: /
            port: http
          periodSeconds: 2
        resources:
          requests:
            cpu: 50m
            memory: 32Mi
          limits:
            cpu: 200m
            memory: 64Mi
---
apiVersion: v1
kind: Service
metadata:
  name: web
  namespace: citadel
spec:
  selector:
    app: web
  ports:
  - port: 80
    targetPort: http

Задание 1. Цепочка объектов.

$ kubectl create namespace citadel
$ kubectl apply -f web.yaml
$ kubectl wait -n citadel --for=condition=Available deployment/web --timeout=120s
$ kubectl get -n citadel deployment,replicaset,pod,service,endpointslice

По ownerReferences одного Pod проследите цепочку Pod → ReplicaSet → Deployment. Найдите selector Service и соответствующие адреса в EndpointSlice. Проверьте путь через DNS и ClusterIP из одного из Pod:

$ kubectl exec -n citadel deployment/web -- wget -qO- http://web

Для доступа с хоста можно выполнить kubectl port-forward -n citadel service/web 8080:80, но такой туннель выбирает Pod через API и не проверяет плоскость данных Service.

Задание 2. Перезапуск контейнера и замена Pod. Сохраните имя и UID одной реплики:

$ CITADEL_POD=$(kubectl get pod -n citadel -l app=web \
    -o jsonpath='{.items[0].metadata.name}')
$ kubectl get pod -n citadel "$CITADEL_POD" \
    -o custom-columns=NAME:.metadata.name,UID:.metadata.uid,RESTARTS:.status.containerStatuses[0].restartCount
$ kubectl exec -n citadel "$CITADEL_POD" -- sh -c 'kill 1'

После восстановления сравните UID и счётчик перезапусков: kubelet должен запустить контейнер снова внутри того же Pod. Затем удалите этот Pod и наблюдайте kubectl get pod -n citadel -w. ReplicaSet создаст замену с новым UID. Так опыт отделяет локальную работу kubelet от работы контроллера.

Задание 3. Неудачное обновление. В web.yaml измените аннотацию шаблона на citadel/revision: "2", а путь readiness-пробы — на /missing. Примените файл и выполните:

$ kubectl apply -f web.yaml
$ kubectl rollout status -n citadel deployment/web --timeout=30s
$ kubectl get -n citadel replicaset,pod,endpointslice
$ kubectl describe -n citadel deployment/web

Новые Pod запускаются, но не становятся Ready, поэтому обновление останавливается. При заданной стратегии часть старых готовых реплик остаётся в обслуживании. Верните путь /, увеличьте номер ревизии, снова примените манифест и дождитесь успешного rollout status.

Задание 4. Requests, limits и QoS. Выведите классы Pod:

$ kubectl get pod -n citadel -l app=web \
    -o custom-columns=NAME:.metadata.name,QOS:.status.qosClass

Исходная конфигурация относится к Burstable. Сделайте requests равными limits для CPU и памяти, измените ревизию шаблона и примените файл. После обновления новые Pod должны получить Guaranteed. Объясните, почему одного равенства только для памяти было бы недостаточно.

Задание 5. Очистка. Сохраните нужные наблюдения и удалите все ресурсы опыта одной командой kubectl delete namespace citadel. Удаление namespace асинхронно; убедитесь, что он исчез, прежде чем удалять локальный кластер.

Итоги

  • Контроллеры используют события для быстрой реакции, но сходятся по наблюдаемому состоянию; восстановление требует доступного API, ресурсов и корректной декларации.
  • etcd хранит данные Kubernetes API, API-сервер предоставляет доступ к ним, планировщик привязывает Pod к узлам, а kubelet через CRI управляет контейнерами на конкретном узле.
  • Pod назначается узлу один раз. Kubelet может перезапустить его контейнер, а контроллер рабочей нагрузки при необходимости создаёт новый Pod с другим UID.
  • Deployment, StatefulSet, DaemonSet и Job задают разные целевые состояния. Service и EndpointSlice отделяют стабильную точку доступа от эфемерных backend-ов.
  • Requests участвуют в размещении, CPU limit ограничивает процессорное время, memory limit действует через OOM. Класс Guaranteed требует равенства CPU- и memory-параметров для всех контейнеров Pod.
  • CRD и контроллер расширяют API; шаблон оператора добавляет предметную эксплуатационную логику, качество которой нужно проверять отдельно.
  • Kubernetes оправдан приобретаемыми свойствами, а не числом YAML-файлов. Управляемый сервис уменьшает, но не устраняет эксплуатационную ответственность.

Литература

  1. Документация Kubernetes: Cluster Architecture и Controllers.
  2. Pod Lifecycle, Service и Resource Management for Pods and Containers.
  3. Operator pattern и kind — первоисточники для раздела о расширении и локального практикума.
  4. B. Burns, B. Grant, D. Oppenheimer, E. Brewer, J. Wilkes, "Borg, Omega, and Kubernetes," ACM Queue 14(1), 2016; B. Burns, J. Beda, K. Hightower, L. Evenson, "Kubernetes: Up and Running," 3rd ed., O'Reilly, 2022.
  5. Материалы ЦИТадели: «Контейнеры: устройство и экосистема», глава об etcd и Raft, глава о репликации и «Надёжность поверх ненадёжной сети».
404 Not Found

404 Not Found


nginx/1.24.0 (Ubuntu)

Связь с редакцией