Раздел 13. Практические задания
Задания проверяют не знание команд, а способность дойти от симптома до причины. Формулировка «найти причину» означает: назвать её и показать проверку, которая её подтвердила.
Часть заданий требует прав root (чтение dmesg, journalctl, каталога Docker). Если прав нет — выполните доступную часть и отметьте невыполненное явно. Проверка, молча пропущенная, хуже отсутствующей.
Как проверять себя
| Формулировка | Что требуется |
|---|---|
| «показать, что…» | Команда и её фактический вывод |
| «найти причину» | Причина плюс проверка, которая её подтвердила |
| «измерить» | Число, полученное командой, а не оценка |
| «объяснить» | Механизм, а не пересказ симптома |
Задание 1. Ротация логов
Тип: обязательное
Материал: 13.1
Настроить ротацию и проверить, что размер ограничен.
Требования:
- Запустить container, выводящий не менее 50 000 строк.
- Показать размер лога без ротации.
- Повторить с
max-sizeиmax-file, показать разницу. - Вычислить верхнюю границу расхода на container и на стек из восьми сервисов.
- Показать, что ранние строки после ротации недоступны.
- Настроить ротацию по умолчанию в
daemon.jsonи объяснить, почему она не применится к существующим container'ам.
Проверка
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 пять разных полей одной командой.
Требования:
- Одной командой получить: статус, код выхода, признак OOM, число перезапусков, состояние здоровья.
- Обработать случай, когда проверка здоровья не настроена, — без ошибки шаблона.
- Извлечь вывод последней неудачной проверки здоровья.
- Показать, что этого вывода нет в
docker logs, и объяснить почему. - Собрать список всех container'ов с их состоянием одной командой.
Проверка
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 и найти след в трёх местах.
Требования:
- Воспроизвести OOM с лимитом
--memory. - Найти след в состоянии container'а.
- Найти событие
oomвdocker events. - Найти запись в журнале ядра — или отметить, что прав нет.
- Показать четыре разные причины кода
137/143и способ различить каждую. - Объяснить, почему
OOMKilled: falseне доказывает отсутствие OOM.
Проверка
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
Найти, что занимает место, и освободить безопасно.
Требования:
- Показать расход по всем категориям.
- Показать категорию, которой нет в
docker system df, и измерить её. - Объяснить, почему сумма колонки
SIZEвdocker imagesбольше занятого места. - Найти container, пишущий в свою файловую систему, и показать какие файлы.
- Составить план очистки, разделив обратимое и необратимое.
- Выполнить обратимую часть; необратимую оставить с обоснованием.
Проверка
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'а, не изменяя его образ.
Требования:
- Запустить сервис на образе без сетевых утилит.
- Показать, что
ssиnetstatв образе отсутствуют. - Посмотреть открытые порты через sidecar с общим сетевым namespace.
- То же через
nsenterс host — или отметить отсутствие прав. - Объяснить разницу между
nsenter -nиnsenter -m -n. - Показать, что sidecar с
--pidне даёт доступа к файловой системе цели.
Проверка
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 во время старта стека.
Требования:
- Собрать стек из трёх сервисов с проверками здоровья и зависимостями.
- Запустить наблюдение за событиями до старта стека.
- Зафиксировать последовательность:
create,start,health_statusдля каждого. - Измерить время от
startдо первогоhealthyдля каждого сервиса. - Объяснить, как
depends_on: condition: service_healthyменяет последовательность. - Показать, что
docker psэтой последовательности не даёт.
Проверка
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.
Требования:
- Создать нагрузку всплесками так, чтобы средняя загрузка была ниже 40 %.
- Запустить с
--cpusи измеритьnr_throttledиnr_periods. - Показать, что
docker statsthrottling не отражает. - Измерить влияние на задержку: медиана и p95 до и после установки лимита.
- Подобрать лимит, при котором доля throttling опускается ниже 5 %.
- Объяснить, почему throttling возможен при низкой средней загрузке.
Проверка
docker exec ИМЯ cat /sys/fs/cgroup/cpu.stat
docker stats --no-stream ИМЯ
Критерий
Показана комбинация «загрузка ниже 40 %, throttling выше 5 %». Задержка измерена в обоих режимах. Объяснение содержит механизм квоты на период 100 мс.
Задание 8. Container без shell
Тип: дополнительное
Материал: 13.5
Провести диагностику container'а, в образе которого нет shell.
Требования:
- Собрать образ на базе distroless (или эквивалент) и убедиться, что
docker exec shневозможен. - Получить: логи, конфигурацию, список процессов.
- Извлечь файл из образа — без shell в нём.
- Показать, что записал container, не имея прав root.
- Проверить сеть через sidecar.
- Объяснить, почему
docker cpработает, аdocker exec— нет.
Проверка
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 секунд». Найти причину, не изменяя приложение.
Одно из требований проверяет, верна ли сама формулировка симптома.
Дано:
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
Требования:
- Проверить по
docker eventsиRestartCount, происходят ли перезапуски вообще. - Определить, к чему на самом деле относится интервал 40 секунд, и объяснить его арифметикой.
- Найти причину, назвав конкретное расхождение в конфигурации.
- Показать вывод неудачной проверки и объяснить, почему его нет в
docker logs. - Исправить конфигурацию и подтвердить исправление проверкой.
- Объяснить, что делает Docker с
unhealthy-container'ом и чего он не делает.
Проверка
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 секунд:
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 ГБ. Составить план освобождения с оценкой рисков.
Требования:
- Разложить 60 ГБ по категориям, включая невидимые для
docker system df. - Для каждой категории: сколько освободится и что будет потеряно.
- Составить план по возрастанию риска с командой на каждый шаг.
- Отдельно перечислить необратимые действия и что нужно проверить перед каждым.
- Выполнить обратимую часть и измерить фактический выигрыш.
- Предложить настройки, после которых ситуация не повторится, и посчитать бюджет.
- Написать проверку, предупреждающую до заполнения диска.
Проверка
Проверка из пункта 7 должна:
- возвращать разные коды при разных уровнях заполнения;
- учитывать логи, а не только
docker system df; - не удалять ничего сама.
Критерий
План содержит все шесть категорий. Необратимые действия отделены и не выполнены автоматически. Фактический выигрыш измерен и сопоставлен с прогнозом. Бюджет посчитан: max-size × max-file × число container'ов плюс предел build cache.
Ориентир: типичная раскладка 60 ГБ
| Категория | Типичная доля | Обратимо |
|---|---|---|
| Логи без ротации | 20–40 ГБ | Да (теряется история) |
| Build cache | 5–20 ГБ | Да (замедлит сборку) |
| Образы без container'а | 5–15 ГБ | Да (повторное скачивание) |
| Слои container'ов | 1–5 ГБ | Да |
| Volumes | 0,5–5 ГБ | Нет |
Порядок именно такой: первые две категории обычно дают 80 % выигрыша и не требуют никаких решений.
Признак того, что раскладка сделана правильно: сумма категорий из docker system df заметно меньше 60 ГБ, и разница объяснена логами.
Сводная таблица
| № | Задание | Тип | Основной материал |
|---|---|---|---|
| 1 | Ротация логов | обяз. | 13.1 |
| 2 | Пять полей одной командой | обяз. | 13.2 |
| 3 | OOM и его следы | обяз. | 13.3 |
| 4 | Что заняло место | обяз. | 13.4 |
| 5 | Вход в namespaces | обяз. | 13.5 |
| 6 | Наблюдение за health-переходами | доп. | 13.2 |
| 7 | CPU throttling | доп. | 13.3 |
| 8 | Container без shell | доп. | 13.5 |
| 9 | Перезапуск каждые 40 секунд | диаг. | весь раздел |
| 10 | Диск заполнен на 95 % | ★ | весь раздел |
Что должно получиться
После заданий 1–5 у вас есть практическое подтверждение пяти утверждений раздела:
- Без ротации логи растут неограниченно, и
docker system dfэтого не показывает. - Вывод неудачной проверки здоровья существует только в
.State.Health.Log. - Код
137имеет три причины, иOOMKilledразличает лишь одну. - Сумма размеров образов не равна занятому месту из-за общих слоёв.
- Диагностика возможна без shell в образе и без изменения образа вообще.
Задания 6–8 добавляют инструменты, применяемые при конкретных симптомах.
Задание 9 проверяет методику на подготовленной неисправности; задание 10 — на реалистичной ситуации, где решений несколько и они имеют разную цену.
Дальше
Отработать методику на двадцати неисправностях: debugging challenges.
Навигация
← Предыдущий материал
Вернуться к разделу
Следующий раздел: Registry →
Главное оглавление