labctl
войти регистрация

Найти и устранить утечку файловых дескрипторов

имя
fd-leak
образ
python:3.12-slim
таймаут
60с
проверка…

Задание

Найти и устранить утечку файловых дескрипторов

В контейнере крутится сервис leaky-service (Python). Каждый запрос он
открывает /etc/hostname для логирования, но не закрывает FD. Со временем
число открытых дескрипторов растёт и в проде оно упрётся в ulimit -n
(Too many open files).

Задача

Довести количество открытых FD процессом leaky-service до значения
≤ FD_BUDGET (по умолчанию 100). Возможные пути:

  1. Перезапустить сервис (FD освободятся) — но это не починка, а скрытие.
    В этой лабе принимается, если после перезапуска счётчик действительно
    держится ниже бюджета.
  2. Найти и устранить причину утечки в коде сервиса — правильное решение,
    но требует больше работы.

В проверочном стенде сервис после up уже проработал некоторое время и
накопил утечку. Минимальный успех = после вашего вмешательства в
/proc/<pid>/fd не больше FD_BUDGET записей.

Симптомы

$ ls /proc/$(pgrep -f leaky-service)/fd | wc -l
327

$ ls -l /proc/$(pgrep -f leaky-service)/fd | awk '{print $11}' | sort | uniq -c | sort -rn | head
    327 /etc/hostname     ← вот ваша утечка
      ...

327 открытых FD на /etc/hostname — однозначный сигнал. Один файл открыт
сотнями дескрипторов одновременно.

Почему это hard

Утечки FD — коварный production-баг: на тестах всё работает (FD копится
медленно), а в проде через сутки падает с EMFILE. Найти утечку — значит
сопоставить «открыл N раз» и «закрыл N раз» в коде, либо пройтись по
/proc/<pid>/fd и увидеть, какой файл утекает. Решение — либо фикс в коде
(with open(...)), либо prlimit/systemd LimitNOFILE (но это маскирует,
не лечит).

Подсказки

  • pgrep -f leaky-service — найти PID.
  • ls /proc/<pid>/fd | wc -l — счётчик FD.
  • ls -l /proc/<pid>/fd | awk '{print $11}' | sort | uniq -c | sort -rn
    какие файлы утекают и сколько раз.
  • Самый быстрый путь к PASS: pkill -f leaky-service && setsid leaky-service &
    (перезапуск). Правильный путь: открыть код (/usr/local/bin/leaky-service),
    увидеть open() без close() или без with, обернуть в with /
    добавить close().
Подсказки

Hints: fd-leak

Как искать утечку FD

  1. Найти PID:
    pgrep -f leaky-service
    (или без pgrep — for p in /proc/[0-9]*; do grep -l leaky-service "$p/cmdline" 2>/dev/null; done)
  2. Счётчик FD:
    ls /proc/<pid>/fd | wc -l
  3. Какие файлы утекают:
    ls -l /proc/<pid>/fd | awk '{print $11}' | sort | uniq -c | sort -rn | head
    Если видите 327 /etc/hostname — это и есть утекающий ресурс.

Два пути решения

Правильный — фикс кода

Прочитайте /usr/local/bin/leaky-service. Найдите open(...) без close()
или вне with. Оберните в with open(...) as f: — Python автоматически
закроет. После правки перезапустите сервис.

Быстрый — перезапуск (маскировка)

pkill -f leaky-service && setsid leaky-service & — стартует свежий процесс
с пустым набором FD. Счётчик обнулится. Лабра примет это решение (но в реальном
проде так делать не стоит — баг вернётся через сутки).

Бонус: предотвратить в проде

  • prlimit --pid <pid> --nofile=100:100 — жёстко ограничить, но это маскировка.
  • systemd unit с LimitNOFILE=100 — то же самое.
  • Правильно — код-ревью на open() без with/close, либо статический
    анализатор (ruff/flake8 ловит не все).

Последние попытки

  • Загрузка…

Разовый запуск (smoke-тест)

Атомарный цикл up → check → down. Полезно для CI; без предварительной подготовки состояния проверка завершится с ошибкой.