Стройте ответ вокруг владения состоянием, лимитов, отказа и восстановления. Определение становится инженерным ответом только в конкретном production-сценарии.
Вопросы и ответы
13 подробных ответов
01Что происходит после `kubectl apply -f deployment.yaml`? Опишите всю цепочку до работающего пода.
senior
Короткий ответ: kubectl валидирует манифест и отправляет его в API server: authn → RBAC → admission-вебхуки → запись в etcd. На этом синхронная часть заканчивается — дальше контроллеры, scheduler и kubelet асинхронно замечают изменения через watch и приводят кластер к желаемому состоянию. Никто ничего не «пушит».
Подробно:
- API server — аутентификация, авторизация (RBAC), admission (mutating → validating вебхуки), объект сохранён в etcd.
- Deployment-контроллер — видит новый Deployment (watch) и создаёт ReplicaSet.
- ReplicaSet-контроллер — создаёт Pod-объекты, пока без ноды.
- Scheduler — находит поды с пустым nodeName, фильтрует и скорит ноды, делает bind.
- kubelet — на выбранной ноде видит «свой» под: тянет образы, запускает контейнеры через CRI, сеть поднимает CNI-плагин.
- Readiness — проба прошла → под попадает в EndpointSlice и получает трафик.
kubectl ──► API server (authn → RBAC → admission) ──► etcd
▲ watch ▲ watch ▲ watch ▲ watch
deploy-ctrl → RS rs-ctrl → Pods scheduler → bind kubelet → CRI/CNI
⚠️ Частая ошибка: «API server шедулит под и запускает контейнеры». Он только хранит и раздаёт состояние — всю работу делают независимые контроллеры по модели watch/reconcile.
02Разберите архитектуру Kubernetes: apiserver, etcd, scheduler, controller-manager, kubelet, kube-proxy — за что отвечает каждый?
middle
Короткий ответ: apiserver — единая точка входа и единственный, кто ходит в etcd; etcd — хранилище состояния; scheduler — выбирает ноду; controller-manager — циклы reconcile; kubelet — запускает контейнеры на ноде; kube-proxy — сетевые правила для Service.
Подробно:
| Компонент | Роль в одну строку |
|---|---|
| kube-apiserver | REST-API кластера: authn/authz, admission, валидация; единственный клиент etcd |
| etcd | консистентное KV-хранилище всего состояния кластера (Raft) |
| kube-scheduler | подбирает ноду для пода (фильтры + скоринг), пишет bind |
| kube-controller-manager | десятки контроллеров — Deployment, ReplicaSet, Node, Job… — reconcile желаемого состояния |
| kubelet | агент на ноде: запускает поды через CRI, гоняет пробы, репортит статус |
| kube-proxy | программирует iptables/IPVS, чтобы работали Service ClusterIP |
Первые четыре — control plane; kubelet и kube-proxy живут на каждой ноде. Ключевая идея: все общаются только с apiserver через watch — компоненты не знают друг о друге и не ходят в etcd напрямую. Поэтому control plane переживает отказ любого компонента, кроме etcd.
⚠️ Частая ошибка: говорить, что scheduler или controller-manager «пишут в etcd». Пишут они в apiserver — он один держит соединение с etcd.
03Почему etcd нужно нечётное число членов и что произойдёт с кластером при потере кворума?
senior
Короткий ответ: etcd работает на Raft: запись подтверждается большинством — (n/2)+1. Чётный член не добавляет отказоустойчивости: и 3, и 4 ноды переживают ровно один отказ. При потере кворума etcd перестаёт принимать записи → API «замирает»: kubectl apply падает, контроллеры и шедулинг стоят. Уже запущенные поды продолжают работать.
Подробно:
| Членов | Кворум | Переживает отказов |
|---|---|---|
| 3 | 2 | 1 |
| 4 | 3 | 1 (чётный член ничего не дал) |
| 5 | 3 | 2 |
- Кворум потерян — записи невозможны; apiserver ещё может отдавать чтения, но не принимает изменений: ни выкаток, ни скейлинга, ни новых подов.
- Data plane живёт — kubelet держит контейнеры, kube-proxy — правила; кластер «заморожен», но трафик идёт.
- Медленный etcd опаснее упавшего — растёт латентность каждого API-запроса, watch отстают, контроллеры реагируют с лагом, leader election флапает.
⚠️ Частая ошибка: «упал etcd — упало всё». Нет: падает management plane; рабочая нагрузка продолжает обслуживать трафик, пока не понадобится что-то изменить.
04Под завис в Pending: как найти причину и какие причины самые частые?
middle
Короткий ответ: Pending значит «scheduler не нашёл ноду». Первый шаг всегда один: kubectl describe pod — в Events scheduler сам объясняет, что его не устроило. Дальше причина почти всегда одна из пяти: ресурсы, taints, affinity, PVC, квота.
Подробно:
kubectl describe pod app-7d9f # Events: "0/12 nodes are available: ..."
kubectl get nodes # ноды живы и Ready?
kubectl describe node n1 # Allocatable vs Allocated: влезают ли requests
kubectl get pvc # PVC тоже Pending?
kubectl describe quota -n team # упёрлись в ResourceQuota?
- Не хватает ресурсов — суммарные requests не влезают ни на одну ноду («Insufficient cpu/memory»).
- Taints без tolerations — «node(s) had untolerated taint».
- nodeSelector / affinity — ни одна нода не подходит под метки.
- PVC не забиндился — под ждёт том: нет StorageClass, не совпала зона.
- ResourceQuota — нюанс: под вообще не создаётся, ошибка видна в Events у ReplicaSet, а не как Pending.
⚠️ Частая ошибка: начинать с логов. У Pending-пода нет логов — он ещё нигде не запущен; вся информация в Events.
05Под в CrashLoopBackOff: какова последовательность отладки?
middle
Короткий ответ: CrashLoopBackOff — контейнер запускается, падает, и kubelet рестартит его с растущей задержкой (10 с, удваивается, потолок 5 мин). Порядок: describe (Events и Last State с exit code) → logs --previous (логи именно упавшего контейнера) → интерпретировать exit code.
Подробно:
kubectl describe pod app-7d9f # Events + Last State: Exit Code
kubectl logs app-7d9f --previous # логи УПАВШЕГО контейнера — ключевой флаг
kubectl logs app-7d9f -c init-db # если падает init-контейнер
| Exit code | Что значит |
|---|---|
| 1 | ошибка приложения — читаем свой лог |
| 127 | нет такого бинаря — опечатка в command/entrypoint |
| 137 | SIGKILL: OOMKilled (смотрим describe) или прибит при eviction |
Дальше по убыванию частоты: битый конфиг или секрет (нет env, не смонтировался volume), падающая liveness-проба (в Events будет «Liveness probe failed… will be restarted»), недоступная на старте зависимость или миграции.
⚠️ Частая ошибка: смотреть kubectl logs без --previous и удивляться пустоте: свежерестартнутый контейнер ещё ничего не написал, а логи упавшего живут за флагом.
06OOMKilled с exit code 137: кто убивает контейнер — Kubernetes или ядро? И при чём здесь QoS-классы?
senior
Короткий ответ: Убивает ядро Linux. Kubernetes лишь записал memory limit в cgroup (memory.max); когда процесс его превышает, срабатывает kernel OOM killer и шлёт SIGKILL (128+9=137). QoS-класс пода задаёт oom_score_adj — кого ядро и kubelet принесут в жертву первым.
Подробно:
| QoS | Условие | oom_score_adj |
|---|---|---|
| Guaranteed | requests = limits по CPU и памяти | −997 (умирает последним) |
| Burstable | requests < limits или заданы частично | промежуточный, зависит от request |
| BestEffort | ни requests, ни limits | +1000 (умирает первым) |
Два разных механизма, которые путают:
- cgroup OOM (ядро) — контейнер превысил СВОЙ limit → мгновенный SIGKILL, статус OOMKilled. Kubernetes в этот момент не участвует.
- Node-pressure eviction (kubelet) — память кончается на ноде в целом → kubelet сам вытесняет поды, ранжируя по QoS и превышению usage над request; статус Evicted, а не OOMKilled.
⚠️ Частая ошибка: «kubelet следит за памятью и убивает контейнер при превышении limit». Нет: per-container limit энфорсит ядро через cgroup; kubelet вмешивается только при давлении на всю ноду.
07Requests и limits: чем управляет каждый и что происходит при превышении?
middle
Короткий ответ: Requests — валюта шедулинга: по ним scheduler бин-пакует поды на ноды, и они же задают вес при борьбе за CPU. Limits — потолок: CPU при превышении троттлится, память — SIGKILL (OOMKilled). CPU — «сжимаемый» ресурс, память — нет, отсюда и разное наказание.
Подробно:
| CPU | Память | |
|---|---|---|
| request | гарантированная доля + вес при contention | учитывается только шедулером |
| limit превышен | throttling: под тормозит, но живёт | OOM kill: мгновенная смерть |
- Requests ≠ реальное потребление — scheduler смотрит только на сумму requests: нода может быть «занята» на бумаге и пустой по факту, и наоборот.
- Limits без requests — Kubernetes автоматически выставит requests = limits.
- Requests без limits — под может burst'ить в свободные ресурсы; для CPU это часто желаемое поведение, для памяти — риск OOM на переподписанной ноде.
⚠️ Частая ошибка: «память при превышении лимита троттлится, как CPU». Память нельзя отнимать у процесса постепенно — только убить: превышение memory limit всегда заканчивается SIGKILL.
08Как Service на самом деле доставляет трафик до подов и что делает kube-proxy?
senior
Короткий ответ: ClusterIP — виртуальный адрес: на нём никто не слушает, и пакет до него не «доходит». kube-proxy на каждой ноде программирует правила iptables (или IPVS): пакет на ClusterIP DNAT'ится в IP:port одного из ready-подов прямо в netfilter ноды. Сам kube-proxy в пути пакета не участвует.
Подробно:
Pod A ──► 10.96.0.10:80 (ClusterIP: виртуальный, процесса нет)
│ iptables DNAT (правила написал kube-proxy)
▼ случайный ready-эндпоинт
10.244.1.5:8080 (Pod B)
- EndpointSlice — контроллер собирает список подов Service'а, прошедших readiness; kube-proxy watch'ит его и переписывает правила.
- Readiness = членство — под провалил пробу → выпал из EndpointSlice → правила больше на него не указывают.
- iptables vs IPVS — при тысячах Service'ов линейная цепочка iptables тормозит; IPVS даёт hash-таблицу.
- Ingress — это уже L7: отдельный контроллер (nginx, envoy) терминирует HTTP и маршрутизирует по host/path; Service под ним — та же L4-механика.
⚠️ Частая ошибка: думать, что kube-proxy — прокси в датапути, через который текут пакеты. Он лишь конфигуратор правил ядра и трафика не видит.
09Чем отличаются liveness, readiness и startup пробы — и как неправильная liveness-проба устраивает каскадный отказ?
middle
Короткий ответ: Readiness управляет трафиком (членство в EndpointSlice), liveness — жизнью контейнера (провал = рестарт), startup откладывает первые две, пока приложение стартует. Классическая авария: liveness проверяет зависимость (БД) — база моргнула, и kubelet рестартит весь флот разом.
Подробно:
| Проба | Провал → | Для чего |
|---|---|---|
| readiness | под выпадает из балансировки | «сейчас не могу принимать трафик» — временно и обратимо |
| liveness | kubelet рестартит контейнер | «процесс завис безнадёжно» — только про сам процесс |
| startup | рестарт после failureThreshold | медленный старт (JVM, прогрев кэша) без раздувания liveness |
Анатомия аварии: liveness дёргает /health, который ходит в БД → база недоступна 30 секунд → liveness провалилась у всех реплик → kubelet рестартит все контейнеры → сервис лёг целиком, а массовые рестарты добивают едва ожившую базу.
Правило: liveness — только о самом процессе (deadlock, зависший event loop); зависимости — территория readiness, и то с осторожностью.
⚠️ Частая ошибка: один endpoint на liveness и readiness, проверяющий всё подряд. Один blip зависимости превращается в рестарт флота вместо паузы трафика.
10Deployment, StatefulSet или DaemonSet: как выбрать и чем обосновать?
middle
Короткий ответ: Deployment — взаимозаменяемые stateless-реплики с rolling update. StatefulSet — когда репликам нужна стабильная идентичность: имя (pod-0, pod-1), свой PVC на реплику, упорядоченная выкатка, стабильный DNS через headless Service. DaemonSet — ровно один под на каждой подходящей ноде: агенты логов, метрик, CNI.
Подробно:
| Deployment | StatefulSet | DaemonSet | |
|---|---|---|---|
| Идентичность | нет, поды взаимозаменяемы | стабильная: web-0, web-1 + DNS | привязка к ноде |
| Хранилище | общее или нет | PVC-шаблон: том на реплику | hostPath или нет |
| Порядок | параллельно | упорядоченные rollout и scale | по нодам |
| Кейсы | API, воркеры | Kafka, etcd, реплики БД | node-exporter, fluent-bit |
Headless Service (clusterIP: None) даёт каждой реплике StatefulSet предсказуемое DNS-имя вида web-0.web.ns.svc — так брокеры и реплики находят друг друга.
⚠️ Частая ошибка: «есть база — значит StatefulSet». Если база managed (RDS, Cloud SQL) — приложению хватит Deployment; а серьёзные БД внутри кластера обычно живут под оператором, который сам решает, чем управлять.
11Как HPA решает, сколько реплик нужно, и почему без resource requests он не работает?
middle
Короткий ответ: По формуле: desiredReplicas = ceil(текущая метрика / целевая × текущие реплики). Для CPU «utilization» считается как процент от requests — нет requests, нет знаменателя, и HPA не может посчитать вообще ничего.
Подробно:
desired = ceil( current / target × currentReplicas )
пример: CPU 80% от request, цель 50%, реплик 4
ceil(80/50 × 4) = ceil(6.4) = 7
- Источник метрик — metrics-server (resource-метрики) либо custom/external metrics API (RPS, глубина очереди).
- % от requests — «CPU 80%» значит «под ест 80% своего request», а не ноды; заниженные requests заставляют HPA скейлить раньше времени, завышенные — не скейлить никогда.
- Стабилизация — окно по умолчанию 300 с на scale-down (вверх — сразу): защита от флаппинга на шумной метрике.
- Границы — ниже minReplicas не уйдёт; при недоступных метриках масштабирование замирает, а не «скейлится в ноль».
⚠️ Частая ошибка: повесить HPA на поды без requests и ждать чуда — в статусе будет FailedGetResourceMetric, и число реплик не сдвинется.
12Во время rolling update часть запросов ловит 5xx. Почему и как сделать выкатку по-настоящему zero-downtime?
senior
Короткий ответ: Из-за гонки при остановке пода: SIGTERM контейнеру и удаление пода из EndpointSlice происходят параллельно, а iptables на нодах и внешние LB обновляются с лагом — какое-то время трафик летит в уже умирающее приложение. Лечится связкой: preStop-пауза + грамотный SIGTERM + запас в grace period + PDB.
Подробно:
t=0 под помечен на удаление
├─► SIGTERM приложению (мгновенно)
└─► удаление из EndpointSlice (асинхронно: kube-proxy, LB — лаг!)
t=0…3с старые правила ещё шлют запросы → connection refused / 5xx
фикс: preStop sleep 5 → сначала дренится трафик, потом умирает процесс
- preStop hook —
sleep 5: даёт правилам и LB время убрать под из ротации до SIGTERM. - SIGTERM в приложении — перестать принимать новые соединения, дорешать текущие, выйти.
- terminationGracePeriodSeconds — больше worst-case дренажа + обработки (по умолчанию 30 с, дальше SIGKILL).
- PodDisruptionBudget — на drain'ах и апгрейдах нод (
cordon+drainуважает PDB) гарантирует минимум живых реплик.
⚠️ Частая ошибка: починить только приложение (SIGTERM-хендлер) и пропустить гонку с эндпоинтами: без preStop-паузы запросы продолжат прилетать в под, который уже начал останавливаться.
13Команде нужен доступ только к её неймспейсу: какие RBAC-объекты вы создадите и как аутентифицируются поды?
middle
Короткий ответ: Role (правила внутри неймспейса) + RoleBinding на группу или пользователей команды. Для переиспользования — один ClusterRole с типовым набором прав и по RoleBinding в каждом неймспейсе: binding неймспейсный, значит и права неймспейсные. Поды аутентифицируются токенами ServiceAccount.
Подробно:
- Role vs ClusterRole — Role живёт в неймспейсе; ClusterRole глобален, но через RoleBinding его права «сужаются» до одного неймспейса — паттерн для одинаковых прав у десятка команд.
- Субъекты — биндить лучше на группу из IdP (
group: team-a), а не на людей поимённо. - ServiceAccount — под получает projected-токен своего SA; если приложению нужен доступ к API, SA биндится теми же Role/RoleBinding.
- Проверка — не гадать, а спросить кластер:
kubectl auth can-i list pods -n team-a --as u@corp --as-group team-a # yes
kubectl auth can-i list pods -n team-b --as u@corp --as-group team-a # no
kubectl auth can-i --list -n team-a --as u@corp # весь набор прав разом
⚠️ Частая ошибка: выдать cluster-admin «временно, чтобы не разбираться». RBAC аддитивен — отнять лишнее потом нельзя, только отзывать биндинги; начинайте с least privilege.
Источники
Источники и редакционная политика
Материалы RecallDeck сопоставлены с официальной документацией и открытыми публикациями компаний, когда первичный источник доступен. Мы не связаны с упомянутыми работодателями, не публикуем конфиденциальные задания и не продаём места в подборках. Формат найма может меняться — уточняйте его у рекрутера.
От чтения к воспроизведению
Отрепетируйте полный цикл интервью.
RecallDeck возвращает сложные темы по расписанию и помогает удерживать в памяти язык, SQL, архитектуру и поведенческие истории.