Вернуться к главной странице, списку всех тем
5. Запуск множества копий приложений, чтобы, если одна вышла из строя, другие продолжали работать. Отслеживание и контроль тысяч приложений
Что ты узнаешь: как запускать приложение в нескольких копиях сразу на многих серверах, переживать падение любой из них без простоя и описывать всё это текстовыми файлами
Что нужно знать заранее: тема 4 обязательно — контейнеры, образы, тома, сети; тема 2 — порты, DNS, HTTP; тема 1 — сигналы и процессы
Сколько времени займёт: 5 часов теории + 10–12 часов практики. Это самая объёмная тема курса
Твой шаг в сквозном проекте: запустишь «Заметки» в трёх копиях, убьёшь одну из них и убедишься, что пользователь ничего не заметил; потом упакуешь всё в Helm-чарт
Представь большой сад, где растут разные растения: помидоры, огурцы, клубника. Всем нужны вода, солнце и удобрения, но каждому по-своему: помидорам нужно больше воды, огурцам — меньше солнца
Теперь вместо растений — программы на компьютерах. За ними нужно следить, обновлять и восстанавливать после поломок. И условия им нужны разные: одним много памяти, другим — быстрый доступ к данным
Здесь и появляется Kubernetes — умный садовник, который следит, чтобы каждая программа получала необходимое. Он распределяет программы по разным компьютерам так, чтобы всем хватало ресурсов, и восстанавливает их после сбоев: сломался сервер — программа переезжает на другой и продолжает работать
Зачем это нужно, если есть Compose
В теме 4 мы подняли стенд одной командой. Почему этого мало?
Compose работает на одной машине. Если она выключится, выключится всё. Обновление означает остановку старого контейнера и запуск нового — с простоем. Если нагрузка выросла, нужно вручную решать, где взять ещё машину. А когда сервисов становится не три, а триста, и машин пятьдесят, никакой человек не удержит в голове, что где запущено
Kubernetes решает ровно эти задачи:
- Автоматизация развёртывания. Вместо ручной настройки каждого сервера ты описываешь желаемое состояние в YAML-файлах, а Kubernetes приводит систему к нему
- Масштабирование. Нагрузка выросла — автоматически поднимаются новые копии приложения и трафик делится между ними
- Отказоустойчивость. Упал контейнер или целый сервер — копии перезапускаются на других серверах, пользователь ничего не замечает
- Управление ресурсами. Каждому приложению задают, сколько CPU и памяти оно может занять, чтобы одно не мешало другим
- Оркестрация микросервисов. Сотни сервисов находят друг друга и общаются без ручной настройки адресов
- Мониторинг. Есть встроенные механизмы и интеграция с Prometheus и Loki — это тема 8
- CI/CD. Пайплайн из темы 3 может выкатывать новые версии сам
Главная идея: желаемое состояние
Это единственная концепция, которую нужно понять по-настоящему — всё остальное следствия
Ты не отдаёшь Kubernetes команды «запусти контейнер», «останови контейнер». Ты описываешь, как должно быть: «приложения notes должно работать три копии, каждой не больше 128 МБ памяти, версия образа 1.0». Дальше кластер непрерывно сравнивает желаемое с фактическим и устраняет разницу сам
Отсюда всё остальное поведение: убил под — кластер поднял новый, потому что желаемое состояние не изменилось; выключился сервер — копии переехали; изменил число реплик в файле — кластер сам добавил недостающие. Такой подход называется декларативным, в отличие от императивного «сделай шаг 1, шаг 2, шаг 3»
Из чего состоит кластер
Кластер — это набор серверов (нод, nodes), работающих вместе. Ноды бывают двух ролей
Управляющие ноды (control plane) — «мозг» кластера:
| Компонент | Что делает |
|---|---|
kube-apiserver |
Единственная дверь в кластер. Все команды, вся автоматика, все компоненты общаются только через него |
etcd |
База данных кластера. Здесь хранится всё желаемое состояние. Потерять etcd = потерять кластер |
kube-scheduler |
Решает, на какой ноде запустить новый под, исходя из свободных ресурсов и ограничений |
kube-controller-manager |
Набор контроллеров, каждый следит за своим типом объектов и приводит фактическое состояние к желаемому |
Рабочие ноды (worker nodes) — где реально работают приложения:
| Компонент | Что делает |
|---|---|
kubelet |
Агент, который получает задания от API-сервера и запускает контейнеры на этой ноде |
kube-proxy |
Настраивает правила сети, чтобы обращение к сервису попадало в нужные поды |
| Среда выполнения (containerd) | Собственно запускает контейнеры — то же самое, что делал Docker в теме 4 |
Почему управляющих нод делают нечётное число. etcd принимает решение большинством голосов (это называется кворум). Из 3 нод кластер переживёт потерю одной, из 5 — двух. А из 4 переживёт тоже только одну — четвёртая нода не добавляет надёжности, зато добавляет стоимость и точку отказа. Поэтому берут 1 (для учёбы), 3 или 5
Основные объекты
Pod (под) — минимальная единица, которую запускает Kubernetes. Это один или несколько контейнеров, у которых общий сетевой адрес и общее хранилище. Контейнеры внутри одного пода видят друг друга по localhost
Обычно в поде один контейнер — твоё приложение. Второй добавляют, когда он бесполезен в отрыве от первого: сборщик логов, прокси, обновлятор конфигов. Такой вспомогательный контейнер называют sidecar
Важное свойство: поды одноразовые. Под нельзя починить, его можно только заменить. У нового пода будет другой IP-адрес и другое имя. Поэтому обращаться к подам по адресу бессмысленно — для этого есть сервисы
Deployment — описание «хочу N одинаковых копий приложения». Он создаёт ReplicaSet, а тот следит за нужным количеством подов. При обновлении образа Deployment делает rolling update: поднимает новый под, дожидается его готовности, гасит один старый, и так по кругу — поэтому обновление проходит без простоя. Если что-то пошло не так, kubectl rollout undo возвращает предыдущую версию
StatefulSet — то же, но для приложений с состоянием: баз данных, очередей. Отличия принципиальные: имена подов постоянные и предсказуемые (db-0, db-1, db-2), у каждого свой персональный диск, который переезжает вместе с ним, а создаются и обновляются они строго по очереди, а не все разом
DaemonSet — «по одной копии на каждой ноде». Используется для агентов: сборщик логов, экспортёр метрик, сетевой плагин. Количество реплик у него задать нельзя — оно по определению равно числу подходящих нод
Job — запустить задачу один раз и дождаться успешного завершения (миграция базы, разовый расчёт). CronJob — то же самое по расписанию, синтаксис расписания тот же, что у cron из темы 1
Service — постоянный адрес и имя для группы подов. Поды приходят и уходят, а сервис остаётся. Он же балансирует трафик между подами. Типы:
ClusterIP— доступен только внутри кластера (по умолчанию)NodePort— открывает порт на каждой ноде, доступ снаружи поIP-ноды:портLoadBalancer— просит у облака настоящий внешний балансировщик- Headless (
clusterIP: None) — без единого адреса, DNS возвращает адреса всех подов. Нужен StatefulSet’ам, чтобы обратиться к конкретной реплике
Внутри кластера сервис доступен по имени: notes из того же пространства имён, либо полностью — notes.default.svc.cluster.local. Это тот же принцип, что у Compose в теме 4, только на масштабе кластера
Ingress — правила маршрутизации HTTP снаружи внутрь: «запросы на notes.local/ веди в сервис notes». Сам по себе это только описание; работу выполняет Ingress-контроллер — по сути тот же nginx из темы 2, запущенный в кластере и настраиваемый автоматически
ConfigMap — конфигурация в виде пар «ключ-значение», которую подключают в под переменными окружения или файлами. Secret — то же самое для паролей и токенов. Важно понимать: содержимое Secret в кластере лишь закодировано в base64, а не зашифровано. Это защита от случайного взгляда, а не от злоумышленника. Настоящее решение — Vault, тема 9
Namespace (пространство имён) — логическое разделение кластера на части. Обычно применяют для разделения окружений (dev, stage, prod), команд или проектов. К имени добавляется область видимости: сервис db в dev и db в prod — разные сервисы. На пространство имён удобно вешать квоты ресурсов и права доступа
Пробы: как кластер понимает, что приложению плохо
Три вида проверок, и путаница между ними — источник большинства проблем новичков:
| Проба | Вопрос | Что будет при провале |
|---|---|---|
startupProbe |
«Приложение уже запустилось?» | Перезапуск, но остальные пробы пока не мешают запуску |
livenessProbe |
«Приложение живо?» | Контейнер перезапускается |
readinessProbe |
«Приложение готово принимать трафик?» | Под убирают из балансировки, но не перезапускают |
Порядок такой: сначала работает startupProbe и никто не мешает медленному старту; как только она прошла, включаются liveness и readiness и работают параллельно всё время жизни пода
Классическая ошибка — сделать livenessProbe, которая ходит в базу данных. База моргнула — все копии приложения одновременно признаны мёртвыми и перезапущены, а нагрузка на базу от этого только выросла. Правило: liveness проверяет только само приложение и отвечает быстро, readiness может проверять зависимости
Наш эндпоинт /healthz из темы 1 был написан ровно под это: он ничего не читает с диска и отвечает мгновенно
Requests и limits
Для каждого контейнера задают два числа по каждому ресурсу:
- requests — сколько гарантированно нужно. По этому числу планировщик выбирает ноду: он ищет ту, где столько свободно
- limits — верхняя граница. Превышение по CPU приводит к торможению (throttling), превышение по памяти — к убийству процесса (
OOMKilled)
resources:
requests:
cpu: 100m # 100 миллиядер = 0,1 ядра
memory: 64Mi
limits:
cpu: 500m
memory: 128Mi
От соотношения этих чисел зависит QoS-класс пода, то есть кого кластер убьёт первым при нехватке памяти на ноде: Guaranteed (requests равны limits) — последним; Burstable (заданы, но не равны) — вторым; BestEffort (не заданы вовсе) — первым. Не указывать ресурсы — значит добровольно вызваться на роль жертвы
Labels и annotations
Labels (метки) — пары «ключ-значение», по которым объекты ищут друг друга. Именно так Service находит свои поды, а Deployment — свои. Метки индексируются, по ним можно фильтровать: kubectl get pods -l app=notes
Annotations (аннотации) — тоже пары «ключ-значение», но для произвольной информации, по которой не ищут: контактные данные владельца, версия сборки, настройки для контроллеров. Ingress-контроллеры традиционно настраиваются именно аннотациями
Масштабирование
- Вручную:
kubectl scale deployment notes --replicas=5 - HPA (Horizontal Pod Autoscaler) — автоматически меняет число подов по загрузке CPU, памяти или своей метрике. Требует установленного
metrics-server - VPA (Vertical Pod Autoscaler) — подбирает requests/limits вместо числа копий
- Cluster Autoscaler — добавляет и убирает сами ноды. Работает только в облаке, где ноду можно заказать по API
Горизонтальное масштабирование работает, только если приложение не хранит состояние в своей памяти. Если пользовательская сессия лежит в оперативной памяти конкретного пода, при переброске на другую копию пользователь «разлогинится»
Ingress и Gateway API
Ingress появился давно и застыл: его возможности ограничены маршрутизацией HTTP по имени хоста и пути, а всё остальное производители контроллеров реализовали через аннотации. В итоге конфигурация оказалась непереносимой между контроллерами, а разграничить права между командами невозможно — весь маршрут описан одним объектом
Gateway API — преемник Ingress, стабильная версия появилась в конце 2023 года. Что даёт:
- Разделение ролей. Администратор кластера описывает
GatewayClassиGateway(точку входа, сертификаты, порты), а команда приложения — толькоHTTPRouteдля своего сервиса. Раньше и то и другое лежало в одном объекте - Не только HTTP — есть
TCPRoute,GRPCRoute,TLSRoute - Возможности в самом стандарте, а не в аннотациях: разделение трафика по весам (для канареечных релизов из темы 9), маршрутизация по заголовкам, переписывание путей
Ingress никуда не денется в ближайшие годы, но новые проекты стоит начинать с Gateway API
Путь запроса от пользователя до контейнера
Полезно держать эту цепочку целиком — по ней и ведут диагностику:
Пользователь
│ DNS: notes.ru → внешний IP
▼
Внешний балансировщик (облако)
▼
Ingress-контроллер (nginx внутри кластера)
│ сверяется с правилами Ingress / HTTPRoute
▼
Service (ClusterIP)
│ kube-proxy выбирает один из готовых подов
▼
Pod → контейнер → твоё приложение
Когда сайт не открывается, проверяют по шагам сверху вниз: разрешается ли имя, доехал ли запрос до Ingress (его логи), знает ли Service о подах (kubectl get endpoints), готовы ли поды (kubectl get pods), что в логах приложения
Ключевая точка — endpoints. Если у сервиса пустой список endpoints, дальше искать бесполезно: либо метки в сервисе не совпадают с метками подов, либо ни один под не прошёл readiness-пробу
RBAC: кому что можно
RBAC (Role-Based Access Control) — разграничение прав. Четыре объекта:
- Role — набор разрешений внутри одного пространства имён: какие действия (
get,list,create,delete) над какими типами объектов - ClusterRole — то же самое, но на весь кластер
- RoleBinding / ClusterRoleBinding — кому выдать роль: пользователю, группе или ServiceAccount
- ServiceAccount — «учётная запись» для приложения, работающего внутри кластера
Принцип — минимально необходимые права. Разработчику дают возможность смотреть и перезапускать свои приложения в своём пространстве имён, но не читать секреты и не трогать чужие окружения
Helm: пакетный менеджер
Одно приложение в Kubernetes — это обычно 5–10 YAML-файлов. Для трёх окружений (dev, stage, prod) их придётся размножить, отличаясь тремя строчками. Так рождается копипаста, которая рано или поздно разъезжается
Helm решает это шаблонами. Основные понятия:
- Chart (чарт) — пакет: шаблоны манифестов плюс значения по умолчанию
- values.yaml — файл со значениями, которые подставляются в шаблоны
- Release (релиз) — установленный в кластер экземпляр чарта под своим именем
helm install notes ./chart -f values-prod.yaml # установить
helm upgrade notes ./chart --set replicaCount=5 # обновить
helm rollback notes 1 # откатиться на предыдущую версию
helm template notes ./chart # просто отрисовать YAML и посмотреть глазами
helm list # что установлено
helm template — самая полезная команда при отладке: она показывает, во что превратились шаблоны, ничего не устанавливая
Помимо своих чартов, Helm — это ещё и способ установить чужое приложение одной командой. В теме 8 мы так поставим весь стек мониторинга
Теоретические вопросы
-
В чём преимущества Kubernetes перед Docker Compose? Ответ: Compose работает на одной машине и не умеет ни переживать её отказ, ни обновляться без простоя. Kubernetes управляет множеством серверов, сам перезапускает и переносит упавшие приложения, обновляет их постепенно, масштабирует по нагрузке и даёт единый способ описывать всё это декларативно
-
Что такое
kubectlи какие команды основные? Ответ: Клиент для общения с API-сервером кластера. Основное:kubectl get(посмотреть список),describe(подробности и события),logs(логи),apply -f(применить манифест),delete,exec -it(зайти внутрь),scale,rollout status/undo,port-forward -
Как подключиться к кластеру? Ответ: Через файл
~/.kube/config, где описаны адрес кластера, сертификаты и пользователь. В одном файле может быть несколько контекстов, переключение —kubectl config use-context имя. Проверка —kubectl cluster-info -
Что такое node и pod простыми словами? Ответ: Node — сервер (физический или виртуальный) в составе кластера. Pod — минимальная запускаемая единица, один или несколько контейнеров с общей сетью и хранилищем, работающие на одной ноде
-
Как диагностировать под в статусе
CrashLoopBackOff? Ответ: Статус означает, что контейнер падает, а Kubernetes перезапускает его со всё большей паузой. Порядок:kubectl describe pod имя(события и причина последнего завершения), затемkubectl logs имя --previous— логи предыдущего, уже упавшего запуска. Частые причины: ошибка в коде или конфиге, нехватка памяти (OOMKilled), отсутствующий Secret или ConfigMap, неверная команда запуска -
Зачем нужны namespace? Ответ: Логически делят кластер: по окружениям (
dev,stage,prod), командам или проектам. Имена объектов уникальны внутри пространства имён, а не во всём кластере. На них удобно вешать квоты ресурсов, сетевые политики и права RBAC -
Какие компоненты входят в control plane? Ответ:
kube-apiserver(единая точка входа),etcd(хранилище состояния),kube-scheduler(выбор ноды для новых подов),kube-controller-manager(контроллеры, приводящие фактическое состояние к желаемому) -
Сколько может быть управляющих нод? Можно ли две или четыре? Ответ: Технически можно любое число, но осмысленны 1 (учебный кластер), 3 или 5.
etcdтребует кворума — большинства узлов. Из 3 переживём отказ 1, из 5 — двух. Из 4 переживём тоже только один отказ, то есть чётное число не добавляет надёжности, зато добавляет стоимость -
Какие компоненты есть на рабочей ноде? Ответ:
kubelet(запускает и следит за контейнерами по заданиям от API),kube-proxy(правила сети для сервисов), среда выполнения контейнеров (обычно containerd) -
Как запустить под на конкретной ноде и зачем это нужно? Ответ: Через
nodeSelectorпо меткам ноды, черезnodeAffinity(гибче) либо черезtolerationsк «затычкам» (taints) на ноде. Нужно, когда приложению требуется особое железо: GPU, быстрый диск, или когда по требованиям данные должны оставаться в определённой зоне -
Сколько контейнеров может быть в одном поде? Ответ: Технически сколько угодно, практически — один основной плюс при необходимости вспомогательные (sidecar). Признак того, что второй контейнер уместен: он бессмысленен без первого и должен жить и умирать вместе с ним
-
Как приложению из одного пода обратиться к приложению в другом поде? Ответ: Через Service по DNS-имени:
http://notes:8080внутри одного пространства имён илиhttp://notes.prod.svc.cluster.local:8080из другого. Обращаться напрямую по IP пода нельзя — он меняется при каждом пересоздании -
Как контейнеру обратиться к другому контейнеру в том же поде? Ответ: По
localhostи нужному порту — у контейнеров одного пода общее сетевое пространство. Следствие: два контейнера в поде не могут слушать один и тот же порт -
В чём разница между Deployment, StatefulSet и DaemonSet? Ответ: Deployment — одинаковые взаимозаменяемые копии приложения без состояния, имена случайные. StatefulSet — приложения с состоянием: постоянные имена (
db-0,db-1), персональный диск у каждой реплики, строгий порядок запуска и обновления. DaemonSet — ровно по одному поду на каждой ноде, для агентов сбора логов и метрик -
Как подключить ConfigMap или Secret к поду и зачем они нужны? Ответ: Двумя способами: как переменные окружения (
envFromилиvalueFrom) или как файлы, смонтированные в каталог (volumeMounts). Нужны, чтобы конфигурация не была вшита в образ: один и тот же образ работает вdevиprod, отличаясь только подключённой конфигурацией -
Можно ли задать число реплик у DaemonSet? Ответ: Нет, и поля такого нет. Число подов равно числу подходящих нод по определению. Ограничить набор нод можно через
nodeSelectorилиaffinity -
Как подключить диск к поду в StatefulSet? Ответ: Через
volumeClaimTemplates: для каждой реплики автоматически создаётся собственный PersistentVolumeClaim, который остаётся привязанным к ней. Подdb-0при пересоздании получит ровно свой прежний диск -
Чем Job отличается от CronJob? Ответ: Job выполняет задачу один раз до успешного завершения (миграция базы, разовая обработка данных). CronJob создаёт Job по расписанию в формате cron (
0 3 * * *— каждую ночь в три) -
Как Deployment создаёт и обновляет поды? Ответ: Он создаёт ReplicaSet, который держит нужное число подов. При изменении шаблона пода создаётся новый ReplicaSet, и трафик перетекает на него постепенно: поднять новый под → дождаться readiness → погасить старый. Параметры
maxSurgeиmaxUnavailableзадают, насколько агрессивно это делать. Старые ReplicaSet сохраняются, поэтому возможенkubectl rollout undo -
Как обновляется StatefulSet? Ответ: По одному поду, строго в обратном порядке номеров: сначала самый старший, потом предыдущий. Следующий не трогается, пока предыдущий не станет готов. Для баз данных это критично — одновременная перезагрузка всех реплик означала бы потерю кворума
-
Зачем нужны requests и limits и чем они отличаются? Ответ: requests — гарантированный минимум, по нему планировщик подбирает ноду. limits — потолок: превышение по CPU приводит к торможению, по памяти — к убийству контейнера. От их соотношения зависит QoS-класс, то есть очередь на выселение при нехватке ресурсов на ноде
-
Как обеспечить высокую доступность приложения? Ответ: Несколько реплик; разнести их по разным нодам и зонам (
podAntiAffinity,topologySpreadConstraints); настроить корректные пробы; задатьPodDisruptionBudget, чтобы при обслуживании нод одновременно не выключили все копии; обновляться постепенно; не хранить состояние в памяти пода -
Чем labels отличаются от annotations? Ответ: По labels ищут и отбирают объекты — именно так Service находит свои поды. Annotations хранят справочную информацию и настройки для инструментов, по ним нельзя фильтровать
-
Какие бывают пробы, в каком порядке срабатывают и зачем нужны? Ответ:
startupProbeзащищает медленно стартующие приложения и работает первой; после её успеха включаютсяlivenessProbe(провал — перезапуск контейнера) иreadinessProbe(провал — под убирают из балансировки, но не перезапускают). Ошибка новичка — заставить liveness проверять базу данных: моргнула база, и перезапустились все копии сразу -
Как Service распределяет трафик между подами? Ответ:
kube-proxyнастраивает на каждой ноде правила ядра (iptables или IPVS), которые распределяют соединения между адресами из списка endpoints. Попадают туда только поды, прошедшие readiness-пробу. Балансировка идёт по соединениям, а не по запросам -
В чём разница между ingress и egress? Ответ: Ingress — входящий в кластер трафик, egress — исходящий из него. В контексте NetworkPolicy это два раздела правил: кому можно к нам и куда можно нам
-
Почему Gateway API считают заменой Ingress? Ответ: Ingress заморожен в развитии, а всё сверх базовой маршрутизации приходилось делать через аннотации конкретного контроллера — конфигурация становилась непереносимой. Gateway API разделяет роли (администратор описывает точку входа, команда — маршрут своего сервиса), поддерживает не только HTTP и включает в сам стандарт разделение трафика по весам, маршрутизацию по заголовкам и переписывание путей
-
Какой путь проходит запрос от пользователя до приложения? Ответ: DNS → внешний балансировщик → Ingress-контроллер → Service → endpoints → под → контейнер. Диагностику ведут по этой цепочке сверху вниз; ключевая проверка — не пуст ли список endpoints у сервиса
-
Какие есть варианты масштабирования? Ответ: Вручную (
kubectl scale), HPA — автоматически по числу подов на основе метрик (нужен metrics-server), VPA — подбор requests/limits вместо количества, Cluster Autoscaler — добавление и удаление самих нод в облаке -
Что такое RBAC и зачем он нужен? Ответ: Разграничение прав по ролям. Role и ClusterRole описывают разрешённые действия над типами объектов, RoleBinding и ClusterRoleBinding выдают их пользователям, группам или ServiceAccount. Принцип — минимально необходимые права
-
Как разрешить разработчику управлять только Deployment и Pod в пространстве
dev, запретив секреты и другие пространства? Ответ: СоздатьRoleв пространствеdevс правилами наapiGroups: ["apps"]дляdeploymentsиapiGroups: [""]дляpods, не включаяsecrets, и связать её с разработчиком черезRoleBindingв этом же пространстве. Поскольку Role действует только в своём пространстве, доступ в остальные не появится. Проверить —kubectl auth can-i delete secrets -n dev --as разработчик -
Что такое Helm и зачем он нужен? Ответ: Пакетный менеджер для Kubernetes. Позволяет параметризовать манифесты шаблонами и ставить одно и то же приложение в разные окружения, меняя только
values.yaml, а также устанавливать чужие приложения одной командой и откатывать неудачные обновления -
Почему Secret нельзя считать безопасным хранилищем? Ответ: Его содержимое в кластере лишь закодировано base64 и по умолчанию не шифруется в etcd. Любой, у кого есть право читать секреты в пространстве имён, видит пароль открытым текстом. Настоящее решение — внешнее хранилище вроде Vault, тема 9
Вопросы с собеседований
Самая большая тема курса — и самый длинный список вопросов на собеседовании
-
Что такое etcd и какого типа эта база? Ответ: Распределённое хранилище пар «ключ-значение», где лежит всё состояние кластера. Согласованность обеспечивается алгоритмом Raft: узлы выбирают лидера, запись считается принятой, когда её подтвердило большинство. Именно поэтому число узлов делают нечётным, а резервная копия etcd — это резервная копия всего кластера
-
Что такое CNI и как каждый под получает свой IP? Ответ: CNI (Container Network Interface) — стандарт подключения контейнеров к сети; конкретную работу делает плагин: Calico, Cilium, Flannel. Кластеру выделяют общий диапазон адресов для подов, из него каждой ноде достаётся кусок, а плагин раздаёт адреса подам на своей ноде. Базовое требование модели сети Kubernetes: любой под может обратиться к любому другому напрямую по его адресу, без преобразования адресов
-
Как обратиться к поду в другом пространстве имён? Ответ: По полному DNS-имени сервиса:
имя-сервиса.пространство.svc.cluster.local, короткая формаимя-сервиса.пространствотоже работает. Внутри своего пространства достаточно просто имени сервиса -
Является ли Service DNS-именем? Ответ: Сам сервис — объект с виртуальным адресом, но при его создании внутренний DNS кластера (CoreDNS) автоматически заводит для него запись. Поэтому обращаться по имени можно всегда, и это единственный правильный способ: адреса подов меняются, имя сервиса — нет
-
Чем headless-сервис отличается от ClusterIP? Ответ: У ClusterIP есть единый виртуальный адрес, и трафик распределяется между подами. У headless (
clusterIP: None) общего адреса нет: DNS-запрос возвращает адреса всех подов сразу, и клиент сам решает, с каким работать. Нужен StatefulSet’ам — чтобы обратиться именно кdb-0, а не к случайной реплике -
Какие Ingress-контроллеры ты знаешь и как подключить к Ingress сертификат? Ответ: ingress-nginx (самый распространённый), Traefik, HAProxy, Kong, шлюз Istio, а также встроенные в облака. Сертификат подключают через секрет типа
tlsи секциюspec.tlsс указанием имени хоста и секрета. Вручную это делают редко: cert-manager выпускает и продлевает сертификаты Let’s Encrypt автоматически -
Что такое lifecycle-хуки? Ответ: Две точки, куда можно вклиниться:
postStart— сразу после запуска контейнера,preStop— перед его остановкой, до отправкиSIGTERM. Используются для прогрева, регистрации в сторонних системах и корректного завершения -
Зачем в
preStopпишутsleep 10? Ответ: При удалении пода два процесса идут параллельно: под убирают из списка endpoints и одновременно посылают емуSIGTERM. Правила сети на нодах обновляются не мгновенно, поэтому какое-то время трафик ещё летит в уже завершающийся под — пользователь получает ошибку. Пауза вpreStopдаёт балансировке успеть перестроиться, пока приложение ещё обслуживает запросы. Это самая частая причина ошибок 502 во время выкладки -
HPA при расчёте использует requests или limits? Ответ: Requests. Загрузка считается как доля потребления от запрошенного: под с
requests: 100m, потребляющий80m, даёт 80%. Отсюда следствие: если requests не заданы, HPA работать не может — не от чего считать процент -
Учитывает ли планировщик limits при выборе ноды? Ответ: Нет, только requests. Планировщик суммирует requests уже размещённых подов и ищет ноду, где хватает свободного места. Limits — это ограничение во время работы, а не при размещении. Поэтому сумма limits на ноде может превышать её ресурсы, и при одновременном всплеске начнётся вытеснение подов
-
Как ограничить права пользователей в кластере? Ответ: Через RBAC:
RoleилиClusterRoleописывают разрешённые действия,RoleBindingилиClusterRoleBindingвыдают их субъекту. Дополнительно ограничивают квотами (ResourceQuota,LimitRange), сетевыми политиками и политиками допуска подов. Проверить права —kubectl auth can-i -
Из каких файлов состоит Helm-чарт? Ответ:
Chart.yaml(описание чарта),values.yaml(значения по умолчанию), каталогtemplates/(шаблоны манифестов),charts/(чарты-зависимости),templates/_helpers.tpl(вспомогательные шаблоны),templates/NOTES.txt(текст, который печатается после установки),.helmignore -
Что находится в
Chart.yamlиvalues.yaml? Ответ: ВChart.yaml— метаданные:name,version(версия самого чарта),appVersion(версия приложения — это разные вещи),description,dependencies. Вvalues.yaml— значения по умолчанию, которые подставляются в шаблоны и переопределяются через-fили--set -
Зачем в
templates/создают_helpers.tpl? Ответ: Файлы, начинающиеся с подчёркивания, не превращаются в манифесты. В_helpers.tplдержат именованные шаблоны — например, вычисление полного имени релиза и стандартный набор меток, которые потом подключают черезincludeво всех манифестах. Смысл тот же, что у функции в коде: не повторять одно и то же -
Как в шаблоне сделать цикл? Ответ: Через
range:ports: {{- range .Values.service.ports }} - port: {{ .port }} name: {{ .name }} {{- end }}Внутри цикла
.указывает на текущий элемент; чтобы добраться до глобальных значений, используют$.Values -
Как сделать условие для булева значения и для строки? Ответ: Для булева —
{{ if .Values.ingress.enabled }} ... {{ end }}, для строки — сравнение:{{ if eq .Values.env "prod" }}. Подвох: шаблонизатор считает ложью не толькоfalse, но и пустую строку, ноль и пустой список. Поэтому для «значение вообще задано?» надёжнее{{ if hasKey .Values "replicaCount" }} -
Что вернёт
{{ divf .Values.replicaCount .Values.zones | ceil }}? Ответ: Деление с плавающей точкой с округлением вверх. При 5 репликах и 3 зонах получится 2. Такая формула нужна, чтобы посчитать, сколько подов максимум допустимо в одной зоне при равномерном распределении: это значение подставляют вtopologySpreadConstraints. Если разложить 5 подов по 3 зонам, идеально ровно не выйдет, и в одной зоне окажется 2 — именно это число и вычисляется -
Как разместить поды по разным нодам и зонам? Ответ: Через
topologySpreadConstraintsс ключамиkubernetes.io/hostname(разные ноды) иtopology.kubernetes.io/zone(разные зоны), либо черезpodAntiAffinity. Без этого планировщик вправе разместить все реплики на одной ноде, и её отказ уронит сервис целиком — то есть три реплики будут только видимостью надёжности
Эксплуатация и отладка
-
Что такое init-контейнеры? Ответ: Контейнеры, которые выполняются до основных, по очереди и до успешного завершения. Используют для подготовки: дождаться готовности базы, применить миграции, скачать конфигурацию. Если init-контейнер падает, под перезапускается и до основного приложения дело не доходит
-
Что такое taints и tolerations? Ответ: Taint — «отметка» на ноде, отталкивающая поды: обычные поды туда не попадут. Toleration — разрешение в описании пода игнорировать конкретную отметку. Так выделяют ноды под особые задачи: с GPU, для системных компонентов. Не путать с
nodeSelector: тот притягивает под к ноде, а taint отталкивает всех, кроме допущенных -
Что такое PodDisruptionBudget и зачем он нужен? Ответ: Правило «в любой момент должно оставаться доступным не меньше N подов». Оно защищает при добровольных нарушениях работы: обслуживании нод, обновлении кластера, работе автоскейлера — команда
kubectl drainне выселит поды, если это нарушит бюджет. От падения ноды он не спасает, для этого нужно распределение по зонам -
Что происходит, когда нода перестаёт отвечать? Ответ: Через некоторое время нода помечается как
NotReady, а её поды — на выселение. Поды под управлением Deployment пересоздаются на других нодах. Важная деталь: между отказом и пересозданием проходят минуты, а не секунды — Kubernetes не может быстро отличить упавшую ноду от кратковременной потери связи. Поэтому реплики держат на разных нодах заранее -
Что такое PV, PVC и StorageClass? Ответ: PersistentVolume — сам кусок хранилища. PersistentVolumeClaim — заявка приложения: «нужно 10 ГБ с такими свойствами». StorageClass — описание типа хранилища, позволяющее создавать тома автоматически по заявке. Приложение работает только с заявкой и не знает, какой диск за ней стоит
-
Что означают режимы доступа RWO, ROX и RWX? Ответ:
ReadWriteOnce— том монтируется на запись только на одной ноде (большинство блочных дисков),ReadOnlyMany— на чтение многим,ReadWriteMany— на запись многим, требует сетевой файловой системы вроде NFS или CephFS. Отсюда практическое ограничение: обычный диск нельзя подключить к нескольким репликам сразу на запись -
Что такое ResourceQuota и LimitRange? Ответ:
ResourceQuotaограничивает суммарное потребление пространства имён: не больше стольких ядер, памяти и объектов.LimitRangeзадаёт границы для отдельного контейнера и значения по умолчанию, если разработчик их не указал. Вместе они не дают одной команде занять весь кластер -
Под в состоянии
Pending. Что проверять? Ответ:kubectl describe podи события в конце вывода. Частые причины: ни на одной ноде не хватает ресурсов под указанныеrequests; не находится подходящая нода из-заnodeSelector, taint или правил распределения; не создаётся том по заявке; исчерпана квота пространства имён.Pending— это всегда «планировщик не смог разместить», а не «приложение сломалось» -
Чем
kubectl applyотличается отcreateиreplace? Ответ:createсоздаёт объект и падает, если он уже есть.replaceзаменяет целиком.applyвносит изменения декларативно, сохраняя поля, которыми управляют другие (например, число реплик, выставленное HPA). В работе используютapply, и лучше — из файлов в git, а не из командной строки -
Зачем в манифесте
imagePullPolicyи как он работает по умолчанию? Ответ: Определяет, скачивать ли образ заново.Always— каждый раз,IfNotPresent— только если нет локально,Never— никогда. По умолчаниюAlwaysдля тегаlatestиIfNotPresentдля любого другого. Отсюда классическая ловушка: пересобрал образ с тем же тегом, а кластер продолжает использовать старый -
Как приложение внутри пода обращается к API Kubernetes? Ответ: Через ServiceAccount: в под монтируется токен, который приложение предъявляет API-серверу; права определяются RBAC. Именно этот механизм используется в теме 9, когда под доказывает свою личность Vault. Отключить монтирование токена, если он не нужен, — хорошая практика:
automountServiceAccountToken: false -
Что такое NetworkPolicy? Ответ: Правила сетевого доступа между подами. По умолчанию в Kubernetes любой под может обратиться к любому — плоская и полностью открытая сеть. NetworkPolicy позволяет разрешить только нужное: например, к базе ходит лишь приложение, а не все подряд. Работает не везде: нужен сетевой плагин с поддержкой (Calico, Cilium), иначе манифест применится и не сделает ничего
-
Как посмотреть, что происходило в кластере за последний час? Ответ:
kubectl get events --sort-by=.lastTimestamp -A. События хранятся около часа, поэтому для настоящего разбора инцидентов их отправляют в систему хранения логов. Прицельно по объекту — нижняя часть выводаkubectl describe -
Чем
kubectl port-forwardотличается отNodePortиLoadBalancer? Ответ:port-forward— временный туннель с твоей машины через API-сервер, только для отладки и только пока запущена команда.NodePortоткрывает порт на всех нодах постоянно.LoadBalancerзаказывает у облака внешний балансировщик. Для отладки достаточно первого, и это безопаснее, чем открывать сервис наружу
Практическая часть
Кластер понадобится локальный. Подойдёт minikube либо kind:
# Вариант 1: minikube
curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
sudo install minikube-linux-amd64 /usr/local/bin/minikube
minikube start --cpus=2 --memory=4096
minikube addons enable ingress
minikube addons enable metrics-server
# kubectl
curl -LO "https://dl.k8s.io/release/$(curl -Ls https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
sudo install kubectl /usr/local/bin/kubectl
kubectl cluster-info
kubectl get nodes
Задание 1. Освоиться в кластере
Цель: научиться смотреть, что происходит
Шаги:
kubectl get nodes -o wide # ноды кластера
kubectl get pods -A # все поды во всех пространствах имён
kubectl get namespaces
kubectl -n kube-system get pods # системные компоненты кластера
kubectl describe node minikube | head -40 # ресурсы ноды и что на ней запущено
kubectl api-resources | head -30 # какие вообще бывают типы объектов
Ответь письменно: какие поды работают в kube-system и за что отвечает каждый? Найди среди них компоненты control plane из теории
Задание 2. Сквозной проект: «Заметки» в кластере
Цель: запустить приложение из темы 4 в трёх копиях и убедиться, что падение копии не влияет на пользователя
Шаги:
-
Сделай образ доступным кластеру. В minikube проще всего собрать его прямо внутри:
cd ~/notes eval $(minikube docker-env) # перенаправить docker-команды в кластер docker build -t notes:1.0 . docker images | grep notes -
Создай
k8s/deployment.yaml:apiVersion: apps/v1 kind: Deployment metadata: name: notes labels: app: notes spec: replicas: 3 # три копии приложения selector: matchLabels: app: notes # по этой метке Deployment находит свои поды template: metadata: labels: app: notes # метка, которую получат поды — должна совпадать с selector spec: containers: - name: notes image: notes:1.0 imagePullPolicy: IfNotPresent # не лезть в интернет за локально собранным образом ports: - containerPort: 8080 env: - name: PORT value: "8080" - name: NOTES_FILE value: /data/notes.txt # Сколько ресурсов нужно и сколько максимум можно resources: requests: cpu: 50m memory: 64Mi limits: cpu: 200m memory: 128Mi # Приложение живо? Не отвечает — перезапустить контейнер livenessProbe: httpGet: path: /healthz port: 8080 initialDelaySeconds: 5 periodSeconds: 10 # Готово принимать трафик? Не готово — убрать из балансировки readinessProbe: httpGet: path: /healthz port: 8080 initialDelaySeconds: 2 periodSeconds: 5 volumeMounts: - name: data mountPath: /data volumes: - name: data emptyDir: {} # временный каталог; переживёт перезапуск контейнера, но не пода -
Создай
k8s/service.yaml:apiVersion: v1 kind: Service metadata: name: notes spec: type: ClusterIP selector: app: notes # трафик пойдёт в поды с этой меткой ports: - port: 8080 # порт самого сервиса targetPort: 8080 # порт внутри пода -
Применяй и смотри:
kubectl apply -f k8s/ kubectl get pods -l app=notes -w # -w = следить в реальном времени, выход Ctrl+C kubectl get endpoints notes # адреса подов, в которые пойдёт трафик kubectl describe deployment notesОжидаемый вывод: три пода в состоянии
Runningи1/1 READY; в endpoints три адреса -
Проверь доступ, не выходя из кластера:
kubectl port-forward svc/notes 8080:8080 & curl http://localhost:8080/healthz kill %1 -
Главный эксперимент — убей под:
kubectl get pods -l app=notes kubectl delete pod <имя-любого-пода> kubectl get pods -l app=notes # новый под уже создаётсяТы описал желаемое состояние «три копии». Kubernetes обнаружил, что копий стало две, и немедленно создал третью. Никто не отдавал команду «запусти под»
-
Масштабируй:
kubectl scale deployment notes --replicas=5 kubectl get pods -l app=notes kubectl scale deployment notes --replicas=3
Типичные ошибки:
ImagePullBackOff— кластер не нашёл образ. В minikube нуженeval $(minikube docker-env)перед сборкой иimagePullPolicy: IfNotPresent- Поды
Running, но0/1 READY— не проходит readiness-проба. Смотриkubectl describe pod— в событиях будет причина - Пустой список endpoints — метки в
selectorсервиса не совпадают с метками подов. Это ошибка номер один у всех начинающих
Задание 3. Конфигурация через ConfigMap и Secret
Цель: вынести настройки из образа
Шаги:
kubectl create configmap notes-config --from-literal=WELCOME="Привет из кластера"
kubectl create secret generic notes-secret --from-literal=DB_PASSWORD=super_secret
kubectl get configmap notes-config -o yaml
kubectl get secret notes-secret -o yaml
Посмотри на вывод последней команды и раскодируй значение:
kubectl get secret notes-secret -o jsonpath='{.data.DB_PASSWORD}' | base64 -d; echo
Ожидаемый вывод: пароль открытым текстом. Убедись сам: Secret — это не шифрование, а всего лишь кодирование
Подключи их к Deployment, добавив в описание контейнера:
envFrom:
- configMapRef:
name: notes-config
- secretRef:
name: notes-secret
kubectl apply -f k8s/deployment.yaml
kubectl exec deploy/notes -- env | grep -E 'WELCOME|DB_PASSWORD'
Обрати внимание: после apply поды пересоздались — изменение шаблона пода запускает новый rolling update
Задание 4. Ingress: доступ снаружи
Цель: пустить внешний трафик в приложение
Шаги: создай k8s/ingress.yaml:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: notes
spec:
ingressClassName: nginx
rules:
- host: notes.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: notes
port:
number: 8080
kubectl apply -f k8s/ingress.yaml
kubectl get ingress
minikube ip # узнать адрес кластера
echo "$(minikube ip) notes.local" | sudo tee -a /etc/hosts
curl http://notes.local/
Это тот же приём с /etc/hosts, что и в теме 2, — только теперь за именем стоит целый кластер
Типичные ошибки:
- 404 от Ingress-контроллера — не совпадает
hostили не включён аддон:minikube addons enable ingress - 503 — Service не находит поды, снова смотри endpoints
Задание 5. Обновление без простоя и откат
Цель: увидеть rolling update своими глазами
Шаги:
-
Измени что-нибудь заметное в
app.py(например, добавь строку в ответ), собери новую версию:eval $(minikube docker-env) docker build -t notes:2.0 . -
В отдельном терминале запусти непрерывную проверку доступности:
while true; do curl -s -o /dev/null -w "%{http_code} " http://notes.local/; sleep 0.3; done -
В основном терминале обнови образ и следи за процессом:
kubectl set image deployment/notes notes=notes:2.0 kubectl rollout status deployment/notes kubectl get pods -l app=notes
Ожидаемый вывод: в первом терминале всё время идут коды 200 — ни одного 502 или 503. Именно ради этого нужны readiness-пробы: старый под гасят только после того, как новый начал отвечать
-
Откатись:
kubectl rollout history deployment/notes kubectl rollout undo deployment/notes kubectl rollout status deployment/notes -
Сломай специально. Выкати заведомо несуществующий образ и посмотри, как поведёт себя кластер:
kubectl set image deployment/notes notes=notes:99.99 kubectl get pods -l app=notes # новый под в ImagePullBackOff curl http://notes.local/ # сайт ПРОДОЛЖАЕТ работать kubectl rollout undo deployment/notesСтарые поды не гасятся, пока новые не станут готовы, поэтому неудачное обновление не роняет сервис. Это ключевое преимущество перед подходом «остановил старое, запустил новое»
Задание 6. Автомасштабирование
Цель: увидеть, как кластер сам добавляет копии под нагрузкой
Шаги:
kubectl autoscale deployment notes --cpu-percent=50 --min=2 --max=10
kubectl get hpa -w # в отдельном терминале
Создай нагрузку:
kubectl run -it --rm load-generator --image=busybox:1.36 --restart=Never -- \
sh -c "while true; do wget -q -O- http://notes:8080/ >/dev/null; done"
Ожидаемый вывод: через 1–2 минуты в выводе kubectl get hpa растёт загрузка CPU, а число реплик увеличивается. После остановки нагрузки (Ctrl+C) реплики уменьшатся обратно, но не сразу — HPA намеренно снижает их медленно, чтобы не «дёргать» приложение
Типичные ошибки:
- В колонке
TARGETSстоит<unknown>— не установлен metrics-server (minikube addons enable metrics-server) либо у контейнера не заданыrequestsпо CPU: не от чего считать процент - В старых руководствах встречается флаг
--generator=run-pod/v1— он удалён из kubectl в версии 1.18 и вызовет ошибку
Задание 7. Диагностика: сломай и почини
Цель: отработать поиск причины — это половина работы инженера
Шаги:
-
Сломай readiness-пробу — укажи несуществующий путь:
kubectl patch deployment notes --type=json \ -p='[{"op":"replace","path":"/spec/template/spec/containers/0/readinessProbe/httpGet/path","value":"/nonexistent"}]' kubectl get pods -l app=notes # поды Running, но 0/1 READY kubectl get endpoints notes # список опустел curl -i http://notes.local/ # 503: трафику некуда идти kubectl describe pod <имя> | tail -20 # в событиях: Readiness probe failedОбрати внимание: поды не перезапускаются. Так и должно быть — за перезапуск отвечает liveness, а readiness лишь убирает под из балансировки
-
Верни как было:
kubectl rollout undo deployment/notes -
Устрой
OOMKilled— поставь заведомо маленький лимит памяти:kubectl set resources deployment/notes --limits=memory=8Mi kubectl get pods -l app=notes kubectl describe pod <имя> | grep -A3 "Last State" kubectl rollout undo deployment/notes
Ожидаемый вывод: в Last State будет Terminated с причиной OOMKilled. Так выглядит нехватка памяти в реальном инциденте
Команды диагностики, которые нужно знать наизусть:
kubectl get pods -o wide # где какой под работает
kubectl describe pod имя # события — самое ценное в конце вывода
kubectl logs имя # логи приложения
kubectl logs имя --previous # логи упавшего запуска
kubectl logs -l app=notes --tail=50 # логи всех подов приложения сразу
kubectl exec -it имя -- sh # зайти внутрь
kubectl get events --sort-by=.lastTimestamp # что вообще происходило в кластере
kubectl top pods # потребление ресурсов
Задание 8. Упаковать «Заметки» в Helm-чарт
Цель: превратить набор YAML в параметризуемый пакет
Шаги:
-
Установи Helm и создай заготовку:
curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash cd ~/notes helm create chart rm -rf chart/templates/tests chart/templates/hpa.yaml chart/templates/ingress.yaml -
Замени
chart/values.yamlна свой:replicaCount: 3 image: repository: notes tag: "1.0" pullPolicy: IfNotPresent service: port: 8080 resources: requests: cpu: 50m memory: 64Mi limits: cpu: 200m memory: 128Mi -
Создай
chart/templates/deployment.yaml, заменив жёстко прописанные значения на подстановки:apiVersion: apps/v1 kind: Deployment metadata: name: {{ .Release.Name }} spec: replicas: {{ .Values.replicaCount }} selector: matchLabels: app: {{ .Release.Name }} template: metadata: labels: app: {{ .Release.Name }} spec: containers: - name: notes image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - containerPort: {{ .Values.service.port }} resources: {{- toYaml .Values.resources | nindent 14 }} livenessProbe: httpGet: path: /healthz port: {{ .Values.service.port }} readinessProbe: httpGet: path: /healthz port: {{ .Values.service.port }} -
Посмотри, во что превращаются шаблоны, не устанавливая ничего:
helm template notes ./chart helm template notes ./chart --set replicaCount=7 | grep replicas -
Установи и обнови:
kubectl delete -f k8s/deployment.yaml # убрать вариант, поставленный вручную helm install notes ./chart helm list kubectl get pods -l app=notes helm upgrade notes ./chart --set replicaCount=5 helm history notes helm rollback notes 1 -
Закоммить всё в репозиторий:
git add k8s/ chart/ git commit -m "Запустить «Заметки» в Kubernetes и упаковать в Helm-чарт" git push
Ожидаемый вывод: helm template показывает готовый YAML с подставленными значениями; --set replicaCount=7 меняет только одну строку в результате; helm rollback возвращает предыдущее состояние
Типичные ошибки:
error converting YAML to JSON— почти всегда сбитый отступ уnindent. Отлаживай черезhelm template, а не через установку- Забыть
{{- ... }}с дефисом там, где нужно убрать лишний перенос строки — YAML разъедется
Проверь себя: тема освоена, если ты можешь
- Объяснить, что такое «желаемое состояние» и почему убитый под возвращается сам
- Перечислить компоненты control plane и сказать, за что отвечает каждый
- Объяснить разницу между liveness и readiness и привести пример, когда неверная liveness роняет весь сервис
- Найти причину
CrashLoopBackOffиImagePullBackOffза три команды - Объяснить, почему у сервиса может быть пустой список endpoints, и назвать две причины
- Провести обновление без простоя и откатить его
- Объяснить, чем StatefulSet отличается от Deployment и когда нужен именно он
- Написать простой Helm-чарт и объяснить, зачем он нужен вместо голых YAML