Проект ЦИТадель
Kubernetes вводит много сущностей — Pod, Deployment, Service, Ingress, оператор, — но значительная часть их поведения строится вокруг общего принципа: пользователь сохраняет в API желаемое состояние, а независимые циклы согласования приближают к нему наблюдаемое состояние. Этого принципа недостаточно для описания всей системы: планировщик выбирает узлы, kubelet управляет процессами на них, сеть и хранилища реализуют отдельные плагины. Однако согласование объясняет, почему компоненты могут восстанавливаться после пропущенных изменений и почему объектами Kubernetes обычно управляют декларативно. Статья продолжает материал о контейнерах и связывает устройство Kubernetes с репликацией, отказами и консенсусом из раздела о распределённых системах.
Императивный сценарий задаёт последовательность действий: остановить старые процессы, запустить новые, изменить балансировщик. После сбоя в середине сценария системе приходится определять, какие шаги успели выполниться. В декларативной модели объект хранит spec с желаемым состоянием, а контроллер повторяет цикл: наблюдение → сравнение → ограниченное действие → новая проверка. Если ReplicaSet требует три Pod, а существует два, его контроллер создаёт недостающий объект через API; другие компоненты назначают ему узел и запускают контейнеры.
События и watch-потоки ускоряют реакцию, но не должны быть единственным источником истины: соединение может оборваться, контроллер — перезапуститься, а его локальный кэш — устареть. Повторное чтение состояния и идемпотентные действия позволяют продолжить работу после восстановления связи. Это даёт сходимость, но не гарантирует мгновенного или безусловного восстановления. Контроллеру нужны доступный API, ресурсы и корректная декларация; неисправность приложения, потерянные данные или исчерпанная ёмкость сами по себе не исчезают.
Управляющий слой (control plane):
Узлы (рабочие машины):
Основная координация управляющих компонентов происходит через объекты API. Планировщик не запускает контейнер на узле: он записывает привязку Pod, а kubelet наблюдает назначенные ему объекты. Это уменьшает связанность и позволяет возобновлять циклы после перезапуска. Но утверждать, что прямых соединений нет, нельзя: например, API-сервер обращается к kubelet для exec, журналов и перенаправления портов, а контроллеры облака и хранилища взаимодействуют с внешними API.
Единица планирования в Kubernetes — не отдельный контейнер, а Pod: один или несколько контейнеров, всегда назначаемых одному узлу. Они разделяют сетевое пространство и IP-адрес, могут обращаться друг к другу через localhost и подключать общие тома. В Linux исполнитель обычно создаёт инфраструктурный sandbox-контейнер, который удерживает общие пространства имён; это распространённая реализация CRI, а не сущность пользовательского API. Несколько контейнеров помещают в один Pod, когда у них общий жизненный цикл и тесное локальное взаимодействие: например, приложение и sidecar. Для независимых сервисов создают разные Pod.
Pod получает UID и назначается узлу только один раз. Kubelet может перезапускать отказавший контейнер внутри того же Pod в соответствии с restartPolicy, но сам Pod на другой узел не переносится. При отказе узла или удалении контроллер рабочей нагрузки создаёт замену с новым UID; Pod, созданный напрямую, такого контроллера не имеет. Имя Pod у StatefulSet может сохраниться, а IP обычно меняется, поэтому идентичность проверяют по UID, а доступ строят через Service или другую устойчивую абстракцию.
Долгоживущие Pod обычно создают не напрямую, а через контроллер рабочей нагрузки:
spec.template создаёт новый ReplicaSet. Стратегия RollingUpdate масштабирует старый и новый наборы с учётом maxSurge, maxUnavailable и готовности Pod; поэтому обновление не сводится к строгой последовательности «плюс один, минус один». Откат восстанавливает предыдущий шаблон.volumeClaimTemplates создаёт отдельные PersistentVolumeClaim. По умолчанию создание, масштабирование и обновление упорядочены, но политика может разрешать параллельность. StatefulSet не реализует протокол репликации СУБД, выбор лидера, резервное копирование и проверку восстановления.Сетевая модель требует, чтобы каждый 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 не стоит транзитивно связывать с доступностью всех зависимостей: краткий отказ общей базы может одновременно вывести из обслуживания все реплики приложения.
Для 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. Значения выбирают по измерениям и пересматривают при изменении нагрузки.
CRD (CustomResourceDefinition) добавляет в API новый тип объектов, а внешний контроллер может согласовывать их состояние. Не всякая пара «CRD + контроллер» является оператором: шаблон оператора (operator pattern) переносит в программу предметные действия, которые иначе выполнял бы оператор-человек. Например, качественный оператор PostgreSQL может управлять конфигурацией репликации, переключением роли, обновлением и резервными копиями. Его наличие не доказывает корректность этих процедур — реализацию и восстановление всё равно проверяют.
Модель API и контроллеров делает Kubernetes основой для внутренних платформ, но не превращает его в готовую PaaS. Для законченного пользовательского пути нужны политики доступа, сборка и доставка образов, секреты, наблюдаемость, сетевые и дисковые плагины. Helm и аналогичные инструменты помогают параметризовать и устанавливать наборы объектов, но пакетирование не заменяет контроллер и не обеспечивает жизненный цикл приложения само по себе.
Цена Kubernetes находится не только в YAML. Управляющий слой — распределённая система с сертификатами, резервными копиями etcd, обновлениями и ограниченным окном совместимости версий; сеть, DNS, ingress и хранилища добавляют собственные компоненты и отказы. Команде нужны наблюдаемость, управление доступом, проверка восстановления и готовность разбирать проблемы сразу в нескольких слоях.
Единого порога по числу сервисов или сотрудников нет. Kubernetes оправдан, когда его планирование, стандартизованный API, изоляция команд, автоматические обновления и экосистема расширений решают измеримые задачи лучше более простого варианта. Для небольшой системы могут быть достаточны systemd и Docker Compose на нескольких ВМ, PaaS или сервис вида «контейнер в URL». Управляемый Kubernetes снимает часть работы с управляющим слоем, но не отвечает за манифесты приложения, права, расходы, сетевые политики и данные; не каждое предложение автоматически обновляет рабочие узлы. Выбор должен перечислять приобретаемые свойства и стоимость их эксплуатации.
Используйте одноразовый локальный кластер 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 асинхронно; убедитесь, что он исчез, прежде чем удалять локальный кластер.