Главная/Observability и диагностика/Практика

Раздел 13. Практические задания

Задания проверяют не знание команд, а способность дойти от симптома до причины. Формулировка «найти причину» означает: назвать её и показать проверку, которая её подтвердила.

Часть заданий требует прав root (чтение dmesg, journalctl, каталога Docker). Если прав нет — выполните доступную часть и отметьте невыполненное явно. Проверка, молча пропущенная, хуже отсутствующей.

Как проверять себя

ФормулировкаЧто требуется
«показать, что…»Команда и её фактический вывод
«найти причину»Причина плюс проверка, которая её подтвердила
«измерить»Число, полученное командой, а не оценка
«объяснить»Механизм, а не пересказ симптома

Задание 1. Ротация логов

Тип: обязательное
Материал: 13.1

Настроить ротацию и проверить, что размер ограничен.

Требования:

  1. Запустить container, выводящий не менее 50 000 строк.
  2. Показать размер лога без ротации.
  3. Повторить с max-size и max-file, показать разницу.
  4. Вычислить верхнюю границу расхода на container и на стек из восьми сервисов.
  5. Показать, что ранние строки после ротации недоступны.
  6. Настроить ротацию по умолчанию в daemon.json и объяснить, почему она не применится к существующим container'ам.

Проверка

bash
docker inspect ИМЯ --format '{{json .HostConfig.LogConfig}}'
docker logs ИМЯ | wc -l
docker logs ИМЯ | head -1
sudo du -h $(docker inspect ИМЯ --format '{{.LogPath}}')

Критерий

Число доступных строк с ротацией меньше выведенного приложением. Верхняя граница посчитана как max-size × max-file. Объяснено, что настройка daemon.json действует только на новые container'ы.


Задание 2. Пять полей одной командой

Тип: обязательное
Материал: 13.2

Извлечь через docker inspect --format пять разных полей одной командой.

Требования:

  1. Одной командой получить: статус, код выхода, признак OOM, число перезапусков, состояние здоровья.
  2. Обработать случай, когда проверка здоровья не настроена, — без ошибки шаблона.
  3. Извлечь вывод последней неудачной проверки здоровья.
  4. Показать, что этого вывода нет в docker logs, и объяснить почему.
  5. Собрать список всех container'ов с их состоянием одной командой.

Проверка

bash
docker inspect ИМЯ --format '{{.State.Status}} {{.State.ExitCode}} {{.State.OOMKilled}} {{.RestartCount}} {{if .State.Health}}{{.State.Health.Status}}{{else}}—{{end}}'
docker inspect ИМЯ --format '{{json .State.Health.Log}}' | python3 -m json.tool

Критерий

Команда работает на container'е с проверкой здоровья и без неё. Вывод неудачной проверки получен и показано, что в docker logs его нет.

Ориентир для самопроверки

docker logs показывает вывод главного процесса. Healthcheck выполняется отдельным процессом, который daemon запускает и завершает; его stdout попадает в .State.Health.Log и больше никуда.


Задание 3. OOM и его следы

Тип: обязательное
Материал: 13.3

Воспроизвести OOM kill и найти след в трёх местах.

Требования:

  1. Воспроизвести OOM с лимитом --memory.
  2. Найти след в состоянии container'а.
  3. Найти событие oom в docker events.
  4. Найти запись в журнале ядра — или отметить, что прав нет.
  5. Показать четыре разные причины кода 137/143 и способ различить каждую.
  6. Объяснить, почему OOMKilled: false не доказывает отсутствие OOM.

Проверка

bash
docker inspect ИМЯ --format '{{.State.ExitCode}} {{.State.OOMKilled}}'
docker events --since 10m --until 0s --filter 'event=oom'
sudo dmesg -T | grep -i 'killed process' | tail -3

Критерий

Три следа найдены (журнал ядра — при наличии прав, иначе отмечено). Четыре причины различены. Объяснено, что OOM на уровне host не выставляет OOMKilled.


Задание 4. Что заняло место

Тип: обязательное
Материал: 13.4

Найти, что занимает место, и освободить безопасно.

Требования:

  1. Показать расход по всем категориям.
  2. Показать категорию, которой нет в docker system df, и измерить её.
  3. Объяснить, почему сумма колонки SIZE в docker images больше занятого места.
  4. Найти container, пишущий в свою файловую систему, и показать какие файлы.
  5. Составить план очистки, разделив обратимое и необратимое.
  6. Выполнить обратимую часть; необратимую оставить с обоснованием.

Проверка

bash
docker system df
docker system df -v | head -20
sudo du -sh /var/lib/docker/containers/*/ | sort -h | tail -5
docker ps -s
docker diff ИМЯ

Критерий

Названа категория логов как отсутствующая в отчёте. Разница SIZE и UNIQUE SIZE объяснена общими слоями. План разделяет обратимое и необратимое, и необратимое не выполнено без явного решения.


Задание 5. Вход в namespaces

Тип: обязательное
Материал: 13.5

Провести диагностику сети работающего container'а, не изменяя его образ.

Требования:

  1. Запустить сервис на образе без сетевых утилит.
  2. Показать, что ss и netstat в образе отсутствуют.
  3. Посмотреть открытые порты через sidecar с общим сетевым namespace.
  4. То же через nsenter с host — или отметить отсутствие прав.
  5. Объяснить разницу между nsenter -n и nsenter -m -n.
  6. Показать, что sidecar с --pid не даёт доступа к файловой системе цели.

Проверка

bash
docker exec ИМЯ sh -c 'command -v ss || echo отсутствует'
docker run --rm --network container:ИМЯ nicolaka/netshoot ss -tlnp
sudo nsenter -t $(docker inspect ИМЯ --format '{{.State.Pid}}') -n -- ss -tlnp

Критерий

Порты определены минимум одним способом без установки чего-либо в целевой образ. Разница флагов nsenter объяснена: с -m запускаемая программа берётся из файловой системы container'а.


Задание 6. Наблюдение за health-переходами

Тип: дополнительное
Материал: 13.2

Наблюдать переходы состояния здоровья через docker events во время старта стека.

Требования:

  1. Собрать стек из трёх сервисов с проверками здоровья и зависимостями.
  2. Запустить наблюдение за событиями до старта стека.
  3. Зафиксировать последовательность: create, start, health_status для каждого.
  4. Измерить время от start до первого healthy для каждого сервиса.
  5. Объяснить, как depends_on: condition: service_healthy меняет последовательность.
  6. Показать, что docker ps этой последовательности не даёт.

Проверка

bash
timeout 90 docker events --format '{{.Time}} {{.Actor.Attributes.name}} {{.Action}}' &
docker compose up -d

Критерий

Последовательность зафиксирована с временными метками. Время до healthy измерено для каждого сервиса. Показано, что порядок стартов определяется условиями depends_on.

Ориентир для самопроверки

Время от start до healthy = start_period + время до первой успешной проверки, но не меньше одного interval. Если получилось больше ожидаемого — вероятно, первые проверки не проходили; их вывод в .State.Health.Log.


Задание 7. CPU throttling

Тип: дополнительное
Материал: 13.3

Продиагностировать throttling через статистику cgroup.

Требования:

  1. Создать нагрузку всплесками так, чтобы средняя загрузка была ниже 40 %.
  2. Запустить с --cpus и измерить nr_throttled и nr_periods.
  3. Показать, что docker stats throttling не отражает.
  4. Измерить влияние на задержку: медиана и p95 до и после установки лимита.
  5. Подобрать лимит, при котором доля throttling опускается ниже 5 %.
  6. Объяснить, почему throttling возможен при низкой средней загрузке.

Проверка

bash
docker exec ИМЯ cat /sys/fs/cgroup/cpu.stat
docker stats --no-stream ИМЯ

Критерий

Показана комбинация «загрузка ниже 40 %, throttling выше 5 %». Задержка измерена в обоих режимах. Объяснение содержит механизм квоты на период 100 мс.


Задание 8. Container без shell

Тип: дополнительное
Материал: 13.5

Провести диагностику container'а, в образе которого нет shell.

Требования:

  1. Собрать образ на базе distroless (или эквивалент) и убедиться, что docker exec sh невозможен.
  2. Получить: логи, конфигурацию, список процессов.
  3. Извлечь файл из образа — без shell в нём.
  4. Показать, что записал container, не имея прав root.
  5. Проверить сеть через sidecar.
  6. Объяснить, почему docker cp работает, а docker exec — нет.

Проверка

bash
docker exec ИМЯ sh -c 'echo тест'
docker cp ИМЯ:/путь - | tar -xO | head
docker diff ИМЯ
docker run --rm --network container:ИМЯ ОБРАЗ КОМАНДА

Критерий

Все шаги, кроме первого, выполнены успешно. Объяснено: docker cp выполняет daemon, процесс внутри container'а не создаётся; docker exec требует программу в файловой системе container'а.


Задание 9. Диагностика: перезапуск каждые 40 секунд

Тип: диагностическое
Материал: весь раздел

Разработчик сообщает: «сервис перезапускается каждые 40 секунд». Найти причину, не изменяя приложение.

Одно из требований проверяет, верна ли сама формулировка симптома.

Дано:

yaml
name: restart-mystery

services:
  api:
    image: python:3.13-slim
    command: >
      python -c "
      import http.server, threading, time, os;
      print('запуск', flush=True);
      h = type('H', (http.server.BaseHTTPRequestHandler,), {
          'do_GET': lambda s: (s.send_response(200), s.end_headers(), s.wfile.write(b'ok')),
          'log_message': lambda s, *a: None})();
      srv = http.server.ThreadingHTTPServer(('0.0.0.0', 8000), type(h));
      threading.Thread(target=srv.serve_forever, daemon=True).start();
      time.sleep(86400)
      "
    healthcheck:
      test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8080/health')"]
      interval: 10s
      timeout: 3s
      retries: 3
      start_period: 5s
    restart: unless-stopped

Требования:

  1. Проверить по docker events и RestartCount, происходят ли перезапуски вообще.
  2. Определить, к чему на самом деле относится интервал 40 секунд, и объяснить его арифметикой.
  3. Найти причину, назвав конкретное расхождение в конфигурации.
  4. Показать вывод неудачной проверки и объяснить, почему его нет в docker logs.
  5. Исправить конфигурацию и подтвердить исправление проверкой.
  6. Объяснить, что делает Docker с unhealthy-container'ом и чего он не делает.

Проверка

bash
docker events --since 5m --filter 'container=ИМЯ' --format '{{.Time}} {{.Action}}'
docker inspect ИМЯ --format '{{json .State.Health}}' | python3 -m json.tool

Критерий

Сформулировано, происходят ли перезапуски на самом деле. Интервал объяснён арифметикой, а не подобран. Причина названа точно. Исправление подтверждено проверкой.

Ориентир: что здесь происходит на самом деле

Перезапусков нет. RestartCount остаётся нулевым, а в docker events нет ни die, ни start — только повторяющиеся health_status: unhealthy.

Симптом описан неверно: container не перезапускается, он переходит в unhealthy. Наблюдатель видел меняющийся статус в docker ps и принял его за перезапуск.

Откуда 40 секунд:

text
start_period + interval × retries = 5 + 10 × 3 = 35 с

Первая проверка после start_period, затем три неудачи подряд с интервалом 10 секунд — около 35–40 секунд до перехода в unhealthy. Дальше проверки продолжаются, и статус остаётся unhealthy.

Причина отказа проверки: приложение слушает порт 8000, а healthcheck обращается к 8080. Проверка не проходит никогда.

Главный вывод задания: Docker сам не перезапускает unhealthy-container'ы. Политика restart: реагирует на завершение процесса, а не на состояние здоровья. Перезапуск по unhealthy выполняют внешние средства — оркестратор или Swarm; в обычном Compose этого не происходит (11.3).

Отсюда практическое следствие: healthcheck без потребителя его результата не даёт ничего, кроме статуса в docker ps. Он полезен, когда на него кто-то опирается: depends_on: condition: service_healthy, балансировщик, оркестратор.


Задание 10. Диск заполнен на 95 % ★

Тип: итоговое
Материал: весь раздел

Диск заполнен на 95 %, Docker занимает 60 ГБ. Составить план освобождения с оценкой рисков.

Требования:

  1. Разложить 60 ГБ по категориям, включая невидимые для docker system df.
  2. Для каждой категории: сколько освободится и что будет потеряно.
  3. Составить план по возрастанию риска с командой на каждый шаг.
  4. Отдельно перечислить необратимые действия и что нужно проверить перед каждым.
  5. Выполнить обратимую часть и измерить фактический выигрыш.
  6. Предложить настройки, после которых ситуация не повторится, и посчитать бюджет.
  7. Написать проверку, предупреждающую до заполнения диска.

Проверка

Проверка из пункта 7 должна:

  • возвращать разные коды при разных уровнях заполнения;
  • учитывать логи, а не только docker system df;
  • не удалять ничего сама.

Критерий

План содержит все шесть категорий. Необратимые действия отделены и не выполнены автоматически. Фактический выигрыш измерен и сопоставлен с прогнозом. Бюджет посчитан: max-size × max-file × число container'ов плюс предел build cache.

Ориентир: типичная раскладка 60 ГБ
КатегорияТипичная доляОбратимо
Логи без ротации20–40 ГБДа (теряется история)
Build cache5–20 ГБДа (замедлит сборку)
Образы без container'а5–15 ГБДа (повторное скачивание)
Слои container'ов1–5 ГБДа
Volumes0,5–5 ГБНет

Порядок именно такой: первые две категории обычно дают 80 % выигрыша и не требуют никаких решений.

Признак того, что раскладка сделана правильно: сумма категорий из docker system df заметно меньше 60 ГБ, и разница объяснена логами.


Сводная таблица

ЗаданиеТипОсновной материал
1Ротация логовобяз.13.1
2Пять полей одной командойобяз.13.2
3OOM и его следыобяз.13.3
4Что заняло местообяз.13.4
5Вход в namespacesобяз.13.5
6Наблюдение за health-переходамидоп.13.2
7CPU throttlingдоп.13.3
8Container без shellдоп.13.5
9Перезапуск каждые 40 секунддиаг.весь раздел
10Диск заполнен на 95 %весь раздел

Что должно получиться

После заданий 1–5 у вас есть практическое подтверждение пяти утверждений раздела:

  1. Без ротации логи растут неограниченно, и docker system df этого не показывает.
  2. Вывод неудачной проверки здоровья существует только в .State.Health.Log.
  3. Код 137 имеет три причины, и OOMKilled различает лишь одну.
  4. Сумма размеров образов не равна занятому месту из-за общих слоёв.
  5. Диагностика возможна без shell в образе и без изменения образа вообще.

Задания 6–8 добавляют инструменты, применяемые при конкретных симптомах.

Задание 9 проверяет методику на подготовленной неисправности; задание 10 — на реалистичной ситуации, где решений несколько и они имеют разную цену.

Дальше

Отработать методику на двадцати неисправностях: debugging challenges.

Навигация

← Предыдущий материал
Вернуться к разделу
Следующий раздел: Registry →
Главное оглавление

Markdown на GitHub ↗