Skip to the content.

Вернуться к главной странице, списку всех тем

5. Запуск множества копий приложений, чтобы, если одна вышла из строя, другие продолжали работать. Отслеживание и контроль тысяч приложений

Что ты узнаешь: как запускать приложение в нескольких копиях сразу на многих серверах, переживать падение любой из них без простоя и описывать всё это текстовыми файлами

Что нужно знать заранее: тема 4 обязательно — контейнеры, образы, тома, сети; тема 2 — порты, DNS, HTTP; тема 1 — сигналы и процессы

Сколько времени займёт: 5 часов теории + 10–12 часов практики. Это самая объёмная тема курса

Твой шаг в сквозном проекте: запустишь «Заметки» в трёх копиях, убьёшь одну из них и убедишься, что пользователь ничего не заметил; потом упакуешь всё в Helm-чарт


Представь большой сад, где растут разные растения: помидоры, огурцы, клубника. Всем нужны вода, солнце и удобрения, но каждому по-своему: помидорам нужно больше воды, огурцам — меньше солнца

Теперь вместо растений — программы на компьютерах. За ними нужно следить, обновлять и восстанавливать после поломок. И условия им нужны разные: одним много памяти, другим — быстрый доступ к данным

Здесь и появляется Kubernetes — умный садовник, который следит, чтобы каждая программа получала необходимое. Он распределяет программы по разным компьютерам так, чтобы всем хватало ресурсов, и восстанавливает их после сбоев: сломался сервер — программа переезжает на другой и продолжает работать

Зачем это нужно, если есть Compose

В теме 4 мы подняли стенд одной командой. Почему этого мало?

Compose работает на одной машине. Если она выключится, выключится всё. Обновление означает остановку старого контейнера и запуск нового — с простоем. Если нагрузка выросла, нужно вручную решать, где взять ещё машину. А когда сервисов становится не три, а триста, и машин пятьдесят, никакой человек не удержит в голове, что где запущено

Kubernetes решает ровно эти задачи:

  1. Автоматизация развёртывания. Вместо ручной настройки каждого сервера ты описываешь желаемое состояние в YAML-файлах, а Kubernetes приводит систему к нему
  2. Масштабирование. Нагрузка выросла — автоматически поднимаются новые копии приложения и трафик делится между ними
  3. Отказоустойчивость. Упал контейнер или целый сервер — копии перезапускаются на других серверах, пользователь ничего не замечает
  4. Управление ресурсами. Каждому приложению задают, сколько CPU и памяти оно может занять, чтобы одно не мешало другим
  5. Оркестрация микросервисов. Сотни сервисов находят друг друга и общаются без ручной настройки адресов
  6. Мониторинг. Есть встроенные механизмы и интеграция с Prometheus и Loki — это тема 8
  7. 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 — постоянный адрес и имя для группы подов. Поды приходят и уходят, а сервис остаётся. Он же балансирует трафик между подами. Типы:

Внутри кластера сервис доступен по имени: 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

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

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-контроллеры традиционно настраиваются именно аннотациями

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

Горизонтальное масштабирование работает, только если приложение не хранит состояние в своей памяти. Если пользовательская сессия лежит в оперативной памяти конкретного пода, при переброске на другую копию пользователь «разлогинится»

Ingress и Gateway API

Ingress появился давно и застыл: его возможности ограничены маршрутизацией HTTP по имени хоста и пути, а всё остальное производители контроллеров реализовали через аннотации. В итоге конфигурация оказалась непереносимой между контроллерами, а разграничить права между командами невозможно — весь маршрут описан одним объектом

Gateway API — преемник Ingress, стабильная версия появилась в конце 2023 года. Что даёт:

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) — разграничение прав. Четыре объекта:

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

Helm: пакетный менеджер

Одно приложение в Kubernetes — это обычно 5–10 YAML-файлов. Для трёх окружений (dev, stage, prod) их придётся размножить, отличаясь тремя строчками. Так рождается копипаста, которая рано или поздно разъезжается

Helm решает это шаблонами. Основные понятия:

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 мы так поставим весь стек мониторинга

Теоретические вопросы

  1. В чём преимущества Kubernetes перед Docker Compose? Ответ: Compose работает на одной машине и не умеет ни переживать её отказ, ни обновляться без простоя. Kubernetes управляет множеством серверов, сам перезапускает и переносит упавшие приложения, обновляет их постепенно, масштабирует по нагрузке и даёт единый способ описывать всё это декларативно

  2. Что такое kubectl и какие команды основные? Ответ: Клиент для общения с API-сервером кластера. Основное: kubectl get (посмотреть список), describe (подробности и события), logs (логи), apply -f (применить манифест), delete, exec -it (зайти внутрь), scale, rollout status/undo, port-forward

  3. Как подключиться к кластеру? Ответ: Через файл ~/.kube/config, где описаны адрес кластера, сертификаты и пользователь. В одном файле может быть несколько контекстов, переключение — kubectl config use-context имя. Проверка — kubectl cluster-info

  4. Что такое node и pod простыми словами? Ответ: Node — сервер (физический или виртуальный) в составе кластера. Pod — минимальная запускаемая единица, один или несколько контейнеров с общей сетью и хранилищем, работающие на одной ноде

  5. Как диагностировать под в статусе CrashLoopBackOff? Ответ: Статус означает, что контейнер падает, а Kubernetes перезапускает его со всё большей паузой. Порядок: kubectl describe pod имя (события и причина последнего завершения), затем kubectl logs имя --previous — логи предыдущего, уже упавшего запуска. Частые причины: ошибка в коде или конфиге, нехватка памяти (OOMKilled), отсутствующий Secret или ConfigMap, неверная команда запуска

  6. Зачем нужны namespace? Ответ: Логически делят кластер: по окружениям (dev, stage, prod), командам или проектам. Имена объектов уникальны внутри пространства имён, а не во всём кластере. На них удобно вешать квоты ресурсов, сетевые политики и права RBAC

  7. Какие компоненты входят в control plane? Ответ: kube-apiserver (единая точка входа), etcd (хранилище состояния), kube-scheduler (выбор ноды для новых подов), kube-controller-manager (контроллеры, приводящие фактическое состояние к желаемому)

  8. Сколько может быть управляющих нод? Можно ли две или четыре? Ответ: Технически можно любое число, но осмысленны 1 (учебный кластер), 3 или 5. etcd требует кворума — большинства узлов. Из 3 переживём отказ 1, из 5 — двух. Из 4 переживём тоже только один отказ, то есть чётное число не добавляет надёжности, зато добавляет стоимость

  9. Какие компоненты есть на рабочей ноде? Ответ: kubelet (запускает и следит за контейнерами по заданиям от API), kube-proxy (правила сети для сервисов), среда выполнения контейнеров (обычно containerd)

  10. Как запустить под на конкретной ноде и зачем это нужно? Ответ: Через nodeSelector по меткам ноды, через nodeAffinity (гибче) либо через tolerations к «затычкам» (taints) на ноде. Нужно, когда приложению требуется особое железо: GPU, быстрый диск, или когда по требованиям данные должны оставаться в определённой зоне

  11. Сколько контейнеров может быть в одном поде? Ответ: Технически сколько угодно, практически — один основной плюс при необходимости вспомогательные (sidecar). Признак того, что второй контейнер уместен: он бессмысленен без первого и должен жить и умирать вместе с ним

  12. Как приложению из одного пода обратиться к приложению в другом поде? Ответ: Через Service по DNS-имени: http://notes:8080 внутри одного пространства имён или http://notes.prod.svc.cluster.local:8080 из другого. Обращаться напрямую по IP пода нельзя — он меняется при каждом пересоздании

  13. Как контейнеру обратиться к другому контейнеру в том же поде? Ответ: По localhost и нужному порту — у контейнеров одного пода общее сетевое пространство. Следствие: два контейнера в поде не могут слушать один и тот же порт

  14. В чём разница между Deployment, StatefulSet и DaemonSet? Ответ: Deployment — одинаковые взаимозаменяемые копии приложения без состояния, имена случайные. StatefulSet — приложения с состоянием: постоянные имена (db-0, db-1), персональный диск у каждой реплики, строгий порядок запуска и обновления. DaemonSet — ровно по одному поду на каждой ноде, для агентов сбора логов и метрик

  15. Как подключить ConfigMap или Secret к поду и зачем они нужны? Ответ: Двумя способами: как переменные окружения (envFrom или valueFrom) или как файлы, смонтированные в каталог (volumeMounts). Нужны, чтобы конфигурация не была вшита в образ: один и тот же образ работает в dev и prod, отличаясь только подключённой конфигурацией

  16. Можно ли задать число реплик у DaemonSet? Ответ: Нет, и поля такого нет. Число подов равно числу подходящих нод по определению. Ограничить набор нод можно через nodeSelector или affinity

  17. Как подключить диск к поду в StatefulSet? Ответ: Через volumeClaimTemplates: для каждой реплики автоматически создаётся собственный PersistentVolumeClaim, который остаётся привязанным к ней. Под db-0 при пересоздании получит ровно свой прежний диск

  18. Чем Job отличается от CronJob? Ответ: Job выполняет задачу один раз до успешного завершения (миграция базы, разовая обработка данных). CronJob создаёт Job по расписанию в формате cron (0 3 * * * — каждую ночь в три)

  19. Как Deployment создаёт и обновляет поды? Ответ: Он создаёт ReplicaSet, который держит нужное число подов. При изменении шаблона пода создаётся новый ReplicaSet, и трафик перетекает на него постепенно: поднять новый под → дождаться readiness → погасить старый. Параметры maxSurge и maxUnavailable задают, насколько агрессивно это делать. Старые ReplicaSet сохраняются, поэтому возможен kubectl rollout undo

  20. Как обновляется StatefulSet? Ответ: По одному поду, строго в обратном порядке номеров: сначала самый старший, потом предыдущий. Следующий не трогается, пока предыдущий не станет готов. Для баз данных это критично — одновременная перезагрузка всех реплик означала бы потерю кворума

  21. Зачем нужны requests и limits и чем они отличаются? Ответ: requests — гарантированный минимум, по нему планировщик подбирает ноду. limits — потолок: превышение по CPU приводит к торможению, по памяти — к убийству контейнера. От их соотношения зависит QoS-класс, то есть очередь на выселение при нехватке ресурсов на ноде

  22. Как обеспечить высокую доступность приложения? Ответ: Несколько реплик; разнести их по разным нодам и зонам (podAntiAffinity, topologySpreadConstraints); настроить корректные пробы; задать PodDisruptionBudget, чтобы при обслуживании нод одновременно не выключили все копии; обновляться постепенно; не хранить состояние в памяти пода

  23. Чем labels отличаются от annotations? Ответ: По labels ищут и отбирают объекты — именно так Service находит свои поды. Annotations хранят справочную информацию и настройки для инструментов, по ним нельзя фильтровать

  24. Какие бывают пробы, в каком порядке срабатывают и зачем нужны? Ответ: startupProbe защищает медленно стартующие приложения и работает первой; после её успеха включаются livenessProbe (провал — перезапуск контейнера) и readinessProbe (провал — под убирают из балансировки, но не перезапускают). Ошибка новичка — заставить liveness проверять базу данных: моргнула база, и перезапустились все копии сразу

  25. Как Service распределяет трафик между подами? Ответ: kube-proxy настраивает на каждой ноде правила ядра (iptables или IPVS), которые распределяют соединения между адресами из списка endpoints. Попадают туда только поды, прошедшие readiness-пробу. Балансировка идёт по соединениям, а не по запросам

  26. В чём разница между ingress и egress? Ответ: Ingress — входящий в кластер трафик, egress — исходящий из него. В контексте NetworkPolicy это два раздела правил: кому можно к нам и куда можно нам

  27. Почему Gateway API считают заменой Ingress? Ответ: Ingress заморожен в развитии, а всё сверх базовой маршрутизации приходилось делать через аннотации конкретного контроллера — конфигурация становилась непереносимой. Gateway API разделяет роли (администратор описывает точку входа, команда — маршрут своего сервиса), поддерживает не только HTTP и включает в сам стандарт разделение трафика по весам, маршрутизацию по заголовкам и переписывание путей

  28. Какой путь проходит запрос от пользователя до приложения? Ответ: DNS → внешний балансировщик → Ingress-контроллер → Service → endpoints → под → контейнер. Диагностику ведут по этой цепочке сверху вниз; ключевая проверка — не пуст ли список endpoints у сервиса

  29. Какие есть варианты масштабирования? Ответ: Вручную (kubectl scale), HPA — автоматически по числу подов на основе метрик (нужен metrics-server), VPA — подбор requests/limits вместо количества, Cluster Autoscaler — добавление и удаление самих нод в облаке

  30. Что такое RBAC и зачем он нужен? Ответ: Разграничение прав по ролям. Role и ClusterRole описывают разрешённые действия над типами объектов, RoleBinding и ClusterRoleBinding выдают их пользователям, группам или ServiceAccount. Принцип — минимально необходимые права

  31. Как разрешить разработчику управлять только Deployment и Pod в пространстве dev, запретив секреты и другие пространства? Ответ: Создать Role в пространстве dev с правилами на apiGroups: ["apps"] для deployments и apiGroups: [""] для pods, не включая secrets, и связать её с разработчиком через RoleBinding в этом же пространстве. Поскольку Role действует только в своём пространстве, доступ в остальные не появится. Проверить — kubectl auth can-i delete secrets -n dev --as разработчик

  32. Что такое Helm и зачем он нужен? Ответ: Пакетный менеджер для Kubernetes. Позволяет параметризовать манифесты шаблонами и ставить одно и то же приложение в разные окружения, меняя только values.yaml, а также устанавливать чужие приложения одной командой и откатывать неудачные обновления

  33. Почему Secret нельзя считать безопасным хранилищем? Ответ: Его содержимое в кластере лишь закодировано base64 и по умолчанию не шифруется в etcd. Любой, у кого есть право читать секреты в пространстве имён, видит пароль открытым текстом. Настоящее решение — внешнее хранилище вроде Vault, тема 9

Вопросы с собеседований

Самая большая тема курса — и самый длинный список вопросов на собеседовании

  1. Что такое etcd и какого типа эта база? Ответ: Распределённое хранилище пар «ключ-значение», где лежит всё состояние кластера. Согласованность обеспечивается алгоритмом Raft: узлы выбирают лидера, запись считается принятой, когда её подтвердило большинство. Именно поэтому число узлов делают нечётным, а резервная копия etcd — это резервная копия всего кластера

  2. Что такое CNI и как каждый под получает свой IP? Ответ: CNI (Container Network Interface) — стандарт подключения контейнеров к сети; конкретную работу делает плагин: Calico, Cilium, Flannel. Кластеру выделяют общий диапазон адресов для подов, из него каждой ноде достаётся кусок, а плагин раздаёт адреса подам на своей ноде. Базовое требование модели сети Kubernetes: любой под может обратиться к любому другому напрямую по его адресу, без преобразования адресов

  3. Как обратиться к поду в другом пространстве имён? Ответ: По полному DNS-имени сервиса: имя-сервиса.пространство.svc.cluster.local, короткая форма имя-сервиса.пространство тоже работает. Внутри своего пространства достаточно просто имени сервиса

  4. Является ли Service DNS-именем? Ответ: Сам сервис — объект с виртуальным адресом, но при его создании внутренний DNS кластера (CoreDNS) автоматически заводит для него запись. Поэтому обращаться по имени можно всегда, и это единственный правильный способ: адреса подов меняются, имя сервиса — нет

  5. Чем headless-сервис отличается от ClusterIP? Ответ: У ClusterIP есть единый виртуальный адрес, и трафик распределяется между подами. У headless (clusterIP: None) общего адреса нет: DNS-запрос возвращает адреса всех подов сразу, и клиент сам решает, с каким работать. Нужен StatefulSet’ам — чтобы обратиться именно к db-0, а не к случайной реплике

  6. Какие Ingress-контроллеры ты знаешь и как подключить к Ingress сертификат? Ответ: ingress-nginx (самый распространённый), Traefik, HAProxy, Kong, шлюз Istio, а также встроенные в облака. Сертификат подключают через секрет типа tls и секцию spec.tls с указанием имени хоста и секрета. Вручную это делают редко: cert-manager выпускает и продлевает сертификаты Let’s Encrypt автоматически

  7. Что такое lifecycle-хуки? Ответ: Две точки, куда можно вклиниться: postStart — сразу после запуска контейнера, preStop — перед его остановкой, до отправки SIGTERM. Используются для прогрева, регистрации в сторонних системах и корректного завершения

  8. Зачем в preStop пишут sleep 10? Ответ: При удалении пода два процесса идут параллельно: под убирают из списка endpoints и одновременно посылают ему SIGTERM. Правила сети на нодах обновляются не мгновенно, поэтому какое-то время трафик ещё летит в уже завершающийся под — пользователь получает ошибку. Пауза в preStop даёт балансировке успеть перестроиться, пока приложение ещё обслуживает запросы. Это самая частая причина ошибок 502 во время выкладки

  9. HPA при расчёте использует requests или limits? Ответ: Requests. Загрузка считается как доля потребления от запрошенного: под с requests: 100m, потребляющий 80m, даёт 80%. Отсюда следствие: если requests не заданы, HPA работать не может — не от чего считать процент

  10. Учитывает ли планировщик limits при выборе ноды? Ответ: Нет, только requests. Планировщик суммирует requests уже размещённых подов и ищет ноду, где хватает свободного места. Limits — это ограничение во время работы, а не при размещении. Поэтому сумма limits на ноде может превышать её ресурсы, и при одновременном всплеске начнётся вытеснение подов

  11. Как ограничить права пользователей в кластере? Ответ: Через RBAC: Role или ClusterRole описывают разрешённые действия, RoleBinding или ClusterRoleBinding выдают их субъекту. Дополнительно ограничивают квотами (ResourceQuota, LimitRange), сетевыми политиками и политиками допуска подов. Проверить права — kubectl auth can-i

  12. Из каких файлов состоит Helm-чарт? Ответ: Chart.yaml (описание чарта), values.yaml (значения по умолчанию), каталог templates/ (шаблоны манифестов), charts/ (чарты-зависимости), templates/_helpers.tpl (вспомогательные шаблоны), templates/NOTES.txt (текст, который печатается после установки), .helmignore

  13. Что находится в Chart.yaml и values.yaml? Ответ: В Chart.yaml — метаданные: name, version (версия самого чарта), appVersion (версия приложения — это разные вещи), description, dependencies. В values.yaml — значения по умолчанию, которые подставляются в шаблоны и переопределяются через -f или --set

  14. Зачем в templates/ создают _helpers.tpl? Ответ: Файлы, начинающиеся с подчёркивания, не превращаются в манифесты. В _helpers.tpl держат именованные шаблоны — например, вычисление полного имени релиза и стандартный набор меток, которые потом подключают через include во всех манифестах. Смысл тот же, что у функции в коде: не повторять одно и то же

  15. Как в шаблоне сделать цикл? Ответ: Через range:

    ports:
      {{- range .Values.service.ports }}
      - port: {{ .port }}
        name: {{ .name }}
      {{- end }}
    

    Внутри цикла . указывает на текущий элемент; чтобы добраться до глобальных значений, используют $.Values

  16. Как сделать условие для булева значения и для строки? Ответ: Для булева — {{ if .Values.ingress.enabled }} ... {{ end }}, для строки — сравнение: {{ if eq .Values.env "prod" }}. Подвох: шаблонизатор считает ложью не только false, но и пустую строку, ноль и пустой список. Поэтому для «значение вообще задано?» надёжнее {{ if hasKey .Values "replicaCount" }}

  17. Что вернёт {{ divf .Values.replicaCount .Values.zones | ceil }}? Ответ: Деление с плавающей точкой с округлением вверх. При 5 репликах и 3 зонах получится 2. Такая формула нужна, чтобы посчитать, сколько подов максимум допустимо в одной зоне при равномерном распределении: это значение подставляют в topologySpreadConstraints. Если разложить 5 подов по 3 зонам, идеально ровно не выйдет, и в одной зоне окажется 2 — именно это число и вычисляется

  18. Как разместить поды по разным нодам и зонам? Ответ: Через topologySpreadConstraints с ключами kubernetes.io/hostname (разные ноды) и topology.kubernetes.io/zone (разные зоны), либо через podAntiAffinity. Без этого планировщик вправе разместить все реплики на одной ноде, и её отказ уронит сервис целиком — то есть три реплики будут только видимостью надёжности

Эксплуатация и отладка

  1. Что такое init-контейнеры? Ответ: Контейнеры, которые выполняются до основных, по очереди и до успешного завершения. Используют для подготовки: дождаться готовности базы, применить миграции, скачать конфигурацию. Если init-контейнер падает, под перезапускается и до основного приложения дело не доходит

  2. Что такое taints и tolerations? Ответ: Taint — «отметка» на ноде, отталкивающая поды: обычные поды туда не попадут. Toleration — разрешение в описании пода игнорировать конкретную отметку. Так выделяют ноды под особые задачи: с GPU, для системных компонентов. Не путать с nodeSelector: тот притягивает под к ноде, а taint отталкивает всех, кроме допущенных

  3. Что такое PodDisruptionBudget и зачем он нужен? Ответ: Правило «в любой момент должно оставаться доступным не меньше N подов». Оно защищает при добровольных нарушениях работы: обслуживании нод, обновлении кластера, работе автоскейлера — команда kubectl drain не выселит поды, если это нарушит бюджет. От падения ноды он не спасает, для этого нужно распределение по зонам

  4. Что происходит, когда нода перестаёт отвечать? Ответ: Через некоторое время нода помечается как NotReady, а её поды — на выселение. Поды под управлением Deployment пересоздаются на других нодах. Важная деталь: между отказом и пересозданием проходят минуты, а не секунды — Kubernetes не может быстро отличить упавшую ноду от кратковременной потери связи. Поэтому реплики держат на разных нодах заранее

  5. Что такое PV, PVC и StorageClass? Ответ: PersistentVolume — сам кусок хранилища. PersistentVolumeClaim — заявка приложения: «нужно 10 ГБ с такими свойствами». StorageClass — описание типа хранилища, позволяющее создавать тома автоматически по заявке. Приложение работает только с заявкой и не знает, какой диск за ней стоит

  6. Что означают режимы доступа RWO, ROX и RWX? Ответ: ReadWriteOnce — том монтируется на запись только на одной ноде (большинство блочных дисков), ReadOnlyMany — на чтение многим, ReadWriteMany — на запись многим, требует сетевой файловой системы вроде NFS или CephFS. Отсюда практическое ограничение: обычный диск нельзя подключить к нескольким репликам сразу на запись

  7. Что такое ResourceQuota и LimitRange? Ответ: ResourceQuota ограничивает суммарное потребление пространства имён: не больше стольких ядер, памяти и объектов. LimitRange задаёт границы для отдельного контейнера и значения по умолчанию, если разработчик их не указал. Вместе они не дают одной команде занять весь кластер

  8. Под в состоянии Pending. Что проверять? Ответ: kubectl describe pod и события в конце вывода. Частые причины: ни на одной ноде не хватает ресурсов под указанные requests; не находится подходящая нода из-за nodeSelector, taint или правил распределения; не создаётся том по заявке; исчерпана квота пространства имён. Pending — это всегда «планировщик не смог разместить», а не «приложение сломалось»

  9. Чем kubectl apply отличается от create и replace? Ответ: create создаёт объект и падает, если он уже есть. replace заменяет целиком. apply вносит изменения декларативно, сохраняя поля, которыми управляют другие (например, число реплик, выставленное HPA). В работе используют apply, и лучше — из файлов в git, а не из командной строки

  10. Зачем в манифесте imagePullPolicy и как он работает по умолчанию? Ответ: Определяет, скачивать ли образ заново. Always — каждый раз, IfNotPresent — только если нет локально, Never — никогда. По умолчанию Always для тега latest и IfNotPresent для любого другого. Отсюда классическая ловушка: пересобрал образ с тем же тегом, а кластер продолжает использовать старый

  11. Как приложение внутри пода обращается к API Kubernetes? Ответ: Через ServiceAccount: в под монтируется токен, который приложение предъявляет API-серверу; права определяются RBAC. Именно этот механизм используется в теме 9, когда под доказывает свою личность Vault. Отключить монтирование токена, если он не нужен, — хорошая практика: automountServiceAccountToken: false

  12. Что такое NetworkPolicy? Ответ: Правила сетевого доступа между подами. По умолчанию в Kubernetes любой под может обратиться к любому — плоская и полностью открытая сеть. NetworkPolicy позволяет разрешить только нужное: например, к базе ходит лишь приложение, а не все подряд. Работает не везде: нужен сетевой плагин с поддержкой (Calico, Cilium), иначе манифест применится и не сделает ничего

  13. Как посмотреть, что происходило в кластере за последний час? Ответ: kubectl get events --sort-by=.lastTimestamp -A. События хранятся около часа, поэтому для настоящего разбора инцидентов их отправляют в систему хранения логов. Прицельно по объекту — нижняя часть вывода kubectl describe

  14. Чем 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 в трёх копиях и убедиться, что падение копии не влияет на пользователя

Шаги:

  1. Сделай образ доступным кластеру. В minikube проще всего собрать его прямо внутри:

    cd ~/notes
    eval $(minikube docker-env)     # перенаправить docker-команды в кластер
    docker build -t notes:1.0 .
    docker images | grep notes
    
  2. Создай 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: {}          # временный каталог; переживёт перезапуск контейнера, но не пода
    
  3. Создай k8s/service.yaml:

    apiVersion: v1
    kind: Service
    metadata:
      name: notes
    spec:
      type: ClusterIP
      selector:
        app: notes                  # трафик пойдёт в поды с этой меткой
      ports:
        - port: 8080                # порт самого сервиса
          targetPort: 8080          # порт внутри пода
    
  4. Применяй и смотри:

    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 три адреса

  5. Проверь доступ, не выходя из кластера:

    kubectl port-forward svc/notes 8080:8080 &
    curl http://localhost:8080/healthz
    kill %1
    
  6. Главный эксперимент — убей под:

    kubectl get pods -l app=notes
    kubectl delete pod <имя-любого-пода>
    kubectl get pods -l app=notes           # новый под уже создаётся
    

    Ты описал желаемое состояние «три копии». Kubernetes обнаружил, что копий стало две, и немедленно создал третью. Никто не отдавал команду «запусти под»

  7. Масштабируй:

    kubectl scale deployment notes --replicas=5
    kubectl get pods -l app=notes
    kubectl scale deployment notes --replicas=3
    

Типичные ошибки:

Задание 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, — только теперь за именем стоит целый кластер

Типичные ошибки:

Задание 5. Обновление без простоя и откат

Цель: увидеть rolling update своими глазами

Шаги:

  1. Измени что-нибудь заметное в app.py (например, добавь строку в ответ), собери новую версию:

    eval $(minikube docker-env)
    docker build -t notes:2.0 .
    
  2. В отдельном терминале запусти непрерывную проверку доступности:

    while true; do curl -s -o /dev/null -w "%{http_code} " http://notes.local/; sleep 0.3; done
    
  3. В основном терминале обнови образ и следи за процессом:

    kubectl set image deployment/notes notes=notes:2.0
    kubectl rollout status deployment/notes
    kubectl get pods -l app=notes
    

Ожидаемый вывод: в первом терминале всё время идут коды 200 — ни одного 502 или 503. Именно ради этого нужны readiness-пробы: старый под гасят только после того, как новый начал отвечать

  1. Откатись:

    kubectl rollout history deployment/notes
    kubectl rollout undo deployment/notes
    kubectl rollout status deployment/notes
    
  2. Сломай специально. Выкати заведомо несуществующий образ и посмотри, как поведёт себя кластер:

    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 намеренно снижает их медленно, чтобы не «дёргать» приложение

Типичные ошибки:

Задание 7. Диагностика: сломай и почини

Цель: отработать поиск причины — это половина работы инженера

Шаги:

  1. Сломай 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 лишь убирает под из балансировки

  2. Верни как было: kubectl rollout undo deployment/notes

  3. Устрой 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 в параметризуемый пакет

Шаги:

  1. Установи 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
    
  2. Замени 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
    
  3. Создай 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 }}
    
  4. Посмотри, во что превращаются шаблоны, не устанавливая ничего:

    helm template notes ./chart
    helm template notes ./chart --set replicaCount=7 | grep replicas
    
  5. Установи и обнови:

    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
    
  6. Закоммить всё в репозиторий:

    git add k8s/ chart/
    git commit -m "Запустить «Заметки» в Kubernetes и упаковать в Helm-чарт"
    git push
    

Ожидаемый вывод: helm template показывает готовый YAML с подставленными значениями; --set replicaCount=7 меняет только одну строку в результате; helm rollback возвращает предыдущее состояние

Типичные ошибки:

Проверь себя: тема освоена, если ты можешь

Следующая тема: облака, SaaS, PaaS, IaaS →