devops-курс Все курсы

✻ Урок 10.7 · Тема 10: Итоговый практикум

Мок-собеседование: уровень junior

⏱ 2 ч

Зачем это нужно

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

Мок-собеседование (mock interview) это учебная репетиция разговора о работе, как пробный экзамен перед настоящим. Ты отвечаешь вслух, получаешь уточняющие вопросы и находишь пробелы до встречи с работодателем. В отличие от экзамена, здесь можно остановиться, разобрать ответ и попробовать снова; без репетиции пробел легко обнаружить только на самом собеседовании.

Junior означает начальный уровень: ты уже умеешь решать простые задачи, но ещё нуждаешься в помощи с незнакомыми системами. Это похоже на начинающего мастера, который может выполнить знакомый ремонт, а сложный случай обсуждает с наставником. DevOps в этом курсе означает совместную работу над доставкой изменений и надёжной работой приложения, которую ты показывал в портфолио из урока 10.6. На репетиции ты связываешь эти задачи с тем, что сделал своими руками, а не изображаешь опыт, которого у тебя нет.

Шаг проекта: код «Заметок» не меняется. На учебном сервере ты повторишь диагностику (troubleshooting), поиск причины ошибки с помощью проверок из урока 2.8. Приложение принимает подключения на порту 8080, номере для выбора программы на машине из урока 2.2. Перед ним стоит nginx, программа-посредник из урока 2.5: она принимает запросы на порту 80 и передаёт их приложению.

В репозитории ~/notes, папке проекта под управлением Git, системы хранения истории файлов из урока 3.1, появится личный файл docs/interview-notes.md. .gitignore из того же урока содержит правила пропуска ещё не отслеживаемых файлов: правило для заметок исключит их из обычного добавления в Git. Это помогает случайно не опубликовать личный черновик; уже сохранённую историю такое правило не удаляет. После всех поломок приложение должно снова работать.

Что нужно знать

Это повторение пройденного, а не требование уже работать в IT. Если команду не узнаёшь, вернись к указанному уроку и повтори его практику.

  • Урок 1.4: процессы и сигналы: процесс (process) это запущенная программа, сигнал (signal) это сообщение ей, например просьба завершиться; понадобятся kill и номера процессов.
  • Урок 1.5: диск, память, CPU: сравнение занятого места через df и размеров файлов через du.
  • Урок 1.8: systemd: служба (service) это фоновая программа, запуском которой управляет systemd; systemctl проверяет её состояние, journalctl читает журнал, записи о событиях и ошибках. Здесь служба называется notes.
  • Урок 2.2: порты и TCP и урок 2.4: HTTP: TCP задаёт правила установления соединения (connection), связи между программами для обмена данными, и передачи данных; HTTP задаёт формат запроса и ответа.
  • Урок 2.5: nginx и урок 2.8: путь запроса: рабочий учебный стенд (test environment), подготовленное место для опытов с программами. Нужен вариант с адресом notes.lab, приложением на 127.0.0.1:8080 и nginx на порту 80; понадобится чтение журнала ошибок.
  • Урок 3.1: Git, урок 3.2: ветки, урок 3.3: CI: ветка (branch) это отдельная линия изменений проекта; CI (continuous integration, непрерывная интеграция) автоматически запускает проверки изменений. Понадобятся история файлов и объединение веток.
  • Урок 5.7: пробы и ресурсы и урок 5.13: диагностика Kubernetes: Kubernetes управляет контейнерами, изолированными средами запуска программ из урока 4.1. Понадобятся проверки готовности принимать запросы, ограничения памяти, события и журналы контейнера.
  • Урок 10.1: дежурство, урок 10.5: безопасность, урок 10.6: портфолио: восстановление сервиса, обращение с паролями и честное описание своего проекта.

Картина целиком

Представь, что на экзамене по вождению машина не заводится. Можно перечислить названия деталей. Можно сказать «заменю двигатель». А можно уточнить, загорается ли приборная панель, проверить аккумулятор и объяснить, какое наблюдение подтверждает предположение. На техническом интервью полезен третий способ.

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

Запрос начинает клиент (client), программа вроде браузера или curl, как в уроке 2.4. Предположение о причине ошибки называют гипотезой (hypothesis): например, «приложение остановлено», как в уроке 2.8. Сначала проверяешь предположение, затем выбираешь действие.

flowchart TD
    A["Вопрос: «Заметки<br>не открываются»"] --> B["Уточняю: адрес,<br>ошибка, когда началось"]
    B --> C["Рисую путь:<br>клиент, nginx:80, app:8080"]
    C --> D["Гипотеза, проверка,<br>результат"]
    D -->|не подтвердилось| C
    D -->|подтвердилось| E["Восстанавливаю работу,<br>проверяю запрос, записываю вывод"]

Ошибка у клиента показывает, где начать проверку, но ещё не называет причину. Цель урока: научиться переходить от ошибки к проверяемому объяснению и рассказывать об этом вслух.

Теория

Собеседование: что показывает твой ответ

Собеседование нужно обеим сторонам. Команда выясняет, с какими задачами ты справишься и где потребуется помощь. Ты выясняешь, чему тебя будут учить и какую ответственность дадут. Единого расписания нет: могут спросить основы, предложить ситуацию или попросить показать проект. Поэтому подготовка только к одному списку вопросов ненадёжна.

Это похоже на пробный урок с преподавателем музыки. Он слушает, как ты играешь, замечаешь ошибки и исправляешь их. Знание названий нот полезно, но само по себе не показывает умение играть. Граница аналогии: в IT часто допустимо пользоваться справкой, потому что точный флаг команды менее важен, чем понимание её действия.

Сильный ответ делает твои знания видимыми. Сначала называешь задачу, потом механизм, затем пример. На вопрос «что делает nginx в проекте?» можно ответить: «Принимает запрос на порту 80 и передаёт его “Заметкам” на 8080. Клиенту достаточно одного адреса. Если приложение остановлено, nginx может ответить 502, а журнал покажет ошибку подключения к приложению».

В ответе есть назначение, устройство и наблюдаемый результат. Интервьюер может уточнить любую часть. Если помнишь только слово «прокси» (proxy), посредник между клиентом и сервером из урока 2.5, второй вопрос быстро обнаружит пробел. Если делал опыт, можешь объяснить, что именно остановил и какой вывод увидел.

Не выдавай учебный стенд за рабочую систему с настоящими пользователями. Продакшен (production, часто «прод») это среда, которой пользуются реальные люди, в отличие от учебного стенда из урока 10.6. Фраза «на учебном сервере воспроизвёл отказ» описывает реальное действие. Фраза «поднял прод после аварии» при тех же обстоятельствах приписывает тебе чужую ответственность. Репетицию проводим отдельно от продакшена.

Начни ответ с двух-трёх предложений. Затем добавляй детали по вопросу собеседника. Короткий ответ хорош, если за ним есть объяснение, а не если в нём просто много сокращений.

Прикинь сам: у тебя только учебный проект. Можно ли рассказывать о найденной в нём ошибке?

Да: назови стенд учебным, объясни свои действия и результат, не выдумывай работу в компании.

Главное: сильный ответ называет задачу, механизм и наблюдаемый результат, а учебный стенд честно называет учебным.

Хороший ответ начинается с чёткого разделения фактов и догадок.

Симптом, гипотеза и проверка: три разных вещи

Симптом (symptom) это наблюдение: «запрос вернул 502». Гипотеза (hypothesis) это возможное объяснение: «приложение остановлено». Диагностика (troubleshooting) это поиск причины с помощью проверок. Эти понятия нужны, чтобы не принять первое объяснение за установленный факт.

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

Сначала уточни наблюдение: какой адрес открывали, какой ответ получили, у всех ли одинаковая ошибка, когда началось и что менялось перед этим. Релиз (release) это выпуск новой версии из урока 5.7, а конфигурация (configuration) это настройки программы, с которыми ты работал в уроке 1.8. Релиз и правка настроек дают направление поиска, но совпадение по времени ещё не доказывает причину.

Затем выбери гипотезу и заранее назови ожидаемый результат. Например: «Если служба остановлена, systemctl is-active notes покажет inactive». systemctl обращается к systemd, is-active спрашивает о текущем состоянии, notes выбирает службу. Проверка состояния ничего не перезапускает.

Допустим, результат оказался active. Это означает, что systemd считает службу работающей. Гипотеза «служба остановлена» не подтвердилась. Но это ещё не доказательство, что приложение отвечает правильно: запущенная программа может зависнуть или слушать другой порт. Следующая проверка должна касаться уже этой неопределённости.

Не все проверки одинаково полезны. Начни с той, которая разделяет гипотезы и меняет как можно меньше условий. Например, при 502 можно предположить остановку приложения или неверный адрес в nginx. Запрос напрямую к ожидаемому порту помогает разделить эти варианты: если приложение отвечает, ищешь различие на участке посредника; если не отвечает, проверяешь службу и порт. Проверка не должна сама устранять симптом, иначе часть исходных наблюдений потеряется.

Допустим, служба active, а на 8080 нет слушателя, программы, готовой принимать подключения. Это не повод выбирать один из результатов и игнорировать другой. Оба могут быть верны: процесс запущен, но порт отличается от ожидаемого. Сверь настройки адреса и порта в /etc/notes/notes.env со строкой upstream, адресом следующего сервера в журнале nginx из урока 2.5. Если программа слушает 8090, а nginx обращается к 8080, гипотеза о несовпадении получила конкретное подтверждение.

Теперь можно описать действие без догадки: привести оба конца к согласованному порту 8080, применить настройки способом из соответствующего урока и повторить оба запроса. Важно проверить, что изменилось именно то условие, которое ты назвал причиной. Если одновременно перезапустить сервер, заменить конфигурацию и обновить приложение, успешный ответ не покажет, какое действие помогло.

На интервью не обязательно перечислять все возможные причины заранее. Назови одну-две, покажи различающую проверку и объясни следующую развилку. Так собеседник видит порядок мысли, а ты не теряешься в длинном списке неподтверждённых предположений.

flowchart TD
    F["Факт: 502"] --> G["Гипотеза:<br>остановлен notes"]
    G --> P["Проверка:<br>is-active"]
    P --> R["Результат: active"]
    R --> V["Вывод: проверяю порт<br>и прямой запрос"]

Каждая запись связывает факт, гипотезу и проверку, поэтому догадка не подменяет наблюдение.

Удобная запись выглядит так: «Факт: 502. Гипотеза: остановлен notes. Проверка: is-active. Результат: active. Вывод: проверяю порт и прямой запрос». Она не даёт незаметно подменить факт догадкой.

Частая путаница: список из десяти команд принимают за план. План объясняет связь каждой команды с вопросом. Без этой связи трудно остановиться, когда причина уже найдена, или изменить направление, когда результат противоречит ожиданию.

Прикинь сам: systemctl is-active notes показал active. Достаточно ли, чтобы сказать «с приложением всё хорошо»?

Нет: это состояние службы, а не ответ на запрос. Нужно проверить адрес и порт и получить ответ приложения.

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

Применим это к самому частому случаю: nginx отвечает 502.

Путь запроса: как сузить поиск при 502

Разделение на участки нужно потому, что ошибка сайта может появиться в нескольких программах. Обратный прокси (reverse proxy) принимает запрос вместо приложения и пересылает его дальше. У нас эту роль выполняет nginx, как в уроке 2.5. Upstream в его журнале означает следующего получателя запроса: здесь 127.0.0.1:8080.

Аналогия: ты звонишь на ресепшен гостиницы, а администратор соединяет с номером. Ответ администратора доказывает, что до ресепшена дозвонились, но ничего не говорит о человеке в номере. Граница аналогии: прокси может сам сформировать ответ, а приложение может вернуть ошибку, которую прокси просто передаст.

flowchart TD
    C["curl или браузер"] -->|запрос| N["nginx, порт 80"]
    N -->|запрос| A["app.py, порт 8080"]
    A -->|ответ приложения| N
    N -->|ответ клиенту| C
    N -.->|приложение остановлено| X["Подключиться нельзя:<br>nginx сам отвечает 502"]

Сплошные стрелки это обычный путь запроса, пунктир показывает, что при остановленном приложении 502 формирует сам nginx.

HTTP-код (HTTP status code) это трёхзначный результат обработки запроса: 200 означает успешный ответ. 502 Bad Gateway означает проблему при получении пригодного ответа от следующего сервера. 504 Gateway Timeout означает, что прокси не дождался ответа вовремя. Подробнее коды разбирались в уроке 2.4.

Сначала проверь точный текст ошибки. Пример строки из лога (log), журнала событий программы: connect() failed (111: Connection refused) while connecting to upstream, upstream: "http://127.0.0.1:8080/notes". connect() это попытка установить соединение; 111 это номер системной ошибки Linux; Connection refused означает отказ в подключении; while connecting to upstream указывает этап; последний адрес показывает, куда nginx действительно обращался.

В этом примере проверяй приложение и порт 8080. При upstream timed out ... while reading response header from upstream соединение уже могло установиться, но nginx не дождался начала HTTP-ответа. Тогда важны время обработки и состояние приложения. Одного числа 502 недостаточно, чтобы выбрать исправление.

Прямой запрос на 8080 обходит nginx. Если он успешен, а запрос через nginx нет, сравни настройки посредника с реальным адресом приложения. Если оба неуспешны, исследуй приложение. Всегда сравнивай одинаковые пути: успешный /healthz, служебная проверка живости, ещё не доказывает успешность /notes, списка заметок.

Прикинь сам: nginx вернул 502. Можно ли сразу сказать, что nginx сломан?

Нет: он мог правильно сообщить об отказе следующего сервера. Читай журнал и проверяй адрес upstream напрямую.

Главное: 502 говорит, что прокси не получил пригодного ответа, а где именно сломано, показывают журнал и прямой запрос к приложению.

Отдельно разберём, чем отказ соединения отличается от долгого ожидания.

Отказ и ожидание: что действительно говорит curl

Соединение (connection) это установленная связь между программами для обмена данными. Connection refused означает явный отказ при попытке её установить. Таймаут (timeout) означает, что отведённое время истекло. Различие нужно, потому что отказ и долгое ожидание требуют разных проверок.

Это похоже на телефон: «номер не обслуживается» и гудки без ответа звучат по-разному. Но по гудкам нельзя узнать, почему человек не ответил. Так же таймаут ещё не называет неисправную деталь системы.

Для соединения выбираются IP-адрес машины и порт программы. IP-адрес (IP address) это числовой адрес в сети; 127.0.0.1 означает эту же машину, как в уроке 2.1. Программа слушает порт (listen), когда готова принимать на нём подключения. Если никто не слушает выбранный локальный порт, обычно получишь быстрый отказ.

Межсетевой экран (firewall) фильтрует сетевые сообщения. Правило REJECT явно отклоняет сообщение, DROP отбрасывает без ответа; см. урок 2.7. Поэтому отказ возможен и при работающей программе, если соединение отвергла защита. Таймаут возможен как до соединения, так и после него: приложение могло принять запрос и долго считать ответ.

У команды есть код выхода (exit status), число для оболочки, программы, которая запускает твои команды. Ноль обычно означает успех. У curl код 7 означает неудачу подключения, а не исключительно Connection refused. Код 28 означает истечение времени операции, а не исключительно отсутствие сетевого пути. Читай сообщение целиком и смотри этап, на котором остановился запрос.

В практике сравним закрытый порт с /slow?sec=5, намеренно медленным адресом «Заметок». sec=5 просит задержку в пять секунд, а --max-time 2 ограничивает весь запрос двумя секундами. Расчёт простой: 5 > 2, клиент прекратит ждать раньше ответа. Соединение при этом есть; это пример таймаута работающего приложения.

Не путай HTTP-код с кодом выхода. Без флага --fail команда curl может успешно получить HTTP-ответ 502 и завершиться с кодом 0. «Данные доставлены» и «сайт выполнил задачу» это разные результаты.

Прикинь сам: curl --max-time 2 к /slow?sec=5 завершился с кодом 28. Нужно чинить firewall?

Нет: 5 больше 2, клиент перестал ждать раньше ответа. Соединение было, причина ожидания в обработке запроса.

Проверь понимание: код 28 доказывает, что нужно чинить firewall?

Ответ

Нет. Нужно выяснить, успело ли установиться соединение и был ли отправлен запрос. При медленном /slow причина ожидания находится в обработке запроса, а не обязательно в сети.

Главное: отказ и таймаут требуют разных проверок, а код выхода curl не равен HTTP-коду.

Следующий случай: диск полон, а файлов не найти.

df и du: почему удалённый файл ещё занимает место

Файловая система (filesystem) организует хранение файлов и каталогов. df спрашивает её, сколько места занято целиком; du обходит доступные имена файлов и складывает их занятие диска. Разница важна, когда диск почти полон, а видимых больших файлов мало; основы были в уроке 1.5.

Представь склад: каталог это список подписанных ящиков, а открытый файл это ящик, который сотрудник уже взял в работу. Снять табличку не означает забрать содержимое из его рук. Граница аналогии: файл может иметь несколько имён, а системой освобождения места управляет Linux, а не сотрудник.

Когда процесс открывает файл, он получает файловый дескриптор (file descriptor), номер открытого канала из урока 1.5. У стандартного входа (standard input), канала для получения данных программой из урока 1.2, номер 0. PID (process ID) это номер процесса из урока 1.4. Linux показывает открытые каналы через /proc/<PID>/fd/: /proc это представление состояния системы в виде файлов, а fd содержит ссылки на каналы процесса.

flowchart TD
    A["Процесс открыл файл"] --> B["rm удалил имя"]
    B --> C["du не видит файл"]
    B --> D["df считает данные занятыми"]
    D --> E["Процесс закрыл файл"]
    E --> F["Место освобождено"]

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

Последовательность такая: создаёшь файл, процесс открывает его, удаляешь имя командой rm, процесс продолжает держать файл. Если других имён нет, место окончательно освобождается после закрытия последнего открытого дескриптора. du уже не найдёт удалённое имя, а df продолжит учитывать данные. Пометка (deleted) у ссылки в /proc показывает именно эту ситуацию.

В практике создадим 50 блоков по 1 MiB. MiB (мебибайт) равен 1 048 576 байтам; байт это единица размера данных. Значит, 50 × 1 048 576 = 52 428 800 байт. После удаления имени эти данные ещё будут заняты. После завершения держателя они освободятся. Из-за округления и других записей изменение в df -h может быть плохо видно, поэтому используем более точные единицы.

Не объявляй любой разрыв между df и du удалённым файлом. Сначала сравни одну и ту же файловую систему, проверь ошибки доступа к каталогам. Inode это запись о файле, включая его свойства и расположение данных. Может закончиться запас таких записей, хотя байты ещё есть: тогда проверяют df -i. Это отдельная причина невозможности создать файл, а не универсальное объяснение расхождения размеров.

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

Прикинь сам: удалили файл в 50 MiB, но процесс его держит. Что покажут df и du?

df продолжит считать эти 52 428 800 байт занятыми, а du их уже не найдёт: имени файла нет.

Проверь понимание: файл исчез из ls, но остался в /proc/1234/fd/0 с (deleted). Это противоречие?

Ответ

Нет. ls ищет имя в каталоге, а /proc показывает открытый канал процесса 1234. Имя удалено, канал ещё существует.

Главное: место освобождается после закрытия последнего дескриптора, поэтому расхождение df и du может значить удалённый, но открытый файл.

Теперь поведение, где причину легко перепутать со следствием: перезапуски контейнера.

Перезапуски и код 137: как не спутать следствие с причиной

Контейнер (container) это изолированная среда для запуска процесса; основы в уроке 4.1. Kubernetes управляет контейнерами на группе машин, кластере (cluster). Под (Pod) это его единица запуска: один или несколько связанных контейнеров. В нашем проекте поды приложения находятся в пространстве имён (namespace) notes, группе объектов внутри кластера.

Состояние перезапуска нужно читать как сообщение о поведении, а не готовый диагноз. Представь, что автоматическая дверь каждый раз закрывается сразу после открытия. Надпись «повторная попытка через минуту» сообщает о работе автоматики, но не объясняет, сломан ли датчик. Граница аналогии: у контейнера есть журнал завершившегося процесса, который даёт дополнительные сведения.

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

В описании можно увидеть Reason: OOMKilled и Exit Code: 137. OOM (out of memory) означает нехватку памяти; лимит (limit) задаёт верхнюю границу ресурса контейнера. В принятой оболочками записи завершения по сигналу число получается как 128 + номер сигнала. У SIGKILL номер 9, поэтому 128 + 9 = 137. SIGKILL прекращает процесс без возможности обработать сигнал; SIGTERM просит завершиться и позволяет закрыть файлы и закончить работу. Напоминание о сигналах: урок 1.4.

Но число 137 само по себе не доказывает OOM: процесс мог получить SIGKILL по другой причине, а программа вообще может самостоятельно вернуть такое число. Для вывода о памяти ищи OOMKilled и сведения о потреблении и лимите. Удаление пода меняет объект, но не исправляет прежние настройки, поэтому сбой может повториться.

Отдельно различай liveness-пробу (liveness probe), проверку живости, и readiness-пробу (readiness probe), проверку готовности принимать запросы. Неудачная liveness после заданного числа попыток может вызвать перезапуск. Неудачная readiness убирает под из обычного обслуживания запросов, но сама не перезапускает контейнер. В «Заметках» им соответствуют /healthz и /readyz; детали в уроке 5.7.

Прикинь сам: под в CrashLoopBackOff, код выхода 137. Это точно нехватка памяти?

Не обязательно: 137 это 128 плюс сигнал 9, но SIGKILL мог прийти и по другой причине. Для вывода о памяти ищи OOMKilled.

Проверь понимание: под работает, но readiness не проходит. Обязательно ли будет CrashLoopBackOff?

Ответ

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

Главное: CrashLoopBackOff описывает поведение, а не причину, а код 137 нужно подтверждать признаком OOMKilled.

Дальше случай, где причина лежит в истории: секрет в git.

История Git: почему удаление файла не отменяет утечку

Коммит (commit) это сохранённое состояние проекта с описанием изменения. Ветка (branch) это имя линии разработки, ведущей к определённому коммиту. История нужна, чтобы сравнивать версии и возвращаться к прежним состояниям; повторение в уроке 3.1. Эта же возможность означает, что прежний пароль не исчезает после удаления файла новым коммитом.

Аналогия: ты разослал черновик письма, а затем отправил исправленный. Новое письмо не забирает старое из чужих почтовых ящиков. Граница аналогии: в Git можно переписать историю на центральном сервере, но это всё равно не стирает уже сделанные другими людьми копии.

Секрет (secret) это значение, дающее доступ: пароль или токен (token), выданный сервисом ключ для программного доступа. Если секрет попал в доступную другим историю, сначала отзывают его или меняют на новый. Только это прекращает доступ по старому значению. Затем убирают значение из файлов и согласованно очищают историю там, где необходимо. Нельзя считать очистку доказательством, что никто не успел скопировать секрет; подробнее в уроке 10.5.

.gitignore это список путей, которые Git пропускает при обычном добавлении ещё не отслеживаемых файлов. Он не прячет уже сохранённую историю и не прекращает отслеживание файла, который уже добавлен. Поэтому личные заметки сначала исключаем, а затем проверяем и правило, и состояние файла. Для работы в команде также важно отличать два способа объединения изменений из урока 3.2.

Merge объединяет линии разработки; при разошедшихся ветках создаёт коммит объединения. Если одна линия просто продолжает другую, возможен переход вперёд без нового коммита. Rebase переносит изменения коммитов на другое основание и создаёт для них новые идентификаторы, номера коммитов. Представь ветки A -> B и A -> C: после rebase изменения B поверх C получится A -> C -> B'. B’ содержит перенесённое изменение, но это уже другой коммит.

Общую ветку переписывать опасно: у коллег остаются прежние коммиты. Force push означает принудительное обновление удалённой ветки вопреки обычной проверке истории. Не предлагай его как автоматический следующий шаг. Сначала нужны договорённость и понимание, какие изменения и чьи копии затронуты.

Прикинь сам: пароль удалён новым коммитом и добавлен в .gitignore. Можно им пользоваться?

Нет: он остался в прежних коммитах и копиях. Нужно отозвать или заменить сам секрет.

Главное: удаление файла не отзывает секрет, а перепись общей истории опасна для чужих копий.

Следующая ситуация: CI упал, хотя код не менялся.

CI: почему проверка может сломаться без нового кода

CI (continuous integration, непрерывная интеграция) автоматически проверяет изменения проекта. Пайплайн (pipeline) это последовательность таких шагов: например, проверка кода, сборка и проверка приложения. Сборка (build) подготавливает запускаемый результат; в нашем курсе это в том числе образ контейнера (container image), набор файлов и настроек для его запуска. Основы в уроке 3.3 и уроке 4.2.

Зачем разбирать падение CI отдельно от кода? Результат зависит и от окружения: доступности сервиса загрузки, версий библиотек, разрешений. Зависимость (dependency) это сторонний компонент, необходимый проекту. Неизменный app.py не означает неизменность всех компонентов вокруг него.

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

Читай журнал первого неуспешного шага, а не только красный значок всего запуска. Допустим, загрузка образа ghcr.io/<user>/notes:0.7.1 закончилась сообщением unauthorized. Здесь <user> это место для имени владельца проекта, notes это имя образа, 0.7.1 это тег (tag), метка версии. Реестр образов (registry) хранит образы для загрузки. Сообщение про доступ направляет к разрешениям и токену реестра, а не к обработчику /notes.

Теперь сравни неуспешный запуск с последним успешным: шаг, текст ошибки, выбранные версии, настройки доступа и время. Если версия зависимости не закреплена, новая сборка могла получить другой код. Если внешний сервис временно недоступен, один повтор проверит эту гипотезу. Нестабильный тест (flaky test) то проходит, то падает без намеренного изменения кода и настроек, как дверной звонок, который иногда не срабатывает при одинаковом нажатии. Название помогает выделить проверку, результату которой пока нельзя доверять, но само не объясняет причину; её ещё предстоит найти.

Условия лишь кажутся одинаковыми: внутри могут отличаться порядок действий, доступность сети или время ответа. Представь проверку, которая ждёт ответ не более двух секунд. В одном запуске ответ пришёл за 1,8 секунды: 1,8 < 2, проверка прошла. В другом за 2,2 секунды: 2,2 > 2, проверка упала. Исходный код мог остаться прежним, а время изменилось. Нужно выяснить, почему проверка зависит от такой задержки и соответствует ли предел реальной задаче, а не просто увеличить ожидание до случайного зелёного результата.

Не называй любую разовую ошибку нестабильным тестом. Если все запуски с новым токеном дают unauthorized, наблюдение указывает на воспроизводимую проблему доступа. Если одинаковая версия то проходит, то падает, сохраняй сообщения обоих запусков и ищи отличающееся условие. Успешный повтор полезен как ещё одно наблюдение; он не доказывает, что первая ошибка исчезла навсегда. Аналогия со звонком ограничена: программа может подробно записать этапы проверки, поэтому у тебя есть больше данных, чем только «звенит или нет».

В «Заметках» используем явные версии, например 0.7.1, а не latest, изменяемую метку «последнего» образа. Тег помогает выбрать версию, хотя сам по себе не запрещает владельцу реестра заменить содержимое под этой меткой. Не утверждай, что закреплённый тег исключает любые изменения окружения.

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

Прикинь сам: шаг загрузки образа пишет unauthorized. Менять app.py?

Нет: сначала проверь доступ к реестру и разрешения токена, приложение на этом шаге ещё не запускалось.

Главное: читай первый упавший шаг и сравнивай с последним успешным запуском, а повтор до зелёного не заменяет поиск причины.

Похожая загадка: вручную работает, автоматически нет.

Окружение команды: почему один запуск отличается от другого

Иногда программа работает при ручном запуске, но падает при автоматическом. Чтобы объяснить это, нужно сравнить условия запуска. Окружение (environment) включает переданные программе настройки и условия её работы. Переменная окружения (environment variable) это именованное значение, которое процесс получает при старте, например PORT=8080. Значения хранятся отдельно от исходного кода, поэтому одна программа может вести себя по-разному. Это разобрано в уроке 1.7.

Представь повара, который приходит на другую кухню с тем же рецептом. Если продукты лежат в других шкафах, а посуда недоступна, привычные действия не сработают. Граница аналогии: программа не осмотрится сама, если ей явно не написали такое поведение; она использует полученные пути и настройки.

Рабочий каталог (working directory) это каталог, относительно которого программа ищет пути без начального /. Абсолютный путь (absolute path), например /opt/notes/app.py, начинается от корня файлового дерева. Относительный путь (relative path), например app.py, зависит от рабочего каталога. Поэтому одинаковая запись python3 app.py в разных каталогах может найти разные файлы или не найти ничего.

Теперь добавим поиск самой команды. PATH это переменная со списком каталогов для поиска исполняемых программ, разделённых двоеточиями. При вводе python3 оболочка ищет это имя в каталогах PATH по порядку. При вводе /usr/bin/python3 поиск по PATH не нужен: путь задан полностью. Но полный путь к Python ещё не определяет, где находится app.py, если имя скрипта осталось относительным.

Проверка условий должна идти от сообщения об ошибке. python3: can't open file '/home/ubuntu/app.py': [Errno 2] No such file or directory говорит, что интерпретатор (interpreter), программа, выполняющая код Python из урока 2.4, найден и запущен, но файла по указанному пути нет. python3: command not found означает другую проблему: оболочка не нашла саму команду. Эти ошибки нельзя исправить одной универсальной сменой прав.

В терминале pwd печатает рабочий каталог, command -v python3 показывает, как оболочка находит команду, а echo "$PATH" печатает значение PATH. $ подставляет значение переменной; кавычки сохраняют его одним аргументом. Представь результаты /home/ubuntu, /usr/bin/python3 и /usr/local/bin:/usr/bin:/bin. Первая строка объясняет, где ищется относительный app.py, вторая показывает найденный интерпретатор, третья порядок поиска команд. Каждая строка отвечает на свой вопрос, ни одна не доказывает правильность всех настроек приложения.

Cron это планировщик запуска команд по времени; он разбирался в уроке 1.7. Не ожидай от него тех же переменных и рабочего каталога, что у открытого терминала. Ручной запуск мог получить настройку из твоей оболочки, а автоматический нет. Сравни также пользователя: владелец терминала и пользователь службы могут иметь разные права на /var/lib/notes, каталог данных проекта.

Для «Заметок» служба из урока 1.8 получает настройки из /etc/notes/notes.env; запуск из терминала не начинает читать этот файл просто потому, что файл существует. Проверяй именно способ передачи настроек. Если программа выводит Permission denied, найди названный путь и пользователя процесса. Если выводит ошибку порта, сверяй адрес и порт. Если путь неверный, исправляй путь, а не выдавай административные права.

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

Прикинь сам: абсолютный путь к python3 гарантирует, что относительный app.py будет найден?

Нет: абсолютный путь выбирает интерпретатор, а скрипт ищется от рабочего каталога. Проверь оба пути.

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

Теперь о том, что сделать после того, как сервис ожил.

Восстановление и исправление: почему это два результата

Митигация (mitigation) уменьшает последствия сбоя: например, возвращает предыдущую рабочую версию. Откат (rollback) это возврат к прежней версии или настройке. Исправление причины меняет то, из-за чего возник отказ. Различие нужно, чтобы не объявить расследование законченным сразу после успешного запроса; оно разбиралось в уроке 10.1. Как при протечке: ведро спасает пол сейчас, а замена прокладки устраняет причину; ниже разберём границу этой аналогии. Выбор восстановления при неполных данных продолжим в уроке 10.8.

Аналогия: под протекающий кран ставят ведро, затем меняют прокладку. Ведро защищает пол, но кран всё ещё течёт. Граница аналогии: в IT откат иногда полностью убирает неисправное изменение, однако нужно понять, почему его приняли за рабочее и как обнаружить повторение.

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

Пример: после релиза notes падает при старте, журнал показывает ошибку чтения настройки. Возврат к рабочей версии может вернуть доступ к /notes. Это результат восстановления. Исправление настройки, проверка запуска до выпуска и понятное сообщение об ошибке относятся к устранению причины и предотвращению повторения.

Алерт (alert) это автоматическое уведомление о заданном условии, например о росте ошибок. Runbook это инструкция для повторяемой диагностики или восстановления; см. урок 10.2. Их имеет смысл добавлять под конкретную найденную проблему. Фраза «добавлю мониторинг» без условия срабатывания и действия получателя ещё не объясняет, чем система станет надёжнее.

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

Прикинь сам: после отката запрос снова успешен. Что осталось?

Выяснить причину, исправить её и добавить проверку; отдельно записать, что откат лишь восстановил сервис.

Главное: откат уменьшает ущерб, а исправление причины и профилактика это отдельная работа.

Остался вопрос, как всё это рассказать, включая случай, когда ответа не знаешь.

Рассказ о проекте и честное «не знаю»

История о проекте нужна, чтобы связать знание с самостоятельным действием. Это похоже на рассказ о ремонте велосипеда: «я заменил цепь» понятнее, если известно, что она перескакивала, как ты проверил износ и что изменилось после замены. Граница аналогии: учебная поломка создана специально, и это нужно прямо сказать.

Собери рассказ из четырёх частей: задача, наблюдения и проверки, действие, результат и вывод. Например: «На учебном сервере остановил “Заметки”, получил 502 через nginx. Журнал показал отказ на 127.0.0.1:8080; служба была неактивна, порт не слушался. Запустил notes, повторил прямой запрос и запрос через nginx, оба дали 200. Понял, что ошибка прокси не обязательно означает ошибку в самом прокси».

Здесь понятно, какое действие сделал ты, на каких данных основан вывод и как проверен результат. Не добавляй число пользователей, время простоя или уменьшение ошибок, если ты их не измерял. Если измерял, назови условия измерения. Две секунды в учебной ВМ, виртуальной машине, не обещают такую же скорость восстановления рабочей системы.

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

Неизвестный термин лучше уточнить. Можно сказать: «Под словом “сервис” ты имеешь в виду службу Linux или объект Kubernetes?» В Linux это управляемая фоновая программа. В Kubernetes Service даёт стабильный способ обращаться к группе подов, как в уроке 5.3. Одинаковое слово без уточнения ведёт к разным проверкам.

После репетиции оценивай не красоту речи, а проверяемость: назван ли симптом, объяснена ли проверка, следует ли вывод из результата. Найди один пробел, повтори соответствующий опыт, затем запиши ответ заново. Заучивание исправленной фразы без понимания не выдержит уточняющего вопроса.

Следующая репетиция в уроке 10.8 посвящена уровню middle: специалист самостоятельно решает знакомые рабочие задачи и объясняет выбор решения, как мастер, которому уже не нужна подсказка для каждого шага. Это ориентир ответственности, а не обещание получить такой уровень после курса. Проектирование системы (system design) это выбор её частей и связей под задачу, как план дома до начала стройки. Оно помогает заранее увидеть, где хранить данные и что произойдёт при отказе одной части; в отличие от дома, работающую программу можно часто менять и дополнять.

Прикинь сам: ты забыл флаг для просмотра предыдущего запуска контейнера. Как продолжить?

Объясни, что нужен журнал завершившегося запуска, и скажи, что уточнишь флаг в справке kubectl logs.

Главное: рассказ из четырёх частей без выдуманных цифр, а «не знаю» делится на известное и предположение.

Теории достаточно. В практике ты повторишь диагностику на стенде и проведёшь репетицию.

Практика

Подготовка: где выполнять команды

Для заданий с Linux используй учебную Ubuntu 24.04 LTS из урока 1.1, где настроены notes.service из урока 1.8 и nginx из урока 2.5. В этом уроке возвращаемся к тому стенду. Терминал macOS и контейнеры портфолио не заменяют этот стенд. kind, инструмент создания учебного Kubernetes-кластера из урока 5.1, тоже не создаёт в нём службу Linux notes. Здесь не переключаем Kubernetes и не меняем его объекты.

Нужны bash, curl, python3, git, nano, ss и стандартные утилиты Ubuntu. Их установка разобрана в указанных уроках. Приложение работает на 8080, nginx на 80; /slow?sec=5 есть в версии v3 из урока 2.4 и последующих версиях с этим маршрутом. Если после курса у тебя другой стенд, сначала восстанови этот учебный вариант по ссылкам.

Задания 1 и 2 выполни в одном терминале Bash на Ubuntu. Команда bash запускает эту оболочку; это нужно для сохранения переменной с номером процесса между шагами. Личные заметки в задании 3 создавай на машине с твоим репозиторием ~/notes, а не обязательно на сервере с /opt/notes.

Задание 1. Удали имя файла, сохрани открытый канал

Цель: увидеть расхождение df и du, не заполняя весь диск. Перед началом предскажи: после удаления файла место освободится сразу или после завершения держателя?

mkdir -p создаёт каталог и не ругается, если родитель уже существует. Здесь отдельный каталог внутри /tmp, временной области Ubuntu. df -h показывает доступное место в удобных единицах. Убедись, что доступно хотя бы 100 MiB.

mkdir -p /tmp/notes-interview
df -h /tmp/notes-interview

Пример вывода:

Filesystem      Size  Used Avail Use% Mounted on
/dev/vda1        20G   12G  7.0G  64% /

Как читать вывод: Filesystem это файловая система, Size её размер, Used занято, Avail доступно, Use% доля занятого места, Mounted on место подключения в дереве каталогов. Сначала смотри Avail; числа и имя устройства у тебя будут другими.

dd копирует данные: if=/dev/zero берёт поток нулевых байтов, of= задаёт выходной файл, bs=1M задаёт блок 1 MiB, count=50 задаёт 50 блоков, status=none выключает сообщение о копировании. В этом новом учебном каталоге файла held.log ещё быть не должно. du -k измеряет занятие файла в KiB, единицах по 1024 байта; df -k использует те же единицы, но для всей файловой системы.

dd if=/dev/zero of=/tmp/notes-interview/held.log bs=1M count=50 status=none
du -k /tmp/notes-interview/held.log
df -k /tmp/notes-interview

Пример на обычной файловой системе без сжатия:

51200   /tmp/notes-interview/held.log
Filesystem     1K-blocks     Used Available Use% Mounted on
/dev/vda1      20511312 12300000   7210000  64% /

Как читать вывод: 50 × 1024 = 51 200 KiB. В du первое число относится к одному файлу. В df колонка Used относится ко всей файловой системе. Запиши её для сравнения; остальные размеры зависят от стенда.

sleep 600 ждёт 600 секунд. < открывает файл как стандартный вход процесса, даже если sleep его не читает. & запускает ожидание в фоне, $! даёт PID последнего фонового процесса. HOLDER_PID=$! сохраняет его, без пробелов вокруг =. rm удаляет имя, а ls -l показывает подробные сведения о дескрипторе 0. Кавычки вокруг пути сохраняют его одним аргументом.

sleep 600 < /tmp/notes-interview/held.log &
HOLDER_PID=$!
rm /tmp/notes-interview/held.log
ls /tmp/notes-interview/held.log
ls -l "/proc/$HOLDER_PID/fd/0"
du -k /tmp/notes-interview
df -k /tmp/notes-interview

Пример важных строк:

ls: cannot access '/tmp/notes-interview/held.log': No such file or directory
lr-x------ 1 ubuntu ubuntu 64 Sep 30 10:00 /proc/1234/fd/0 -> /tmp/notes-interview/held.log (deleted)
4       /tmp/notes-interview

Как читать вывод: первая ошибка ожидаема: имени больше нет. Стрелка -> показывает, на какой файл ссылается открытый канал процесса 1234; (deleted) подтверждает удаление имени. du теперь учитывает только маленький каталог. Used в повторном df ещё включает данные файла; точное число может меняться из-за других программ. Bash также напечатает номер фонового задания и PID.

Закрой держателя: kill без дополнительного флага посылает SIGTERM сохранённому процессу. wait ждёт завершения именно этого фонового процесса. Ненулевой код wait после сигнала ожидаем, это не новая поломка. rmdir удаляет только пустой каталог.

kill "$HOLDER_PID"
wait "$HOLDER_PID"
df -k /tmp/notes-interview
rmdir /tmp/notes-interview

Как читать вывод: после завершения держателя Used должен уменьшиться примерно на 51 200 KiB, если параллельно никто не записывал данные. Bash может сообщить Terminated. rmdir при успехе молчит. Важнее сочетание наблюдений, чем точное совпадение всей строки df.

Типичные ошибки: dd: failed to open ...: Permission denied означает отсутствие права записи, проверь свой учебный каталог. No space left on device означает нехватку ресурса: удали только созданный здесь файл и выбери учебный диск с запасом места. kill: ... No such process означает, что 600 секунд прошли или терминал был закрыт; повтори опыт в одном терминале. Если df не показывает ожидаемую разницу, проверь сжатие файловой системы и посторонние записи; наличие открытого (deleted) не зависит от точности округлённых чисел.

Задание 2. Сравни отказ соединения и медленный ответ

Цель: проверить две разные гипотезы, а не запомнить два номера ошибок. Команда ss -ltn 'sport = :9999' показывает слушающие TCP-порты: -l только слушающие, -t TCP, -n числовые адреса; выражение в кавычках выбирает локальный порт 9999.

ss -ltn 'sport = :9999'

Ожидается только заголовок:

State Recv-Q Send-Q Local Address:Port Peer Address:Port Process

Как читать вывод: отсутствие строк под заголовком означает отсутствие слушателя. State это состояние, две колонки Q относятся к очередям, следующие колонки задают местный и удалённый адреса. Если есть строка LISTEN, выбери другой свободный порт и замени 9999 в следующей команде.

curl делает запрос. --noproxy '*' обращается напрямую, игнорируя настройки внешнего HTTP-прокси; '*' в кавычках относится ко всем адресам. -sS убирает индикатор прогресса, но оставляет ошибки, --max-time 2 ограничивает всё ожидание двумя секундами. echo печатает $?, код выхода предыдущей команды; между curl и echo не вставляй другую команду.

curl --noproxy '*' -sS --max-time 2 http://127.0.0.1:9999/
echo "код=$?"

Типичный результат curl 8.5.0:

curl: (7) Failed to connect to 127.0.0.1 port 9999 after 0 ms: Couldn't connect to server
код=7

Как читать вывод: соединение не установлено, ожидание почти отсутствует. В этом контролируемом опыте мы уже проверили отсутствие слушателя. Точная формулировка и время могут отличаться; один код 7 без других наблюдений был бы недостаточен.

Теперь запроси /slow напрямую у работающего приложения. -v печатает этапы обмена; -o /dev/null отбрасывает тело ответа, если оно придёт. Кавычки вокруг адреса защищают ? от обработки оболочкой. Предскажи: успеет ли приложение ответить за две секунды при задержке пять?

curl --noproxy '*' -v --max-time 2 -o /dev/null 'http://127.0.0.1:8080/slow?sec=5'
echo "код=$?"

Фрагмент ожидаемого вывода:

* Connected to 127.0.0.1 (127.0.0.1) port 8080
> GET /slow?sec=5 HTTP/1.1
> Host: 127.0.0.1:8080
curl: (28) Operation timed out after 2001 milliseconds with 0 bytes received
код=28

Как читать вывод: * Connected подтверждает установление соединения, > отмечает отправленные строки запроса, GET просит получить ресурс, Host указывает адрес получателя. Ответ ещё не получен, а время уже истекло. Индикатор прогресса и дополнительные строки пропущены. В журнале приложения возможен BrokenPipeError: клиент закрыл соединение до отправки ответа, это следствие учебного таймаута.

Увеличь только предел ожидания и повтори тот же запрос. -w печатает итоговые поля: %{http_code} это HTTP-код, %{time_total} это длительность в секундах, \n перевод строки. Здесь одинарные фигурные скобки являются форматом curl.

curl --noproxy '*' -sS --max-time 7 -o /dev/null -w 'HTTP=%{http_code} время=%{time_total}\n' 'http://127.0.0.1:8080/slow?sec=5'

Пример:

HTTP=200 время=5.003421

Как читать вывод: 200 и длительность около пяти секунд подтверждают намеренную задержку. Мы изменили ожидание клиента, а не починили приложение. Предел 7 выбран потому, что 7 > 5 и остаётся запас на обработку.

Типичные ошибки: (7) Failed to connect ... port 8080 означает, что сначала нужно восстановить рабочий стенд по подготовке. HTTP 404, код отсутствующего маршрута, на /slow означает другую версию приложения: проверь вариант из урока 2.4. (28) и после семи секунд требует проверки фактического запроса и состояния приложения; не назначай сеть виноватой только по номеру.

Задание 3. Создай личные заметки и проведи репетицию

Перейди в репозиторий командой cd ~/notes. ~ обозначает домашний каталог. git ls-files перечисляет уже отслеживаемые файлы, здесь только по указанному пути. Сначала проверь, что заметки ещё не отслеживаются.

cd ~/notes
git ls-files docs/interview-notes.md

Как читать вывод: пустой вывод означает отсутствие этого пути среди отслеживаемых файлов. Если путь напечатан, одно правило игнорирования не поможет. git rm --cached docs/interview-notes.md уберёт файл из списка для следующего коммита и сохранит его на диске; если он уже был опубликован, это не удалит прежнюю историю. Не применяй команду к другим путям.

Редактор nano .gitignore открывает список игнорирования. Добавь отдельную строку docs/interview-notes.md, если её ещё нет, сохрани Ctrl+O, подтверди имя Enter, выйди Ctrl+X. Остальные правила сохрани. Затем создай каталог и открой заметки тем же редактором.

nano .gitignore
mkdir -p docs
nano docs/interview-notes.md

Запиши минимум три настоящие истории: удалённый открытый файл, таймаут /slow, диагностика 502 из следующего раздела. Шаблон одной записи:

### Учебный таймаут «Заметок»

Ситуация: /slow?sec=5 не успевал ответить клиенту за две секунды.
Наблюдения: соединение установилось, запрос ушёл, curl завершился с кодом 28.
Действие: увеличил предел ожидания клиента до семи секунд и повторил запрос.
Результат: HTTP 200 примерно через пять секунд.
Вывод: таймаут не доказывает отсутствие связи с сервером.

Как читать запись: первая строка называет условия, вторая отделяет наблюдения от объяснения, третья описывает твоё действие, четвёртая результат, пятая переносимый вывод. Дополни две другие истории только после своих опытов.

Проверь игнорирование: git check-ignore -v печатает сработавшее правило, а git status --short кратко показывает изменения выбранных файлов. -- отделяет флаги от путей.

git check-ignore -v docs/interview-notes.md
git status --short -- .gitignore docs/interview-notes.md

Пример для уже существовавшего .gitignore:

.gitignore:14:docs/interview-notes.md    docs/interview-notes.md
 M .gitignore

Как читать вывод: 14 это пример номера строки правила; вторая часть показывает исключённый путь. M означает изменение .gitignore. Новая .gitignore может отображаться как ??, ещё не отслеживаемый файл. Самих личных заметок в статусе быть не должно. Для прежнего отслеживаемого файла после rm --cached ожидается удаление из следующего коммита, хотя файл на диске остаётся. Игнорирование не шифрует заметки и не защищает их от чтения другими пользователями компьютера.

Проведи разговор на 25 минут с напарником или диктофоном телефона. За 3 минуты расскажи о проекте, за 12 ответь на шесть вопросов ниже, за 5 разберись с 502 вслух, за 5 прослушай ответ и запиши пробелы. Сумма: 3 + 12 + 5 + 5 = 25 минут. Сначала отвечай без раскрытых подсказок.

Для каждого сценарного ответа поставь по одному баллу за конкретный симптом, гипотезу с проверкой, объяснение результата и проверку восстановления. Максимум четыре. Это учебная шкала, а не обещание пройти интервью. Если ответ получил один балл, найди отсутствующую часть, повтори опыт и запиши новую версию ответа. Также проверь честность: выдуманный опыт не становится хорошим от наличия четырёх пунктов.

Типичные ошибки: fatal: not a git repository означает неверный каталог, вернись в ~/notes. The following paths are ignored by one of your .gitignore files при попытке добавить заметки ожидаем: не обходи правило через -f, принудительное добавление. Если история состоит только из названий команд, допиши ожидаемый и фактический результат каждой проверки.

Сломай и почини

Поломка: nginx работает, а «Заметки» остановлены

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

Здесь -H 'Host: notes.lab' задаёт HTTP-заголовок, поле запроса с именем сайта. nginx выбирает по нему настройки «Заметок», хотя подключаемся к локальному IP. Такой запрос не требует изменения DNS, системы перевода имён в IP-адреса, из урока 2.3. -w печатает HTTP-код, остальные флаги curl уже разобраны выше.

curl --noproxy '*' -sS --max-time 3 -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/notes
curl --noproxy '*' -sS --max-time 3 -o /dev/null -w '%{http_code}\n' -H 'Host: notes.lab' http://127.0.0.1/notes

Ожидается:

200
200

Как читать вывод: первая строка относится к приложению напрямую, вторая к тому же пути через nginx. Если вместо второй строки 301 или 308, код перенаправления на другой адрес, у тебя настроен иной стенд, например с HTTPS. Вернись к учебному HTTP-варианту урока 2.5 перед выполнением этой поломки. Если исходные коды не 200, сначала восстанови базовый стенд.

Останови только приложение: sudo выполняет административную команду, systemctl stop notes просит systemd остановить службу. Это намеренная поломка. Повтори запрос через работающий nginx.

sudo systemctl stop notes
curl --noproxy '*' -sS --max-time 3 -o /dev/null -w '%{http_code}\n' -H 'Host: notes.lab' http://127.0.0.1/notes

Ожидается:

502

Как читать вывод: nginx смог ответить клиенту, но не получить ответ приложения. stop при успехе молчит. HTTP 502 не равен коду выхода 502 и не доказывает поломку самого nginx.

Теперь произнеси гипотезу до проверки: «Возможно, приложение остановлено; проверю службу, слушающий порт и журнал посредника». is-active уже разобран; его ненулевой код при inactive ожидаем. tail -n 3 выводит последние три строки файла, sudo нужен для доступа к системному журналу. Смотри строки именно своего запроса и времени, старые ошибки не объясняют текущую ситуацию.

systemctl is-active notes
ss -ltn 'sport = :8080'
sudo tail -n 3 /var/log/nginx/error.log

Пример значимых строк:

inactive
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
2026/09/30 10:05:11 [error] 812#812: *7 connect() failed (111: Connection refused) while connecting to upstream, client: 127.0.0.1, server: notes.lab, request: "GET /notes HTTP/1.1", upstream: "http://127.0.0.1:8080/notes", host: "notes.lab"

Как читать вывод: inactive подтверждает остановку службы, заголовок без строк подтверждает отсутствие слушателя на 8080. В журнале дата и время привязывают событие к запросу, [error] это уровень ошибки, 812#812 обозначает процесс и поток nginx, *7 номер соединения. client это адрес клиента, server выбранный сайт, request полученный запрос, upstream адрес приложения, host имя из заголовка. Главная часть для диагноза: отказ подключения к ожидаемому 8080, а не просто наличие слова error.

Почини: systemctl start notes запускает службу. Дай приложению несколько секунд на запуск, затем проверь тот же путь напрямую и через nginx, а не только состояние службы. Если немедленный запрос получает отказ, повтори через несколько секунд; устойчивый отказ требует чтения журнала, а не бесконечного ожидания.

sudo systemctl start notes
curl --noproxy '*' -sS --max-time 3 -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/notes
curl --noproxy '*' -sS --max-time 3 -o /dev/null -w '%{http_code}\n' -H 'Host: notes.lab' http://127.0.0.1/notes

Ожидается:

200
200

Как читать вывод: обе части пути снова работают. Данные заметок не удалялись, app.py и /etc/notes/notes.env не менялись. Добавь историю в личный файл: это было учебное отключение, поэтому причина заранее известна; в реальной ситуации после восстановления ещё нужно выяснить причину остановки.

Типичные ошибки: Unit notes.service not found означает, что нет службы из урока 1.8, сначала настрой её по ссылке; запуск вручную не делает этот опыт равнозначным. (7) Failed to connect ... port 80 означает, что nginx тоже не принимает соединения, проверь его состояние по уроку 2.5. Если start пишет Job for notes.service failed, нужно прочитать журнал службы.

Для последнего случая journalctl -u notes -n 30 --no-pager выбирает журнал службы через -u, последние 30 записей через -n и вывод прямо в терминал через --no-pager.

sudo journalctl -u notes -n 30 --no-pager

Один возможный фрагмент:

Sep 30 10:10:00 lab python3[1234]: OSError: [Errno 98] Address already in use

Как читать вывод: время и имя машины указывают место события, python3[1234] программу и PID. Address already in use означает занятие адреса и порта другим процессом. Это пример дополнительной неисправности, а не ожидаемое следствие stop; проверяй слушателя и исходные настройки по уроку 1.8.

Почини также объяснение

До раскрытия разбора перепиши три ответа. В каждом добавь наблюдение, проверку и вывод.

  1. «При 502 перезапущу весь сервер».
  2. «Код 28 доказывает, что сети нет».
  3. «Код 137 всегда означает нехватку памяти».
Разбор

Первому ответу не хватает поиска участка отказа: уточни адрес и время, прочитай журнал nginx, проверь upstream, восстанови приложение подходящим действием, повтори запрос. Перезапуск всей машины меняет слишком много условий сразу.

Во втором перепутаны истечение времени и причина. В нашем опыте Connected и отправленный запрос уже видны, а /slow намеренно ждёт. Нужно учитывать этап обмена.

В третьем сделан вывод по одному числу. 137 согласуется с SIGKILL, но для OOM нужны дополнительные сведения, например причина OOMKilled. Удалять под только по номеру не требуется.

ИИ в помощь

Нейросеть хорошо играет роль интервьюера и находит дыры в ответе, но не знает твоего стенда и может уверенно объявить причину по одной строке лога. Общие правила: ИИ-помощник.

Задача: провести репетицию собеседования.

Ты интервьюер на позицию junior DevOps. Задавай по одному вопросу про диагностику 502, df и du, код 137,
историю git и падение CI. После каждого моего ответа скажи: назван ли симптом, объяснена ли проверка,
следует ли вывод из результата. Не подсказывай ответ, пока я не скажу «не знаю».

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

Задача: разобрать текст ошибки по одной строке.

Вот строка из лога nginx: <вставь строку без секретов>. Раздели: что это факт, какие две гипотезы
возможны, какая одна проверка их различает и чего я ожидаю увидеть при каждой.

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

Задача: улучшить рассказ о проекте.

Вот мой рассказ о проекте на две минуты: <вставь текст>. Отметь утверждения без числа или без проверки,
места, где учебный стенд выдан за рабочую систему, и предложи честные формулировки без выдуманных цифр.

Проверь ответ: оставляй только то, что ты сам делал и измерял. Типичная ошибка: нейросеть добавляет красивые цифры и опыт, которых у тебя не было.

Словарик урока

Термин Простыми словами
Мок-собеседование (mock interview) Репетиция разговора о работе.
Junior, DevOps Начальный уровень; работа над доставкой изменений и надёжностью приложения.
Продакшен (production), стенд Среда настоящих пользователей; подготовленное окружение для проверки.
Клиент (client) Программа, отправляющая запрос.
Слушатель (listener) Программа, готовая принимать подключения на выбранном порту.
Процесс (process), PID Запущенная программа; её номер.
Служба (service), systemd Управляемая фоновая программа; система её запуска.
Симптом, гипотеза, диагностика Наблюдение; возможное объяснение; поиск причины проверками.
Релиз (release), откат (rollback) Выпуск версии; возврат к предыдущему состоянию.
Конфигурация (configuration) Настройки программы.
Reverse proxy, nginx, upstream Посредник; программа-посредник; следующий сервер.
IP-адрес, порт Адрес машины; номер для выбора программы.
Соединение (connection), TCP Связь программ; протокол её установления и обмена.
HTTP, HTTP-код Формат запросов и ответов; результат обработки запроса.
Заголовок (header), Host Поле HTTP-сообщения; имя получателя запроса.
DNS Перевод имён в IP-адреса.
Connection refused, timeout Явный отказ подключения; истечение времени ожидания.
Firewall, REJECT, DROP Сетевой фильтр; явное отклонение; отбрасывание без ответа.
Код выхода (exit status) Числовой результат команды для оболочки.
Оболочка (shell), Bash Программа запуска команд; один из её вариантов.
Лог (log) Журнал событий.
Файловая система, inode Организация хранения; запись о свойствах и расположении файла.
Дескриптор, /proc, (deleted) Номер открытого канала; состояние Linux; отметка удалённого имени.
Байт, KiB, MiB Единицы размера: KiB = 1024 байта, MiB = 1024 KiB.
Контейнер, образ (image) Среда запуска процесса; файлы и настройки для запуска.
Kubernetes, кластер Управление контейнерами; группа машин.
Pod, namespace, Service Единица запуска; группа объектов; обращение к группе подов.
CrashLoopBackOff Ожидание между повторными запусками после завершений.
OOM, OOMKilled, лимит Нехватка памяти; отметка завершения из-за неё; верхняя граница ресурса.
Сигнал (signal), SIGTERM, SIGKILL Сообщение процессу; просьба завершиться; принудительное прекращение.
Liveness, readiness Проверки живости и готовности.
Git, коммит, ветка История файлов; сохранённое состояние; линия разработки.
.gitignore Пропуск ещё не отслеживаемых путей при добавлении.
Merge, rebase Объединение истории; перенос изменений в новые коммиты.
Force push Принудительное обновление удалённой ветки.
Секрет, токен Значение для доступа; ключ программного доступа.
CI, пайплайн Автоматические проверки; последовательность шагов.
Сборка, зависимость Подготовка запускаемого результата; необходимый сторонний компонент.
Реестр, тег Хранилище образов; метка версии.
Flaky test Нестабильная проверка при одинаковых условиях.
Окружение, переменная окружения Условия запуска; именованная настройка процесса.
Стандартный вход (standard input), интерпретатор (interpreter) Канал получения данных программой; программа выполнения исходного кода.
Рабочий каталог, абсолютный и относительный путь Основа поиска; путь от корня; путь от текущего каталога.
PATH, cron Список каталогов поиска команд; планировщик запуска по времени.
Митигация, алерт, runbook Уменьшение последствий; уведомление; инструкция действий.
ВМ (virtual machine), kind Программный компьютер; инструмент создания учебного Kubernetes-кластера.
Middle, проектирование системы (system design) Уровень самостоятельного решения знакомых рабочих задач; выбор частей системы и связей между ними под конкретную задачу.

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

Раздел для повторения: ответь вслух, потом открой ответ. Короткие вопросы с пометкой [на скорость] тренируй на время: ответ за 30 секунд.

1. [junior] [часто] «Заметки» через nginx отвечают 502. С чего начнёшь?

Ответ

Уточню адрес, путь, масштаб ошибки, время начала и последние изменения. Прочитаю соответствующую строку error.log, проверю адрес upstream, состояние notes и слушающий порт. Сравню прямой запрос на 8080 с запросом через nginx к тому же пути. По результату восстановлю работу и повторю запрос, затем разберу причину.

Что хотят услышать: связь гипотезы с проверкой и объяснение, почему ответ 502 не доказывает поломку nginx.

Красный флаг: «перезапущу весь сервер» без наблюдений и проверки результата.

2. [junior] [часто] Под в CrashLoopBackOff. Что проверишь до удаления пода?

Ответ

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

Что хотят услышать: использование сведений о завершившемся запуске и объяснение, почему пересоздание с прежними настройками может повторить отказ.

Красный флаг: «удалю, чтобы Kubernetes всё починил».

3. [junior] [часто] Не проходит readiness. Чем это отличается от неудачной liveness?

Ответ

Readiness проверяет готовность принимать запросы: неготовый под исключается из обычного обслуживания. Сама эта проверка не перезапускает контейнер. Liveness проверяет живость; повторные неудачи могут вызвать перезапуск. У «Заметок» это /readyz и /healthz. Работающий процесс может оставаться неготовым.

Что хотят услышать: разные последствия двух проверок и связь с маршрутами проекта.

Красный флаг: «любая неудачная проба означает перезапуск».

4. [junior] Почему df показывает занятое место, которого не видно в du?

Ответ

df учитывает занятое место файловой системы, du обходит доступные имена файлов. Возможен удалённый файл, который ещё держит процесс: проверю открытые дескрипторы и (deleted). Сначала удостоверюсь, что сравниваю одну файловую систему и не пропускаю каталоги из-за прав. После корректного закрытия последнего держателя место освободится. Нехватку inode проверю отдельно, если проблема в создании новых файлов.

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

Красный флаг: форматирование диска или завершение случайного процесса как первая проверка.

5. [junior] Чем отличаются Connection refused и timeout? Достаточно ли кодов 7 и 28?

Ответ

Refused это явный отказ установить соединение; timeout это истечение времени. Код curl 7 шире конкретного отказа, а 28 возможен на разных этапах. Читаю сообщение и подробный обмен: если уже видно соединение и отправленный запрос, проверяю ожидание ответа приложения. В нашем опыте /slow?sec=5 дал 28 при пределе две секунды, хотя порт работал.

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

Красный флаг: «28 всегда означает сломанную сеть».

6. [junior] Служба notes не запускается. Как найдёшь причину?

Ответ

Проверю её состояние через systemctl и журнал через journalctl с выбором службы. При Address already in use проверю слушающий порт; при ошибке доступа проверю права на указанный файл и пользователя службы. Действие выбираю по сообщению. После исправления повторю запуск и запрос к /notes, потому что состояние active само не доказывает успешный ответ.

Что хотят услышать: журнал с конкретной ошибкой, проверка соответствующей гипотезы и проверка работы приложения.

Красный флаг: запускать всё с административными правами без выяснения, к какому файлу нет доступа.

7. [junior] Пароль попал в коммит. Поможет удалить файл следующим коммитом?

Ответ

Нет, прежний коммит остаётся. Сначала отзову или заменю пароль, потом уберу его из файлов и согласую очистку опубликованной истории. Даже очищенная центральная история не отменяет чужих копий. .gitignore полезен для ещё не отслеживаемых файлов, но не отзывает секрет и не удаляет старые коммиты.

Что хотят услышать: прекращение доступа по старому секрету раньше косметического удаления файла.

Красный флаг: «нового файла не видно, значит пароль снова безопасен».

8. [junior] [на скорость] Контейнер завершился с кодом 137. Это точно нехватка памяти?

Ответ

Нет. В записи завершения по сигналу 137 = 128 + 9, а номер 9 соответствует SIGKILL. Нужно проверить причину завершения, журнал и сведения о памяти. OOMKilled подтверждает направление поиска нехватки памяти; одно число не объясняет, кто и почему прекратил процесс. Программа также может сама вернуть такой код.

Что хотят услышать: полный расчёт и отдельные доказательства причины.

Красный флаг: автоматически увеличивать память при любом 137.

9. [junior] Чем merge отличается от rebase и почему важна общая ветка?

Ответ

Merge объединяет линии истории и при разошедшихся ветках создаёт коммит объединения; иногда возможен переход вперёд без нового коммита. Rebase переносит изменения на другую основу и создаёт новые коммиты. У коллег может уже быть прежняя история общей ветки, поэтому её переписывание требует согласования, а не автоматического force push.

Что хотят услышать: что именно меняется в истории и почему другие копии имеют значение.

Красный флаг: «всегда делаю rebase и принудительную отправку, так красивее».

10. [junior] CI упал, хотя app.py не менялся. Куда смотришь?

Ответ

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

Что хотят услышать: различие кода приложения и окружения проверки, действие по тексту ошибки.

Красный флаг: менять приложение наугад или повторять CI до зелёного результата без чтения журнала.

11. [junior] Расскажи о сложной проблеме, которую ты решил в проекте.

Ответ

Выбери свой опыт: задача, симптом, гипотезы и результаты проверок, действие, подтверждённый результат, вывод. Например, опиши учебный 502 после остановки notes и различие прямого запроса и запроса через nginx. Скажи, что стенд учебный. Если ошибся в первой гипотезе, объясни, какой результат заставил её изменить.

Что хотят услышать: твои конкретные действия, честные условия и вывод из наблюдений.

Красный флаг: выдумывать пользователей, должность или измерения вместо рассказа о своём опыте.

12. [junior] [на скорость] Что скажешь, если не знаешь ответ или забыл флаг команды?

Ответ

Разделю то, что знаю, и то, что предполагаю. Уточню незнакомый термин. Назову нужное наблюдение и способ его получить, а точный флаг предложу проверить в справке. Например, при повторных падениях нужен журнал предыдущего запуска контейнера, даже если синтаксис команды не помню.

Что хотят услышать: честность и проверяемое продолжение рассуждения.

Красный флаг: придумывать точный ответ и держаться за него вопреки результату проверки.

13. [junior] Как узнать, какой процесс слушает порт 8080?

Ответ

Выполняю ss -ltnp 'sport = :8080': показывает слушающие TCP-порты и процессы. Флаг -p нужен для имени процесса и требует прав, поэтому без sudo чужих процессов можно не увидеть. Альтернатива: lsof -nP -iTCP:8080 -sTCP:LISTEN. Если порт не слушает никто, вижу пустой вывод, и значит, клиент получит Connection refused, если ничего не мешает по пути. Дальше смотрю, почему служба не запущена или слушает другой адрес, например только 127.0.0.1.

Что хотят услышать: ss или lsof, флаги, права для имени процесса, отличие адресов 127.0.0.1 и 0.0.0.0.

Красный флаг: советует просто перезагрузить сервер.

14. [junior] Чем Pod отличается от Deployment и зачем нужен Deployment?

Ответ

Pod - минимальная единица запуска: один или несколько контейнеров с общей сетью. Если под умер или нода пропала, сам он не вернётся. Deployment описывает желаемое состояние: образ, число реплик, стратегию обновления, и через ReplicaSet поддерживает нужное число подов. Он же даёт rolling update и откат (kubectl rollout undo). Поэтому приложения запускают через Deployment, а голые поды оставляют для отладки.

Что хотят услышать: под временный, Deployment держит число реплик, обновление и откат, ReplicaSet.

Красный флаг: запускает продовые приложения голыми подами.

15. [junior] Чем контейнер отличается от виртуальной машины?

Ответ

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

Что хотят услышать: общее ядро, namespaces и cgroups, лёгкость, слабее изоляция, когда нужна ВМ.

Красный флаг: говорит, что контейнер - это «маленькая ВМ».

Проверено на версиях

Материал пересмотрен в сентябре 2026 года. Практические команды проверены в отдельном контейнере Ubuntu 24.04 LTS с systemd: Bash 5.2, coreutils 9.4, curl 8.5.0, Git 2.43.0, Python 3.12.3, systemd 255, nginx 1.24.0 из пакетов Ubuntu. Прогнаны открытый удалённый файл, два варианта ошибки curl, восстановление после 502 и проверка игнорирования заметок. Редактирование в nano и устная репетиция выполняются вручную.

Для HTTP-опытов нужен app.py с /notes и /slow?sec=5, как в версии v3 из урока 2.4, и HTTP-конфигурация nginx из урока 2.5. Версия исходника v3 и тег образа 0.7.1 обозначают разные вещи; не заменяй работающую позднюю версию только из-за примера в тексте. Кластер kind и namespace notes используются лишь в разборе вопросов, команды изменения кластера в этом уроке не выполняются.

Размеры диска, PID, даты и длительности в примерах зависят от стенда. На файловой системе со сжатием нулевые данные могут занимать меньше указанного размера. Для опыта с диском важны удалённое имя, открытый канал и освобождение места после закрытия, а не универсальная разница ровно в 50 MiB.

Итог урока: ты умеешь

  • Отличать наблюдаемый симптом от гипотезы о его причине.
  • Объяснять, какой результат ожидаешь от проверки и как прочитать фактический.
  • Разбирать 502 по пути клиент -> nginx:80 -> приложение:8080 и подтверждать восстановление запросом.
  • Различать отказ подключения и таймаут, не назначая причину только по коду curl.
  • Объяснять занятое место удалённого открытого файла и завершать свой учебный держатель.
  • Различать код 137, подтверждение OOM и состояние повторных запусков.
  • Объяснять последствия утечки секрета и ограничения .gitignore.
  • Вести личные заметки, проверять их игнорирование и рассказывать о настоящем учебном опыте.
  • Честно обозначать пробел в знаниях и предлагать конкретную следующую проверку.

Дальше: Урок 10.8: мок-собеседование: уровень middle и system design

Проверь себя

Короткий тест по уроку: 5 вопросов из банка в 30. Засчитывается только полностью правильный ответ, порог 60%. Каждая новая попытка даёт другие вопросы, пока банк не закончится. Ответы видны после проверки.

Тест работает с включённым JavaScript.

тема 10 урок 10.7 2 ч курс 0/0 ← → уроки