Полный курс подготовки к сертификации Certified Kubernetes Administrator (CKA)

Интенсивная программа обучения, охватывающая все домены экзамена CKA: от архитектуры и установки кластера до глубокого траблшутинга и управления безопасностью. Курс ориентирован на практическое освоение kubectl и администрирование production-ready сред.

1. Архитектура Kubernetes: компоненты Control Plane и узлов инфраструктуры

Вы вводите в терминале команду kubectl run web --image=nginx и нажимаете Enter. Через пару секунд терминал сообщает: pod/web created. Для пользователя это выглядит как мгновенная магия, но под капотом разворачивается сложнейшая хореография распределенной системы. Десятки компонентов обмениваются сертификатами, опрашивают базы данных, оценивают свободную оперативную память на серверах и настраивают правила маршрутизации ядра Linux. Понимание того, кто именно, в какой последовательности и на каком сервере выполняет эту работу — фундаментальный навык для сдачи экзамена CKA и ежедневного администрирования Kubernetes.

Kubernetes не является монолитным приложением. Это набор независимых бинарных файлов (процессов), которые общаются друг с другом исключительно через REST API. Концептуально кластер разделен на две большие части: Control Plane (плоскость управления, или управляющие узлы) и Worker Nodes (рабочие узлы).

Control Plane принимает глобальные решения по кластеру (например, на каком узле запустить приложение), обнаруживает события и реагирует на них. Worker Nodes — это «рабочие лошадки», которые непосредственно запускают контейнеры с вашим кодом и обеспечивают их сетевую доступность.

!Архитектура компонентов Kubernetes

Компоненты Control Plane: мозг кластера

В Production-средах компоненты Control Plane обычно разворачиваются на нескольких выделенных серверах для обеспечения отказоустойчивости (High Availability). Если падает один управляющий узел, кластер продолжает работу. В рамках подготовки к CKA важно досконально знать зону ответственности каждого из четырех главных компонентов Control Plane.

kube-apiserver: центральный узел связи

kube-apiserver — это фасад всего кластера. Это единственный компонент, с которым взаимодействует администратор (через утилиту kubectl), и единственный компонент, который имеет право напрямую читать и писать в базу данных состояния кластера (etcd).

Все остальные компоненты, будь то планировщик, контроллеры или агенты на рабочих узлах, не общаются друг с другом напрямую. Они постоянно опрашивают API-сервер (механизм watch) на предмет изменений.

Ключевые функции kube-apiserver:

  • Аутентификация и авторизация: проверка того, кто прислал запрос и есть ли у него права на выполнение действия (RBAC).
  • Мутация и валидация (Admission Control): перед сохранением объекта в базу данных API-сервер может изменить его (например, автоматически добавить дефолтные лимиты ресурсов) или отклонить (например, если образ контейнера скачивается из неразрешенного реестра).
  • Маршрутизация к etcd: трансляция REST-запросов в операции с базой данных.
  • API-сервер спроектирован так, чтобы масштабироваться горизонтально. Он не хранит состояние внутри себя (stateless). Вы можете запустить 2, 3 или 10 экземпляров kube-apiserver за балансировщиком нагрузки, и они будут работать корректно, так как единым источником истины выступает внешнее хранилище. По умолчанию API-сервер слушает защищенный порт 6443.

    etcd: память кластера

    Если kube-apiserver — это мозг, то etcd — это его память. Это распределенное, строго согласованное (strongly consistent) хранилище типа «ключ-значение» (key-value store). В нем хранится абсолютно всё состояние кластера: какие узлы подключены, какие поды должны работать, какие секреты и конфигурации существуют.

    Для обеспечения отказоустойчивости etcd работает в кластерном режиме (обычно 3 или 5 узлов) и использует алгоритм консенсуса Raft. Чтобы кластер etcd мог принимать решения (например, записывать новые данные), необходимо, чтобы большинство узлов (кворум) были живы и могли договориться. Формула кворума: , где — общее количество узлов etcd. При трех узлах кворум равен 2. Если два узла из трех выйдут из строя, etcd перейдет в режим «только чтение», и API-сервер Kubernetes не сможет создавать или изменять объекты.

    Особенности etcd в контексте администрирования:

  • Слушает порт 2379 для клиентских запросов (от API-сервера) и порт 2380 для общения узлов etcd между собой.
  • Хранит данные в виде бинарного дерева (BboltDB).
  • Является самым критичным компонентом для резервного копирования. Потеря данных etcd означает полную потерю кластера Kubernetes, даже если все контейнеры на рабочих узлах продолжают физически работать.
  • kube-scheduler: логистика и планирование

    Когда вы просите Kubernetes создать Pod (минимальную единицу развертывания), API-сервер просто сохраняет запись об этом в etcd. У этого Pod'а в поле nodeName (имя узла) изначально пусто.

    kube-scheduler — это компонент, который непрерывно следит за API-сервером в поисках Pod'ов без назначенного узла. Как только он находит такой Pod, его задача — выбрать оптимальный сервер для запуска.

    Процесс выбора узла состоит из двух этапов:

  • Фильтрация (Filtering / Predicates): планировщик отбрасывает узлы, которые физически не могут принять Pod. Например, если Pod требует 2 ГБ оперативной памяти, а на узле свободно только 1 ГБ, этот узел исключается. Также на этом этапе проверяются ограничения, заданные администратором (Taints, Node Selectors, Affinity).
  • Оценка (Scoring / Priorities): оставшиеся после фильтрации узлы получают баллы от 0 до 100. Планировщик оценивает, где Pod будет работать эффективнее. Например, узел, на котором останется больше свободных ресурсов после размещения Pod'а, получит более высокий балл. Также учитывается распределение Pod'ов одного приложения по разным узлам для отказоустойчивости.
  • Узел с максимальным баллом выбирается победителем. Планировщик отправляет API-серверу запрос на обновление Pod'а (операция Binding), вписывая имя выбранного сервера в поле nodeName. На этом работа планировщика заканчивается — он не запускает контейнеры.

    kube-controller-manager: поддержание желаемого состояния

    В Kubernetes применяется декларативный подход: вы описываете желаемое состояние системы (Desired State), а кластер сам приводит текущее состояние (Current State) к желаемому. За эту работу отвечает kube-controller-manager.

    Это единый бинарный файл, внутри которого в виде отдельных потоков (goroutines) работают десятки различных контроллеров. Каждый контроллер отвечает за свой тип объектов. Они работают в бесконечном цикле (Control Loop), постоянно сравнивая желаемое состояние с текущим. Если , контроллер предпринимает действия для устранения разницы.

    Ключевые контроллеры внутри менеджера:

  • Node Controller: следит за состоянием рабочих узлов. Если kubelet на узле перестает присылать сигналы жизни (heartbeats), Node Controller ждет определенное время (по умолчанию 40 секунд), помечает узел как NotReady, а еще через 5 минут начинает процесс принудительного удаления (eviction) Pod'а с этого узла.
  • ReplicaSet Controller: гарантирует, что в кластере запущено строго заданное количество копий (реплик) Pod'а. Если вы указали 3 реплики, а один узел сгорел вместе с Pod'ом, контроллер увидит, что текущее количество равно 2, и создаст запрос на запуск нового Pod'а.
  • Endpoints Controller: связывает сервисы (Services) и Pod'ы, обновляя списки IP-адресов, на которые нужно направлять трафик.
  • Service Account & Token Controllers: создают учетные записи по умолчанию и токены доступа для новых пространств имен (Namespaces).
  • Компоненты Worker Node: исполнители на местах

    Рабочие узлы — это серверы, на которых выполняется полезная нагрузка. Компоненты на этих узлах отвечают за поддержание работы Pod'ов и обеспечение сетевой связности.

    kubelet: капитан узла

    kubelet — это главный агент Kubernetes, который работает на каждом узле кластера (включая управляющие узлы). В отличие от большинства других компонентов, которые запускаются в виде контейнеров, kubelet обычно устанавливается как системный сервис (systemd daemon) прямо в операционную систему сервера.

    Его главная задача — убедиться, что контейнеры, описанные в спецификации Pod'а, запущены и здоровы. Как работает kubelet:

  • Он слушает API-сервер. Когда kube-scheduler назначает Pod на его узел, kubelet скачивает спецификацию этого Pod'а (PodSpec).
  • Он обращается к Container Runtime (среде выполнения контейнеров) и дает команду: «скачай такой-то образ и запусти контейнер с такими-то параметрами».
  • Он постоянно мониторит состояние запущенных контейнеров (выполняет Liveness и Readiness пробы) и регулярно отправляет отчет о статусе узла и Pod'ов обратно на API-сервер.
  • Важный нюанс для экзамена CKA: kubelet может запускать Pod'ы не только по команде от API-сервера. Если положить YAML-манифест Pod'а в специальную директорию на сервере (обычно /etc/kubernetes/manifests/), kubelet прочитает файл и запустит так называемый Static Pod. Именно таким образом утилита kubeadm разворачивает компоненты Control Plane (kube-apiserver, etcd, kube-scheduler, kube-controller-manager) — как статические поды, которыми управляет локальный kubelet на мастер-узле.

    Container Runtime: двигатель контейнеров

    Сам по себе Kubernetes не умеет запускать контейнеры. Ему нужен сторонний софт, который умеет работать с пространствами имен Linux (namespaces) и контрольными группами (cgroups). Для стандартизации взаимодействия kubelet с различными средами выполнения был создан интерфейс CRI (Container Runtime Interface).

    Исторически Kubernetes использовал Docker, но сейчас поддержка Docker как среды выполнения (Dockershim) удалена. В современных кластерах используются среды, напрямую реализующие CRI:

  • containerd: изначально часть Docker, теперь самостоятельная, легковесная и невероятно стабильная среда выполнения. Является стандартом де-факто в большинстве современных инсталляций.
  • CRI-O: среда выполнения, разработанная Red Hat специально для Kubernetes, строго следующая стандартам OCI (Open Container Initiative).
  • Container Runtime отвечает за стягивание образов из реестров (например, Docker Hub), распаковку слоев, настройку изоляции на уровне ядра ОС и непосредственный запуск процессов.

    kube-proxy: сетевой регулировщик

    Каждый Pod в Kubernetes получает свой собственный IP-адрес. Однако Pod'ы эфемерны: они удаляются, пересоздаются на других узлах и получают новые IP-адреса. Чтобы приложения могли стабильно общаться друг с другом, используются Сервисы (Services) — абстракции, предоставляющие постоянный виртуальный IP-адрес (ClusterIP), балансирующий трафик на группу динамических Pod'ов.

    За реализацию этой магии отвечает kube-proxy. Это сетевой агент, работающий на каждом узле. Он следит за API-сервером на предмет создания новых Сервисов или изменения Endpoints (IP-адресов Pod'ов). Когда происходит изменение, kube-proxy не маршрутизирует трафик сам (он не является прокси-сервером в классическом понимании). Вместо этого он программирует сетевой стек ядра Linux на своем узле.

    kube-proxy может работать в нескольких режимах:

  • iptables (по умолчанию): создает огромное количество правил в брандмауэре Linux. Когда пакет пытается уйти на виртуальный IP Сервиса, ядро Linux перехватывает его на уровне iptables, случайным образом выбирает IP-адрес одного из живых Pod'ов и подменяет адрес назначения (DNAT).
  • IPVS: более современный и производительный режим. Использует технологию балансировки нагрузки внутри ядра Linux (IP Virtual Server). В отличие от iptables, который читает правила последовательно (что замедляет работу в кластерах с тысячами сервисов), IPVS использует хеш-таблицы, обеспечивая мгновенный поиск маршрута.
  • Симфония компонентов: жизненный цикл создания Pod'а

    Чтобы по-настоящему понять архитектуру, нужно проследить, как компоненты взаимодействуют во времени. Рассмотрим пошагово, что происходит после выполнения команды kubectl run.

    !Процесс создания Pod'а: взаимодействие компонентов

  • Инициация: kubectl формирует JSON-объект, описывающий Pod, и отправляет POST-запрос на порт 6443 компоненту kube-apiserver.
  • Аутентификация и валидация: API-сервер проверяет ваши сертификаты. Если у вас есть права, запрос пропускается через Admission Controllers.
  • Сохранение состояния: API-сервер записывает объект Pod в etcd. На этот момент статус Pod'а — Pending (в ожидании), а поле nodeName пустое. API-сервер отвечает kubectl, что Pod успешно создан (хотя контейнеры еще не запущены).
  • Реакция планировщика: kube-scheduler через механизм watch видит новый Pod без узла. Он выполняет фильтрацию и оценку, выбирает, например, worker-node-2, и отправляет API-серверу запрос на привязку (Binding).
  • Обновление базы: API-сервер обновляет запись в etcd, указывая, что Pod теперь привязан к worker-node-2.
  • Реакция kubelet: kubelet на узле worker-node-2 через свой watch-канал замечает, что ему назначен новый Pod.
  • Запуск: kubelet обращается к Container Runtime (например, containerd) через интерфейс CRI с командой запустить контейнеры.
  • Обратная связь: Container Runtime запускает процесс и сообщает об успехе. kubelet отправляет API-серверу обновление статуса: Pod переходит в состояние Running. API-сервер сохраняет этот статус в etcd.
  • На протяжении всего этого процесса ни один компонент не общался с другим напрямую. Все взаимодействия происходили исключительно через kube-apiserver.

    Диагностика компонентов: взгляд администратора

    На экзамене CKA вам часто придется сталкиваться со сломанными кластерами. Понимание архитектуры позволяет быстро локализовать проблему.

    Если вы создаете Deployment, но Pod'ы даже не появляются в списке (kubectl get pods ничего не выдает), проблема скорее всего в kube-controller-manager. Именно он должен был увидеть Deployment и создать ReplicaSet, который в свою очередь создал бы Pod'ы.

    Если Pod'ы появились, но висят в статусе Pending, это классический симптом проблем с kube-scheduler (он не работает) или нехватки ресурсов в кластере (планировщик работает, но фильтрация отбросила все узлы).

    Если Pod назначен на узел (поле NODE в выводе kubectl get pods -o wide заполнено), но статус застрял на ContainerCreating, проблема находится на конкретном рабочем узле. Виноват либо kubelet (не может связаться с API-сервером), либо Container Runtime (не может скачать образ из интернета), либо сетевой плагин CNI (не может выдать IP-адрес).

    В кластерах, развернутых с помощью kubeadm, компоненты Control Plane работают как статические поды в пространстве имен kube-system. Вы можете проверить их статус обычной командой: kubectl get pods -n kube-system

    Если API-сервер недоступен, команда kubectl перестанет работать. В этом случае диагностику нужно проводить прямо на сервере Control Plane. Так как kubelet управляет статическими подами, его логи — первый источник правды. На узле с ОС Linux это делается командой systemd: journalctl -u kubelet -f

    Также можно проверить статус контейнеров компонентов напрямую через утилиту crictl (клиент командной строки для CRI), которая работает в обход Kubernetes: crictl ps

    Архитектура Kubernetes, несмотря на кажущуюся сложность, построена на элегантном принципе разделения обязанностей и декларативного управления состоянием. Понимание роли каждого винтика в этом механизме — от kube-apiserver до kube-proxy — превращает кластер из «черного ящика» в предсказуемую и управляемую систему, готовую к любым операционным вызовам.

    10. Стратегия сдачи экзамена CKA: разбор типовых сценариев и оптимизация работы

    Стратегия сдачи экзамена CKA: разбор типовых сценариев и оптимизация работы

    На экзамене Certified Kubernetes Administrator у вас есть 120 минут на решение 15–17 практических задач в "голом" терминале Linux. Это означает, что на одну задачу отводится в среднем около семи минут. Главным препятствием для большинства кандидатов становится не сложность самих концепций Kubernetes, а катастрофическая нехватка времени. Успешная сдача требует трансформации теоретических знаний в мышечную память, умения мгновенно находить нужные фрагменты в официальной документации и навыка слепой печати базовых конструкций.

    Оптимизация рабочего окружения

    Экзаменационная среда предоставляет вам удаленный рабочий стол с браузером и терминалом. Первые две минуты экзамена должны быть посвящены настройке этого окружения. Без базовой оптимизации вы потеряете драгоценные минуты на исправление отступов в YAML и набор длинных команд.

    !Оптимальная организация рабочего пространства на CKA

    В первую очередь необходимо настроить псевдонимы (aliases) в командной оболочке. Экзаменационная среда Ubuntu обычно уже содержит базовый алиас k для kubectl и настроенное автодополнение, но это стоит проверить. Дополнительно критически важно создать переменные для генерации YAML-манифестов без их применения к кластеру.

    Выполните в терминале следующие команды: export do="--dry-run=client -o yaml" export now="--force --grace-period=0"

    Теперь вместо длинной конструкции при создании манифеста вы пишете: k run nginx --image=nginx now спасет вас при удалении зависших Pod'ов: k delete pod broken-pod do --command -- sleep 3600 > app.yaml

    Затем открываете app.yaml в Vim и добавляете только секции volumes и volumeMounts:

    Императивные команды особенно эффективны при работе с RBAC. Создание Role и RoleBinding через YAML требует точного знания структуры API-групп. Императивно это делается за секунды: k create role pod-manager --verb=create,list,get --resource=pods -n web k create rolebinding pod-manager-bind --role=pod-manager --user=john -n web

    Если задача требует создать Deployment с определенным ServiceAccount и ограничениями ресурсов (requests/limits), вы генерируете Deployment: k create deploy web-app --image=nginx --replicas=3 $do > deploy.yaml А затем редактируете файл, добавляя блок resources и поле serviceAccountName, так как эти параметры нельзя задать напрямую через флаги команды kubectl create deploy.

    Эффективная навигация по документации

    На экзамене разрешено использовать только одну вкладку браузера с открытыми ресурсами kubernetes.io/docs. Использование закладок (bookmarks) запрещено правилами. Это означает, что вы должны виртуозно владеть встроенным поиском на сайте документации.

    Ключ к успеху — знание точных поисковых запросов, которые приводят к нужным страницам с примерами YAML, готовыми для копирования.

  • Если нужен пример Persistent Volume типа hostPath, введите в поиск: pv hostpath. Первая же ссылка приведет на страницу с готовым манифестом.
  • Для настройки Node Affinity ищите: assign pod node.
  • Для создания Ingress ресурса: ingress.
  • Для настройки Taints и Tolerations: taint toleration.
  • Скопировав манифест из документации, удалите все лишние комментарии и поля, которые не требуются в задаче. Чем меньше строк в вашем итоговом YAML, тем меньше шансов допустить синтаксическую ошибку. В Vim для быстрого удаления строк используйте клавиши dd (удалить текущую строку) или 5dd (удалить пять строк вниз).

    Разбор высокобалльных сценариев

    Задачи на экзамене имеют разный вес — от 4% до 11%. Застревать на сложной задаче стоимостью 4% нерационально. Стратегия распределения времени требует быстрого сканирования всех задач и решения в первую очередь тех, которые имеют высокий вес и понятный алгоритм действий.

    Сценарий 1: Обновление кластера (Cluster Upgrade)

    Эта задача встречается почти всегда и стоит дорого. Обычно требуется обновить один из узлов Control Plane или Worker Node до конкретной версии.

    Алгоритм действий должен быть отработан до автоматизма:

  • Выяснить точную версию для обновления (например, 1.29.1-1.1).
  • Снять блокировку пакетов операционной системы: apt-mark unhold kubeadm kubelet kubectl.
  • Обновить утилиту kubeadm: apt-get update && apt-get install -y kubeadm=1.29.1-1.1.
  • Подготовить узел к обновлению (эвакуация): kubectl drain <node-name> --ignore-daemonsets.
  • Применить обновление кластера: kubeadm upgrade plan (для проверки) и kubeadm upgrade apply v1.29.1 (для Control Plane) или kubeadm upgrade node (для Worker Node).
  • Обновить компоненты узла: apt-get install -y kubelet=1.29.1-1.1 kubectl=1.29.1-1.1.
  • Перезапустить службу: systemctl daemon-reload && systemctl restart kubelet.
  • Вернуть узел в работу: kubectl uncordon <node-name>.
  • Заблокировать версии пакетов: apt-mark hold kubeadm kubelet kubectl.
  • Главная ловушка здесь — забыть флаг --ignore-daemonsets при выполнении команды drain, из-за чего процесс эвакуации зависнет, ожидая удаления Pod'ов, которые контролируются DaemonSet (а они не могут быть удалены таким образом).

    Сценарий 2: Резервное копирование и восстановление etcd

    Еще одна классическая задача с высоким весом. Ошибка в путях к сертификатам приведет к неработоспособности базы данных.

    Для создания снимка (backup) требуется использовать утилиту etcdctl с указанием трех сертификатов. Пути к ним всегда можно подсмотреть в манифесте статического Pod'а etcd: cat /etc/kubernetes/manifests/etcd.yaml.

    Команда резервного копирования выглядит так: ETCDCTL_API=3 etcdctl --endpoints=https://127.0.0.1:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=/etc/kubernetes/pki/etcd/server.crt --key=/etc/kubernetes/pki/etcd/server.key snapshot save /opt/etcd-backup.db

    При восстановлении (restore) критически важно указать новую директорию для данных, чтобы не повредить текущую (даже если она сломана): ETCDCTL_API=3 etcdctl --data-dir=/var/lib/etcd-restored snapshot restore /opt/etcd-backup.db

    После восстановления необходимо сообщить кластеру о новой директории. Для этого откройте /etc/kubernetes/manifests/etcd.yaml и измените путь в секции hostPath для тома etcd-data с /var/lib/etcd на /var/lib/etcd-restored. Kubelet автоматически заметит изменение файла и перезапустит Pod etcd с новыми данными.

    Сценарий 3: Траблшутинг неисправного узла

    Вам предоставляется узел со статусом NotReady, и задача — вернуть его в строй. В таких задачах нельзя действовать хаотично, нужен строгий алгоритм спуска по уровням абстракции.

    Сначала проверяем статус самого узла в кластере: k describe node <node-name>. Если узел не отправляет статус, проблема на уровне операционной системы узла. Подключаемся к узлу по SSH: ssh <node-name>. Проверяем статус службы kubelet, так как именно она отвечает за связь узла с Control Plane: systemctl status kubelet. Если служба остановлена или постоянно перезапускается, читаем логи: journalctl -u kubelet -e.

    Чаще всего в логах будет явная ошибка: неверный путь к конфигурационному файлу, просроченный сертификат или конфликт драйверов cgroup (например, Docker использует cgroupfs, а kubelet ожидает systemd). Исправьте конфигурацию в /var/lib/kubelet/config.yaml или параметрах запуска службы, после чего выполните systemctl daemon-reload && systemctl restart kubelet.

    Сценарий 4: Настройка сетевых политик (Network Policies)

    Задачи на изоляцию трафика требуют предельного внимания к меткам (Labels). Если вы ошибетесь в селекторе, политика либо заблокирует лишнее, либо разрешит запрещенное.

    Всегда начинайте с копирования базового примера из документации (запрос network policy). Обязательно проверяйте, требует ли задача ограничения входящего (Ingress), исходящего (Egress) трафика или обоих направлений. Если в секции policyTypes указан только Ingress, правила Egress применяться не будут, и исходящий трафик останется полностью открытым.

    Для проверки правильности написания селекторов пространства имен используйте команду: k get ns --show-labels. Часто пространства имен не имеют нужных меток по умолчанию, и политика, опирающаяся на namespaceSelector, не сработает, пока вы не добавите метку на сам объект Namespace.

    Верификация выполненных задач

    Написанный манифест и команда kubectl apply -f — это только половина дела. Вторая половина — доказательство того, что ваше решение работает так, как описано в условии. Экзаменационная система проверяет конечное состояние кластера, а не ваши YAML-файлы.

    Если вы создали Service типа NodePort, проверьте его доступность с узла: Выясните порт: k get svc <service-name> Выполните запрос: curl http://localhost:<node-port>

    Если вы настроили RBAC для пользователя, проверьте его права с помощью механизма имперсонации: k auth can-i list secrets --as=developer -n project-x Ответ yes означает, что Role и RoleBinding настроены корректно. Ответ no требует перепроверки поля subjects в RoleBinding.

    Если вы создали Pod, который должен писать логи в примонтированный том, проверьте наличие файла: k exec <pod-name> -- ls -l /path/to/mount

    При траблшутинге сети между Pod'ами используйте временный контейнер для отправки запросов: k run test-net --image=busybox:1.28 --rm -it --restart=Never -- nc -vz <target-ip> <target-port>

    Оставляйте 10-15 минут в конце экзамена на финальную проверку. Пройдитесь по списку задач и убедитесь, что все созданные вами Pod'ы находятся в статусе Running. Pod в статусе CrashLoopBackOff или Pending баллов не принесет. Если вы понимаете, что не можете исправить ошибку быстро, оставьте задачу и переходите к проверке следующей.

    Успех на CKA — это комбинация глубокого понимания архитектуры, знания точных путей в документации и отработанной моторики работы в терминале.

    2. Установка, конфигурация и жизненный цикл кластера с использованием kubeadm

    Установка, конфигурация и жизненный цикл кластера с использованием kubeadm

    До появления утилиты kubeadm развертывание Kubernetes было процессом, который метко называли «Kubernetes The Hard Way». Инженерам приходилось вручную генерировать десятки TLS-сертификатов, настраивать systemd-юниты для каждого компонента Control Plane и самостоятельно прописывать конфигурации для аутентификации узлов. Сегодня kubeadm выполняет эту работу за пару минут, скрывая сложность под капотом. Однако на экзамене CKA от вас потребуется не просто запустить команду, а точно понимать, какие файлы она создает, где лежат сертификаты и как вмешаться в этот процесс, если кластер перестал подавать признаки жизни.

    Подготовка фундамента: что нужно узлам до старта

    Утилита kubeadm не устанавливает среду выполнения контейнеров (Container Runtime) и сам kubelet. Она лишь оркестрирует их работу. Поэтому до инициализации кластера на всех узлах должен быть подготовлен фундамент.

    Первое и самое жесткое требование Kubernetes — отключение файла подкачки (swap). Планировщик Kubernetes (kube-scheduler) распределяет Pod'ы на основе запрошенных ресурсов (Memory Requests). Если операционная система начнет сбрасывать память контейнеров в swap, планировщик потеряет контроль над реальным потреблением ресурсов, что приведет к непредсказуемым задержкам (latency) и нарушению работы механизмов изоляции cgroups. Отключение производится командой swapoff -a, а для персистентности необходимо закомментировать соответствующую строку в /etc/fstab.

    Второй критический шаг — настройка драйвера cgroups. Среда выполнения контейнеров (например, containerd) и kubelet должны использовать один и тот же драйвер для управления ресурсами. В современных дистрибутивах Linux стандартом является systemd. Если containerd использует cgroupfs, а kubelet — systemd, кластер будет нестабилен под нагрузкой. В конфигурационном файле containerd /etc/containerd/config.toml параметр SystemdCgroup должен быть установлен в true.

    После настройки ОС устанавливаются три бинарных файла:

  • kubeadm — утилита для инициализации и управления жизненным циклом (обновления, токены).
  • kubelet — агент, который будет запускать контейнеры. До инициализации кластера он находится в циклической перезагрузке (crashloop), ожидая конфигурацию от kubeadm.
  • kubectl — CLI для взаимодействия с API-сервером (устанавливается для удобства администрирования).
  • Рождение Control Plane: анатомия kubeadm init

    Инициализация первого управляющего узла запускается командой kubeadm init. На экзамене CKA вам часто придется использовать дополнительные флаги. Самый важный из них — --pod-network-cidr. Он указывает диапазон IP-адресов, которые будут выдаваться Pod'ам. Этот диапазон должен строго совпадать с настройками сетевого плагина (CNI), который вы установите позже. Например, для Flannel стандартом является 10.244.0.0/16, а для Calico — 192.168.0.0/16.

    !Пошаговый процесс инициализации Control Plane

    Когда вы нажимаете Enter, kubeadm запускает строгую последовательность действий (Phases):

  • Preflight Checks. Утилита проверяет систему на соответствие требованиям: открыты ли порты (6443 для API, 2379-2380 для etcd), отключен ли swap, запущен ли containerd. Если проверка не пройдена, процесс прерывается.
  • Certs. Генерируется удостоверяющий центр (CA) кластера и все необходимые TLS-сертификаты для компонентов. По умолчанию они складываются в директорию /etc/kubernetes/pki/. Именно здесь лежат ключи, с помощью которых компоненты доверяют друг другу.
  • Kubeconfig. Создаются конфигурационные файлы для аутентификации в API-сервере. Они сохраняются в /etc/kubernetes/. Самый важный из них — admin.conf, обладающий правами суперпользователя.
  • Kubelet-start. kubeadm генерирует конфигурацию для локального kubelet (файл /var/lib/kubelet/config.yaml) и запускает его службу.
  • Control-plane. Создаются манифесты Static Pods для kube-apiserver, kube-controller-manager и kube-scheduler. Они помещаются в /etc/kubernetes/manifests/. Как мы помним, kubelet непрерывно мониторит эту директорию. Обнаружив файлы, он мгновенно запускает компоненты Control Plane в виде контейнеров.
  • Etcd. Аналогично создается манифест для локального хранилища etcd.
  • Mark-control-plane. Узлу назначаются специальные метки (Taints), чтобы планировщик не размещал на нем обычные пользовательские нагрузки (по умолчанию это node-role.kubernetes.io/control-plane:NoSchedule).
  • Addons. В кластер устанавливаются системные компоненты: CoreDNS (для разрешения имен внутри кластера) и kube-proxy (для маршрутизации трафика сервисов).
  • После успешного завершения kubeadm выведет на экран инструкцию по копированию admin.conf в домашнюю директорию пользователя:

    Без этого шага команда kubectl get nodes вернет ошибку аутентификации, так как kubectl по умолчанию ищет учетные данные именно в ~/.kube/config.

    Подключение рабочих узлов и сетевой плагин

    Если сразу после инициализации выполнить kubectl get nodes, вы увидите, что узел находится в статусе NotReady. Причина кроется в отсутствии сетевого плагина. Kubernetes делегирует управление сетью сторонним решениям через стандарт CNI (Container Network Interface). Пока CNI не установлен, CoreDNS будет висеть в статусе Pending, а узлы не смогут обмениваться трафиком.

    Установка CNI обычно сводится к применению готового манифеста. Например, для Calico: kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.26.1/manifests/calico.yaml

    Как только DaemonSet сетевого плагина развернет свои агенты на узле, статус изменится на Ready.

    Теперь можно подключать рабочие узлы (Worker Nodes). В конце вывода kubeadm init предоставляется готовая команда, содержащая токен и хэш сертификата CA. Она выполняется на рабочем узле:

    Процесс подключения использует механизм TLS Bootstrapping. Рабочий узел не имеет сертификатов для полноценного общения с API-сервером. Используя короткоживущий токен (по умолчанию он истекает через 24 часа), kubelet на рабочем узле устанавливает временное доверенное соединение, проверяет подлинность API-сервера с помощью хэша CA (discovery-token-ca-cert-hash) и отправляет запрос на подпись сертификата (Certificate Signing Request, CSR). Control Plane автоматически одобряет этот запрос, генерирует уникальный сертификат для данного kubelet и возвращает его. Только после этого узел становится полноправной частью кластера.

    Если вы добавляете узел спустя несколько дней и токен истек, новую команду подключения можно сгенерировать на Control Plane: kubeadm token create --print-join-command

    Резервное копирование и восстановление etcd

    Одна из самых важных практических задач на экзамене CKA (и в реальной жизни) — создание резервной копии базы данных etcd и ее восстановление. Поскольку etcd хранит абсолютно все состояние кластера (конфигурации, секреты, статусы Pod'ов), потеря этих данных означает полную гибель кластера, даже если контейнеры продолжают работать на узлах.

    Взаимодействие с etcd осуществляется через утилиту etcdctl. Так как база данных защищена взаимным TLS (mTLS), для подключения к ней недостаточно просто указать IP-адрес. Необходимо предоставить три сертификата:

  • CA сертификат, которым подписаны ключи etcd.
  • Клиентский сертификат для аутентификации.
  • Приватный ключ клиентского сертификата.
  • Где их взять? Если кластер развернут через kubeadm, etcd работает как Static Pod. Заглянув в файл /etc/kubernetes/manifests/etcd.yaml, в блоке command можно найти точные пути к нужным файлам внутри контейнера, которые проброшены из файловой системы хоста через hostPath. Стандартные пути на узле Control Plane:

  • CA: /etc/kubernetes/pki/etcd/ca.crt
  • Сертификат: /etc/kubernetes/pki/etcd/server.crt
  • Ключ: /etc/kubernetes/pki/etcd/server.key
  • Создание снапшота (Backup)

    Перед выполнением команд необходимо убедиться, что утилита использует 3-ю версию API (в современных версиях это поведение по умолчанию, но на экзамене лучше перестраховаться, задав переменную окружения ETCDCTL_API=3).

    Команда для создания резервной копии:

    После выполнения команды файл etcd-backup.db будет содержать полный слепок ключей и значений на момент времени. Для проверки целостности снапшота используется команда etcdctl snapshot status /opt/etcd-backup.db -w table.

    !Архитектура восстановления etcd: пути, сертификаты и тома

    Восстановление из снапшота (Restore)

    Процесс восстановления требует предельной аккуратности. Главное правило: никогда не восстанавливайте данные поверх существующей директории etcd. Процесс etcd не сможет корректно обработать такую подмену «на лету» и уйдет в отказ. Данные восстанавливаются в новую пустую директорию, после чего конфигурация Static Pod'а изменяется так, чтобы указывать на нее.

    Шаг 1. Выполняем восстановление в новую папку (например, /var/lib/etcd-restored):

    Обратите внимание: для команды restore не требуются сертификаты или эндпоинты, так как утилита просто распаковывает файл базы данных локально на диск, не обращаясь к работающему процессу etcd.

    Шаг 2. Переключение etcd на новые данные. Теперь нужно сказать кластеру использовать новую директорию. Для этого открываем манифест /etc/kubernetes/manifests/etcd.yaml в текстовом редакторе (например, vim).

    В самом низу файла находится секция volumes. Нужно найти том, который монтирует данные с хоста (обычно он называется etcd-data), и изменить путь path с /var/lib/etcd на /var/lib/etcd-restored.

    Как только вы сохраните файл и закроете редактор, kubelet (благодаря механизму inotify, отслеживающему изменения файлов в директории манифестов) мгновенно заметит обновление. Он остановит текущий контейнер etcd и запустит новый, подмонтировав к нему директорию с восстановленными данными. API-сервер на несколько секунд потеряет связь с базой, после чего переподключится, и кластер вернется к состоянию на момент создания снапшота.

    Умение работать с kubeadm и etcdctl формирует базовый инстинкт администратора Kubernetes. Понимание того, что конфигурация кластера — это не магия, а набор файлов в /etc/kubernetes/ и /var/lib/, позволяет быстро находить корневую причину неисправностей. Будь то просроченный сертификат, неверно указанный CIDR сети или упавшая база данных, вы всегда знаете, в какой файл заглянуть и какую команду применить для восстановления работоспособности системы.

    3. Управление рабочими нагрузками: Pods, Deployments и контроллеры репликации

    Управление рабочими нагрузками: Pods, Deployments и контроллеры репликации

    Если в классической инфраструктуре администратор просыпается в три часа ночи от алерта, подключается по SSH к серверу и вручную перезапускает упавший процесс, то в Kubernetes эта парадигма полностью уничтожена. Вы больше не нянчитесь с отдельными контейнерами. Вместо этого вы декларируете желаемое состояние системы, а Kubernetes берет на себя рутину по его поддержанию. На экзамене CKA от вас потребуется филигранное владение инструментами, которые превращают набор разрозненных контейнеров в отказоустойчивые, самовосстанавливающиеся приложения.

    Pod: атомарная единица кластера

    В Kubernetes нельзя запустить просто «контейнер». Минимальной развертываемой единицей является Pod. Это логическая оболочка, внутри которой работает один или несколько контейнеров.

    Главное правило: все контейнеры внутри одного Pod'а делят общее пространство имен (namespaces) операционной системы. Они имеют один IP-адрес, могут общаться друг с другом через localhost (разделяя сетевой namespace) и могут монтировать одни и те же тома (разделяя файловую систему).

    Многоконтейнерные паттерны

    На экзамене CKA часто встречается задача на создание многоконтейнерных Pod'ов. Самый популярный паттерн — Sidecar (прицепная коляска). Основной контейнер выполняет главную бизнес-логику, а вспомогательный (sidecar) расширяет его функционал, не вмешиваясь в код.

    Классический пример: веб-сервер Nginx пишет логи в локальный файл, а sidecar-контейнер (например, Fluentd или busybox) читает эти логи и отправляет их в централизованное хранилище.

    !Структура многоконтейнерного Pod'а с общим томом

    Рассмотрим манифест такого Pod'а:

    Здесь мы используем том типа emptyDir. Он создается в момент назначения Pod'а на узел и существует ровно столько, сколько живет сам Pod. Оба контейнера монтируют этот директорий, что позволяет Nginx писать туда файлы, а Busybox — читать их.

    Init-контейнеры

    Другой важный концепт — Init-контейнеры. В отличие от обычных контейнеров, которые работают параллельно на протяжении всей жизни Pod'а, init-контейнеры запускаются строго последовательно перед стартом основных контейнеров. Если init-контейнер завершается с ошибкой, Kubernetes будет перезапускать весь Pod до тех пор, пока init-контейнер не отработает успешно (при условии политики RestartPolicy: Always).

    Это идеально подходит для задач подготовки: дождаться доступности базы данных, применить миграции схемы БД или скачать конфигурационный файл из внешнего источника.

    Императивный подход для скорости на экзамене

    На CKA время ограничено. Писать YAML с нуля — непозволительная роскошь. Вы должны генерировать базовые манифесты императивными командами с флагами --dry-run=client -o yaml.

    Чтобы получить заготовку для Pod'а: kubectl run my-pod --image=nginx:alpine --dry-run=client -o yaml > pod.yaml

    После этого вы открываете pod.yaml в редакторе (vi/nano) и дописываете нужные тома, переменные окружения или дополнительные контейнеры.

    Labels и Selectors: клей Kubernetes

    Прежде чем переходить к контроллерам, необходимо понять, как они находят «свои» Pod'ы. В Kubernetes нет жестких иерархических связей. Вместо этого используется система меток (Labels) и селекторов (Selectors).

    Метка — это пара ключ-значение, прикрепленная к объекту (например, к Pod'у). app: frontend, env: production, tier: cache.

    Селектор — это фильтр, который контроллер использует для поиска объектов с нужными метками.

    Если вы создадите Pod без меток, ни один контроллер не сможет взять его под свое управление. Если вы создадите Pod с меткой app: my-web, а в кластере уже работает контроллер, ищущий метку app: my-web, контроллер немедленно «усыновит» этот Pod.

    ReplicaSet: гарантия доступности

    Запускать «голые» Pod'ы (Bare Pods) в рабочей среде — антипаттерн. Если узел, на котором работает Pod, выйдет из строя, Pod умрет навсегда. Никто не создаст его заново на другом узле.

    Эту проблему решает ReplicaSet. Его единственная задача — гарантировать, что в кластере в любой момент времени запущено строго определенное количество реплик Pod'ов с заданными метками.

    В основе работы ReplicaSet лежит бесконечный цикл согласования (Control Loop). Он постоянно сравнивает желаемое состояние (например, 3 реплики) с фактическим. Если фактическое число Pod'ов с меткой tier: backend равно 2, ReplicaSet создает еще один. Если кто-то вручную запустит четвертый Pod с такой же меткой, ReplicaSet немедленно убьет один из них, чтобы вернуть баланс.

    Манифест ReplicaSet выглядит так:

    Обратите внимание на блок template. Это «штамп», по которому ReplicaSet создает новые Pod'ы. Метки в template.metadata.labels должны строго совпадать с тем, что указано в selector.matchLabels. Если они будут отличаться, API-сервер отклонит такой манифест, так как ReplicaSet создал бы Pod'ы, которые сам не смог бы найти.

    Несмотря на важность ReplicaSet, на практике (и на экзамене) вы почти никогда не создаете их напрямую. Причина в том, что ReplicaSet не умеет плавно обновлять версии приложений. Если вы измените образ в шаблоне ReplicaSet с myapp:1.0 на myapp:2.0, ничего не произойдет с уже запущенными Pod'ами. Изменения применятся только к новым Pod'ам, если старые будут удалены. Для управления обновлениями существует Deployment.

    Deployments: декларативные обновления

    Deployment — это абстракция более высокого уровня, которая управляет объектами ReplicaSet. Именно Deployments используются для развертывания подавляющего большинства stateless-приложений (не хранящих состояние).

    Когда вы создаете Deployment, он создает ReplicaSet, а тот, в свою очередь, создает Pod'ы. Когда вы обновляете Deployment (например, меняете версию образа контейнера), он не редактирует текущий ReplicaSet. Вместо этого он создает новый ReplicaSet с новым шаблоном и начинает процесс плавного перекатывания трафика — Rolling Update.

    Стратегии обновления

    Deployment поддерживает две стратегии обновления:

  • Recreate: Сначала полностью удаляются все старые Pod'ы, и только после их уничтожения создаются новые. Приводит к даунтайму (простою) приложения. Используется редко, в основном когда две версии приложения не могут работать одновременно (например, из-за конфликтов в структуре БД).
  • RollingUpdate (по умолчанию): Плавная замена старых Pod'ов на новые без прерывания обслуживания.
  • Процесс RollingUpdate управляется двумя критически важными параметрами:

  • maxUnavailable: Какое максимальное количество Pod'ов может быть недоступно в процессе обновления (в процентах или абсолютных числах).
  • maxSurge: Какое максимальное количество Pod'ов может быть создано сверх желаемого количества (replicas) во время обновления.
  • Например, если у вас , и . В процессе обновления Deployment сначала создаст 1 новый Pod (так как , общее число Pod'ов станет 5). Как только новый Pod будет готов, Deployment удалит один старый Pod. При этом он следит, чтобы количество рабочих Pod'ов никогда не падало ниже формулы (в нашем случае не ниже 3).

    !Процесс Rolling Update в Deployment

    Работа с Deployments на экзамене CKA

    Создание Deployment одной командой: kubectl create deployment web-app --image=nginx:1.19 --replicas=3

    Обновление образа приложения (запускает Rolling Update): kubectl set image deployment/web-app nginx=nginx:1.20

    Просмотр статуса раскатки: kubectl rollout status deployment/web-app

    Откат (Rollback) — одна из самых частых задач на экзамене. Если вы выкатили сломанный образ (например, nginx:1.999, которого не существует), Pod'ы зависнут в статусе ImagePullBackOff. Deployment остановит раскатку, чтобы не «положить» весь сервис.

    Посмотреть историю версий: kubectl rollout history deployment/web-app

    Вернуться на предыдущую стабильную версию: kubectl rollout undo deployment/web-app

    Или откатиться на конкретную ревизию: kubectl rollout undo deployment/web-app --to-revision=1

    Секрет механизма отката кроется в том, что Deployment не удаляет старые ReplicaSet после успешного обновления. Он просто масштабирует их до нуля (replicas: 0). При команде undo Deployment снова масштабирует старый ReplicaSet до нужного значения, а текущий (сломанный) сводит к нулю.

    Масштабирование

    Изменить количество реплик можно императивно: kubectl scale deployment/web-app --replicas=5

    Или декларативно, отредактировав объект на лету: kubectl edit deployment/web-app Эта команда откроет манифест в редакторе по умолчанию (обычно vi). Вы меняете поле replicas, сохраняете файл, и Kubernetes немедленно применяет изменения. На экзамене kubectl edit экономит огромное количество времени при траблшутинге.

    DaemonSets: фоновые демоны узлов

    В то время как Deployment гарантирует, что в кластере работает N реплик приложения (и они могут быть запущены на любых узлах), DaemonSet решает принципиально иную задачу.

    DaemonSet гарантирует, что на каждом узле кластера (или на подмножестве узлов) запущена ровно одна копия определенного Pod'а. Когда в кластер добавляется новый узел (например, через kubeadm join), DaemonSet автоматически создает на нем свой Pod. Когда узел удаляется, Pod уничтожается.

    Этот контроллер используется для инфраструктурных задач:

  • Сетевые плагины (CNI): Calico, Flannel или WeaveNet разворачивают свои агенты через DaemonSet на каждом узле для управления маршрутизацией.
  • Сбор логов: Fluentd или Promtail должны работать на каждом сервере, чтобы собирать логи со всех запущенных там контейнеров.
  • Мониторинг: Node Exporter (компонент Prometheus) собирает метрики CPU/RAM с каждого узла физической машины.
  • Манифест DaemonSet практически идентичен Deployment, за исключением kind и отсутствия поля replicas (так как количество реплик жестко привязано к количеству узлов).

    DaemonSets и планирование

    Исторически DaemonSet имел собственный механизм запуска Pod'ов, игнорируя kube-scheduler. Однако в современных версиях Kubernetes Pod'ы DaemonSet создаются с автоматически добавленным NodeAffinity, что заставляет стандартный планировщик назначать их на конкретные узлы.

    Часто возникает ситуация, когда DaemonSet должен работать абсолютно везде, включая узлы Control Plane. По умолчанию рабочие нагрузки не запускаются на мастер-узлах из-за специальных ограничительных меток (Taints). Чтобы Pod DaemonSet'а мог запуститься на Control Plane, в его спецификацию добавляют Tolerations (допуски). Детально механизмы Taints, Tolerations и Affinity мы разберем в следующей главе, но при работе с DaemonSet вы будете сталкиваться с ними постоянно.

    Развитие инфраструктуры в Kubernetes строится на наслаивании абстракций. Контейнер изолирует процесс. Pod объединяет контейнеры общим контекстом. ReplicaSet защищает Pod'ы от сбоев узлов. Deployment дает ReplicaSet'ам возможность безопасного обновления. А DaemonSet закрывает потребность в системных агентах на уровне железа. Уверенная навигация по этим слоям — от создания манифеста через --dry-run до мгновенного отката сломанного релиза через rollout undo — составляет ядро практических навыков администратора и является ключом к успешной сдаче CKA.

    4. Продвинутые механизмы планирования: Taints, Tolerations и Affinity

    По умолчанию планировщик Kubernetes (kube-scheduler) распределяет рабочие нагрузки, опираясь исключительно на доступные ресурсы: процессорное время и оперативную память. Если на узле достаточно свободного места, Pod будет туда назначен. Однако в реальных кластерах инфраструктура редко бывает однородной. Часть серверов может быть оснащена дорогими GPU для машинного обучения, другая часть — находиться в демилитаризованной зоне (DMZ) для приема внешнего трафика, а третья — использовать медленные HDD вместо NVMe-накопителей. Опираться только на метрики CPU и RAM в таких условиях невозможно: база данных может оказаться на узле с медленными дисками, а легковесный Nginx займет сервер с видеокартами. Для решения таких задач требуются механизмы прямого вмешательства в логику планирования.

    Базовое ограничение: nodeSelector

    Самый простой способ указать планировщику, куда поместить Pod — использовать поле nodeSelector. Это механизм точного совпадения пар «ключ-значение». Администратор назначает метку (Label) на узел, а затем указывает ту же метку в спецификации Pod'а.

    Назначение метки на узел: kubectl label nodes worker-1 disktype=ssd

    В манифесте Pod'а это выглядит так:

    При всей своей простоте nodeSelector обладает критическими недостатками для сложных инфраструктур. Он поддерживает только жесткое требование (логическое «И» при указании нескольких меток) и не умеет работать с операторами неравенства или множествами (например, «разместить на узле с SSD или NVMe, но не HDD»). Кроме того, nodeSelector работает только на «притяжение» — он заставляет конкретный Pod искать конкретный узел, но никак не защищает этот узел от других Pod'ов, у которых нет никаких ограничений.

    Taints и Tolerations: механизм отторжения

    В отличие от меток, которые притягивают Pod'ы к узлам, механизм Taints (пятна/загрязнения) работает на отторжение. Taint применяется к узлу и сообщает планировщику: «Не размещай здесь ничего, если у Pod'а нет специального разрешения». Это разрешение называется Toleration (допуск).

    !Механика взаимодействия Taints и Tolerations

    Этот механизм идеален для выделения узлов под специфические задачи. Если сервер оснащен GPU, администратор «загрязняет» его соответствующим Taint. Обычные веб-серверы или фоновые воркеры будут отброшены планировщиком при попытке разместиться на этом узле. Только Pod'ы, в манифесте которых явно прописан Toleration к этому Taint, смогут пройти фильтрацию.

    Taint состоит из трех элементов: ключа, значения и эффекта. Применяется он императивной командой: kubectl taint nodes worker-2 gpu=true:NoSchedule

    Для удаления Taint используется та же команда с добавлением знака минус в конце (частая задача на экзамене CKA): kubectl taint nodes worker-2 gpu=true:NoSchedule-

    Эффекты Taints

    Ключевым параметром является эффект, который определяет степень агрессивности отторжения:

  • NoSchedule — жесткое ограничение. Планировщик не назначит новый Pod на узел, если у Pod'а нет соответствующего Toleration. Однако, если Pod уже работал на узле до того, как на него повесили Taint, он продолжит работать.
  • PreferNoSchedule — мягкое ограничение. Планировщик постарается не назначать Pod на этот узел, но если в кластере больше нет свободных ресурсов, он проигнорирует Taint и разместит Pod здесь.
  • NoExecute — самое агрессивное ограничение. Оно влияет не только на этап планирования (как NoSchedule), но и на этап выполнения. Если повесить Taint с эффектом NoExecute на узел, kubelet немедленно начнет процесс вытеснения (eviction) всех запущенных на нем Pod'ов, у которых нет нужного Toleration.
  • Эффект NoExecute часто используется самим контроллером узлов (Node Controller) при возникновении проблем. Если узел теряет связь с Control Plane (статус NotReady), контроллер автоматически вешает на него Taint node.kubernetes.io/unreachable:NoExecute.

    Настройка Tolerations в Pod

    Чтобы Pod мог быть запланирован на «загрязненный» узел, в его манифест добавляется массив tolerations.

    Оператор Equal требует точного совпадения ключа и значения. Существует также оператор Exists, при котором поле value писать не нужно — Pod будет терпеть любой Taint с указанным ключом, независимо от его значения.

    > Важный нюанс: Toleration не заставляет Pod планироваться на узел с Taint. Он лишь дает ему такое право. Если в кластере есть другие чистые узлы с достаточными ресурсами, планировщик может отправить gpu-worker на обычный узел. Чтобы гарантировать попадание Pod'а именно на GPU-узел, Taints и Tolerations необходимо комбинировать с Node Affinity.

    Для эффекта NoExecute существует дополнительное поле tolerationSeconds. Оно определяет, сколько времени Pod может оставаться на узле после появления Taint, прежде чем будет вытеснен. По умолчанию Kubernetes добавляет всем Pod'ам Toleration к недоступности узла на 300 секунд. Если узел «моргнул» сетью на пару минут, Pod'ы не будут сразу же пересозданы на других серверах — это предотвращает лавинообразную перебалансировку кластера при кратковременных сбоях.

    Node Affinity: продвинутое притяжение

    Node Affinity (сходство с узлами) — это эволюционное развитие nodeSelector. Этот механизм предоставляет богатый язык выражений для выбора узлов, поддерживает логические операторы и позволяет задавать как жесткие, так и мягкие правила.

    Node Affinity делится на два типа, названия которых в Kubernetes намеренно сделаны длинными и самодокументируемыми:

  • requiredDuringSchedulingIgnoredDuringExecution — жесткое требование. Если планировщик не найдет узел, удовлетворяющий правилу, Pod останется в статусе Pending.
  • preferredDuringSchedulingIgnoredDuringExecution — предпочтение. Планировщик попытается найти подходящий узел, но если его нет, разместит Pod на любом другом доступном.
  • Вторая часть названий (IgnoredDuringExecution) означает, что если после запуска Pod'а метки на узле изменятся и перестанут подходить под правила, Pod не будет вытеснен. Он продолжит работу.

    Синтаксис и операторы

    Node Affinity настраивается в блоке affinity спецификации Pod'а и использует поле matchExpressions.

    В этом примере Pod'ы Deployment'а обязаны быть размещены на узлах, где метка environment имеет значение production ИЛИ staging. При этом планировщик отдаст предпочтение узлам с меткой disktype=nvme.

    Параметр weight (вес от 1 до 100) используется на этапе Scoring. Если планировщик нашел несколько узлов, подходящих под жесткие требования, он оценивает их по мягким правилам. Узел с NVMe получит дополнительные 50 баллов, что повысит его шансы на выбор.

    Доступные операторы (operator):

  • In / NotIn — значение метки должно (или не должно) входить в предоставленный массив.
  • Exists / DoesNotExist — проверяется только наличие (или отсутствие) самого ключа метки, массив values не указывается.
  • Gt / Lt — значения интерпретируются как целые числа (Greater than / Less than).
  • Оператор NotIn часто используется для создания анти-аффинности к узлам. Например, чтобы гарантировать, что критичные базы данных никогда не попадут на узлы с меткой tier=frontend.

    Pod Affinity и Anti-Affinity: распределение относительно других нагрузок

    Если Node Affinity определяет отношение Pod'а к узлам, то Pod Affinity и Anti-Affinity определяют отношение Pod'а к другим Pod'ам. Этот механизм позволяет решать две фундаментальные архитектурные задачи:

  • Колокация (Pod Affinity): разместить компоненты, интенсивно обменивающиеся трафиком, максимально близко друг к другу, чтобы снизить сетевые задержки (например, веб-сервер и кэш Redis).
  • Отказоустойчивость (Pod Anti-Affinity): разнести реплики одного приложения по разным серверам, стойкам или дата-центрам, чтобы выход из строя одного физического домена не привел к простою сервиса.
  • Правила строятся на основе меток (Labels), которые висят на уже запущенных Pod'ах. Планировщик сканирует кластер, находит Pod'ы с нужными метками, определяет, на каких узлах они находятся, и принимает решение.

    Топологические домены (topologyKey)

    Ключевым понятием при работе с межподовым взаимодействием является topologyKey. Это метка узла, которая определяет границы домена (зоны), внутри которого действует правило.

    Представим кластер из 6 узлов. Три узла находятся в стойке A (имеют метку rack=A), три — в стойке B (rack=B). Каждый узел также имеет стандартную метку kubernetes.io/hostname, содержащую его уникальное имя.

    Если мы настраиваем Pod Anti-Affinity с topologyKey: kubernetes.io/hostname, планировщик будет воспринимать каждый отдельный сервер как независимый домен. Он не позволит запустить две реплики приложения на одном сервере, но легко разместит их на соседних серверах в одной стойке.

    Если мы изменим topologyKey на rack, доменом станет вся стойка. Планировщик увидит, что в стойке A уже есть реплика, и запретит размещать туда вторую реплику, даже если в этой стойке есть абсолютно пустые серверы. Вторая реплика будет принудительно отправлена в стойку B.

    !Интерактивный планировщик с правилами Affinity

    Пример: обеспечение высокой доступности (Anti-Affinity)

    Типичная задача на экзамене CKA — развернуть Deployment из трех реплик так, чтобы ни одна из них не оказалась на одном узле с другой.

    В этом манифесте Pod'ы ищут другие Pod'ы с меткой app=redis. Если на узле (в пределах домена hostname) такой Pod найден, планировщик отвергает этот узел. Важно понимать, что для работы этого механизма планировщику приходится выполнять ресурсоемкие вычисления, сканируя метки всех Pod'ов в кластере, поэтому злоупотребление сложными правилами Anti-Affinity в кластерах на тысячи узлов может замедлить работу Control Plane.

    Паттерн Dedicated Nodes (Синтез механизмов)

    На экзамене CKA и в реальной практике часто требуется реализовать паттерн «Выделенные узлы». Задача звучит так: есть группа серверов с доступом к защищенному банковскому шлюзу. Нужно гарантировать, что только Pod'ы приложения payment-gateway будут работать на этих серверах, и при этом payment-gateway не должен запускаться нигде больше.

    Использование только одного механизма не решит задачу:

  • Если использовать только Node Affinity: Pod payment-gateway попадет на нужные узлы. Но туда же попадут и любые другие Pod'ы кластера (например, Nginx или тестовые скрипты), так как узлы открыты для всех.
  • Если использовать только Taints и Tolerations: мы повесим Taint на банковские узлы, и чужие Pod'ы туда не пройдут. Мы дадим payment-gateway нужный Toleration. Но Toleration не заставляет Pod идти на этот узел — планировщик может отправить payment-gateway на обычный worker-узел, у которого нет Taint.
  • Решение заключается в строгой комбинации обоих механизмов.

    Шаг 1. Подготовка узла. Узел маркируется меткой для притяжения и «загрязняется» для отторжения чужих. kubectl label nodes secure-node-1 role=pci-dss kubectl taint nodes secure-node-1 secure=true:NoSchedule

    Шаг 2. Настройка манифеста. В спецификацию Pod'а добавляются и Node Affinity (чтобы притянуться к метке role=pci-dss), и Toleration (чтобы пройти сквозь Taint secure=true).

    Благодаря Node Affinity планировщик отбросит все обычные узлы и попытается разместить Pod на secure-node-1. Благодаря Toleration, Pod успешно пройдет фильтрацию Taint на этом узле. Ни один другой Pod без такого же набора настроек на этот узел не попадет.

    Понимание разницы между притяжением (Affinity) и отторжением (Taints), а также умение управлять границами топологических доменов, позволяет администратору Kubernetes строить отказоустойчивые и предсказуемые архитектуры любой сложности, полностью контролируя, где и в каком окружении выполняются контейнеры.

    5. Сетевое взаимодействие: сервисы, Ingress-контроллеры и сетевые политики

    Сетевое взаимодействие: сервисы, Ingress-контроллеры и сетевые политики

    Контейнеры смертны, и Kubernetes безжалостен к ним. При каждом обновлении Deployment, сбое узла или масштабировании старые Pod'ы уничтожаются, а новые создаются с совершенно новыми IP-адресами. Если frontend-приложение попытается подключиться к базе данных по статичному IP-адресу Pod'а, связь оборвется при первом же пересоздании базы. Возникает фундаментальная проблема маршрутизации: как надежно направлять трафик к группе объектов, чьи координаты постоянно меняются?

    Службы (Services): стабильный якорь в изменчивом кластере

    Kubernetes решает проблему динамических IP-адресов с помощью абстракции Service. Служба предоставляет единый, неизменный IP-адрес и постоянное DNS-имя для набора Pod'ов, выполняющих одинаковую функцию.

    Связь между Service и Pod'ами строится на основе Label Selectors, с которыми вы уже знакомы по контроллерам рабочих нагрузок. Однако Service не управляет жизненным циклом Pod'ов. Он лишь непрерывно сканирует кластер в поисках Pod'ов с нужными метками и направляет на них трафик.

    За кулисами магия маршрутизации обеспечивается объектом Endpoints (или его современной, более масштабируемой версией EndpointSlice). Когда вы создаете Service с селектором метки, Kubernetes автоматически создает одноименный объект Endpoints. Именно в нем хранится актуальный список IP-адресов живых и готовых к работе Pod'ов.

    !Схема связи Service, Endpoints и Pods

    Понимание объекта Endpoints критически важно для сдачи CKA и реального траблшутинга. Если Service существует, но трафик никуда не идет, первая команда, которую нужно выполнить — kubectl get endpoints <имя-сервиса>. Если в колонке ENDPOINTS пусто, значит, либо селектор в Service написан с опечаткой и не совпадает с метками Pod'ов, либо Pod'ы находятся в статусе CrashLoopBackOff или не прошли Readiness Probe. Service направляет трафик только на те Pod'ы, которые имеют статус Ready.

    Типы сервисов: от внутреннего к внешнему

    В зависимости от того, откуда должен приходить трафик, Kubernetes предлагает четыре типа сервисов.

    1. ClusterIP (по умолчанию) Создает виртуальный IP-адрес, доступный только внутри кластера. Это идеальный выбор для внутренних компонентов, таких как базы данных или кэши, к которым не должно быть доступа из интернета.

    В этом манифесте важно строго различать порты:

  • port — это порт самого сервиса (на него будут обращаться другие Pod'ы).
  • targetPort — это порт, который слушает контейнер внутри Pod'а.
  • Часто они совпадают, но если вы решите изменить порт контейнера с 5432 на 5433, вам достаточно изменить targetPort в сервисе. Приложения, обращающиеся к базе по адресу backend-db:5432, даже не заметят подмены.

    2. NodePort Открывает доступ к сервису снаружи кластера. Kubernetes резервирует определенный порт (по умолчанию в диапазоне ) на IP-адресе каждого рабочего узла. Трафик, приходящий на любой узел по этому порту, перенаправляется на внутренний ClusterIP сервиса, а затем на нужный Pod.

    При такой конфигурации пользователь может открыть в браузере http://<IP-любого-узла>:30080 и попасть в приложение. Однако NodePort имеет недостатки: порты нестандартные, а клиентам нужно знать IP-адреса узлов, которые могут меняться.

    На экзамене CKA часто требуется быстро создать сервис императивно. Команда kubectl expose deployment frontend --port=80 --target-port=8080 --type=NodePort сделает это за секунду, автоматически скопировав селекторы из Deployment.

    3. LoadBalancer Используется в облачных средах (AWS, GCP, Azure). При создании такого сервиса Kubernetes делает запрос к API облачного провайдера на выделение внешнего балансировщика нагрузки. Балансировщик получает публичный IP-адрес и направляет трафик на NodePort'ы узлов кластера. На "голом железе" (bare-metal) этот тип без дополнительных инструментов (например, MetalLB) будет бесконечно висеть в состоянии Pending.

    4. ExternalName Специфический тип, который не использует селекторы и не маршрутизирует трафик на Pod'ы. Он работает как CNAME-запись на уровне внутреннего DNS кластера. Если вы мигрируете базу данных из кластера в облачный сервис (например, Amazon RDS), вы можете создать Service типа ExternalName со значением my-db.rds.amazonaws.com. Внутренние Pod'ы продолжат обращаться к сервису по его короткому имени внутри кластера, а CoreDNS прозрачно перенаправит их на внешний адрес.

    Ingress: умная маршрутизация на прикладном уровне (L7)

    По мере роста кластера вы столкнетесь с ограничением сервисов. Если у вас 50 веб-приложений, создание 50 сервисов типа LoadBalancer обойдется очень дорого, а использование 50 NodePort'ов превратит управление портами в хаос. Кроме того, Service работает на транспортном уровне (L4) — он знает только IP-адреса и порты, но ничего не понимает в HTTP-запросах.

    Здесь на сцену выходит Ingress — механизм маршрутизации трафика на прикладном уровне (L7). Ingress позволяет использовать единый внешний IP-адрес (один LoadBalancer или NodePort) для обслуживания десятков различных приложений, распределяя трафик на основе доменного имени (Host) или пути (Path) в URL.

    !Схема L7 маршрутизации Ingress

    Важнейший нюанс, на котором спотыкаются многие новички: сам по себе манифест Ingress — это просто кусок текста с правилами. Чтобы эти правила заработали, в кластере должен быть запущен Ingress Controller. Ingress Controller — это специализированный Pod (чаще всего на базе Nginx, HAProxy или Traefik), который слушает внешний трафик, читает правила из объектов Ingress и динамически обновляет свою конфигурацию (например, nginx.conf), чтобы проксировать запросы к нужным сервисам.

    Рассмотрим пример маршрутизации на основе пути:

    В этом сценарии все HTTP-запросы к store.example.com приходят на единый Ingress Controller. Если пользователь запрашивает /catalog, контроллер перенаправляет трафик на внутренний сервис catalog-svc. Если /cart — на cart-svc.

    Обратите внимание на поле ingressClassName: nginx. В кластере может работать несколько разных Ingress-контроллеров одновременно (например, один внутренний для админки, другой внешний для клиентов). Это поле указывает, какой именно контроллер должен обрабатывать данное правило.

    Network Policies: сетевой фаервол внутри кластера

    По умолчанию сеть в Kubernetes плоская и максимально открытая: любой Pod может установить соединение с любым другим Pod'ом в кластере, даже если они находятся в разных пространствах имен (Namespaces). Если злоумышленник взломает уязвимый frontend-контейнер, он без препятствий сможет просканировать сеть и подключиться к backend-базе данных.

    Для ограничения трафика на уровнях L3/L4 используются сетевые политики (Network Policies). Это внутренний распределенный фаервол кластера.

    Важное условие: сетевые политики реализуются не самим Kubernetes, а сетевым плагином (CNI). Если ваш кластер использует CNI Flannel, вы можете создать сколько угодно объектов NetworkPolicy, API-сервер их примет, но они не будут работать. Для поддержки политик требуется CNI вроде Calico, Cilium или Weave Net. На экзамене CKA среда всегда настроена с поддержкой сетевых политик.

    Сетевая политика оценивает трафик по трем критериям:

  • К каким Pod'ами применяется политика (целевые Pod'ы).
  • Направление трафика: Ingress (входящий в целевой Pod) или Egress (исходящий из целевого Pod'а).
  • Правила: кому разрешено взаимодействовать с целевыми Pod'ами.
  • Как только к Pod'у применяется хотя бы одна политика NetworkPolicy, его поведение по умолчанию меняется с «разрешено всё» на «запрещено всё, что не разрешено явно».

    !Интерактивная проверка Network Policy

    Паттерн Default Deny All

    Лучшая практика безопасности в Kubernetes — изоляция пространств имен. Первым шагом при настройке защищенного окружения всегда является создание политики «Default Deny All» (запретить всё по умолчанию) для конкретного Namespace.

    Поле podSelector: {} означает выбор абсолютно всех Pod'ов в пространстве имен secure-project. Указание типов Ingress и Egress без описания конкретных правил приводит к тому, что любой входящий и исходящий трафик для всех Pod'ов в этом пространстве имен блокируется (включая запросы к DNS кластера).

    Точечное разрешение трафика

    После изоляции пространства имен необходимо явно разрешить легитимный трафик. Задача: разрешить Pod'ам с меткой app=backend принимать трафик по порту 5432 только от Pod'ов с меткой app=frontend, находящихся в пространстве имен с меткой env=prod.

    В этом манифесте кроется одна из самых коварных ловушек для инженеров. Обратите внимание на блок from. В нем namespaceSelector и podSelector находятся на одном уровне иерархии (в одном элементе массива, начинающемся с дефиса). Это логическое «И» (AND). Трафик будет разрешен только в том случае, если источник удовлетворяет обоим условиям: это Pod app=frontend, и он находится в Namespace env=prod.

    Если бы перед podSelector стоял дефис, это создало бы два отдельных элемента массива. Это означало бы логическое «ИЛИ» (OR): трафик был бы разрешен от любого Pod'а из Namespace env=prod, либо от Pod'а app=frontend из любого Namespace. Ошибка в одном дефисе полностью ломает логику безопасности.

    Также важно помнить, что Network Policies работают по аддитивному принципу. Если к одному Pod'у применяются три разные политики, и хотя бы одна из них разрешает конкретное соединение, трафик пройдет, даже если две другие о нем не упоминают. Явного правила Deny (запретить) в Network Policies не существует — всё строится на белых списках.

    Понимание того, как трафик попадает в кластер через Ingress, распределяется через Service (на основе Endpoints) и фильтруется на уровне узла через Network Policies, дает вам полный контроль над сетевой инфраструктурой Kubernetes и является обязательным фундаментом для успешной сдачи экзамена CKA.

    6. Управление постоянным хранилищем: PV, PVC и динамическое выделение ресурсов

    Управление постоянным хранилищем: PV, PVC и динамическое выделение ресурсов

    Представьте, что вы развернули базу данных MySQL внутри кластера. Приложение работает, пользователи сохраняют данные. Внезапно рабочий узел, на котором запущен Pod с базой данных, выходит из строя. Контроллер ReplicaSet мгновенно реагирует на это и пересоздает Pod на другом, здоровом узле. База данных снова доступна, но она абсолютно пуста. Все пользовательские данные исчезли. Эта ситуация наглядно демонстрирует фундаментальное свойство контейнеров: их файловая система эфемерна. Жизненный цикл данных внутри контейнера жестко привязан к жизненному циклу самого Pod'а. Чтобы разорвать эту связь и сохранить данные независимо от того, где и как перезапускаются контейнеры, Kubernetes предлагает мощную подсистему управления постоянным хранилищем.

    Эволюция томов: от локальных директорий к сетевым абстракциям

    В Kubernetes существует множество типов томов (Volumes). Некоторые из них мы уже упоминали при разборе многоконтейнерных Pod'ов, например, emptyDir. Этот тип тома создается в момент назначения Pod'а на узел и существует ровно до тех пор, пока жив Pod. Он отлично подходит для обмена временными файлами между основным контейнером и Sidecar-контейнером, но совершенно непригоден для долгосрочного хранения.

    Другой базовый тип — hostPath. Он монтирует файл или директорию из файловой системы самого рабочего узла (Worker Node) прямо в Pod.

    В этом примере Nginx будет отдавать файлы из директории /data/web конкретного физического сервера. Проблема hostPath очевидна: если Pod будет перепланирован на другой узел, он потеряет доступ к этим данным, так как на новом узле директория /data/web будет пустой или содержать другие файлы. Тем не менее, hostPath часто встречается на экзамене CKA в задачах, где нужно обеспечить доступ Pod'а к системным файлам узла (например, к логам Docker/containerd) или когда в тестовой среде нет полноценного сетевого хранилища.

    Для создания отказоустойчивых систем требуются сетевые хранилища (NFS, iSCSI, облачные диски AWS EBS, GCE PD и т.д.). Однако, если мы начнем прописывать параметры подключения к AWS EBS прямо в манифесте Pod'а, мы нарушим принцип переносимости. Разработчику придется знать детали инфраструктуры, а манифест перестанет работать при переносе из облака на локальный bare-metal кластер.

    Для решения этой проблемы Kubernetes вводит две ключевые абстракции: Persistent Volume (PV) и Persistent Volume Claim (PVC).

    Разделение ответственности: PV и PVC

    Архитектура хранения в Kubernetes построена на строгом разделении ролей между администратором кластера и разработчиком приложения.

    Persistent Volume (PV) — это ресурс хранения в кластере, созданный администратором. Это кусок физического или виртуального диска (NFS-шара, LUN в SAN, облачный диск). PV существует независимо от любого Pod'а. Это ресурс уровня всего кластера (cluster-scoped), он не принадлежит какому-либо пространству имен (Namespace).

    Persistent Volume Claim (PVC) — это запрос на выделение хранилища со стороны пользователя (разработчика). PVC существует в конкретном пространстве имен. Разработчик не знает, где физически лежат данные, он лишь говорит: «Мне нужен том размером 10 Гигабайт с возможностью чтения и записи».

    !Архитектура статического выделения хранилища: связь PV, PVC и Pod

    Процесс связывания (Binding) происходит автоматически: контроллер Kubernetes постоянно ищет новые PVC и пытается подобрать для них подходящие свободные PV.

    Глубокий разбор Persistent Volume (PV)

    Рассмотрим типичный манифест PV, который часто требуется написать с нуля на экзамене CKA:

    Здесь определены критически важные параметры, определяющие поведение тома.

    Capacity (Емкость): Указывает размер тома. В данном случае 5 Гибибайт (5Gi).

    Access Modes (Режимы доступа): Определяют, как именно узлы кластера могут монтировать этот том. Существует четыре режима:

  • ReadWriteOnce (RWO) — том может быть смонтирован на чтение и запись только одним узлом (Node) кластера. Важно: это ограничение на уровне узла, а не Pod'а. Несколько Pod'ов на одном и том же узле могут писать в этот том одновременно.
  • ReadOnlyMany (ROX) — том может быть смонтирован только для чтения множеством узлов одновременно.
  • ReadWriteMany (RWX) — том может быть смонтирован на чтение и запись множеством узлов. Поддерживается далеко не всеми типами хранилищ (например, AWS EBS не поддерживает RWX, а NFS — поддерживает).
  • ReadWriteOncePod (RWOP) — новейший режим. Том может быть смонтирован на чтение и запись строго одним единственным Pod'ом во всем кластере.
  • Persistent Volume Reclaim Policy (Политика возврата): Что должен сделать кластер с физическим хранилищем (PV), когда пользователь удалит свой запрос (PVC)?

  • Retain (Сохранить) — PV переходит в состояние Released, но данные остаются нетронутыми. Том нельзя сразу использовать для другого PVC. Администратор должен вручную очистить данные и пересоздать PV. Это самая безопасная политика для критичных баз данных.
  • Delete (Удалить) — удаляется и объект PV в Kubernetes, и физический ресурс во внешнем хранилище (например, удаляется диск в AWS).
  • Recycle (Устарело) — выполняет базовую очистку (эквивалент rm -rf /thevolume/*) и делает том снова доступным. Сейчас этот режим заменен динамическим выделением ресурсов.
  • Persistent Volume Claim (PVC) и процесс Binding'а

    Теперь разработчик создает запрос на хранилище в своем пространстве имен:

    Как только этот манифест применяется в кластере, начинается процесс Binding'а. Контроллер ищет PV, который удовлетворяет следующим условиям:

  • Имеет точно такой же или более широкий набор accessModes.
  • Имеет емкость (capacity) больше или равную запрошенной (requests.storage).
  • Находится в статусе Available (не привязан к другому PVC).
  • В нашем примере PVC просит 3Gi и RWO. В кластере есть свободный PV pv-data-01 размером 5Gi и режимом RWO. Условия выполнены. Происходит связывание. Статус обоих объектов меняется на Bound.

    Здесь кроется важный нюанс: PVC запросил 3Gi, но связался с PV размером 5Gi. Оставшиеся 2Gi на этом томе не могут быть использованы другим PVC. Связь PV и PVC эксклюзивна (один к одному). Емкость расходуется целиком, что при статическом выделении часто приводит к неэффективному использованию ресурсов.

    Подключение PVC к Pod'у

    После успешного связывания, PVC можно использовать в Pod'е. Это делается в два этапа внутри манифеста Pod'а: сначала мы объявляем том на уровне Pod'а, ссылаясь на PVC, а затем монтируем его в конкретную директорию внутри контейнера.

    Обратите внимание: Pod ссылается только на claimName: my-pvc. Он ничего не знает о pv-data-01 или о том, что физически данные лежат в /mnt/data на каком-то узле. Абстракция работает безупречно.

    Траблшутинг: освобождение тома с политикой Retain

    На экзамене CKA часто встречается сценарий: вы удалили PVC, но PV не возвращается в статус Available, а зависает в статусе Released. Вы создаете новый PVC, но он висит в Pending, так как нет свободных томов.

    Причина в политике Retain. Когда PVC удаляется, PV сохраняет данные, но внутри объекта PV остается системная запись claimRef, указывающая на уже удаленный PVC. Кластер блокирует этот том для безопасности.

    Чтобы вернуть такой PV в строй (если вы уверены, что данные можно перезаписать или они вам нужны для нового PVC), необходимо отредактировать PV: kubectl edit pv pv-data-01 И удалить весь блок claimRef:

    Сразу после сохранения изменений статус PV изменится с Released на Available, и он сможет связаться с новым PVC.

    Динамическое выделение хранилища: StorageClass

    Статическая модель (администратор создает PV -> пользователь создает PVC) работает в небольших кластерах. Но в масштабах предприятия, где разработчики постоянно создают и удаляют среды, ручное создание сотен PV становится невыполнимой задачей.

    Здесь на сцену выходит StorageClass (SC) — механизм динамического выделения ресурсов (Dynamic Provisioning).

    С помощью StorageClass администратор кластера определяет «профили» хранилища (например, «медленные HDD», «быстрые SSD», «сетевые диски с репликацией»), но не создает сами тома заранее.

    Ключевое поле здесь — provisioner. Это указатель на внутренний или внешний плагин (CSI - Container Storage Interface), который умеет общаться с API облачного провайдера или системы хранения. В данном случае указан провижионер AWS EBS.

    Теперь, когда разработчику нужно хранилище, он создает PVC, но добавляет в него поле storageClassName:

    !Процесс динамического выделения хранилища через StorageClass

    Как только этот PVC попадает в кластер, контроллер видит, что запрошен класс fast-ssd. Он обращается к соответствующему StorageClass, читает параметры (нужен диск gp2 в AWS) и передает команду провижионеру. Провижионер делает API-вызов в AWS, создает реальный EBS-том на 10Gi, затем автоматически генерирует объект PV в Kubernetes и связывает его с нашим PVC.

    Ручной труд администратора полностью исключен.

    Нюанс: VolumeBindingMode

    В манифесте StorageClass выше есть критически важный для сложных кластеров параметр: volumeBindingMode: WaitForFirstConsumer.

    По умолчанию этот параметр имеет значение Immediate. Это означает, что облачный диск создается в ту же секунду, когда создается PVC. В облаке диск всегда создается в конкретной зоне доступности (Availability Zone, AZ), например, us-east-1a. Но что, если Pod, который будет использовать этот PVC, требует мощного GPU, а узлы с GPU есть только в зоне us-east-1b? Планировщик попытается запустить Pod в зоне 1b, но диск уже жестко привязан к зоне 1a. Pod зависнет в статусе Pending.

    Параметр WaitForFirstConsumer решает эту проблему. Он приказывает StorageClass отложить создание физического диска и PV до тех пор, пока не появится первый Pod, использующий этот PVC. Планировщик сначала выберет оптимальный узел для Pod'а (учитывая Taints, Affinity и наличие ресурсов), и только после этого StorageClass создаст диск в той же зоне доступности, где находится выбранный узел.

    Default Storage Class

    В современных кластерах часто настраивается класс хранилища по умолчанию. Если администратор пометил один из StorageClass аннотацией storageclass.kubernetes.io/is-default-class: "true", разработчикам даже не нужно указывать storageClassName в своих PVC. Если поле пропущено, кластер автоматически применит класс по умолчанию. На экзамене CKA полезно проверить наличие такого класса командой kubectl get sc. Если он есть, его имя будет отмечено суффиксом (default).

    Понимание связки PV, PVC и StorageClass дает полный контроль над персистентностью данных. Выбор между статическим hostPath для системных нужд узла и динамическим StorageClass для масштабируемых приложений — это базовый навык, который проверяется на сертификации CKA множеством практических задач.

    7. Безопасность кластера: RBAC, Service Accounts и контексты безопасности

    Разработчик разворачивает в кластере простой скрипт для сбора метрик, а через день злоумышленник, эксплуатируя уязвимость в этом скрипте, удаляет все рабочие нагрузки в пространстве имен production. Причина кроется не в сложности атаки, а в конфигурации по умолчанию: Pod со скриптом получил неограниченный доступ к API-серверу Kubernetes, а его контейнер был запущен с правами root. Безопасность в Kubernetes не возникает сама по себе — это многослойная система, требующая явной настройки на уровне доступа к API и на уровне изоляции процессов внутри узла.

    Любой запрос к kube-apiserver (будь то от администратора с ноутбука или от Pod'а внутри кластера) проходит три этапа: аутентификацию (кто это?), авторизацию (имеет ли он на это право?) и контроль допуска (Admission Control, не нарушает ли запрос глобальные политики?). На сертификации CKA основной упор делается на второй этап — авторизацию на базе ролей (RBAC) — и на локальную изоляцию контейнеров через Security Context.

    Архитектура управления доступом (RBAC)

    Role-Based Access Control (RBAC) — это стандартный механизм авторизации в Kubernetes. Его логика строится на том, что права не выдаются пользователям напрямую. Вместо этого создаются роли с описанием разрешенных действий, а затем эти роли «привязываются» к субъектам.

    RBAC оперирует тремя основными категориями:

  • Субъекты (Subjects): Пользователи (User), группы (Group) или сервисные аккаунты (ServiceAccount).
  • Ресурсы (Resources) и Глаголы (Verbs): Над чем выполняется действие (pods, deployments, services) и что именно делается (get, list, watch, create, update, patch, delete).
  • Область действия (Scope): Ограничены ли права конкретным пространством имен (Namespace) или действуют на весь кластер.
  • В зависимости от области действия, Kubernetes предлагает четыре типа объектов RBAC, образующих две пары.

    Role и RoleBinding (Уровень Namespace)

    Role описывает набор разрешений в рамках одного конкретного пространства имен. Если вы создаете Role в namespace: frontend, она физически не может разрешить просмотр Pod'ов в namespace: backend.

    RoleBinding связывает созданную Role с субъектом в том же пространстве имен.

    !Схема взаимодействия субъектов, ролей и привязок в RBAC

    На экзамене CKA критически важно экономить время, поэтому манифесты для RBAC редко пишут с нуля. Используются императивные команды:

    ClusterRole и ClusterRoleBinding (Уровень кластера)

    Некоторые ресурсы в Kubernetes не принадлежат пространствам имен (например, Nodes, PersistentVolumes). Для управления ими невозможно использовать обычную Role. Здесь применяется ClusterRole.

    Кроме того, ClusterRole используется для определения универсальных политик, которые должны действовать во всем кластере.

    ClusterRoleBinding применяет эти права глобально. Если связать пользователя с ClusterRole через ClusterRoleBinding, он получит указанные права во всех пространствах имен кластера.

    Гибридный паттерн: RoleBinding + ClusterRole

    Существует неочевидная, но частая задача: администратору нужно дать пользователю стандартный набор прав (например, edit), но только в одном пространстве имен project-a. Вместо того чтобы создавать копию роли edit в пространстве project-a, можно использовать RoleBinding внутри project-a, сославшись на глобальную ClusterRole.

    В этом случае права из ClusterRole будут ограничены границами того пространства имен, где создан RoleBinding. Это позволяет избежать дублирования ролей и упрощает аудит безопасности.

    Проверка прав: kubectl auth can-i

    При настройке RBAC легко ошибиться. Для проверки эффективных прав Kubernetes предоставляет встроенный инструмент симуляции.

    !Интерактивный симулятор проверки прав RBAC

    Учетные записи служб (Service Accounts)

    Если пользователи (Users) — это люди, управляющие кластером извне, то сервисные аккаунты (Service Accounts, SA) — это удостоверения личности для процессов, запущенных внутри Pod'ов. Когда приложению (например, Ingress-контроллеру или CI/CD агенту) нужно обратиться к API-серверу Kubernetes, оно использует токен своего Service Account.

    В каждом пространстве имен при его создании автоматически генерируется Service Account с именем default. Если при создании Pod'а явно не указать поле serviceAccountName, планировщик автоматически назначит ему этот default аккаунт.

    Более того, kubelet автоматически примонтирует токен этого аккаунта внутрь файловой системы контейнера по пути /var/run/secrets/kubernetes.io/serviceaccount. Любой процесс внутри контейнера может прочитать этот токен и отправить авторизованный запрос к API-серверу.

    Это создает серьезный вектор атаки. Если злоумышленник получит RCE (Remote Code Execution) в вашем приложении, он найдет этот токен. Если администратор ранее, по незнанию, выдал аккаунту default широкие права через ClusterRoleBinding, кластер будет скомпрометирован.

    Правила безопасной работы с Service Accounts

  • Отключение автоматического монтирования. Если приложению в Pod'е не нужно общаться с API Kubernetes (а это справедливо для 95% бизнес-приложений: баз данных, веб-серверов, кэшей), автоматическое монтирование токена необходимо отключить.
  • Использование выделенных SA. Если приложению все же нужен доступ к API (например, Prometheus должен собирать метрики с узлов), никогда не используйте аккаунт default. Создайте отдельный SA, выдайте ему минимально необходимые права через RBAC и явно укажите его в спецификации Pod'а.
  • Привязка Service Account к Pod'у осуществляется через поле serviceAccountName:

    Контексты безопасности (Security Contexts)

    RBAC защищает API-сервер от несанкционированных запросов. Но что защищает сам рабочий узел (Worker Node) от вредоносных действий контейнера? Контейнеры делят одно ядро Linux с хост-системой. Если процесс внутри контейнера запущен от имени пользователя root (UID 0), и контейнеру удастся совершить побег (container breakout), он получит root-права на самом узле.

    Для ограничения прав процессов внутри контейнеров на уровне операционной системы используется securityContext. Он может быть применен на двух уровнях: для всего Pod'а (действует на все контейнеры и тома) и для конкретного контейнера (переопределяет настройки Pod'а).

    Управление пользователями и группами

    По умолчанию многие публичные образы (включая популярные версии Nginx или Redis) запускают свой основной процесс от имени root. Это плохая практика. Мы можем принудительно заставить контейнер работать от имени непривилегированного пользователя.

    Поле fsGroup особенно важно при работе с Persistent Volumes. Если Pod запускается от UID 1000, а примонтированный файловый том принадлежит root, приложение не сможет записать туда данные. fsGroup заставляет kubelet рекурсивно изменить владельца файлов на томе перед запуском контейнера, обеспечивая права на чтение и запись.

    Запрет эскалации привилегий

    В Linux существует механизм SUID/SGID битов. Если на исполняемом файле стоит такой бит, то даже обычный пользователь, запустив этот файл, временно получит права владельца файла (часто root). Классический пример — утилита sudo или ping.

    Чтобы предотвратить атаки, когда процесс стартует как непривилегированный, но затем повышает свои права через уязвимые бинарные файлы внутри контейнера, используется флаг allowPrivilegeEscalation.

    Привилегированный режим (Privileged)

    Параметр privileged: true — это самый опасный флаг в Kubernetes. Он дает контейнеру доступ ко всем устройствам хоста (/dev/) и снимает практически все ограничения изоляции cgroups и namespaces. Контейнер с privileged: true фактически имеет те же права, что и процесс, запущенный непосредственно на самом узле.

    Этот режим необходим для системных компонентов (например, сетевых плагинов CNI, таких как Calico или Flannel, которым нужно управлять сетевыми интерфейсами хоста), но его использование для бизнес-приложений категорически недопустимо.

    Управление возможностями Linux (Capabilities)

    Традиционно в Linux права делятся на две категории: root (может всё) и обычный пользователь (может мало). Linux Capabilities разбивают монолитные права root на десятки мелких, независимых разрешений.

    Например, чтобы приложение могло слушать порты ниже 1024 (например, порт 80 для HTTP), ему исторически требовался полный root. С помощью Capabilities достаточно выдать только CAP_NET_BIND_SERVICE.

    Среда выполнения контейнеров (containerd, CRI-O) по умолчанию отбирает у контейнеров часть опасных Capabilities, но оставляет базовый набор. Для максимальной безопасности рекомендуется применять паттерн «запретить всё, разрешить необходимое»: использовать drop: ["ALL"], а затем через add вернуть только то, что действительно нужно для работы.

    Комплексный пример защищенной рабочей нагрузки

    На экзамене CKA вас могут попросить исправить существующий Deployment так, чтобы он соответствовал строгим требованиям безопасности. Объединим изученные концепции в едином манифесте.

    Задача: развернуть приложение, которое не имеет доступа к API Kubernetes, запускается от имени пользователя с UID 5000, не может повышать привилегии и не имеет лишних Linux Capabilities.

    В этом манифесте добавлена директива readOnlyRootFilesystem: true. Это еще один мощный инструмент securityContext. Если злоумышленник найдет уязвимость в приложении и попытается загрузить скрипт-майнер или изменить бинарные файлы, он получит ошибку файловой системы. Приложение сможет писать данные только в явно примонтированные тома (в данном случае в emptyDir, смонтированный в /tmp).

    Разделение безопасности на внешний контур (RBAC для защиты API) и внутренний контур (Security Context для изоляции процессов) позволяет реализовать принцип наименьших привилегий. При правильной настройке даже полная компрометация контейнера не приведет к захвату узла или падению всего кластера Kubernetes.

    8. Обслуживание инфраструктуры: обновление компонентов и управление жизненным циклом узлов

    Жизненный цикл минорной версии Kubernetes составляет всего 14 месяцев. По истечении этого срока сообщество прекращает выпуск патчей безопасности, оставляя кластер уязвимым. Это делает регулярное обновление компонентов не просто хорошей практикой, а суровой необходимостью. В условиях production-среды администратор не может позволить себе остановить кластер на выходные, обновить серверы и запустить всё заново. Процесс должен происходить «на лету», без прерывания обслуживания пользовательского трафика. На экзамене CKA задачи по безопасному выводу узлов из эксплуатации и обновлению кластера с помощью kubeadm встречаются в 100% случаев и имеют высокий вес в итоговой оценке.

    Политика перекоса версий (Version Skew Policy)

    Прежде чем приступать к обновлению, необходимо понять строгие правила совместимости компонентов Kubernetes. Кластер не обновляется мгновенно: в процессе апгрейда неизбежно возникает ситуация, когда часть компонентов уже работает на новой версии, а часть — ещё на старой.

    Kubernetes гарантирует работоспособность при соблюдении Version Skew Policy. Центральным элементом, определяющим версии, является kube-apiserver. Он всегда должен иметь самую свежую версию в кластере.

    !Допустимый перекос версий компонентов Kubernetes

    Правила перекоса версий выглядят следующим образом (где — минорная версия kube-apiserver, например, 1.29):

  • kube-apiserver: Если в кластере несколько серверов API (High Availability), разница между ними не может превышать одну минорную версию ( и ).
  • kube-controller-manager и kube-scheduler: Могут быть той же версии, что и API-сервер, или на одну минорную версию ниже ( или ). Они никогда не могут быть новее API-сервера.
  • kubelet: Начиная с версии Kubernetes 1.28, kubelet может отставать от kube-apiserver на три минорные версии (, , , ). До версии 1.28 допускалось отставание только на две версии. Это расширение окна совместимости сделано специально, чтобы дать администраторам больше времени на обновление парка рабочих узлов.
  • kubectl: Поддерживает перекос в одну версию в обе стороны (, , ). Вы можете использовать kubectl версии 1.30 для управления кластером 1.29.
  • Из этих правил вытекает единственный правильный порядок обновления кластера: сначала обновляется Control Plane (начиная с kube-apiserver), и только затем, поочередно — рабочие узлы (kubelet). Пропуск минорных версий запрещен. Чтобы обновиться с 1.27 до 1.29, необходимо сначала обновить кластер до 1.28, а затем провести повторную процедуру до 1.29.

    Управление состоянием узла: Cordon и Drain

    Любое обслуживание узла — будь то обновление Kubernetes, установка патчей ядра Linux или замена планки оперативной памяти — требует безопасного удаления с него рабочих нагрузок. Для этого используются императивные команды cordon и drain.

    Блокировка планирования (Cordon)

    Команда kubectl cordon <node-name> помечает узел как недоступный для планирования новых Pod'ов.

    Под капотом эта команда делает очень простую вещь: она добавляет на объект Node специфичный Taint — node.kubernetes.io/unschedulable:NoSchedule. Планировщик (kube-scheduler) видит этот Taint и перестает рассматривать узел как кандидата для размещения новых нагрузок.

    При этом уже запущенные на узле Pod'ы продолжают работать в штатном режиме. Никакого прерывания трафика не происходит. Узел переходит в статус Ready,SchedulingDisabled. Команда cordon полезна, когда вы заметили нестабильность сервера (например, сбои диска) и хотите предотвратить попадание туда новых приложений до выяснения причин, не убивая при этом текущие процессы.

    Эвакуация нагрузок (Drain)

    Для полного освобождения узла используется команда kubectl drain <node-name>. Она автоматически выполняет cordon, а затем начинает процесс вытеснения (eviction) всех Pod'ов.

    !Процесс эвакуации Pod'ов при drain

    В отличие от жесткого удаления (kubectl delete pod), команда drain использует Eviction API. Это означает, что Kubernetes уважает параметры мягкого завершения работы. Pod'ам отправляется сигнал SIGTERM, и им дается время на корректное закрытие соединений (определяется параметром terminationGracePeriodSeconds в манифесте Pod'а, по умолчанию 30 секунд). В это же время Endpoints контроллер удаляет IP-адреса этих Pod'ов из всех сервисов, чтобы Ingress и Service перестали направлять к ним новый трафик.

    Контроллеры рабочих нагрузок (ReplicaSet, Deployment, StatefulSet), заметив исчезновение Pod'ов, немедленно создают их новые копии. Поскольку исходный узел уже заблокирован (cordon), планировщик размещает новые Pod'ы на других доступных узлах кластера.

    На практике выполнение простой команды kubectl drain почти всегда завершается ошибкой. Kubernetes имеет встроенные механизмы защиты от случайного разрушения инфраструктуры. Чтобы операция прошла успешно, необходимо явно указать флаги для обхода этих защит:

  • Игнорирование DaemonSet (--ignore-daemonsets). По умолчанию drain откажется работать, если на узле есть Pod'ы, управляемые DaemonSet (например, kube-proxy или плагины CNI). Логика проста: если drain удалит такой Pod, контроллер DaemonSet тут же создаст его заново на этом же узле, так как DaemonSet игнорирует статус SchedulingDisabled. Флаг --ignore-daemonsets приказывает процессу эвакуации оставить эти системные Pod'ы в покое и выселить только пользовательские приложения.
  • Удаление локальных данных (--delete-emptydir-data). Если Pod использует том emptyDir (например, для временного кэша), при удалении Pod'а эти данные будут безвозвратно потеряны. Kubernetes требует от администратора явного подтверждения того, что потеря этих эфемерных данных допустима.
  • Принудительное удаление изолированных Pod'ов (--force). Если на узле запущен Pod, созданный напрямую (без Deployment или ReplicaSet), его удаление приведет к полному исчезновению приложения из кластера — никто не пересоздаст его на другом узле. Флаг --force заставляет drain удалить даже такие Pod'ы.
  • Типовая команда для безопасной подготовки узла к обслуживанию на экзамене выглядит так: kubectl drain worker-node-1 --ignore-daemonsets --delete-emptydir-data --force

    Влияние PodDisruptionBudgets (PDB)

    Важный нюанс, который часто становится камнем преткновения при обслуживании крупных кластеров — объект PodDisruptionBudget. PDB позволяет разработчикам приложения указать минимальное количество реплик, которые должны быть доступны в любой момент времени.

    Если Deployment состоит из двух реплик, и PDB требует minAvailable: 2, команда drain зависнет в ожидании. Eviction API откажется удалять Pod, так как это нарушит бюджет доступности (останется 1 реплика из требуемых 2). В такой ситуации администратору придется либо временно масштабировать Deployment до 3 реплик, либо редактировать/удалять сам PDB.

    Возврат узла в строй (Uncordon)

    После завершения технического обслуживания (обновления пакетов, перезагрузки сервера) узел необходимо вернуть в пул доступных ресурсов. Команда kubectl uncordon <node-name> снимает Taint unschedulable. Узел переходит в статус Ready.

    Важно понимать: после выполнения uncordon старые Pod'ы, которые были эвакуированы, не вернутся на этот узел автоматически. Kubernetes не занимается перебалансировкой запущенных нагрузок без необходимости. Узел будет постепенно заполняться новыми Pod'ами по мере масштабирования приложений или развертывания новых релизов.

    Обновление Control Plane с помощью kubeadm

    Процесс обновления кластера, развернутого через kubeadm, строго регламентирован. Сначала обновляется первый узел Control Plane, затем остальные узлы Control Plane (если они есть), и в конце — рабочие узлы.

    В системах на базе Debian/Ubuntu (которые используются в среде CKA) пакеты Kubernetes зафиксированы от случайного обновления с помощью утилиты apt-mark hold. Это предотвращает поломку кластера при выполнении рутинной команды apt upgrade системным администратором.

    Процесс обновления первого управляющего узла состоит из следующих шагов:

    Шаг 1. Обновление самого инструмента kubeadm. Сначала необходимо снять блокировку с пакета, установить нужную версию и снова заблокировать его.

    Убедиться, что версия изменилась: kubeadm version.

    Шаг 2. Планирование обновления. Команда kubeadm upgrade plan анализирует текущее состояние кластера, проверяет доступность новых образов в реестре registry.k8s.io и выводит подробный отчет. В отчете будет указано, какие компоненты будут обновлены, и требуется ли ручное вмешательство в конфигурацию. На экзамене вывод этой команды часто просят сохранить в файл.

    Шаг 3. Применение обновления. Выполнение команды kubeadm upgrade apply v1.29.0 запускает фактический процесс обновления Control Plane. Что происходит под капотом в этот момент:

  • kubeadm скачивает новые контейнерные образы для kube-apiserver, kube-controller-manager, kube-scheduler и etcd.
  • Генерируются новые TLS-сертификаты, если срок действия текущих истекает менее чем через 180 дней.
  • kubeadm перезаписывает манифесты Static Pods в директории /etc/kubernetes/manifests/.
  • Локальный kubelet, отслеживающий эту директорию, замечает изменения файлов. Он останавливает старые контейнеры компонентов Control Plane и запускает новые из обновленных образов.
  • В течение 1-2 минут API-сервер будет недоступен, пока поднимается новый контейнер. Команда apply дождется успешного старта всех компонентов и сообщит об успехе.

    Шаг 4. Обновление kubelet и kubectl. Хотя Control Plane уже работает на новой версии, локальный kubelet (который управляет этими Static Pods) и утилита kubectl все еще используют старую версию. Их нужно обновить через пакетный менеджер ОС. Узел Control Plane предварительно нужно вывести из эксплуатации командой drain (да, управляющие узлы тоже нужно дренировать, так как на них могут работать DaemonSets вроде CNI).

    После обновления бинарного файла kubelet, необходимо перезапустить системный сервис, чтобы изменения вступили в силу:

    Завершающий этап — возврат узла в работу:

    Обновление рабочих узлов (Worker Nodes)

    Процедура обновления рабочих узлов концептуально похожа, но имеет важное отличие: рабочие узлы не управляют компонентами Control Plane, поэтому команда upgrade apply на них не используется.

    Процесс выполняется последовательно для каждого рабочего узла. Одновременное обновление всех узлов приведет к полной недоступности приложений.

    Шаг 1. Подготовка узла. Находясь на узле Control Plane (или имея доступ к API), выполняем эвакуацию нагрузок: kubectl drain worker-1 --ignore-daemonsets --delete-emptydir-data --force

    Шаг 2. Обновление kubeadm на рабочем узле. Подключаемся по SSH к узлу worker-1 и обновляем пакет kubeadm:

    Шаг 3. Обновление конфигурации узла. Вместо apply используется команда node: kubeadm upgrade node Эта команда не трогает Static Pods (их здесь нет). Она обновляет локальную конфигурацию kubelet (файл /var/lib/kubelet/config.yaml), прописывая туда новые параметры, требуемые для версии 1.29, и обновляет сертификаты для взаимодействия с API-сервером.

    Шаг 4. Обновление бинарных файлов. Снимаем блокировку, обновляем kubelet и перезапускаем службу:

    Шаг 5. Возврат в строй. Возвращаемся на машину с доступом к кластеру и снимаем блокировку планирования: kubectl uncordon worker-1

    Этот цикл повторяется для каждого рабочего узла в кластере. В крупных инфраструктурах этот процесс автоматизируют с помощью Ansible или Terraform, но для успешной сдачи CKA необходимо уметь выполнять все шаги вручную, понимая влияние каждой команды на состояние кластера.

    Обслуживание ОС и патчинг ядра

    Жизненный цикл узлов не ограничивается обновлением самого Kubernetes. Серверы требуют регулярной установки патчей безопасности операционной системы (Ubuntu, CentOS, Rocky Linux) и обновления ядра.

    Процесс установки системных обновлений полностью интегрируется в описанный выше пайплайн обслуживания. Если системному администратору нужно обновить ядро Linux, что требует обязательной перезагрузки сервера, последовательность действий будет следующей:

  • Выполняется kubectl drain для безопасного вывода узла.
  • Устанавливаются системные пакеты (apt upgrade).
  • Сервер отправляется в перезагрузку (reboot).
  • После старта ОС процесс systemd автоматически запускает Container Runtime (например, containerd) и службу kubelet.
  • kubelet связывается с API-сервером и сообщает о своей готовности. Узел переходит в статус Ready,SchedulingDisabled.
  • Выполняется kubectl uncordon.
  • Строгое соблюдение этой последовательности гарантирует, что инфраструктурные работы на уровне железа или операционной системы останутся абсолютно незаметными для конечных пользователей ваших приложений.

    9. Комплексный траблшутинг: диагностика сбоев кластера, сети и приложений

    Комплексный траблшутинг: диагностика сбоев кластера, сети и приложений

    Вы вводите kubectl get nodes и получаете пугающий ответ: The connection to the server <IP>:6443 was refused - did you specify the right host or port?. Кластер не отвечает, API недоступен, и стандартные инструменты Kubernetes внезапно становятся бесполезными. На экзамене CKA подобные ситуации — не случайность, а целенаправленная проверка способности администратора мыслить за пределами абстракций kubectl и спускаться на уровень операционной системы, системных служб и контейнерных сред выполнения.

    Траблшутинг в Kubernetes требует строгой изоляции проблемы. Хаотичное чтение логов редко приводит к успеху. Эффективная диагностика строится по принципу матрешки: от инфраструктурного слоя (узлы, сертификаты, системные службы) к слою управления (Control Plane), затем к рабочим нагрузкам (Pods) и, наконец, к сетевому взаимодействию.

    Диагностика на уровне узла: когда падает фундамент

    Если узел переходит в статус NotReady или кластер полностью теряет управление, проблема почти всегда кроется в компонентах, работающих непосредственно на хостовой ОС: kubelet или Container Runtime (например, containerd).

    Восстановление службы kubelet

    kubelet — это единственный компонент Kubernetes, который управляется системой инициализации ОС (systemd), а не самим кластером. Если kubelet не стартует, узел становится «слепым и глухим». Это частый сценарий после обновления кластера.

    Первый шаг при недоступности узла — проверка статуса службы: systemctl status kubelet

    Если служба находится в состоянии activating (auto-restart) или failed, необходимо изучить системный журнал. Команда journalctl — главный инструмент на этом этапе. Для фильтрации вывода используйте флаги -u (указание юнита) и -e (переход в конец лога): journalctl -u kubelet -e --no-pager

    Типичная ошибка, которую можно обнаружить в логах: failed to run Kubelet: misconfiguration: kubelet cgroup driver: "systemd" is different from docker cgroup driver: "cgroupfs"

    Эта ошибка означает рассинхронизацию конфигурации. Среда выполнения контейнеров ожидает один драйвер управления ресурсами, а kubelet настроен на другой. Для исправления потребуется отредактировать файл конфигурации (обычно /var/lib/kubelet/config.yaml), изменить параметр cgroupDriver на systemd, после чего выполнить systemctl daemon-reload и systemctl restart kubelet.

    Другая распространенная причина отказа kubelet — просроченные или неверно указанные TLS-сертификаты для связи с API-сервером. В логах это выглядит как x509: certificate signed by unknown authority. В этом случае необходимо проверить пути к сертификатам в файле /etc/kubernetes/kubelet.conf.

    Отладка Control Plane без kubectl

    Если kubelet работает нормально, но API-сервер недоступен (ошибка connection refused), у вас нет возможности использовать kubectl. Компоненты Control Plane (api-server, etcd, scheduler, controller-manager) разворачиваются как Static Pods. Их манифесты лежат в директории /etc/kubernetes/manifests/.

    Если в манифесте допущена синтаксическая ошибка (например, опечатка в пути к сертификату --tls-cert-file=/etc/kubernetes/pki/apiserver.crtt вместо .crt), kubelet попытается запустить контейнер, но тот сразу упадет.

    Как прочитать логи падающего API-сервера, если kubectl logs не работает? Необходимо обратиться напрямую к среде выполнения контейнеров с помощью утилиты crictl, которая реализует интерфейс CRI.

    Сначала находим идентификатор упавшего контейнера: crictl ps -a | grep kube-apiserver

    Затем читаем его логи: crictl logs <CONTAINER_ID>

    Альтернативный путь — чтение сырых логов из файловой системы узла. kubelet складывает логи всех контейнеров в директорию /var/log/containers/. Выполнив ls -l /var/log/containers/ | grep apiserver, можно найти нужный файл и прочитать его через tail или cat. Найдя ошибку (например, stat /etc/kubernetes/pki/apiserver.crtt: no such file or directory), достаточно исправить опечатку в манифесте. kubelet автоматически отследит изменение файла в директории manifests и перезапустит Static Pod.

    Диагностика рабочих нагрузок: чтение симптомов Pod'ов

    Если инфраструктура здорова, фокус смещается на объекты кластера. Жизненный цикл Pod'а сопровождается четкими статусами, каждый из которых указывает на конкретный класс проблем.

    Зависание в статусе Pending

    Статус Pending означает, что Pod принят API-сервером, но еще не назначен ни на один узел. Проблема всегда находится на этапе планирования. Главный инструмент здесь — kubectl describe pod <pod-name>.

    В секции Events в самом низу вывода будет указана причина отказа от планировщика (kube-scheduler). Варианты могут быть следующими:

  • 0/3 nodes are available: 3 Insufficient cpu. — кластеру не хватает ресурсов, запрошенных в блоке resources.requests манифеста Pod'а.
  • 0/3 nodes are available: 1 node(s) had taint {node-role.kubernetes.io/master: }, that the pod didn't tolerate, 2 node(s) didn't match Pod's node affinity/selector. — конфликт правил размещения. Планировщик не может найти узел, который одновременно удовлетворяет nodeSelector и не отторгает Pod своими Taints.
  • Ошибки запуска: ImagePullBackOff и ErrImagePull

    Если Pod перешел на узел, но не может запустить контейнер, статус меняется на ErrImagePull, а затем на ImagePullBackOff. Это означает, что kubelet не смог скачать образ контейнера.

    Причины и методы проверки (через kubectl describe):

  • Опечатка в имени образа или тега (например, nginx:1.244 вместо nginx:1.24). В логах событий будет rpc error: code = NotFound.
  • Отсутствие прав на скачивание из приватного реестра. Если образ существует, но реестр требует авторизации, событие покажет pull access denied. Решение — создание секрета типа docker-registry и его указание в поле imagePullSecrets спецификации Pod'а.
  • Цикличные падения: CrashLoopBackOff

    Статус CrashLoopBackOff — один из самых частых и сложных для новичков. Он означает, что контейнер успешно скачан и запущен, но процесс внутри него завершился с ошибкой (код возврата ), либо процесс завершился успешно (код ), но Pod не предназначен для одноразовых задач. kubelet реагирует на это перезапуском контейнера.

    !Анимация экспоненциальной задержки при CrashLoopBackOff

    При каждом последующем падении kubelet увеличивает паузу перед следующим перезапуском (10 секунд, 20, 40, 80 и так до 5 минут). Это защитный механизм кластера от перегрузки циклично падающими контейнерами.

    Для диагностики CrashLoopBackOff команда describe бесполезна — она покажет лишь факт перезапуска. Необходимо смотреть логи самого приложения: kubectl logs <pod-name>

    Если Pod состоит из нескольких контейнеров, обязательно укажите имя падающего: kubectl logs <pod-name> -c <container-name>

    Часто бывает так, что контейнер падает мгновенно, и текущий лог пуст. В этом случае критически важно прочитать логи предыдущего, уже мертвого экземпляра контейнера. Для этого используется флаг -p (или --previous): kubectl logs <pod-name> -p

    Типичные причины CrashLoopBackOff:

  • Ошибка в конфигурационном файле приложения (например, неверный синтаксис nginx.conf).
  • Приложение не может подключиться к базе данных при старте и падает (отсутствует обработка потери соединения).
  • Неверно переопределена команда запуска. Если в манифесте Pod'а указано command: ["sleep", "abc"], утилита sleep завершится с ошибкой синтаксиса, утянув за собой весь контейнер.
  • Сетевой траблшутинг: поиск потерянных пакетов

    Сетевые проблемы в Kubernetes — самые неочевидные. Pod работает, логи чистые, но сервис недоступен. Диагностику сети нужно проводить строго по пути следования трафика.

    Разрыв связи Service и Endpoints

    Объект Service сам по себе не маршрутизирует трафик в Pod'ы. Он опирается на объект Endpoints (или EndpointSlice в современных версиях), который содержит актуальный список IP-адресов целевых Pod'ов. Связь между Service и Pod'ами устанавливается исключительно через совпадение меток (Labels и Selectors).

    !Схема разрыва маршрутизации из-за несовпадения меток

    Если клиент отправляет запрос на IP-адрес сервиса и получает Connection refused или таймаут, первым делом проверьте конечные точки: kubectl get endpoints <service-name>

    Если в колонке ENDPOINTS вы видите <none>, это означает, что контроллер не нашел ни одного Pod'а, чьи метки полностью совпадают с селектором сервиса. Сравните селектор сервиса: kubectl get svc <service-name> -o yaml | grep -A 2 selector И метки Pod'а: kubectl get pods --show-labels

    Даже опечатка в одном символе (например, app: frotend в сервисе и app: frontend в Pod'е) приведет к тому, что трафик уйдет в никуда. Если же Endpoints заполнены правильными IP-адресами Pod'ов, но связи нет, проблема спускается на уровень сетевого плагина (CNI) или правил безопасности.

    Блокировки Network Policies

    Если Endpoints корректны, но два Pod'а не могут связаться друг с другом, вероятной причиной является NetworkPolicy. По умолчанию в Kubernetes разрешен любой трафик. Но как только в пространстве имен появляется политика, выбирающая определенный Pod, для него включается режим изоляции (Default Deny) для того направления трафика (Ingress или Egress), которое описано в политике.

    Для проверки сетевой связности изнутри кластера лучше всего запустить временный отладочный Pod. Использование образа busybox или alpine позволяет получить доступ к утилитам wget, nc (netcat) и ping.

    Проверка доступности порта базы данных (например, 3306) из временного Pod'а: kubectl run test-net -it --rm --image=busybox:1.28 --restart=Never -- nc -vz <db-service-ip> 3306

    Если команда зависает (таймаут), ищите политику, блокирующую трафик: kubectl get networkpolicies Затем внимательно изучите правила ingress целевого Pod'а и egress исходного. Часто забывают разрешить Egress-трафик к DNS-серверу (порт 53 UDP), из-за чего Pod не может разрешить имя сервиса в IP-адрес, что выглядит как полная потеря сети.

    Диагностика CoreDNS

    Служба разрешения имен (CoreDNS) — критический компонент кластера. Если Pod'ы могут связываться по IP-адресам, но не по DNS-именам (например, curl http://10.96.0.10 работает, а curl http://my-service выдает Could not resolve host), проблема в DNS.

    CoreDNS работает как обычный Deployment в пространстве имен kube-system. Проверьте состояние его Pod'а: kubectl get pods -n kube-system -l k8s-app=kube-dns

    Если Pod'ы в статусе Running, проверьте их логи на наличие ошибок конфигурации: kubectl logs -n kube-system -l k8s-app=kube-dns

    Для ручного тестирования DNS-запросов изнутри кластера используйте утилиту nslookup во временном Pod'е. Важный нюанс для экзамена CKA: в новых версиях образа busybox утилита nslookup содержит баги при работе с форматом ответов CoreDNS. Всегда используйте версию 1.28: kubectl run dns-test -it --rm --image=busybox:1.28 --restart=Never -- nslookup kubernetes.default

    Успешный ответ должен вернуть IP-адрес внутреннего сервиса API. Если возвращается Server can't find kubernetes.default: NXDOMAIN, проверьте конфигурацию kubelet на узлах — параметр --cluster-dns должен указывать на правильный IP-адрес сервиса kube-dns.

    Ошибки авторизации: RBAC в действии

    Часто проблема маскируется под технический сбой, хотя на самом деле является административным запретом. Если приложение внутри Pod'а (например, Ingress-контроллер или CI/CD агент) пытается обратиться к Kubernetes API и получает ошибку 403 Forbidden, проблема кроется в настройках Role-Based Access Control (RBAC).

    Каждый Pod использует ServiceAccount для общения с API. Если в манифесте Pod'а не указано поле serviceAccountName, ему назначается аккаунт default в текущем пространстве имен, который по умолчанию не имеет никаких прав.

    Для быстрой проверки прав конкретного сервисного аккаунта используйте механизм имперсонации kubectl auth can-i: kubectl auth can-i list secrets --as=system:serviceaccount:<namespace>:<service-account-name>

    Если ответ no, необходимо проверить цепочку привязок. Сначала найдите RoleBinding или ClusterRoleBinding, которые ссылаются на этот аккаунт: kubectl get rolebindings -o wide | grep <service-account-name> Затем изучите саму Role, к которой ведет привязка, чтобы убедиться, что в блоке rules указаны нужные apiGroups, resources и verbs.

    Успешное решение задач на CKA зависит от способности не паниковать при виде красного текста ошибок. Каждое сообщение в логах journalctl, каждый статус CrashLoopBackOff, каждый пустой объект Endpoints — это не тупик, а четкий указатель на конкретный слой архитектуры. Разделив систему на независимые уровни (ОС, среда выполнения, Control Plane, Pod, сеть, доступы) и последовательно исключая исправные компоненты, можно локализовать и устранить любую неисправность в отведенное время.