66 вопросов на собеседовании DevOps — с ответами и разбором

Как пользоваться подборкой

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

Выберите блок, ответьте вслух до чтения разбора, потом проверьте себя на дополнительном вопросе. Если можете только повторить определение, но не объяснить пример, вот с этим и стоит разобраться перед собесом. Сами разговоры можно посмотреть в архиве собеседований.

Подборки вопросов: DevOps · SRE · DevSecOps · MLOps · AIOps · Linux

Хочешь сравнить банк с живым разговором — вот мой собес на 450К с разбором ошибок. Там я не вспомнил anti-affinity и поплыл на StatefulSet. Ответы здесь — возможность разобраться после записи, а не попытка переписать то, что я сказал тогда.

Блок 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» нельзя отвечать без оговорки: по умолчанию они сохраняются (Retain), но persistentVolumeClaimRetentionPolicy.whenDeleted: Delete меняет это поведение. Отдельная политика whenScaled действует при уменьшении числа реплик. Сам механизм стабилен с Kubernetes 1.32.

Что могут спросить дальше: как 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.

Что могут спросить дальше: а если scheduler лежит? — новые поды зависнут в Pending, но всё уже запущенное продолжит работать как ни в чём не бывало.

3. Чем liveness-проба отличается от readiness и startup?

Liveness отвечает «жив ли контейнер»: провалилась — kubelet перезапускает контейнер. Readiness — «готов ли принимать трафик»: провалилась — под вылетает из endpoints, но продолжает работать; так сервис сам выключает себя из балансировки, пока прогревает кеш или ждёт базу. Startup — для медленно стартующих приложений: пока не пройдёт, liveness и readiness молчат. Конкретика, которую любят: startupProbe с failureThreshold: 30 и periodSeconds: 10 даёт приложению 5 минут на старт — без неё пришлось бы раздувать initialDelaySeconds у liveness. И главное правило прода: liveness должна проверять только сам процесс, без походов в базу и соседние сервисы — иначе отказ зависимости превращается в волну рестартов всего, что от неё зависит.

Говорят «readiness перезапускает под» и не знают, что агрессивный liveness-таймаут под нагрузкой устраивает каскад рестартов.

Что могут спросить дальше: что будет, если startup-проба так и не пройдёт? — контейнер перезапустят: провал сверх failureThreshold — это тоже рестарт, а не вечное ожидание.

4. Под в CrashLoopBackOff / Pending — твои действия?

CrashLoopBackOff: контейнер стартует и падает, kubelet повторяет запуск с нарастающей паузой. Привычный предел — 5 минут, но он зависит от настроек kubelet и включённых возможностей кластера. Смотрю kubectl logs --previous (логи именно упавшего контейнера, а не нового) и kubectl describe pod: код 137 означает завершение по SIGKILL, но сам по себе ещё не доказывает OOM: проверяю lastState.terminated.reason, события и логи ядра. Код 1 — общий ненулевой код выхода, причину ищу в логах приложения. Частые причины: кривой конфиг, недоступная зависимость на старте, слишком злой liveness. Pending — под принят кластером, но ещё не все его контейнеры готовы к запуску: это может быть и ожидание планирования, и загрузка образа. Если причина в планировании, проверяю доступные ресурсы под requests, taints и tolerations, nodeSelector и привязку томов. В describe и Events обычно видно, на каком этапе всё остановилось; при FailedScheduling шедулер перечисляет, почему ноды не подошли. Это вопрос про метод: сначала events и логи, потом гипотезы — а не наоборот.

Не знают про --previous, а на Pending сразу лечат рестарты, не проверив, назначена ли нода и что происходит с запуском контейнеров.

Что могут спросить дальше: ноды есть, ресурсы есть, под всё равно 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 — сжимаемый ресурс, за него только троттлят. Убивают за память.

Что могут спросить дальше: а стоит ли вообще ставить 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.

Что могут спросить дальше: как трафик с ClusterIP попадает на конкретный под? — kube-proxy держит правила iptables/IPVS, которые DNAT'ят виртуальный IP в адрес одного из подов из EndpointSlice.

7. ConfigMap vs Secret — в чём разница на самом деле?

ConfigMap — для несекретной конфигурации, Secret — для чувствительных значений: паролей, токенов, ключей. Оба можно передать в контейнер через env или файлы. В поле data у Secret используется base64 — это кодирование, а не шифрование. При наличии права чтения команда kubectl get secret app -o jsonpath='{.data.password}' | base64 -d вернёт значение; название Secret не делает его недоступным. Защита строится на RBAC, ограничении прав на создание подов, шифровании данных в etcd и аудите доступа без записи самих секретов в журналы. Vault и Kubernetes Secret не обязаны конкурировать: внешний сервис может отвечать за выдачу и ротацию, а Secret — за доставку в приложение. Для изменяемого Secret, смонтированного обычным томом, kubelet обновляет файлы с задержкой, зависящей от синхронизации и кеша, а не гарантированно за минуту. При subPath автоматического обновления нет; приложение должно уметь перечитать изменённый файл. Значения env у уже работающего процесса сами не поменяются — нужен перезапуск, например через rollout или reloader.

Две крайности: назвать base64 шифрованием или сказать «Secret для несерьёзного, всё настоящее только в Vault». В обоих случаях пропущен главный вопрос: кто имеет доступ к значению, где оно хранится и как обновляется. Внешнее хранилище само по себе не исправит права внутри кластера.

Что могут спросить дальше: как под получает секрет из Vault? — External Secrets Operator синхронизирует значение в обычный Kubernetes Secret, то есть оно попадает в etcd. Secrets Store CSI Driver с соответствующим провайдером может монтировать значение напрямую в под. Но если у CSI включена дополнительная синхронизация в Kubernetes Secret, копия тоже появится в etcd. Поэтому ответ «CSI всегда минует etcd» без проверки конфигурации неполный.

8. Как работает HPA?

HPA меняет число реплик по метрикам: CPU/память из metrics-server или кастомные (RPS, длина очереди) через adapter. Формула: desired = ceil(current × текущая метрика / целевая). Посчитай пример вслух — это производит впечатление: 4 реплики при утилизации 90% и цели 60% → ceil(4 × 90/60) = 6. Нюансы настройки: процент утилизации CPU/памяти считается от requests — без нужного request эту метрику посчитать нельзя; стабилизационное окно (по умолчанию 300 секунд на scale-down) спасает от флаппинга; скорость реакции тюнится блоком behavior. С GitOps дружить надо аккуратно: фиксированный replicas в git будет драться с HPA. Рядом: VPA меняет сами requests, KEDA скейлит от событий (длина очереди, лаг Kafka) вплоть до нуля.

Для ресурсных метрик обычно нужен metrics-server; для custom/external metrics — соответствующий API и адаптер. Поэтому отсутствие metrics-server не делает любой HPA бесполезным. Скейлинг JVM по памяти тоже надо проверять на поведении конкретного приложения, а не включать по шаблону.

Что могут спросить дальше: почему по памяти скейлить опасно? — если рантайм долго удерживает память после нагрузки, ресурсная метрика может не дать уменьшить число реплик. GC-паузы она напрямую не измеряет; проверьте, насколько метрика связана с реальной нагрузкой приложения.

9. Что такое etcd и почему узлов нечётное число?

Распределённое KV-хранилище — единственное место, где живёт всё состояние кластера: манифесты, статусы, секреты. Консенсус — Raft, для записи нужен кворум n/2+1, поэтому 3 или 5 узлов: тройка переживает потерю одного, пятёрка — двух, а двойка не переживает ни одного — чётный узел не добавляет отказоустойчивости, только трафик. Бэкап etcd — это и есть бэкап кластера: etcdctl snapshot save. Ходить в etcd напрямую нельзя — только через API-сервер, он единственный клиент. Практический нюанс: etcd болезненно чувствителен к диску — медленный fsync устраивает перевыборы лидера и «моргающий» кластер, поэтому под ним только быстрый SSD и отдельный том, а не общий диск с логами.

«Без etcd кластер умрёт». Поды продолжат работать — но никаких изменений: ни задеплоить, ни отскейлить.

Что могут спросить дальше: из 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 — паттерн переиспользования, который любят спрашивать сеньоров.

Что могут спросить дальше: как дать разработчику доступ только к его 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 на каждом деплое.

Что могут спросить дальше: зачем 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 не умеет. Тот, кто про это знает, сразу выглядит человеком с продом за плечами.

Что могут спросить дальше: закрыли egress — что сломается первым? — DNS: без явного разрешения порта 53 UDP/TCP до kube-dns под «слепнет» раньше, чем дойдёт до полезного трафика.

Блок 2. Вопросы по Docker и контейнерам

13. Чем контейнер отличается от виртуалки?

Контейнер — это процесс на общем ядре хоста, изолированный через namespaces и cgroups. Виртуалка — своё ядро и виртуальное железо через гипервизор. Отсюда всё остальное: старт за миллисекунды против минут, образ в мегабайтах против гигабайтов, плотность на хосте выше в разы. Обратная сторона: изоляция слабее — побег из контейнера означает доступ к ядру хоста, а уязвимость ядра бьёт по всем контейнерам сразу. И следствие, которое проверяет понимание: ядро общее, поэтому Linux-контейнеры на Windows и macOS крутятся внутри скрытой виртуалки — Docker Desktop это ровно она. На собесе сильный ход — сразу назвать критерий выбора: недоверенный код и жёсткая мультитенантность — VM или песочницы, свой код и плотность — контейнеры.

«Контейнер — это лёгкая виртуалка». Фраза настолько заезженная, что интервьюеры специально её ждут, чтобы копнуть.

Что могут спросить дальше: а если нужна изоляция сильнее, но UX контейнерный? — gVisor, Kata Containers, Firecracker: контейнерный интерфейс с изоляцией ближе к VM, их берут для мультитенантных сред.

14. Что такое слои образа и как уменьшить его размер?

Файловые слои образа хранят изменения файловой системы: например, результат RUN, COPY или ADD. Инструкции вроде CMD и ENV задают конфигурацию образа, поэтому «любая строка 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 и удивляются, что образ не похудел — файлы никуда не делись, они остались в предыдущем слое.

Что могут спросить дальше: как найти, какой слой раздул образ? — 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-форма. Мало кто отвечает.

Что могут спросить дальше: когда 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.

Не могут объяснить «зачем, если и так работает». Ответ из трёх слов: размер, безопасность, кеш.

Что могут спросить дальше: как в 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 — будь готов.

Что могут спросить дальше: перезапустили докер-демон — что с контейнерами? — по умолчанию умрут вместе с ним; спасает 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-режим.

Путают местами — изоляцию с лимитами. Это вопрос-фильтр: по нему сразу видно, понимает человек, как докер устроен под капотом, или просто выучил команды.

Что могут спросить дальше: что происходит при 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 по имени».

Что могут спросить дальше: чем 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» — это и есть сеньорская подача: вы показываете карту, а не зубрёжку.

Начинают с середины и тонут в деталях одного слоя, не показав картину целиком. Или на «а где здесь может быть медленно?» не могут назвать ни одной точки — хотя их тут буквально каждая.

Что могут спросить дальше: где на этом пути кеши? — 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 надёжный» мало: стоит объяснить цену этой надёжности.

Что могут спросить дальше: почему 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 видел только в панели регистратора.

Что могут спросить дальше: поменяли 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-сертификат: слово «цепочка» слышали, звенья не назовут.

Что могут спросить дальше: в браузере работает, а из 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 легко пропустить, если смотреть только серверные 5xx.

Что могут спросить дальше: в логах всплеск 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-сервер» — слово «буферизация» не всплывает вообще.

Что могут спросить дальше: чем 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. «Место есть, но писать нельзя» ставит в тупик даже мидлов — а это вопрос-фильтр на реальный прод-опыт.

Что могут спросить дальше: удалили оригинал — что с 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, ЦПУ ни при чём.

Что могут спросить дальше: как доказать, что это 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 — сразу свой.

Что могут спросить дальше: 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 нужны раньше, чем доступен корень, где они лежат.

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

Что могут спросить дальше: что значит 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.

Что могут спросить дальше: причём тут контейнеры? — в контейнере 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. Интервьюеры это спрашивают именно потому, что на этом горел каждый.

Что могут спросить дальше: чем 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, тот прод видел.

Что могут спросить дальше: а если это сегфолт? — coredumpctl list и разбор дампа, если coredump'ы включены; в journalctl будет запись о SIGSEGV.

Блок 5. Вопросы по CI/CD и деплою

34. Как устроен нормальный пайплайн?

Стадии: build → тесты и линтеры → сборка артефакта (образ + push в registry) → деплой в staging → интеграционные и смоук-тесты → прод, с ручным гейтом или автоматом. Два принципа, которые стоит объяснить на примере пайплайна: артефакт собирается один раз и едет по средам неизменным, меняется только конфиг — иначе в проде крутится не то, что тестировали; fail fast — дешёвые проверки стоят первыми, чтобы не ждать 20 минут ради упавшего линтера. И весь пайплайн лежит кодом в репозитории рядом с приложением: ревьюится, версионируется, откатывается как любой другой код. Хорошо звучит и разделение CI/CD по смыслу: CI отвечает на вопрос «этот коммит вообще жизнеспособен?», CD — «как он доедет до юзеров?»

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

Что могут спросить дальше: пайплайн медленный — что делать? — кеш зависимостей, параллельные джобы, 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), иначе мгновенный откат — фикция. Именно здесь становится важна совместимость схемы и данных.

Что могут спросить дальше: как сделать canary по проценту трафика в Kubernetes? — стандартным Deployment никак, только пропорцией подов; процент дают сервис-меш или Argo Rollouts/Flagger.

36. Секреты в CI/CD — как правильно?

Никогда: в репозитории, в Dockerfile (слои помнят всё), в логах пайплайна. Минимум: встроенное хранилище CI с masked и protected переменными, привязка к защищённым веткам — секрет прода не должен читаться из ветки любого разработчика. Правильно: внешний Vault или SSM, короткоживущие токены, OIDC-федерация вместо статичных ключей — раннер получает временный доступ к облаку без вечного секрета в настройках CI, красть становится просто нечего. Плюс ротация, отдельные секреты на каждую среду и аудит доступа. Отдельно назовите угрозу, о которой забывают: секрет утекает не только из хранилища — echo в лог, дамп env в отладке, артефакт с конфигом. Masked-переменные прикрывают первое, культура ревью пайплайнов — остальное.

«Храним в переменных CI» — и всё, дальше пусто. Вопрос «как сделать, чтобы секрет не был доступен из ветки любого разработчика» разделяет тех, кто настраивал, и тех, кто слышал.

Что могут спросить дальше: как раннер ходит в облако без вечного ключа? — 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». Следующий вопрос всегда — «какая версия сейчас в проде и как откатиться на предыдущую?» Дальше неловкая пауза.

Что могут спросить дальше: тег могли перезаписать — как гарантировать, что в проде тот самый образ? — деплой по digest и подпись артефактов (cosign): на позициях с уклоном в supply chain это уже спрашивают.

38. Прод лёг после релиза — действия?

Сначала вернуть сервис, потом разбираться — дебаг на лежащем проде оплачивают юзеры. Механика отката: kubectl rollout undo, helm rollback или переключение blue-green — предыдущий образ ещё в registry, на то и версионирование. Главная засада — миграции БД: если схема уже несовместима со старой версией, откат приложения уронит всё ещё сильнее. Поэтому expand-contract: сначала добавляем совместимое (новые колонки, nullable), старое удаляем только через релиз, когда старой версии уже нет нигде. Feature flags дают откат вообще без деплоя — выключили флаг — живёте дальше. После пожара — post-mortem без поиска виноватых: цель — чтобы класс проблемы не повторился, а не чтобы кто-то покаялся.

Бодро рассказывают про rollout undo, а вопрос «миграция уже прошла — что теперь?» показывает, был ли человек в проде в три часа ночи или читал про это в статьях.

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

39. GitFlow vs trunk-based?

GitFlow: develop, feature, release, hotfix — подходит коробочным продуктам с версиями и долгими релизными циклами, но долгоживущие ветки означают merge-ад и медленную поставку: интеграция откладывается неделями, а потом взрывается конфликтами разом. Trunk-based: все мёржат в main мелкими порциями, ветка живёт максимум пару дней, недоделанные фичи прячутся за флагами, каждый мёрж потенциально деплоится. Для частого continuous deployment trunk-based обычно проще: меньше долгоживущих веток и отложенной интеграции. Но автоматический деплой можно настроить и при GitFlow — вопрос в том, из какой ветки, как часто и сколько ручных согласований осталось до прода. И честно скажи про цену: trunk-based не живёт без быстрого CI, ревью в тот же день и культуры выпиливания мёртвых feature-флагов — иначе main превращается в минное поле из полувыключенных фич. Ответ уровня сеньора — не «что лучше», а «что чему соответствует»: модель веток — производная от модели релизов.

«У нас GitFlow, значит CD невозможен» — слишком простой вывод. Уточните, имеется в виду continuous delivery или continuous deployment: возможность выпустить проверенный артефакт по решению человека и автоматический выпуск прошедших проверки изменений — не одно и то же. Название модели веток за вас на этот вопрос не ответит.

Что могут спросить дальше: а релизы в 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 хранит состояние релиза.

Что могут спросить дальше: чем 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.

Что могут спросить дальше: HPA меняет replicas, ArgoCD возвращает как в git — что делать? — ignoreDifferences на это поле либо вообще не фиксировать replicas в манифестах.

Блок 6. Вопросы по Terraform и IaC

42. Что такое state и зачем нужны локи?

terraform.tfstate — карта «что в коде ↔ что реально создано»: ID ресурсов, атрибуты, зависимости. Именно по state строится diff в plan: без него Terraform не знает, что вот этот aws_instance в коде — это вон та машина в облаке. В команде state обычно лежит в remote backend с поддержкой блокировки. Для S3 в актуальном Terraform её включают через use_lockfile = true: сама по себе загрузка state в бакет лок не включает. Старую связку S3 + DynamoDB ещё можно встретить, но DynamoDB-locking уже помечен deprecated. Без блокировки одновременные apply могут повредить состояние или применить несовместимые изменения. Важно: state может содержать секреты открытым текстом. Одна пометка sensitive не убирает значение из state, поэтому шифрование и ограничение доступа обязательны. Где возможно, используйте поддерживаемые провайдером write-only аргументы и ephemeral-значения, но не считайте, что они автоматически очищают уже сохранённые секреты. И знай хирургию state руками: terraform state list/show/mv/rm — про них спрашивают сразу после теории, потому что при смене адреса ресурса нужно явно связать старый адрес с новым: через moved-блок или state mv. Иначе plan может предложить удаление и создание вместо переноса.

«Потеряли state — не страшно, Terraform же видит инфру». Не видит. Для него без state ничего не существует, и apply попробует создать всю инфраструктуру заново поверх живой. Вот тут и начинается настоящее веселье.

Что могут спросить дальше: два инженера запустили apply одновременно — что будет? — при включённой блокировке второй apply не получит лок; ожидание можно настроить через -lock-timeout, иначе он завершится с ошибкой; без лока оба писали бы в 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 читать — отдельный навык, и его проверяют.

Что могут спросить дальше: как защитить критичный ресурс от случайного пересоздания? — 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, среда за средой — dev, staging, прод, — а не молча во все среды при следующем apply.

Не пиннят версии модулей. Обновил общий модуль — и изменение молча приехало во все среды при следующем apply. Вопрос «как обновлять модуль постепенно» это и проверяет.

Что могут спросить дальше: модули или workspaces для сред? — workspaces делят один код и бэкенд, различающиеся среды на них собирать больно; каталог на среду со своими tfvars — скучно, но предсказуемо.

45. Кто-то поправил инфру руками в консоли — что будет?

Drift. Следующий plan покажет расхождение, и Terraform захочет вернуть всё как в коде — он верит коду, а не консоли. Дальше два пути: изменение нужное — вносим в код (или import для созданных руками ресурсов); ненужное — apply откатит. Регулярный plan в CI по расписанию ловит дрифт раньше, чем он стреляет в момент срочного релиза. Важное ограничение: plan видит не всякий дрифт — ресурсы, созданные мимо Terraform, для него не существуют, он сверяет только то, что есть в state; для полного аудита есть driftctl. Культурное решение сильнее технического: закрыть консоль на запись (read-only для людей, запрет ClickOps на уровне IAM/SCP) и вести все изменения через PR.

Ждут, что Terraform «подхватит» ручные правки. Не подхватит — он верит коду. И вторая мина: ресурс, удалённый руками, apply молча пересоздаст — вместе с дефолтными параметрами, которых никто не ждал.

Что могут спросить дальше: ресурс создали руками мимо 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 — мина с таймером.

Что могут спросить дальше: как вывести ресурс из-под 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: читаете с реплики сразу после записи — можете получить прошлое.

Что могут спросить дальше: юзер сохранил профиль и увидел старые данные — что это и как лечить? — read-after-write на отстающей реплике: писавшему клиенту читать с мастера или использовать sticky-сессии.

48. Бэкапы: как делать и как понять, что они живые?

Правило 3-2-1: три копии, два типа носителей, одна вне площадки. Логический бэкап (mysqldump, для InnoDB — обязательно с --single-transaction: консистентный срез без глобального лока) — переносимый, но медленный на объёмах; физический (xtrabackup, снапшоты) — быстрый, восстановление терабайта из логического дампа — часы, и это надо знать до аварии, а не во время. Point-in-time recovery: полный бэкап + binlog, восстановиться можно на секунду до DROP TABLE. Главное: бэкап, из которого ни разу не восстанавливались — это не бэкап, а файл с надеждой. Restore-тесты по расписанию, в идеале автоматические: подняли из бэкапа, прогнали проверки, отчитались. RPO (сколько данных можно потерять) и RTO (сколько можно лежать) определены заранее и записаны — под них и выбирается вся схема.

«Бэкапы есть» — «когда последний раз проверяли восстановлением?» — тишина. Этот вопрос-фильтр я слышал десяток раз, и тишина в ответ — самый частый исход.

Что могут спросить дальше: бэкап снимаете с реплики — какой подвох? — унесёшь её лаг: реальный RPO хуже, чем думаешь, если реплика отставала в момент бэкапа.

49. Kafka на пальцах

Это не очередь, а распределённый лог. Продюсеры пишут в топики, топики нарезаны на партиции, консюмеры читают группами: внутри группы каждую партицию читает ровно один консюмер. Offset — позиция чтения, хранится per group, поэтому один топик спокойно читают десять разных сервисов независимо, каждый в своём темпе. Сообщения при чтении не удаляются — живут по retention (по умолчанию 7 дней), и это фундаментальное отличие от очередей: прочитанное можно перечитать, новый сервис может отмотать историю. Порядок гарантирован только внутри партиции, ключ сообщения определяет, в какую партицию оно попадёт — события одного юзера с ключом user_id всегда придут по порядку. Гарантии доставки: по умолчанию at-least-once, exactly-once — это идемпотентный продюсер плюс транзакции, и это надо явно включать и понимать цену.

«Kafka — это как RabbitMQ, только быстрее». Дальше добивающий: «консюмеров в группе больше, чем партиций — что будет?» Лишние будут курить: партиции — потолок параллелизма. На этой связке легко запутаться, если не разобрать устройство consumer group.

Что могут спросить дальше: главная метрика здоровья консюмеров? — 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 жил, а не читал про него.

Что могут спросить дальше: почему KEYS * в проде — преступление? — Redis однопоточный: одна тяжёлая команда останавливает всех; обход ключей — только SCAN.

51. Как устроен Prometheus?

Pull-модель: Prometheus сам ходит по HTTP на /metrics таргетов с заданным scrape_interval — а не приложения шлют в него. Таргеты находятся через service discovery: в Kubernetes — через настроенный service discovery и relabeling, либо ServiceMonitor/PodMonitor у оператора. Одной произвольной аннотации недостаточно: конфигурация должна её учитывать. Зачем pull: метрика up бесплатно (таргет не ответил — сразу видно), нагрузкой управляет мониторинг, а не тысяча пушащих сервисов. Экспортеры переводят чужие метрики в формат Prometheus: node_exporter — железо, blackbox — проверки снаружи, postgres_exporter и десятки других. Хранение — локальная TSDB; долгие сроки и HA — Thanos, Mimir или VictoriaMetrics сверху. Pushgateway — костыль для короткоживущих джобов: cron-задача умирает раньше, чем её поскрейпят, поэтому пушит результат в gateway. Алерты считает сам Prometheus по правилам, а маршрутизирует и группирует Alertmanager.

Совать сервисы в Pushgateway — анти-паттерн, который выдаёт непонимание модели: метрики там не протухают, up не работает, мёртвый сервис выглядит живым. На вопрос «зачем pull, а не push» полезно объяснить, как обнаруживается исчезнувший таргет.

Что могут спросить дальше: cron-джоба живёт 10 секунд — как снять метрики? — для короткой сервисной batch-задачи может подойти Pushgateway; для задач конкретной машины часто удобнее textfile collector node_exporter. Важно определить, кто удаляет устаревшие метрики.

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, который просят объяснить.

Что могут спросить дальше: почему нельзя алертить просто на http_requests_total > N? — counter монотонно растёт с рестарта: график вечно вверх, порог сработает у всех — информации ноль без rate.

53. SLI, SLO и error budget — объясни на пальцах

SLI — измеримый показатель того, как сервис видит юзер: доля успешных запросов, доля запросов быстрее 300 мс. SLO — цель по SLI: 99,9% успешных за 30 дней. SLA — договорённость с оговорёнными последствиями нарушения, часто финансовыми; внутреннюю цель обычно делают строже внешнего обещания, чтобы оставить запас до договорных последствий. Это выбор целевых значений, а не часть определения. Error budget — узаконенная ненадёжность: 100% минус SLO; для временного SLI при 99,9% за 30 дней это 43,2 минуты недоступности; для SLI по запросам бюджет считают в неуспешных запросах. Это допустимый запас ошибок, с которым команда соотносит риск релизов, экспериментов и аварий. Механика, ради которой всё затевалось: бюджет цел — катим фичи смело; бюджет сожгли — фичи стоп, работаем над надёжностью. Вечный спор «скорость против стабильности» превращается из политики в арифметику. Алерты в этой модели — по burn rate: не «случилась ошибка», а «бюджет сгорит за четыре часа, если так продолжится».

«Наш SLO — 100%» без объяснения, зачем это пользователю и сколько стоит, — плохая отправная точка. Нулевой бюджет не оставляет запаса на ошибки, а следующая девятка может потребовать непропорционально дорогих изменений. Сначала считайте последствия простоя, потом выбирайте красивое число.

Что могут спросить дальше: чем SLO отличается от SLA? — SLO — цель по измеримому показателю, SLA — договорное обещание с последствиями нарушения; SLO обычно задают с запасом относительно SLA.

54. Как построить алертинг, чтобы не сойти с ума?

Алерт — это то, что требует действия человека прямо сейчас. Всё остальное — дашборды. Алертим на симптомы, а не на причины: «у юзеров растут ошибки и latency», а не «CPU 80%» — процессору может быть 95%, а юзерам норм. Уровни severity: что будит ночью, а что превращается в тикет утром — и это два разных канала доставки. В каждом алерте — ссылка на runbook: дежурный в три ночи не должен вспоминать, что делать. Главный враг — alert fatigue: 50 алертов в день равны нулю алертов, их просто перестают читать. База — golden signals: latency, traffic, errors, saturation. Инструментально в Prometheus/Alertmanager: for: в правиле против дребезга, группировка по сервису, inhibition — чтобы упавший кластер не родил сотню дочерних алертов. И полезная метрика для проверки алертинга: сколько алертов за месяц не потребовали действия — по ним видно, что чистить следующим.

«Алертим на CPU больше 80%». А юзерам-то что? Ответ «симптомы — в алерты, причины — в дашборды» сразу выдаёт человека, который дежурил, а не настраивал мониторинг по туториалу.

Что могут спросить дальше: упал кластер — как не получить сто алертов разом? — группировка и inhibition в Alertmanager: корневой алерт подавляет дочерние.

Блок 8. HR, деньги, красные флаги

55. «Расскажите о себе» — коротко, по делу, с зацепкой для разговора

Это не биография, это питч. Формула: кто я сейчас и на каком стеке → 2-3 релевантных вакансии достижения, желательно цифрами → что ищу и почему сюда. Всё. Заготовить заранее и отрепетировать вслух — именно вслух, в голове все звучат гладко. Достижения формулируйте как мини-STAR: контекст → что сделали именно вы → измеримый результат («сократил время деплоя с 40 минут до 8»). И заканчивайте мостиком к их вакансии — интервьюеру будет проще подхватить разговор, а разговор пойдёт туда, куда вы его сами направили.

Монолог на десять минут от универа до вчерашнего дня. Интервьюер скучает, время технической части съедено, первое впечатление — «не умеет выделять главное». Ориентир — полторы-две минуты, а дальше пусть уточняют то, что им интересно.

Что могут спросить дальше: «а расскажите подробнее про этот проект» — ради этого мостики и нужны: заранее решите, куда поведёте разговор, и оставьте туда зацепки.

56. Зарплата: кто первый называет цифру?

Постарайся узнать их вилку раньше, чем назвал свою: «какая вилка заложена под позицию?» — нормальный рабочий вопрос, а не наглость. Если давят — называйте вилку, а не точку, и нижняя граница должна быть цифрой, на которую вы реально согласны без обид. Если вилку не говорят ни в какую — называйте свою с запасом 10-15% сверху: подвинуться вниз всегда проще, чем вверх. Рынок исследуйте до собеса, а не после оффера. Обсуждайте total comp целиком: база, бонусы, опционы, а на валютной удалёнке — кто платит налоги. И оффер не требует ответа в ту же секунду: «возьму пару дней подумать» — норма.

Называют цифру первыми и занижают от страха «а вдруг откажут». Компания не обидится на вашу вилку — она молча сэкономит. Помните: компаний много, а вы один.

57. «Почему уходите с текущего места?»

Одно правило: не поливать бывших. Даже если там был полный звездец — формулируйте через рост: «упёрся в потолок по задачам», «хочу масштаб побольше», «команда сворачивает направление». Интервьюер слушает и примеряет на себя: сегодня вы рассказываете гадости про них — завтра про нас. Подготовь одну и ту же формулировку для HR и для техлида — расхождения версий на разных этапах замечают и запоминают. Усилитель: свяжи причину ухода с тем, что есть у них, — тогда тот же ответ работает ещё и как «почему именно мы».

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

58. Что спрашивать у них — и какие ответы означают «беги»

Собес — двусторонний, вы тоже выбираете. Минимум вопросов: почему открыта позиция и что стало с предыдущим человеком, как устроены релизы и онколл, что с переработками, как выглядит испытательный. Ещё два вопроса с высокой отдачей: «как выглядит типичная неделя человека на этой роли» и «как компенсируется онколл» — конкретный ответ про график, нагрузку и компенсацию полезнее обещаний «у нас всё нормально». Красные флаги: «мы тут одна семья», мутность про деньги, «испытательный на особых условиях» и легендарное «первые две недели поработайте бесплатно» — да, такое реально предлагают — я сталкивался с этим лично.

«Вопросов нет». Читается как безразличие к месту, куда вы якобы хотите. И практический момент: ваши 3-4 вопроса дают про компанию больше информации, чем весь их рассказ о себе.

59. Как говорить «не знаю», чтобы это работало на вас

«Не знаю» с ходом мысли полезнее, чем уверенно нести техническую чушь. Если интервьюер разбирается в теме, он может копнуть глубже — и заготовленная фраза уже не спасёт. Формула: «с этим не работал, но рассуждая от X, предположу Y, а проверил бы через Z». Пробел в отдельной теме ещё не означает отказ: можно разобрать задачу через знакомые принципы и показать, как будете проверять гипотезу. Оценивают не процент правильных ответов, а как вы думаете и что делаете на границе своих знаний. Работает и отложенный ход: «не знаю, но разберусь и напишу после собеса» — и реально написать: такие письма запоминаются. Только не обольщайся: одним ходом мысли пробелы по всему основному стеку вакансии не закроешь.

Начинают выдумывать. Один уточняющий вопрос — и человек тонет на глазах у всех. Одно честное «не знаю» стоит дороже трёх сочинённых ответов.

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 вместо тестового — тоже валидный ход. Отказаться от неадекватного тестового — нормально.

Неделю пилят бесплатный прототип, получают отказ без фидбека, а через месяц видят свою фичу в проде. Не бойтесь отказов — бойтесь бесплатной работы.

Git на собеседовании: когда одного git pull уже мало

Пока всё работает, Git кажется набором из add, commit и push. На собесе интереснее другое: как подтянуть чужие изменения, отменить свои и не превратить одну ошибку в работу на весь вечер. Здесь пять сценариев для подготовки, а не ещё одна таблица команд на память.

Перед операциями проверьте git status и сохраните нужные незакоммиченные изменения. Примеры рассчитаны на отдельный учебный репозиторий или вашу рабочую ветку с понятной историей. FOUND_SHA, FIX_SHA и другие условные имена заменяются реальными идентификаторами после проверки — это не команды для слепого копирования в main.

62. Merge или rebase: в main появились изменения, пока ты делал свою задачу. Что выберешь?

Сначала git fetch origin: обновляю сведения об удалённых ветках, ещё не смешивая их со своей работой. На feature-ветке git merge origin/main объединит истории, сохранив существующие коммиты; при разошедшихся ветках появится merge-коммит с двумя родителями, а при fast-forward достаточно передвинуть указатель. git rebase origin/main перенесёт изменения моих коммитов на новую основу — переписанные коммиты получат другие SHA. Для своей ещё не опубликованной ветки это удобный способ убрать лишние развилки. Если на моих коммитах уже построена чужая работа, «причесать историю» без договорённости означает заставить остальных разбирать последствия. Здесь нет победителя на все случаи: выбираю, нужно ли сохранить существующую историю или пересобрать свою последовательность изменений.

«После rebase просто force push, ничего страшного». --force-with-lease проверяет ожидаемое состояние удалённой ветки, но не выдаёт разрешение переписать общую историю. Без явно указанного ожидаемого SHA эта проверка опирается на локальную remote-tracking ветку; фоновый fetch может её обновить. Lease не заменяет просмотр чужих изменений и согласование переписывания.

Что могут спросить дальше: rebase остановился на конфликте — что делать? — понять обе версии, исправить файл, добавить его через git add и выполнить git rebase --continue. Если выбрали не ту основу — git rebase --abort. У незавершённого merge своя команда: git merge --abort; сохранённые до начала операции изменения избавляют от отдельного квеста по их восстановлению.

63. Reset или revert: плохой коммит уже уехал в общую ветку. Как отменять?

Если коммит уже забрали другие, обычно выбираю git revert BAD_SHA: Git добавляет новый компенсационный коммит, который отменяет изменения выбранного, а сам исходный коммит остаётся в истории. Это не возврат всей системы в прошлое: последующие изменения могут конфликтовать, а миграцию базы Git обратно не прокрутит. git reset в форме с коммитом двигает текущую ветку и по режиму меняет index и рабочие файлы. На своей неопубликованной ветке git reset --soft HEAD~1 уберёт последний коммит с вершины, оставив его изменения подготовленными к новому коммиту. --mixed оставит содержимое файлов, но сбросит подготовку в index. --hard также перезапишет рабочие файлы: несохранённые изменения можно потерять, включая мешающие восстановлению отслеживаемых путей неотслеживаемые файлы. Поэтому это не универсальная кнопка «починить Git».

Удалить коммит из общей ветки через reset и продавить push — не то же самое, что отменить изменение через revert. В первом случае вы меняете историю, на которую уже могут ссылаться чужие ветки и сборки. Во втором история остаётся, но компенсацию всё равно нужно проверить тестами.

Что могут спросить дальше: а если отменяем merge-коммит? — сначала смотрю его родителей через git show --no-patch --pretty=raw MERGE_SHA. У git revert -m 1 MERGE_SHA число 1 означает выбранного первого родителя, относительно которого отменяются изменения; это не универсальное «вернуть main». Кроме того, revert не стирает факт слияния: повторный merge той же истории сам по себе не вернёт отменённый код.

64. После reset или rebase пропали коммиты. Что искать в reflog?

Первое — перестать дёргать reset наугад. git reflog show --date=iso показывает локальные перемещения HEAD: где были до reset, на что переключались, как шёл rebase. Нахожу нужный SHA, проверяю git show --stat FOUND_SHA, затем создаю на нём отдельную ветку через git branch rescue-before-reset FOUND_SHA. Текущую ветку при этом не двигаю: сначала закрепил найденный коммит, потом спокойно сравниваю и решаю, что возвращать. Reflog — не второй git log: log идёт по истории коммитов, а reflog помогает найти прежние положения ссылок, в том числе коммиты, которых в текущей ветке уже не видно.

git reflog show --date=iso
git show --stat FOUND_SHA
git branch rescue-before-reset FOUND_SHA

«В Git вообще ничего не теряется». Reflog локальный и не приезжает к коллеге вместе с push. Записи имеют срок хранения: обычные значения по умолчанию — 90 дней, для недостижимых записей — 30; настройки и очистка могут это изменить. После удаления записей и недостижимых объектов обещать восстановление уже нельзя. Это страховочная сетка, а не бэкап.

Что могут спросить дальше: а если файл меняли, но не коммитили и не сохраняли в stash? — reflog не хранит историю каждого редактирования файла и не гарантирует восстановление этих изменений. Тогда проверяют локальную историю редактора, резервные копии и то, успел ли Git вообще сохранить нужный объект.

65. Нужен один hotfix из другой ветки, а не вся ветка. Когда использовать cherry-pick?

Например, исправление уже есть в новой версии, а его нужно перенести в поддерживаемую release-ветку без остальных фич. Проверяю diff и зависимости изменения, переключаюсь в целевую ветку и выполняю git cherry-pick -x FIX_SHA. Git применяет изменение выбранного коммита поверх текущей ветки и обычно создаёт новый коммит: это перенос правки, а не слияние истории исходной ветки. Опция -x добавляет в сообщение ссылку на исходный SHA — удобно для backport между доступными команде ветками; после разрешения конфликтов проверьте, что эта отметка сохранилась. Один выбранный коммит может зависеть от предыдущего рефакторинга, новой функции или миграции: Git проверит возможность применить патч, а не работоспособность всего решения.

«Конфликтов нет — значит, всё перенёс правильно». Патч может примениться чисто, но код вызовет функцию, которой в старой версии ещё нет. После cherry-pick нужны сборка и проверка именно исправленного сценария. Если переносите целую цепочку, сначала разберитесь в её порядке и зависимостях, а не выбирайте SHA пачкой на удачу.

Что могут спросить дальше: cherry-pick остановился на конфликте — как продолжить или выйти? — исправить конфликт, git add нужные файлы и git cherry-pick --continue. Если перенос оказался не тем — git cherry-pick --abort. Команда --skip пропускает текущий коммит; это не способ автоматически «разрешить» его конфликт.

66. Вчера работало, сегодня сломалось, между ними десятки коммитов. Как поможет bisect?

Нужен воспроизводимый признак одной конкретной поломки и две проверенные точки: хорошая и плохая. git bisect выбирает промежуточные коммиты; после проверки отмечаю git bisect good или git bisect bad, постепенно сужая диапазон. Это быстрее, чем читать историю и угадывать по фамилии автора. Проверку можно автоматизировать через git bisect run: код выхода 0 означает good, 1 — bad, 125 — текущую ревизию нельзя проверить, её нужно пропустить. Другие коды 1–127, кроме 125, Git тоже считает bad, поэтому ошибка запуска теста может испортить результат. После поиска выполняю git bisect reset, чтобы выйти из режима и вернуться к исходной позиции. Найденный коммит — начало разбора причины, а не автоматический приговор всему изменению.

git bisect start
git bisect bad HEAD
git bisect good KNOWN_GOOD_SHA
# Скрипт уже должен проверять нужную регрессию и возвращать 0 / 1 / 125
git bisect run /absolute/path/check-regression.sh
git bisect reset

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

Что могут спросить дальше: старая ревизия не собирается — она bad? — если ищем другую регрессию, не обязательно: вручную git bisect skip, в автопроверке — выход 125. Если пропущенные коммиты окажутся возле границы поломки, Git может оставить несколько кандидатов вместо одного точного ответа.

Есть вопрос по подготовке?

В закрытом сообществе «Будни Айтишника» обсуждаем найм и работу: я отвечаю на вопросы и помогаю разбираться. Это платное комьюнити, а не индивидуальная программа.

Если нужно личное сопровождение до оффера, напишите мне — это отдельная история, которую согласуем лично. Подробнее о сообществе и помощи.

Готовитесь к первому собеседованию с нуля — начните с маршрута подготовки на Dev0pser. Уже работаете и выбираете следующую компанию — пригодится разбор для middle и senior на IT-Para.