Подборки вопросов: DevOps · SRE · DevSecOps · MLOps · AIOps
Блок 1. Вопросы по Kubernetes
1. Чем Deployment отличается от StatefulSet и DaemonSet?
Deployment — для stateless-приложений: поды взаимозаменяемы, имена рандомные, скейлится в любую сторону, любой под можно убить без церемоний. StatefulSet — когда подам нужна личность: стабильные имена (db-0, db-1), у каждого свой PVC через volumeClaimTemplates, старт и остановка строго по порядку. Это базы, Kafka, всё с выборами лидера. Ему обязателен headless service — он даёт подам стабильные DNS-имена вида db-0.db.ns.svc, по которым реплики находят друг друга. DaemonSet — по одному поду на каждую ноду: агенты логов, мониторинг, CNI; новая нода в кластере — под приезжает сам, без скейлинга руками. Выбор на собесе формулируй через данные: нет состояния — Deployment, есть состояние и роли — StatefulSet, нужен агент на каждой машине — DaemonSet.
Вопрос «что будет с PVC при удалении StatefulSet» (останутся — защита данных) и зачем StatefulSet нужен headless service (стабильные DNS-имена подов).
Follow-up интервьюера: как StatefulSet обновляется? — RollingUpdate строго с последнего пода к первому, а partition позволяет катить канарейку на часть подов: Deployment так не умеет.
2. Что происходит после kubectl apply?
kubectl шлёт манифест в API-сервер → валидация, аутентификация, admission-контроллеры, запись в etcd. Дальше всё асинхронно: controller manager видит Deployment, создаёт ReplicaSet, тот — поды в статусе Pending. Scheduler подбирает ноду по requests, affinity и taints и пишет её имя в под. Kubelet на этой ноде видит «своего» пода, дёргает container runtime, тянет образ, запускает контейнеры и репортит статус обратно. Kube-proxy обновляет правила, когда под попадает в endpoints. Ключевая мысль, которую интервьюер хочет услышать: Kubernetes — это декларативная система на контроллерах, каждый из которых через watch бесконечно тянет реальность к желаемому состоянию. Проследить цепочку событий вживую можно через kubectl get events --sort-by=.metadata.creationTimestamp.
Рассказывают, будто API-сервер сам создаёт поды и раскидывает по нодам. Нет: он единственная точка записи в etcd, всё остальное делают контроллеры через watch.
Follow-up интервьюера: а если scheduler лежит? — новые поды зависнут в Pending, но всё уже запущенное продолжит работать как ни в чём не бывало.
3. Чем liveness-проба отличается от readiness и startup?
Liveness отвечает «жив ли контейнер»: провалилась — kubelet перезапускает контейнер. Readiness — «готов ли принимать трафик»: провалилась — под вылетает из endpoints, но продолжает работать; так сервис сам выключает себя из балансировки, пока прогревает кеш или ждёт базу. Startup — для медленно стартующих приложений: пока не пройдёт, liveness и readiness молчат. Конкретика, которую любят: startupProbe с failureThreshold: 30 и periodSeconds: 10 даёт приложению 5 минут на старт — без неё пришлось бы раздувать initialDelaySeconds у liveness. И главное правило прода: liveness должна проверять только сам процесс, без походов в базу и соседние сервисы — иначе отказ зависимости превращается в волну рестартов всего, что от неё зависит.
Говорят «readiness перезапускает под» и не знают, что агрессивный liveness-таймаут под нагрузкой устраивает каскад рестартов.
Follow-up интервьюера: что будет, если startup-проба так и не пройдёт? — контейнер перезапустят: провал сверх failureThreshold — это тоже рестарт, а не вечное ожидание.
4. Под в CrashLoopBackOff / Pending — твои действия?
CrashLoopBackOff: контейнер стартует и падает, kubelet рестартит с растущей паузой — до 5 минут между попытками. Смотрю kubectl logs --previous (логи именно упавшего контейнера, а не нового) и kubectl describe pod: exit code 137 — OOMKill, 1 — ошибка приложения. Частые причины: кривой конфиг, недоступная зависимость на старте, слишком злой liveness. Pending — под некуда воткнуть, он ещё ни разу не запускался: не хватает ресурсов под requests, taints без tolerations, не совпал nodeSelector, нет свободного PV. В describe в Events всегда написана причина — шедулер честно перечисляет, сколько нод и почему отвалилось. Это вопрос про метод: сначала events и логи, потом гипотезы — а не наоборот.
Не знают про --previous, а на Pending начинают говорить про рестарты — хотя под ещё нигде даже не запускался.
Follow-up интервьюера: ноды есть, ресурсы есть, под всё равно Pending — куда смотреть? — topology spread constraints и volume node affinity: PV создан в другой зоне, а под обязан жить рядом с диском.
5. Что такое requests и limits? Какие QoS-классы знаешь?
Requests — сколько шедулер резервирует при выборе ноды: под с requests.cpu: 500m встанет только туда, где эти 500m ещё не обещаны другим. Limits — потолок в рантайме: CPU троттлится, память — OOMKill с exit code 137. QoS-классы выводятся из этой пары: Guaranteed (requests = limits по всем контейнерам), Burstable (задано частично), BestEffort (ничего) — при нехватке памяти на ноде первыми умирают BestEffort, последними Guaranteed. Класс пода проверяется так: kubectl get pod -o jsonpath='{.status.qosClass}'. Requests без limits — нормальная практика для CPU: троттлинг многопоточных приложений бьёт по latency сильнее, чем гипотетический шумный сосед. А вот memory limits ставить надо всегда — иначе утечка одного пода кладёт ноду.
«Превысил CPU limit — под убьют». CPU — сжимаемый ресурс, за него только троттлят. Убивают за память.
Follow-up интервьюера: а стоит ли вообще ставить CPU limits? — во многих продах их снимают, оставляя requests: честный ответ «зависит от мультитенантности» звучит сильнее заученного «ставить всегда».
6. Типы Service и зачем нужен Ingress?
ClusterIP — виртуальный IP внутри кластера, дефолт. NodePort — порт на каждой ноде из диапазона 30000-32767, наружу торчит, но в проде напрямую почти не используется. LoadBalancer — внешний балансер облака поверх NodePort, по балансеру на сервис — дорого. Headless (clusterIP: None) — DNS сразу на IP подов, для StatefulSet. Service — это L4: он ничего не знает про HTTP. Ingress — L7-маршрутизация HTTP(S): хосты, пути, TLS-терминация, один внешний IP на десятки сервисов. Работает только в паре с ingress-контроллером (nginx, traefik): Ingress — правила, контроллер — исполнитель. Service находит поды по селектору, а фактические адреса живут в объектах EndpointSlice. И упомяни Gateway API — официального преемника Ingress: про него уже спрашивают.
«Ingress — это такой сервис». Ingress — правила, контроллер — исполнитель. Без контроллера Ingress — просто запись в etcd.
Follow-up интервьюера: как трафик с ClusterIP попадает на конкретный под? — kube-proxy держит правила iptables/IPVS, которые DNAT'ят виртуальный IP в адрес одного из подов из EndpointSlice.
7. ConfigMap vs Secret — в чём разница на самом деле?
Оба выносят конфигурацию из образа, монтируются как env или файлы. Secret — это base64, а base64 — не шифрование, а кодирование: kubectl get secret app -o jsonpath='{.data.password}' | base64 -d — вот и вся «защита». Реальная разница — в обвязке: Secret не пишется в логи describe, для него отдельно настраивается encryption at rest в etcd и жёсткий RBAC (право читать секреты — отдельно от права читать конфиги). Серьёзные секреты — во внешних хранилищах: Vault, external-secrets. Бонус, который отличает практика: конфиг, смонтированный файлом, обновляется в поде без рестарта (с задержкой до минуты), а env-переменные — нет, только пересоздание пода. Отсюда паттерн: reloader или checksum-аннотация в Helm-чарте, чтобы деплой перекатывался при смене конфига.
Уверенно называют base64 «шифрованием». После этой фразы интервьюер начинает копать глубже — и находит дно.
Follow-up интервьюера: как под получает секрет из Vault? — External Secrets Operator синхронизирует его в обычный Secret, либо CSI-драйвер монтирует напрямую, минуя etcd.
8. Как работает HPA?
HPA меняет число реплик по метрикам: CPU/память из metrics-server или кастомные (RPS, длина очереди) через adapter. Формула: desired = ceil(current × текущая метрика / целевая). Посчитай пример вслух — это производит впечатление: 4 реплики при утилизации 90% и цели 60% → ceil(4 × 90/60) = 6. Нюансы, на которых видно практика: утилизация считается от requests — без них HPA слепой; стабилизационное окно (по умолчанию 300 секунд на scale-down) спасает от флаппинга; скорость реакции тюнится блоком behavior. С GitOps дружить надо аккуратно: фиксированный replicas в git будет драться с HPA. Рядом: VPA меняет сами requests, KEDA скейлит от событий (длина очереди, лаг Kafka) вплоть до нуля.
Не знают, что без metrics-server HPA мёртв, и что скейлить по памяти JVM-приложения — почти всегда плохая идея.
Follow-up интервьюера: почему по памяти скейлить опасно? — память после нагрузки не возвращается (особенно у JVM), реплики не сожмутся обратно, а под GC-паузы HPA вообще не видит.
9. Что такое etcd и почему узлов нечётное число?
Распределённое KV-хранилище — единственное место, где живёт всё состояние кластера: манифесты, статусы, секреты. Консенсус — Raft, для записи нужен кворум n/2+1, поэтому 3 или 5 узлов: тройка переживает потерю одного, пятёрка — двух, а двойка не переживает ни одного — чётный узел не добавляет отказоустойчивости, только трафик. Бэкап etcd — это и есть бэкап кластера: etcdctl snapshot save. Ходить в etcd напрямую нельзя — только через API-сервер, он единственный клиент. Практический нюанс: etcd болезненно чувствителен к диску — медленный fsync устраивает перевыборы лидера и «моргающий» кластер, поэтому под ним только быстрый SSD и отдельный том, а не общий диск с логами.
«Без etcd кластер умрёт». Поды продолжат работать — но никаких изменений: ни задеплоить, ни отскейлить.
Follow-up интервьюера: из 5 узлов умерло 2 — что с кластером? — жив: кворум 3 из 5 на месте, записи проходят.
10. Как устроен RBAC?
Role/ClusterRole — набор разрешений: verbs (get, list, create, delete) на resources (pods, secrets). RoleBinding/ClusterRoleBinding — привязка к субъекту: user, group, ServiceAccount. Role живёт в namespace, ClusterRole — на весь кластер. Запрещающих правил нет: всё, что не разрешено, запрещено, поэтому «отобрать право» — значит не выдавать его. Проверка — kubectl auth can-i, а права конкретного SA: kubectl auth can-i list pods --as=system:serviceaccount:ns:name. Про ServiceAccount спросят следом: его токен монтируется в под автоматически, поэтому тем, кому API не нужен, ставь automountServiceAccountToken: false — меньше подарков атакующему при компрометации пода.
Не знают, что ClusterRole можно прибиндить RoleBinding'ом в конкретный namespace — паттерн переиспользования, который любят спрашивать сеньоров.
Follow-up интервьюера: как дать разработчику доступ только к его namespace? — общая ClusterRole с нужными verbs плюс RoleBinding в его namespace: одна роль, много биндингов.
11. Как сделать rolling update реально без даунтайма?
Deployment и так катит rolling (темп — maxSurge/maxUnavailable), но zero downtime он сам по себе не даёт. Нужен весь комплект: readiness-проба, чтобы трафик шёл только на готовые поды; graceful shutdown — приложение по SIGTERM дорабатывает текущие запросы и закрывается; preStop-хук; адекватный terminationGracePeriodSeconds — дефолтные 30 секунд для тяжёлых шатдаунов мало, дальше прилетает SIGKILL; PodDisruptionBudget от добровольных выселений при drain нод. Тонкость, которую проверяют отдельно: удаление пода из endpoints и отправка SIGTERM происходят параллельно, поэтому preStop со sleep 5 — не костыль, а способ дождаться, пока балансеры перестанут слать трафик на умирающий под. Строгий режим «сначала новый, потом убьём старый» — maxUnavailable: 0. Откат — kubectl rollout undo.
Уверены, что rolling update = zero downtime из коробки. Без readiness и graceful shutdown юзеры ловят 502 на каждом деплое.
Follow-up интервьюера: зачем preStop со sleep, это же костыль? — нет: удаление из endpoints и SIGTERM идут параллельно, sleep даёт балансерам время перестать слать трафик.
12. Network Policies — как ограничить трафик между подами?
По умолчанию все поды видят всех — плоская сеть без ограничений. NetworkPolicy — файрвол уровня подов: селекторами выбираешь цели и описываешь разрешённый ingress/egress по подам, namespace'ам и CIDR. Политики аддитивны: запрещающих правил нет, каждая новая только расширяет разрешённое. Базовый паттерн — deny-all с пустым podSelector, дальше точечные разрешения:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
spec:
podSelector: {}
policyTypes: ["Ingress", "Egress"]
Главное: работают только если CNI их поддерживает — Calico и Cilium умеют, flannel нет: политика применится, запишется в etcd и не сделает ничего. Отлаживать удобно временным подом с netshoot: kubectl run tmp --rm -it --image=nicolaka/netshoot.
Пишут политику, она «не работает» — а это CNI не умеет. Тот, кто про это знает, сразу выглядит человеком с продом за плечами.
Follow-up интервьюера: закрыли egress — что сломается первым? — DNS: без явного разрешения порта 53 UDP/TCP до kube-dns под «слепнет» раньше, чем дойдёт до полезного трафика.
Блок 2. Вопросы по Docker и контейнерам
13. Чем контейнер отличается от виртуалки?
Контейнер — это процесс на общем ядре хоста, изолированный через namespaces и cgroups. Виртуалка — своё ядро и виртуальное железо через гипервизор. Отсюда всё остальное: старт за миллисекунды против минут, образ в мегабайтах против гигабайтов, плотность на хосте выше в разы. Обратная сторона: изоляция слабее — побег из контейнера означает доступ к ядру хоста, а уязвимость ядра бьёт по всем контейнерам сразу. И следствие, которое проверяет понимание: ядро общее, поэтому Linux-контейнеры на Windows и macOS крутятся внутри скрытой виртуалки — Docker Desktop это ровно она. На собесе сильный ход — сразу назвать критерий выбора: недоверенный код и жёсткая мультитенантность — VM или песочницы, свой код и плотность — контейнеры.
«Контейнер — это лёгкая виртуалка». Фраза настолько заезженная, что интервьюеры специально её ждут, чтобы копнуть.
Follow-up интервьюера: а если нужна изоляция сильнее, но UX контейнерный? — gVisor, Kata Containers, Firecracker: контейнерный интерфейс с изоляцией ближе к VM, их берут для мультитенантных сред.
14. Что такое слои образа и как его похудеть?
Каждая инструкция Dockerfile — слой, слои неизменяемы, кешируются и переиспользуются между образами и сборками. Изменил инструкцию — инвалидируются она и всё, что ниже. Отсюда правила похудения и скорости: маленький базовый образ (alpine, distroless), multi-stage build, объединение RUN с чисткой кешей в том же слое (apt-get install && rm -rf /var/lib/apt/lists/* одной инструкцией), .dockerignore, зависимости ставим до копирования кода — lock-файл меняется редко, код каждый коммит, и кеш зависимостей живёт. Современный штрих: BuildKit с RUN --mount=type=cache кеширует пакетные менеджеры между сборками, не раздувая сами слои. Похудение — это не эстетика: меньше образ — быстрее pull при скейлинге и меньше поверхность атаки.
Делают rm -rf отдельным RUN и удивляются, что образ не похудел — файлы никуда не делись, они остались в предыдущем слое.
Follow-up интервьюера: как найти, какой слой раздул образ? — docker history --no-trunc или утилита dive: она показывает содержимое каждого слоя пофайлово.
15. ENTRYPOINT vs CMD?
ENTRYPOINT — что запускаем, CMD — аргументы по умолчанию (или команда целиком, если ENTRYPOINT не задан). docker run image args подменяет CMD, --entrypoint — ENTRYPOINT. Рабочий паттерн: ENTRYPOINT ["/app/server"] плюс CMD ["--config", "/etc/app.yaml"] — образ ведёт себя как бинарь, которому можно передать другие флаги. Тонкость, которая решает: exec-форма против shell-формы. Shell-форма (ENTRYPOINT /app/server) оборачивает команду в /bin/sh -c, PID 1 становится sh — и сигналы до приложения не доходят: SIGTERM ловит шелл и молчит. Exec-форма пишется JSON-массивом и делает PID 1 само приложение. Быстрая самопроверка: зайди в контейнер и глянь ps — если PID 1 это sh, проблемы с graceful shutdown уже заложены.
Вопрос «почему контейнер не гасится по SIGTERM, а через 10 секунд прибивается насильно». Ответ — shell-форма. Мало кто отвечает.
Follow-up интервьюера: когда shell-форма всё-таки уместна? — когда нужна подстановка переменных или пайп в команде, но тогда сигналы обрабатывай явно через exec в скрипте.
16. Зачем нужен multi-stage build?
Несколько FROM в одном Dockerfile: собираешь в тяжёлом stage со всеми компиляторами, в финальный тонкий образ забираешь только артефакт через COPY --from. В проде нет исходников, тулчейна и лишних 800 МБ — меньше размер, меньше поверхность атаки. Ответ на «зачем» из трёх слов: размер, безопасность, кеш.
FROM golang:1.22 AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /app ./cmd/server
FROM gcr.io/distroless/static
COPY --from=build /app /app
ENTRYPOINT ["/app"]
Стадия с go mod download пересобирается только при смене lock-файла — это и есть третье слово, кеш. Финальный образ на distroless — десятки мегабайт без шелла: атакующему внутри буквально нечем работать, но и kubectl exec в него не зайдёшь — дебаг через ephemeral containers.
Не могут объяснить «зачем, если и так работает». Ответ из трёх слов: размер, безопасность, кеш.
Follow-up интервьюера: как в CI гонять тесты, не собирая финальный образ? — docker build --target build: собирается только нужный stage.
17. Что происходит при docker run?
Клиент → dockerd → containerd → runc: демон принимает запрос, containerd управляет жизненным циклом, runc — маленькая утилита, которая реально создаёт контейнер и завершается. Проверяется локальный образ, нет — pull из registry по слоям. Создаются namespaces и cgroups, поверх read-only слоёв образа вешается тонкий RW-слой контейнера (copy-on-write: файл копируется в него при первой записи), настраивается сеть — veth-пара, один конец в контейнере, другой в bridge docker0, NAT наружу. Стартует процесс с PID 1. Важное следствие: данные в RW-слое умирают вместе с контейнером — всё, что должно жить дольше, выносится в volumes. Именно сюда интервьюер обычно перекидывает мостик следующим вопросом — будь готов.
Не знают, что данные в RW-слое умирают вместе с контейнером. Отсюда интервьюер обычно перекидывает мостик к volumes — будь готов.
Follow-up интервьюера: перезапустили докер-демон — что с контейнерами? — по умолчанию умрут вместе с ним; спасает live-restore: true в daemon.json.
18. Namespaces и cgroups — что за что отвечает?
Namespaces определяют, что процесс видит: pid (своя нумерация процессов, внутри свой PID 1), net (свой сетевой стек и интерфейсы), mnt (свои маунты), uts (свой hostname), ipc, user (маппинг UID). Cgroups — сколько ему можно: CPU, память, IO, число процессов. Контейнер = обычный процесс + namespaces + cgroups + слоёная файловая система. Никакой магии — с хоста контейнерные процессы видны обычным ps. Наглядный трюк для собеса: docker run --pid=host — и контейнер видит все процессы хоста, вот и вся «изоляция», она опциональна и разбирается на части. Отсюда же вопрос про безопасность: user namespace по умолчанию не включён, поэтому root в контейнере — это root на хосте, пока не настроен userns-remap или rootless-режим.
Путают местами — изоляцию с лимитами. Это вопрос-фильтр: по нему сразу видно, понимает человек, как докер устроен под капотом, или просто выучил команды.
Follow-up интервьюера: что происходит при docker run с --memory=512m под капотом? — докер пишет лимит в cgroup контейнера, и при превышении процесс убивает ядро, а не докер.
19. Какие сети есть в Docker?
Bridge — дефолт, NAT через docker0. Host — без сетевой изоляции, контейнер сидит на портах хоста: минус изоляция, плюс производительность, так гоняют сетевые агенты. None — без сети вообще. Overlay — связь контейнеров между хостами в Swarm-сетапах. Публикация портов: -p host:container. Ключевая разница, которую спрашивают: в user-defined bridge контейнеры видят друг друга по имени через встроенный DNS, в дефолтной bridge резолв по имени не работает — отсюда классика «почему app не видит db». Диагностика — docker network inspect bridge: подсети, шлюз, кто подключён. Неприятный граничный случай: -p 8080:80 открывает порт всему миру в обход UFW — докер пишет правила напрямую в iptables, поэтому для локального доступа публикуй явно: -p 127.0.0.1:8080:80.
Не знают, что в дефолтной bridge резолв по имени не работает — только в user-defined. Классический вопрос «почему app не видит db по имени».
Follow-up интервьюера: чем compose решает проблему «app не видит db»? — он сам создаёт user-defined сеть проекта, и сервисы резолвятся по именам из compose-файла.
Блок 3. Вопросы по сетям
20. Что происходит после ввода URL в браузере?
Вопрос-скелет: интервьюер смотрит, умеешь ли ты раскладывать систему по слоям, и в любой точке может копнуть глубже. Каркас такой. Сначала DNS: кеш браузера → кеш ОС → рекурсивный резолвер провайдера или 8.8.8.8 → в худшем случае полная рекурсия до авторитативного сервера. Получили IP — TCP-рукопожатие: SYN → SYN-ACK → ACK, полтора round-trip'а до первого байта данных. Для https сверху TLS-рукопожатие: обмен ключами, проверка сертификата. Дальше HTTP-запрос — и на стороне сервера своя цепочка: CDN или балансер → reverse proxy → приложение → база. Ответ едет обратно, браузер парсит HTML и тянет ресурсы. Рассказывай именно каркасом, вслух обозначая «здесь могу углубиться в DNS, здесь в TLS» — это и есть сеньорская подача: ты показываешь карту, а не зубрёжку.
Начинают с середины и тонут в деталях одного слоя, не показав картину целиком. Или на «а где здесь может быть медленно?» не могут назвать ни одной точки — хотя их тут буквально каждая.
Follow-up интервьюера: где на этом пути кеши? — DNS по TTL, keep-alive и переиспользование TCP/TLS-сессий, CDN, кеш браузера — назови хотя бы три уровня.
21. TCP vs UDP — почему это до сих пор спрашивают?
TCP — соединение с гарантиями: доставка, порядок, ретрансмиты потерянного, flow control и congestion control. Цена — рукопожатие перед первым байтом и задержки на восстановление потерь. UDP — просто датаграммы: отправил и забыл, ни порядка, ни гарантий, зато без setup-задержек и state на сервере. Выбор по задаче: HTTP-API, базы, всё транзакционное — TCP; DNS-запросы, метрики statsd, стриминг и VoIP, где опоздавший пакет хуже потерянного, — UDP. Прод-угол, который выделяет DevOps-ответ: у TCP есть операционные болячки — забитые очереди соединений, куча сокетов в TIME_WAIT после шторма коротких коннектов, обрывы по таймауту у NAT/балансеров на долгих молчаливых соединениях. Смотреть — ss -s и ss -tan state time-wait | wc -l.
Отвечают определением из учебника и не могут привести ни одного примера, где осознанно выбрали бы UDP. «Надёжный TCP» без слов про цену этой надёжности — ответ джуна.
Follow-up интервьюера: почему HTTP/3 ушёл на UDP? — чтобы избавиться от head-of-line blocking TCP: QUIC сам рулит доставкой и мультиплексирует потоки независимо.
22. Как работает DNS: рекурсия, TTL, типы записей
Клиентский stub-резолвер спрашивает рекурсивный резолвер (провайдер, 8.8.8.8, корпоративный). Тот при пустом кеше идёт по иерархии: корневые серверы → серверы TLD-зоны (.ru) → авторитативный сервер домена — и кеширует ответ на срок TTL. Поэтому «обновление DNS» не мгновенное: пока TTL не истёк у резолверов по всему миру, часть юзеров ходит по старому адресу — перед миграцией TTL заранее снижают до 60-300 секунд. Записи, которые надо знать: A/AAAA — имя в IP, CNAME — алиас на другое имя (на apex-домене запрещён, отсюда ALIAS/ANAME у провайдеров), TXT — верификации и SPF/DKIM, SRV — хост+порт для сервисов, NS и MX. Отладка — dig +trace example.com для полной цепочки и dig @8.8.8.8 чтобы спросить конкретный резолвер мимо локального кеша.
«Поменяли запись — сайт сразу переехал». Не сразу: TTL. Кто не может объяснить, чем рекурсивный резолвер отличается от авторитативного сервера, DNS видел только в панели регистратора.
Follow-up интервьюера: поменяли A-запись, половина юзеров ходит на старый IP — почему? — TTL ещё не истёк, а часть резолверов кеширует дольше положенного; лечится заблаговременным снижением TTL.
23. TLS-рукопожатие и цепочка сертификатов — что происходит при https?
Клиент шлёт ClientHello: поддерживаемые версии, шифры и SNI — имя хоста открытым текстом, чтобы сервер с десятком сайтов на одном IP отдал нужный сертификат. Сервер отвечает выбранным шифром и сертификатом, стороны договариваются о сессионном ключе — дальше летит симметричное шифрование, потому что асимметричное дорогое. В TLS 1.3 рукопожатие ужато до одного round-trip. Проверка сертификата — это цепочка доверия: leaf подписан промежуточным CA, тот — корневым, а корневой лежит в хранилище доверия клиента. Сервер обязан отдавать intermediate вместе со своим сертификатом — самая частая ошибка конфигурации. Let's Encrypt автоматизирует всё это через протокол ACME: докажи владение доменом (http-01 — файл на сервере, dns-01 — TXT-запись, wildcard только так) — получи сертификат на 90 дней; короткий срок и есть причина, почему продление обязано быть автоматическим: certbot или cert-manager в Kubernetes. Отладка: openssl s_client -connect host:443 -servername host.
«TLS шифрует асимметрично» — нет, асимметрика только на рукопожатии. И не могут объяснить, зачем intermediate-сертификат: слово «цепочка» слышали, звенья не назовут.
Follow-up интервьюера: в браузере работает, а из curl/Java — ошибка цепочки, почему? — сервер не отдаёт intermediate: браузеры умеют докачивать его сами, строгие клиенты — нет.
24. HTTP-коды, по которым дебажат прод
301 vs 302: постоянный редирект кешируется браузерами намертво — перепутал при переезде, будешь долго выковыривать из кешей; временный — нет. 401 vs 403: 401 — «ты не аутентифицирован, представься», 403 — «я знаю, кто ты, и тебе нельзя»; когда API отдаёт 403 на протухший токен, дебаг у клиентов превращается в гадание. 499 — код nginx, не стандарта: клиент закрыл соединение, не дождавшись ответа — его таймаут короче, чем latency твоего бэкенда. И главная триада прода за reverse proxy: 502 — upstream оборвал соединение или ответил мусором (приложение упало, рестартует, OOM); 503 — некуда слать: нет живых upstream или сработали лимиты; 504 — upstream жив, но не ответил за proxy_read_timeout (по умолчанию у nginx 60 секунд) — ищи медленные запросы и залипшую базу. Эта триада — готовое дерево диагностики: по коду сразу видно, куда смотреть.
«5xx — значит сервер упал». Три разных кода — три разных диагноза, и кто их не различает, будет дебажить всё подряд. Про 499 не слышали почти все, кто не работал с nginx в проде.
Follow-up интервьюера: в логах всплеск 499 — кто виноват? — бэкенд тормозит, клиенты уходят раньше ответа: чини latency или согласуй таймауты, nginx тут только вестник.
25. Reverse proxy: зачем nginx перед приложением?
Reverse proxy стоит на стороне сервера и представляет бэкенды наружу одним фасадом. Что он забирает на себя: TLS-терминация (сертификаты в одном месте, приложение живёт по plain HTTP внутри); буферизация — nginx принимает запрос от медленного клиента целиком и отдаёт бэкенду мгновенно, поэтому дорогие воркеры приложения не висят на клиентах с плохим интернетом; балансировка между инстансами с health-check'ами; статика; rate limiting; gzip; единые логи и заголовки — X-Forwarded-For, чтобы приложение видело реальный IP клиента, а не адрес прокси. Минимальный рабочий конфиг:
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 60s;
}
В Kubernetes та же роль у ingress-контроллера — который чаще всего и есть nginx.
«Nginx — это веб-сервер для статики» и всё. Или не могут объяснить, зачем прокси перед приложением, у которого «и так есть свой HTTP-сервер» — слово «буферизация» не всплывает вообще.
Follow-up интервьюера: чем reverse proxy отличается от forward proxy? — reverse стоит у сервера и скрывает бэкенды, forward — у клиента и скрывает клиентов.
Блок 4. Вопросы по Linux
26. Что такое inode и почему «места нет», когда df показывает свободно?
Inode — структура метаданных файла: права, владелец, timestamps, указатели на блоки данных. Имя файла живёт не в inode, а в директории — директория и есть таблица «имя → номер inode». Inodes — отдельный ресурс: df -h показывает байты, df -i — inodes. Классика прода: миллионы мелких файлов (сессии, кеш, недоротированные логи) съедают inodes при свободных гигабайтах — и система орёт «No space left on device». Найти виновника: du --inodes -s /* и дальше вглубь по самому жирному каталогу. Важная деталь: число inodes в ext4 фиксируется при mkfs — «долить» их на живой ФС нельзя, только пересоздавать. Бонус: hard link — то же inode под другим именем, symlink — отдельный файл со ссылкой на путь.
Про df -i не знают вообще. «Место есть, но писать нельзя» ставит в тупик даже мидлов — а это вопрос-фильтр на реальный прод-опыт.
Follow-up интервьюера: удалили оригинал — что с hard link и symlink? — hard link продолжит работать (это то же inode), symlink протухнет: он ссылался на путь.
27. Load average — как читать эти три числа?
Средняя длина очереди процессов за 1, 5 и 15 минут: те, кто выполняется или ждёт CPU, плюс те, кто висит в D-state (uninterruptible sleep, обычно IO). Сравнивать надо с числом ядер: LA 8 на 8 ядрах — впритык, на 32 — тишина, абсолютное число само по себе не значит ничего. Три числа дают динамику: 12 / 4 / 1 — что-то началось только что, 1 / 4 / 12 — уже отпускает. Ключевое отличие Linux от учебников: из-за D-state высокий LA не обязательно означает загруженный процессор — сервер может «гореть» по LA при idle CPU, потому что толпа процессов стоит в очереди за диском или зависшей NFS. Кто именно висит в D-state, видно через ps -eo state,pid,cmd | grep '^D'.
Уверены, что LA — это проценты загрузки CPU. Потом видят LA 40 при idle-процессоре и зависают. А это диск или NFS: процессы стоят в очереди за IO, ЦПУ ни при чём.
Follow-up интервьюера: как доказать, что это IO, а не CPU? — vmstat 1: колонка r — очередь на процессор, b — заблокированные на IO; плюс iostat -x по утилизации дисков.
28. Сервер тормозит: кто сожрал CPU, память, диск?
CPU: top/htop, ps aux --sort=-%cpu. Память: free -h, смотреть на available, а не на free — Linux специально держит свободную память под page cache и отдаёт по требованию, «свободной» памяти на здоровом сервере и должно быть мало. Диск по месту: df -h, потом du -sh /* вглубь или сразу ncdu. Дисковая нагрузка (не место) — iostat -x 1 и iotop: кто именно пишет. Коварный кейс, который отличает практика: удалили гигабайты логов, а df не изменился — процесс держит открытый дескриптор удалённого файла, место освободится только с его смертью; ищется через lsof +L1, лечится рестартом или truncate по дескриптору в /proc. Отвечай методом, а не перечислением команд: сначала определить тип голода, потом найти процесс, потом решать.
«У нас память кончилась, free почти ноль» — а это кеш, так и должно быть. И вечная история: удалили гигабайты логов, df не изменился, паника. Кто называет lsof +L1 — сразу свой.
Follow-up интервьюера: CPU занят, но не твоими процессами — что за %st в top? — steal time: процессор отбирают соседи по гипервизору, изнутри виртуалки это не лечится.
29. Как грузится Linux — от кнопки до логина?
BIOS/UEFI инициализирует железо и находит загрузчик → GRUB грузит в память ядро и initramfs → ядро распаковывается, поднимает драйверы из initramfs и монтирует настоящий корень → стартует PID 1, то есть systemd → тот по графу зависимостей параллельно поднимает юниты до нужного target — и ты видишь логин. Главный проверочный вопрос здесь — «зачем initramfs?», и ответ простой: драйверы диска, LVM и RAID нужны раньше, чем доступен корень, где они лежат, — курица и яйцо решаются временной файловой системой в памяти. Практическая часть, за которую любят: медленный старт разбирается через systemd-analyze blame, а ещё точнее — systemd-analyze critical-chain: он показывает цепочку блокировок, а не просто список долгих юнитов.
После слова «GRUB» сразу «и система загрузилась». Вопрос «зачем initramfs» — тишина. А ответ простой: драйверы диска и LVM/RAID нужны раньше, чем доступен корень, где они лежат.
Follow-up интервьюера: откуда ядро берёт initramfs? — его загружает в память GRUB вместе с ядром, оба пути прописаны в конфиге загрузчика.
30. Права, SUID, sticky bit — что за магия с /tmp и passwd?
База: rwx для owner/group/other, числами — 755, 644. Дальше спецбиты, на которых и проверяют: SUID — бинарь исполняется от владельца файла, а не запустившего, именно поэтому обычный юзер меняет себе пароль — /usr/bin/passwd с SUID пишет в /etc/shadow от root; SGID на директории — новые файлы наследуют её группу, паттерн общих папок команд; sticky bit на /tmp — писать могут все, а удалить файл может только его владелец, иначе юзеры сносили бы чужие файлы. В числовой записи спецбиты — четвёртая цифра спереди: 4755 (SUID), 2775 (общая папка), 1777 (/tmp). Поиск SUID-бинарей — find / -perm -4000 — классический вектор эскалации привилегий, security-часть собеса начинается отсюда. Умение прочитать rwsr-xr-x вслух — маленький, но верный сигнал практики.
«Почему обычный юзер меняет себе пароль, если /etc/shadow доступен только root?» Ответ — SUID на /usr/bin/passwd. Простейший вопрос, а срезает многих.
Follow-up интервьюера: что значит 1777 на /tmp? — rwx для всех плюс sticky bit: пишут все, удаляет только владелец.
31. Зомби-процессы — что это и как убить?
Процесс завершился, но родитель не прочитал его exit-код через wait() — в таблице процессов висит запись со статусом Z. Ресурсы он не ест — ни памяти, ни CPU, только строчка в таблице и занятый PID. Но толпа зомби — симптом бага в родителе, и это не безобидно: PID конечны (/proc/sys/kernel/pid_max), зомби, плодящиеся в цикле, в итоге кладут форки по всей системе. Убить зомби нельзя — он уже мёртв, kill по нему бессмысленен по определению. Лечится починкой родителя или его добиванием: сирот усыновляет PID 1 и подчищает через wait(). Найти: ps aux | awk '$8 ~ /Z/' или счётчик zombie в шапке top.
Предлагают kill -9 по зомби. И мало кто связывает с контейнерами: если PID 1 в контейнере — твоё приложение без reaper-логики, зомби копятся, отсюда tini и init: true в compose.
Follow-up интервьюера: причём тут контейнеры? — в контейнере PID 1 — твоё приложение, а оно чужих детей не подчищает: нужен tini или init: true.
32. Напиши сервис под systemd
Юнит-файл в /etc/systemd/system/myapp.service, три секции:
[Unit]
Description=My app
After=network.target
[Service]
ExecStart=/opt/myapp/bin/server --config /etc/myapp.yaml
User=myapp
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
Дальше systemctl daemon-reload (обязательно после любой правки юнита), systemctl enable --now myapp. Логи — journalctl -u myapp -f. Target — это группа юнитов, multi-user.target — примерно бывший runlevel 3. К Restart всегда добавляй RestartSec — иначе получишь systemd-версию CrashLoopBackOff с молотиловкой рестартов. Сеньорский бонус — hardening прямо в юните: NoNewPrivileges=true, ProtectSystem=full, PrivateTmp=true; насколько сервис «голый», покажет systemd-analyze security myapp.
Правят юнит-файл и полчаса дебажат, почему ничего не поменялось — забыли daemon-reload. Интервьюеры это спрашивают именно потому, что на этом горел каждый.
Follow-up интервьюера: чем enable отличается от start? — start запускает сейчас, enable прописывает автостарт (симлинк в target); enable --now делает оба.
33. Приложение умерло молча — где искать?
Порядок: journalctl -u сервис --since "1 hour ago", потом логи ядра — dmesg или journalctl -k: если процесс прибил OOM killer, запись будет именно там, а не в логах приложения — приложению никто не даёт слова, ему прилетает SIGKILL. Полезно глянуть и systemctl status: он показывает exit code и сигнал — SIGKILL при исчерпанной памяти сразу намекает на OOM, там же видно время смерти. Классика по файлам: /var/log/syslog или messages, auth.log для SSH и sudo. По ротированным архивам — zgrep, чтобы не распаковывать руками. Этот вопрос — фильтр на системность: интервьюер слушает, есть ли у тебя порядок поиска, или ты тыкаешься наугад.
«Приложение упало без единой строчки в логах, мистика». Не мистика, а OOM killer — он пишет в kernel log. Кто сразу говорит dmesg | grep -i oom, тот прод видел.
Follow-up интервьюера: а если это сегфолт? — coredumpctl list и разбор дампа, если coredump'ы включены; в journalctl будет запись о SIGSEGV.
Блок 5. Вопросы по CI/CD и деплою
34. Как устроен нормальный пайплайн?
Стадии: build → тесты и линтеры → сборка артефакта (образ + push в registry) → деплой в staging → интеграционные и смоук-тесты → прод, с ручным гейтом или автоматом. Два принципа, которые отличают взрослый пайплайн от набора скриптов: артефакт собирается один раз и едет по средам неизменным, меняется только конфиг — иначе в проде крутится не то, что тестировали; fail fast — дешёвые проверки стоят первыми, чтобы не ждать 20 минут ради упавшего линтера. И весь пайплайн лежит кодом в репозитории рядом с приложением: ревьюится, версионируется, откатывается как любой другой код. Хорошо звучит и разделение CI/CD по смыслу: CI отвечает на вопрос «этот коммит вообще жизнеспособен?», CD — «как он доедет до юзеров?»
«На каждой среде собираем заново». Значит, в проде крутится не тот артефакт, который тестировали. Для интервьюера это красный флаг размером с баннер.
Follow-up интервьюера: пайплайн медленный — что делать? — кеш зависимостей, параллельные джобы, docker layer cache, запуск стадий только по изменённым путям: назови хотя бы три.
35. Blue-green vs canary — что выбрать?
Blue-green: две одинаковые среды, трафик переключается разом, откат — переключение обратно, мгновенный. Цена — двойная инфраструктура и требование совместимости обеих версий с одной базой. Canary: новая версия получает 1-5-25% трафика, метрики сравниваются с базовой, раскатка постепенная — дешевле и ловит проблемы на малой доле юзеров, но требует нормального мониторинга и автоматики отката: canary без метрик — это просто медленный деплой. Rolling — дефолт Kubernetes, серединка без контроля процента трафика. Нюанс для Kubernetes: без сервис-меша canary — это пропорция подов, а не трафика; честный процентный split дают Istio/Linkerd или Argo Rollouts и Flagger, которые заодно автоматизируют анализ метрик и откат. Выбор формулируй от риска и бюджета: деньги на двойную среду и страх даунтайма — blue-green, зрелый мониторинг — canary.
Добивающий вопрос — «а что с миграциями БД при blue-green?» Схема должна быть совместима с обеими версиями одновременно (expand-contract), иначе мгновенный откат — фикция. На этом сыпятся почти все.
Follow-up интервьюера: как сделать canary по проценту трафика в Kubernetes? — стандартным Deployment никак, только пропорцией подов; процент дают сервис-меш или Argo Rollouts/Flagger.
36. Секреты в CI/CD — как правильно?
Никогда: в репозитории, в Dockerfile (слои помнят всё), в логах пайплайна. Минимум: встроенное хранилище CI с masked и protected переменными, привязка к защищённым веткам — секрет прода не должен читаться из ветки любого разработчика. Правильно: внешний Vault или SSM, короткоживущие токены, OIDC-федерация вместо статичных ключей — раннер получает временный доступ к облаку без вечного секрета в настройках CI, красть становится просто нечего. Плюс ротация, отдельные секреты на каждую среду и аудит доступа. Отдельно назови угрозу, о которой забывают: секрет утекает не только из хранилища — echo в лог, дамп env в отладке, артефакт с конфигом. Masked-переменные прикрывают первое, культура ревью пайплайнов — остальное.
«Храним в переменных CI» — и всё, дальше пусто. Вопрос «как сделать, чтобы секрет не был доступен из ветки любого разработчика» разделяет тех, кто настраивал, и тех, кто слышал.
Follow-up интервьюера: как раннер ходит в облако без вечного ключа? — OIDC: CI предъявляет JWT с клеймами (репозиторий, ветка), а IAM-роль облака доверяет только им.
37. Как версионировать артефакты?
Библиотеки — SemVer: major ломает, minor добавляет, patch чинит. Сервисы — тег образа из версии плюс короткого SHA коммита: 1.4.2-a1b2c3d. Это трассируемость: от контейнера в проде до конкретного коммита за один взгляд, в обе стороны. latest в проде — табу: невоспроизводимо, непонятно, что реально крутится, откат превращается в угадайку, а imagePullPolicy добавляет лотерею «какой именно latest притянула нода». В registry — retention-политики, чтобы не хранить тысячи мёртвых тегов, но теги, которые были в проде, не трогаем — они нужны для отката и расследований. Граничный случай: docker-тег мутабелен — тот же 1.4.2 можно молча перезаписать, поэтому строгая воспроизводимость — это деплой по digest (image@sha256:...).
«Деплоим latest». Следующий вопрос всегда — «какая версия сейчас в проде и как откатиться на предыдущую?» Дальше неловкая пауза.
Follow-up интервьюера: тег могли перезаписать — как гарантировать, что в проде тот самый образ? — деплой по digest и подпись артефактов (cosign): на позициях с уклоном в supply chain это уже спрашивают.
38. Прод лёг после релиза — действия?
Сначала вернуть сервис, потом разбираться — дебаг на лежащем проде оплачивают юзеры. Механика отката: kubectl rollout undo, helm rollback или переключение blue-green — предыдущий образ ещё в registry, на то и версионирование. Главная засада — миграции БД: если схема уже несовместима со старой версией, откат приложения уронит всё ещё сильнее. Поэтому expand-contract: сначала добавляем совместимое (новые колонки, nullable), старое удаляем только через релиз, когда старой версии уже нет нигде. Feature flags дают откат вообще без деплоя — выключил флаг, живёшь дальше. После пожара — post-mortem без поиска виноватых: цель — чтобы класс проблемы не повторился, а не чтобы кто-то покаялся.
Бодро рассказывают про rollout undo, а вопрос «миграция уже прошла — что теперь?» показывает, был ли человек в проде в три часа ночи или читал про это в статьях.
Follow-up интервьюера: что, кроме схемы БД, должна переварить старая версия при откате? — данные, которые успела создать новая: сообщения в очередях, записи в кеше, новые форматы полей.
39. GitFlow vs trunk-based?
GitFlow: develop, feature, release, hotfix — подходит коробочным продуктам с версиями и долгими релизными циклами, но долгоживущие ветки означают merge-ад и медленную поставку: интеграция откладывается неделями, а потом взрывается конфликтами разом. Trunk-based: все мёржат в main мелкими порциями, ветка живёт максимум пару дней, недоделанные фичи прячутся за флагами, каждый мёрж потенциально деплоится. Для continuous deployment выбор один — trunk-based. И честно скажи про цену: trunk-based не живёт без быстрого CI, ревью в тот же день и культуры выпиливания мёртвых feature-флагов — иначе main превращается в минное поле из полувыключенных фич. Ответ уровня сеньора — не «что лучше», а «что чему соответствует»: модель веток — производная от модели релизов.
«У нас GitFlow» без понимания зачем. Честный ответ на «как GitFlow уживается с CD?» — никак. Сказать это вслух решаются единицы, и именно они выглядят сильно.
Follow-up интервьюера: а релизы в trunk-based как? — тегом прямо из main либо короткой release-веткой только на стабилизацию.
40. Helm: что такое chart, release и values — и как откатываться?
Helm — пакетный менеджер Kubernetes. Chart — шаблоны манифестов плюс values.yaml с дефолтами; release — установленный экземпляр chart'а в конкретном namespace, со своей историей ревизий. Values перекрываются слоями: дефолты chart'а → -f values-prod.yaml → --set — так один chart обслуживает все среды, различия только в values-файлах. В CI деплой — идемпотентный helm upgrade --install. Состояние release Helm хранит в Secret'ах вида sh.helm.release.v1.myapp.v3 в namespace релиза — поэтому история переживает что угодно, кроме удаления namespace. Откат: helm rollback myapp 3 — возвращает манифесты ревизии 3. Отладка шаблонов до кластера: helm template, а diff перед апгрейдом — плагин helm-diff: без него upgrade — прыжок с завязанными глазами.
«helm rollback вернёт всё как было». Манифесты — да, данные — нет: прошедшие миграции и содержимое PVC откатом не лечатся. И почти никто не знает, где Helm хранит состояние релиза.
Follow-up интервьюера: чем helm upgrade лучше kubectl apply? — Helm ведёт историю ревизий и сам удаляет ресурсы, выпиленные из chart'а; apply брошенное не подчищает.
41. GitOps и ArgoCD — чем это лучше «CI пушит в кластер»?
Классика: пайплайн делает kubectl apply или helm upgrade. Проблемы: у CI-системы вечные админ-креды кластера (жирная цель для атаки), реальное состояние прода нигде не зафиксировано, ручные правки в кластере живут незамеченными до следующего деплоя. GitOps разворачивает поток: в git лежит желаемое состояние, а оператор внутри кластера (ArgoCD, Flux) сам тянет изменения и непрерывно реконсилирует — сравнивает кластер с git и чинит расхождения. Что это даёт: git — единственный источник правды, аудит — это git log, откат — git revert, дрифт виден в UI и при включённом self-heal чинится автоматически, кластерные креды не покидают кластер. CI при этом остаётся: собирает образ, гоняет тесты и коммитит новый тег в репозиторий манифестов — руками или через image updater. Деплой перестаёт быть действием и становится состоянием.
Называют GitOps'ом «у нас манифесты лежат в гите», хотя деплоит их CI пушем. Суть не в месте хранения, а в pull-модели и постоянной реконсиляции — без оператора это просто git с YAML.
Follow-up интервьюера: HPA меняет replicas, ArgoCD возвращает как в git — что делать? — ignoreDifferences на это поле либо вообще не фиксировать replicas в манифестах.
Блок 6. Вопросы по Terraform и IaC
42. Что такое state и зачем нужны локи?
terraform.tfstate — карта «что в коде ↔ что реально создано»: ID ресурсов, атрибуты, зависимости. Именно по state строится diff в plan: без него Terraform не знает, что вот этот aws_instance в коде — это вон та машина в облаке. В команде state лежит в remote backend (S3 + DynamoDB, GCS) с локом: без лока два одновременных apply рвут state в клочья. Важно: в state секреты лежат открытым текстом — пароль от базы, созданной Terraform'ом, читается из tfstate как из блокнота, поэтому шифрование бакета и доступ по минимуму обязательны. И знай хирургию state руками: terraform state list/show/mv/rm — про них спрашивают сразу после теории, потому что рефакторинг кода без state mv означает пересоздание ресурсов.
«Потеряли state — не страшно, Terraform же видит инфру». Не видит. Для него без state ничего не существует, и apply попробует создать всю инфраструктуру заново поверх живой. Вот тут и начинается настоящее веселье.
Follow-up интервьюера: два инженера запустили apply одновременно — что будет? — второй упрётся в лок и подождёт; без лока оба писали бы в state наперегонки.
43. terraform plan vs apply — что происходит под капотом?
Plan: читает state, делает refresh — опрашивает провайдера о реальном состоянии ресурсов, строит граф зависимостей и показывает diff: create, update, destroy. Apply исполняет план, параллеля независимые ветки графа. Нюанс, который проверяют: apply без сохранённого плана пересчитывает всё заново — между «посмотрел plan» и «нажал apply» мир мог измениться. Хочешь исполнить ровно то, что видел — plan -out=tfplan и apply этого файла; в CI это оформляется как план-артефакт, который апрувят и применяют именно его — иначе апрув ничего не гарантирует. И следи за «forces replacement» в выводе: это не update, это снос и пересоздание ресурса — для стейтфульных вещей равно потере данных. Отдельно полезен plan -refresh-only: показывает дрифт, не предлагая его «исправить».
Пропускают forces replacement глазами и удивляются, куда делась база. Diff читать — отдельный навык, и его проверяют.
Follow-up интервьюера: как защитить критичный ресурс от случайного пересоздания? — lifecycle { prevent_destroy = true }: apply с destroy этого ресурса просто упадёт.
44. Модули — как не копипастить инфру?
Модуль — папка с .tf: variables на входе, outputs на выходе, внутри — связанная группа ресурсов. Корневой модуль вызывает дочерние через source (registry, git, локальный путь) с закреплённой версией. Смысл: один модуль «vpc» или «кластер» на все среды, различия — в tfvars. Выход одного модуля идёт на вход другого: module.network.vpc_id → переменная в module.app — так Terraform сам понимает порядок создания. Анти-паттерн — модуль-монстр на всю инфраструктуру разом: его невозможно ни версионировать, ни применять по частям, blast radius любой правки — вся компания. Версии пиннить обязательно: обновление модуля должно приезжать через осознанный bump в PR, средa за средой — dev, staging, прод, — а не молча во все среды при следующем apply.
Не пиннят версии модулей. Обновил общий модуль — и изменение молча приехало во все среды при следующем apply. Вопрос «как обновлять модуль постепенно» это и проверяет.
Follow-up интервьюера: модули или workspaces для сред? — workspaces делят один код и бэкенд, различающиеся среды на них собирать больно; каталог на среду со своими tfvars — скучно, но предсказуемо.
45. Кто-то поправил инфру руками в консоли — что будет?
Drift. Следующий plan покажет расхождение, и Terraform захочет вернуть всё как в коде — он верит коду, а не консоли. Дальше два пути: изменение нужное — вносим в код (или import для созданных руками ресурсов); ненужное — apply откатит. Регулярный plan в CI по расписанию ловит дрифт раньше, чем он стреляет в момент срочного релиза. Тонкость, которая отличает понимание от заучивания: plan видит не всякий дрифт — ресурсы, созданные мимо Terraform, для него не существуют, он сверяет только то, что есть в state; для полного аудита есть driftctl. Культурное решение сильнее технического: закрыть консоль на запись (read-only для людей, запрет ClickOps на уровне IAM/SCP) и вести все изменения через PR.
Ждут, что Terraform «подхватит» ручные правки. Не подхватит — он верит коду. И вторая мина: ресурс, удалённый руками, apply молча пересоздаст — вместе с дефолтными параметрами, которых никто не ждал.
Follow-up интервьюера: ресурс создали руками мимо Terraform — plan его покажет? — нет: plan сверяет только то, что в state; ищется driftctl'ом или закрывается запретом ClickOps.
46. Как затащить живую инфраструктуру под Terraform?
terraform import — по одному ресурсу: пишешь блок в коде, импортируешь ID в state, гоняешь plan до нулевого diff — только нулевой plan означает, что код действительно описывает реальность. С версии 1.5 есть import-блоки с генерацией конфигурации — сильно быстрее: описываешь что импортировать, Terraform сам предлагает код. Массово — terraformer, но его выхлоп надо причёсывать руками: сгенерированный код без структуры хуже отсутствующего. Стратегия: начать с критичного (сеть, кластер, базы), остальное подтягивать по мере касания — тотальный импорт всего разом парализует команду на месяц. И сразу раскладывай импортированное по модулям: переносить потом через state mv — отдельная боль. Обратную задачу решают removed-блоки (с 1.7): убрать ресурс из state, не разрушая его в облаке.
Импортируют в state и бросают, не доведя код до нулевого plan. Следующий apply предлагает половину «поправить». Импорт без чистого plan — мина с таймером.
Follow-up интервьюера: как вывести ресурс из-под Terraform, не удаляя его? — removed-блок или terraform state rm: ресурс живёт дальше, Terraform о нём забывает.
Блок 7. Базы данных, очереди, мониторинг
47. Как устроена репликация MySQL?
Мастер пишет изменения в binlog, реплики тянут его и проигрывают у себя. По умолчанию всё асинхронно: мастер не ждёт никого, поэтому реплика может отставать — replication lag это не сбой, а свойство схемы. Semi-sync — мастер ждёт подтверждения хотя бы одной реплики; уточнение, которое ценят: реплика подтверждает получение события в relay log, а не его применение — «прочитать своё» с реплики всё равно не гарантировано. GTID сильно упрощает failover: не надо руками высчитывать позиции в binlog, реплика сама знает, что уже проиграла. Схема использования: запись — в мастер, чтение — раскидываем по репликам. Лаг смотрят через SHOW REPLICA STATUS (Seconds_Behind_Source), и это метрика для алертинга, а не для разового взгляда.
«Реплика — это и есть наш бэкап». Нет: DROP TABLE прилетит на реплику через секунду и честно исполнится. Реплика — про доступность и чтение, бэкап — отдельная история. И вечный вопрос про replication lag: читаешь с реплики сразу после записи — можешь получить прошлое.
Follow-up интервьюера: юзер сохранил профиль и увидел старые данные — что это и как лечить? — read-after-write на отстающей реплике: писавшему клиенту читать с мастера или использовать sticky-сессии.
48. Бэкапы: как делать и как понять, что они живые?
Правило 3-2-1: три копии, два типа носителей, одна вне площадки. Логический бэкап (mysqldump, для InnoDB — обязательно с --single-transaction: консистентный срез без глобального лока) — переносимый, но медленный на объёмах; физический (xtrabackup, снапшоты) — быстрый, восстановление терабайта из логического дампа — часы, и это надо знать до аварии, а не во время. Point-in-time recovery: полный бэкап + binlog, восстановиться можно на секунду до DROP TABLE. Главное: бэкап, из которого ни разу не восстанавливались — это не бэкап, а файл с надеждой. Restore-тесты по расписанию, в идеале автоматические: подняли из бэкапа, прогнали проверки, отчитались. RPO (сколько данных можно потерять) и RTO (сколько можно лежать) определены заранее и записаны — под них и выбирается вся схема.
«Бэкапы есть» — «когда последний раз проверяли восстановлением?» — тишина. Этот вопрос-фильтр я слышал десяток раз, и тишина в ответ — самый частый исход.
Follow-up интервьюера: бэкап снимаете с реплики — какой подвох? — унесёшь её лаг: реальный RPO хуже, чем думаешь, если реплика отставала в момент бэкапа.
49. Kafka на пальцах
Это не очередь, а распределённый лог. Продюсеры пишут в топики, топики нарезаны на партиции, консюмеры читают группами: внутри группы каждую партицию читает ровно один консюмер. Offset — позиция чтения, хранится per group, поэтому один топик спокойно читают десять разных сервисов независимо, каждый в своём темпе. Сообщения при чтении не удаляются — живут по retention (по умолчанию 7 дней), и это фундаментальное отличие от очередей: прочитанное можно перечитать, новый сервис может отмотать историю. Порядок гарантирован только внутри партиции, ключ сообщения определяет, в какую партицию оно попадёт — события одного юзера с ключом user_id всегда придут по порядку. Гарантии доставки: по умолчанию at-least-once, exactly-once — это идемпотентный продюсер плюс транзакции, и это надо явно включать и понимать цену.
«Kafka — это как RabbitMQ, только быстрее». Дальше добивающий: «консюмеров в группе больше, чем партиций — что будет?» Лишние будут курить: партиции — потолок параллелизма. На этой связке валится каждый второй.
Follow-up интервьюера: главная метрика здоровья консюмеров? — consumer lag: разница между последним offset партиции и позицией группы; растёт — потребители не вывозят.
50. Redis — кеш или база?
In-memory хранилище со структурами: strings, hashes, lists, sets, sorted sets. Кейсы: кеш, сессии, rate limiting, лидерборды на sorted sets, простые очереди. Персистентность двух видов: RDB — снапшоты по расписанию (быстрый рестарт, но теряешь хвост с последнего снапшота), AOF — лог операций (надёжнее, тяжелее), можно совмещать. Ключевой вопрос, по которому отличают практика: что происходит при переполнении памяти — политика eviction: noeviction (ошибки на запись), allkeys-lru (выселение по LRU — режим кеша), volatile-* (только ключи с TTL). maxmemory задавай явно, иначе Redis растёт, пока его не прибьёт OOM killer. HA — Sentinel (failover для пары мастер-реплика) или Redis Cluster (шардирование). И помни: команды выполняются в один поток — один KEYS * на большой базе стопорит всех.
Держат в Redis важные данные с дефолтным конфигом и без понимания eviction. Память кончилась — ключи полетели. «Почему у юзеров в пиковые часы слетают сессии?» — вот ровно поэтому. Кто отвечает через eviction policy, тот с Redis жил, а не читал про него.
Follow-up интервьюера: почему KEYS * в проде — преступление? — Redis однопоточный: одна тяжёлая команда останавливает всех; обход ключей — только SCAN.
51. Как устроен Prometheus?
Pull-модель: Prometheus сам ходит по HTTP на /metrics таргетов с заданным scrape_interval — а не приложения шлют в него. Таргеты находятся через service discovery: в Kubernetes — по аннотациям или ServiceMonitor'ам оператора, никаких ручных списков — под родился, его уже скрейпят. Зачем pull: метрика up бесплатно (таргет не ответил — сразу видно), нагрузкой управляет мониторинг, а не тысяча пушащих сервисов. Экспортеры переводят чужие метрики в формат Prometheus: node_exporter — железо, blackbox — проверки снаружи, postgres_exporter и десятки других. Хранение — локальная TSDB; долгие сроки и HA — Thanos, Mimir или VictoriaMetrics сверху. Pushgateway — костыль для короткоживущих джобов: cron-задача умирает раньше, чем её поскрейпят, поэтому пушит результат в gateway. Алерты считает сам Prometheus по правилам, а маршрутизирует и группирует Alertmanager.
Совать сервисы в Pushgateway — анти-паттерн, который выдаёт непонимание модели: метрики там не протухают, up не работает, мёртвый сервис выглядит живым. И на вопрос «зачем pull, а не push» отвечает внятно один из пяти.
Follow-up интервьюера: cron-джоба живёт 10 секунд — как снять метрики? — Pushgateway, и это единственный его легальный кейс.
52. Типы метрик: counter, gauge, histogram — и зачем rate()?
Counter только растёт: http_requests_total, байты, ошибки. Абсолютное значение бессмысленно — интересна скорость: rate(http_requests_total[5m]) даёт прирост в секунду и корректно переживает рестарты (сброс в ноль rate понимает и не рисует провал в минус миллион). Gauge — значение «сейчас», ходит в обе стороны: память, температура, длина очереди, число горутин; rate по нему — бессмыслица, тут avg_over_time и просто график. Histogram раскладывает наблюдения по бакетам (_bucket, _sum, _count), перцентили считаются на стороне запроса: histogram_quantile(0.99, rate(..._bucket[5m])) — и главное, бакеты агрегируются между инстансами. Summary считает квантили на клиенте: дёшево читать, но агрегировать между подами математически нельзя — поэтому в Kubernetes почти всегда histogram.
Усредняют перцентили: среднее от p99 десяти подов — это не p99 сервиса, это бессмыслица. И берут rate от gauge. Обе ошибки видны на первом же скрине Grafana, который просят объяснить.
Follow-up интервьюера: почему нельзя алертить просто на http_requests_total > N? — counter монотонно растёт с рестарта: график вечно вверх, порог сработает у всех — информации ноль без rate.
53. SLI, SLO и error budget — объясни на пальцах
SLI — измеримый показатель того, как сервис видит юзер: доля успешных запросов, доля запросов быстрее 300 мс. SLO — цель по SLI: 99,9% успешных за 30 дней. SLA — то же, но в договоре с клиентом и с деньгами за нарушение; внутренняя цель всегда строже внешнего обещания. Error budget — узаконенная ненадёжность: 100% минус SLO; при 99,9% это примерно 43 минуты даунтайма в месяц, которые можно легально потратить на релизы, эксперименты и аварии. Механика, ради которой всё затевалось: бюджет цел — катим фичи смело; бюджет сожгли — фичи стоп, работаем над надёжностью. Вечный спор «скорость против стабильности» превращается из политики в арифметику. Алерты в этой модели — по burn rate: не «случилась ошибка», а «бюджет сгорит за четыре часа, если так продолжится».
«Наш SLO — 100%». Такого не бывает и не нужно: каждая следующая девятка на порядок дороже, а нулевой бюджет ошибок означает запрет на любые релизы. Кто это говорит, SRE-книжку не открывал.
Follow-up интервьюера: чем SLO отличается от SLA? — SLO — внутренняя цель, SLA — внешнее обещание со штрафами, и SLA всегда мягче SLO.
54. Как построить алертинг, чтобы не сойти с ума?
Алерт — это то, что требует действия человека прямо сейчас. Всё остальное — дашборды. Алертим на симптомы, а не на причины: «у юзеров растут ошибки и latency», а не «CPU 80%» — процессору может быть 95%, а юзерам норм. Уровни severity: что будит ночью, а что превращается в тикет утром — и это два разных канала доставки. В каждом алерте — ссылка на runbook: дежурный в три ночи не должен вспоминать, что делать. Главный враг — alert fatigue: 50 алертов в день равны нулю алертов, их просто перестают читать. База — golden signals: latency, traffic, errors, saturation. Инструментально в Prometheus/Alertmanager: for: в правиле против дребезга, группировка по сервису, inhibition — чтобы упавший кластер не родил сотню дочерних алертов. И метрика зрелости: сколько алертов за месяц не потребовали действия — по ним видно, что чистить следующим.
«Алертим на CPU больше 80%». А юзерам-то что? Ответ «симптомы — в алерты, причины — в дашборды» сразу выдаёт человека, который дежурил, а не настраивал мониторинг по туториалу.
Follow-up интервьюера: упал кластер — как не получить сто алертов разом? — группировка и inhibition в Alertmanager: корневой алерт подавляет дочерние.
Блок 8. HR, деньги, красные флаги
55. «Расскажите о себе» — 90 секунд, не больше
Это не биография, это питч. Формула: кто я сейчас и на каком стеке → 2-3 релевантных вакансии достижения, желательно цифрами → что ищу и почему сюда. Всё. Заготовить заранее и отрепетировать вслух — именно вслух, в голове все звучат гладко. Достижения формулируй как мини-STAR: контекст → что сделал именно ты → измеримый результат («сократил время деплоя с 40 минут до 8»). И закончи мостиком к их вакансии — интервьюеру будет проще подхватить разговор, а разговор пойдёт туда, куда ты его сам направил.
Монолог на десять минут от универа до вчерашнего дня. Интервьюер скучает, время технической части съедено, первое впечатление — «не умеет выделять главное». 90 секунд — и мяч на их стороне.
Follow-up интервьюера: «а расскажите подробнее про этот проект» — ради этого мостики и нужны: заранее реши, куда поведёшь разговор, и оставь туда зацепки.
56. Зарплата: кто первый называет цифру?
Постарайся узнать их вилку раньше, чем назвал свою: «какая вилка заложена под позицию?» — нормальный рабочий вопрос, а не наглость. Если давят — называй вилку, а не точку, и нижняя граница должна быть цифрой, на которую ты реально согласен без обид. Если вилку не говорят ни в какую — называй свою с запасом 10-15% сверху: подвинуться вниз всегда проще, чем вверх. Рынок исследуй до собеса, а не после оффера. Обсуждай total comp целиком: база, бонусы, опционы, а на валютной удалёнке — кто платит налоги. И оффер не требует ответа в ту же секунду: «возьму пару дней подумать» — норма.
Называют цифру первыми и занижают от страха «а вдруг откажут». Компания не обидится на твою вилку — она молча сэкономит. Помни: компаний много, а ты один.
57. «Почему уходите с текущего места?»
Одно правило: не поливать бывших. Даже если там был полный звездец — формулируй через рост: «упёрся в потолок по задачам», «хочу масштаб побольше», «команда сворачивает направление». Интервьюер слушает и примеряет на себя: сегодня ты рассказываешь гадости про них — завтра про нас. Подготовь одну и ту же формулировку для HR и для техлида — расхождения версий на разных этапах замечают и запоминают. Усилитель: свяжи причину ухода с тем, что есть у них, — тогда тот же ответ работает ещё и как «почему именно мы».
Пять минут страданий про токсичного тимлида. Всё, крест на кандидате — даже если тимлид реально был токсичный. Правда никого не оправдывает, такова игра.
58. Что спрашивать у них — и какие ответы означают «беги»
Собес — двусторонний, ты тоже выбираешь. Минимум вопросов: почему открыта позиция и что стало с предыдущим человеком, как устроены релизы и онколл, что с переработками, как выглядит испытательный. Ещё два вопроса с высокой отдачей: «как выглядит типичная неделя человека на этой роли» и «как компенсируется онколл» — уверенный ответ про дежурства без пауз и увиливаний выдаёт взрослые процессы. Красные флаги: «мы тут одна семья», мутность про деньги, «испытательный на особых условиях» и легендарное «первые две недели поработайте бесплатно» — да, такое реально предлагают, у меня есть запись такого собеса.
«Вопросов нет». Читается как безразличие к месту, куда ты якобы хочешь. И практический момент: твои 3-4 вопроса дают про компанию больше информации, чем весь их рассказ о себе.
59. Как говорить «не знаю», чтобы это работало на тебя
«Не знаю» плюс ход мысли — это плюс в карму, блеф — минус навсегда: интервьюер копнёт на уровень глубже и всё увидит. Формула: «с этим не работал, но рассуждая от X, предположу Y, а проверил бы через Z». Я так и устраивался: завалил половину вопросов, честно об этом сказал — и оффер всё равно пришёл. Оценивают не процент правильных ответов, а как ты думаешь и что делаешь на границе своих знаний. Работает и отложенный ход: «не знаю, но разберусь и напишу после собеса» — и реально написать: такие письма запоминаются. Только не обольщайся: три «не знаю» подряд по core-стеку вакансии никакая честность не вытянет.
Начинают выдумывать. Один уточняющий вопрос — и человек тонет на глазах у всех. Одно честное «не знаю» стоит дороже трёх сочинённых ответов.
60. Английское интервью: заготовки, которые спасают
Первые пять минут — чистый скрипт, его можно выучить: приветствие, «thanks for having me», рассказ о себе на 90 секунд — наизусть, до автоматизма. Про опыт: «I'm responsible for...», «I've been working with X for N years», «in my current role I...». Не понял вопрос — «could you rephrase that?» звучит нормально и абсолютно легально. Уровень B1 с заготовками бьёт C1 без подготовки. Отдельно проговори вслух термины своего стека: rollback, downtime, incident, on-call — половина фриза от того, что по-английски они ни разу не произносились. Бесплатный тренажёр — голосовой мок-собес с любой LLM: она и вопросы задаст, и произношение стерпит.
Фриз на первом же вопросе, потому что рассказ о себе ни разу не проговаривался на английском вслух. Я свои английские собесы заваливал именно на мычании, а не на грамматике — запиши себя на диктофон, послушай, поплачь, перепиши. Работает.
61. Тестовое задание: когда делать, а когда бежать
Нормальное тестовое: ограничено 2-4 часами, релевантно позиции, с обещанным фидбеком. Красные флаги: «сделайте прототип нашей фичи», задание на неделю, отсутствие критериев оценки. Альтернатива, которую мало кто использует: предложи вместо тестового разбор твоего пет-проекта или прошлых задач — сильному интервьюеру этого достаточно. Рабочий компромисс: сделать урезанную версию строго в оговорённые 2 часа и разобрать её на созвоне — интервьюеру важен ход мысли, а не полнота фичи. Попросить live coding вместо тестового — тоже валидный ход. Отказаться от неадекватного тестового — нормально.
Неделю пилят бесплатный прототип, получают отказ без фидбека, а через месяц видят свою фичу в проде. Не бойтесь отказов — бойтесь бесплатной работы.
Подготовка к собеседованию
Помогаю готовиться лично: мок-собеседования, разбор ответов, стратегия под конкретную компанию. Пишите в Telegram.
Мок-собесы и разбор ваших ответов — в менторстве и закрытом чате.