Главная/Python внутри Container/Обзор

Раздел 6. Python внутри Container

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

У Python есть специфика, которую нельзя вывести из общих правил Docker. Alpine, экономящий 40 MB на других языках, для Python часто увеличивает и размер, и время сборки — из-за musl и отсутствия готовых wheels. print() не появляется в логах из-за буферизации при перенаправлении вывода. Приложение игнорирует SIGTERM, потому что запущено через shell form и не является PID 1. Gunicorn с восемью worker-процессами падает по OOM, потому что видит CPU host, а не выделенный лимит.

Раздел разбирает каждую из этих ситуаций: сначала механизм, затем практическое решение, затем рабочий пример. Все примеры существуют в виде запускаемых проектов в resources/examples/.

Цели обучения

После раздела учащийся сможет:

  • выбрать base image для Python-приложения и обосновать выбор измерениями, а не привычкой;
  • объяснить влияние glibc и musl на доступность бинарных wheels;
  • зафиксировать зависимости и объяснить trade-offs между pip, Poetry и uv;
  • ответить, нужен ли venv внутри container, для конкретного случая;
  • написать Dockerfile, в котором изменение кода не пересобирает зависимости;
  • настроить PYTHONUNBUFFERED, PYTHONDONTWRITEBYTECODE и понимать эффект каждой переменной;
  • организовать structured logging в stdout и объяснить, почему не в файл;
  • запустить приложение от non-root user и решить возникающие проблемы с правами;
  • реализовать graceful shutdown для CLI, web-приложения и worker;
  • контейнеризировать Python CLI, Flask, FastAPI и background worker;
  • рассчитать число worker-процессов с учётом CPU и memory limits;
  • запускать pytest внутри container;
  • объяснить, почему Python-приложение под memory limit ведёт себя иначе, чем на host.

Предварительные знания

  • Раздел 05. Dockerfile — все инструкции, build cache, multi-stage;
  • Раздел 04. Containers и lifecycle — сигналы и PID 1;
  • Python на рабочем уровне: пакеты, зависимости, точка входа;
  • базовое знакомство с Flask или FastAPI (достаточно понимать, что это web-фреймворки).

Материалы

  1. Выбор base image
    python:X.Y, -slim, -alpine, distroless. Debian Trixie и Bookworm. glibc против musl и почему это ломает установку научных пакетов. Измеримое сравнение размера и времени сборки. Выбор версии Python и стратегия обновления.

  2. Управление зависимостями
    requirements.txt, pyproject.toml, Poetry, uv, pip-tools. Что такое lock file и почему он важнее, чем кажется. Wheels против сборки из исходников, --no-cache-dir, системные зависимости для компиляции. Нужен ли venv внутри container — три сценария с разными ответами.

  3. Python Dockerfile
    Пошаговое построение правильного Dockerfile: от наивной версии к оптимизированной. Установка зависимостей до копирования кода. Multi-stage с uv. Измерение результата на каждом шаге.

  4. Environment variables
    PYTHONUNBUFFERED, PYTHONDONTWRITEBYTECODE, PYTHONPATH, PIP_NO_CACHE_DIR, PYTHONFAULTHANDLER, PYTHONHASHSEED. Что делает каждая, когда нужна и когда вредна. Конфигурация приложения через environment: подход и границы.

  5. Сигналы и PID 1 в Python
    Почему приложение не получает SIGTERM. Обработчики сигналов в Python: signal.signal, особенности asyncio, поведение под Gunicorn и Uvicorn. Реализация graceful shutdown для трёх типов приложений. Когда нужен tini.

  6. Logging
    Буферизация stdout и почему print() пропадает. Настройка logging для контейнеризованного приложения. Structured logging в JSON. stdout против stderr. Почему не нужно писать логи в файл внутри container. Интеграция с логами Uvicorn и Gunicorn.

  7. Non-root user и permissions
    Создание пользователя в образе, USER, фиксированные UID/GID. Права на каталоги приложения, кэш, временные файлы. Проблема записи в примонтированный volume. Решения без chmod 777.

  8. CLI-приложение
    Контейнеризация CLI: ENTRYPOINT против CMD, передача аргументов, чтение конфигурации, работа со stdin, корректные exit codes, вывод в stdout и диагностика в stderr. Рабочий пример.

  9. Flask
    Почему flask run — не production server. Gunicorn с синхронными worker-процессами, привязка к 0.0.0.0, healthcheck endpoint, разница development и production конфигурации. Рабочий пример.

  10. FastAPI
    Uvicorn как ASGI-сервер, fastapi run как официальный способ запуска и почему Gunicorn с UvicornWorker больше не рекомендуется, --reload только для разработки, lifespan и graceful shutdown, раздельные liveness и readiness, валидация конфигурации при старте. Рабочий пример.

  11. Worker processes
    Расчёт числа worker-процессов, --workers и --threads, влияние CPU limits, память на worker, preload и copy-on-write. Background worker: очередь на Redis, обработка SIGTERM в цикле, идемпотентность. Scheduled jobs. Рабочий пример.

  12. Тестирование в container
    Запуск pytest внутри образа, отдельная стадия для тестов, тестовые зависимости без раздувания production-образа, exit code тестов как результат сборки. Связь с разделом 15.

  13. Resource limits и память Python
    --memory, --cpus, --pids-limit. Почему Python не «видит» лимит и как это меняет поведение. os.cpu_count() против доступных CPU. Поведение аллокатора, фрагментация, OOM killer и exit code 137. Практический подбор лимитов.

  14. Практические задания
    Лабораторные задания раздела с проверкой результата.

Рекомендуемый порядок чтения

Последовательный: 01 → 02 → 03 → 04 → 05 → 06 → 07 → 08 → 09 → 10 → 11 → 12 → 13 → exercises.

Уроки 08–11 можно читать выборочно по типу вашего приложения, но урок 10 (FastAPI) нужен для проекта 2, а урок 11 — для проекта 3.

Уроки 01–07 обязательны все: они формируют базу, на которую опираются остальные.

Рабочие примеры

ПримерКаталогУрок
Минимальный Python-образresources/examples/hello-python/03
CLI-приложениеresources/examples/python-cli/08
Multi-stage с uvresources/examples/multistage-uv/02, 03
Flask под Gunicornresources/examples/flask-basic/09
FastAPI под Uvicornresources/examples/fastapi-basic/10
Background workerresources/examples/worker-redis/11
Тесты в containerresources/examples/pytest-container/12

Практические задания

ЗаданиеТип
1Собрать один образ на slim и на alpine, сравнить размер и время сборки, объяснить результатобяз.
2Написать Dockerfile, где изменение кода пересобирает только последний слой; доказать измерениемобяз.
3Воспроизвести пропажу print() из логов и исправить двумя разными способамиобяз.
4Запустить приложение от non-root и починить возникшую ошибку прав без chmod 777обяз.
5Реализовать graceful shutdown в FastAPI и доказать, что активный запрос завершаетсяобяз.
6Собрать multi-stage образ с uv, сравнить с вариантом на pipдоп.
7Настроить structured logging в JSON и проверить вывод через docker logsдоп.
8Запустить pytest как отдельную стадию сборки; сборка должна падать при падении тестовдоп.
9Приложение с --memory=256m падает с exit code 137. Найти причину и подобрать лимитдиаг.
10Gunicorn с --workers 9 на машине с --cpus=2: объяснить проблему и рассчитать корректное значение

Полные формулировки — в exercises.md.

Критерии завершения раздела

Раздел пройден, когда учащийся может без подсказок:

  1. Обосновать выбор base image для конкретного Python-проекта, включая случай с научными пакетами.
  2. Написать Dockerfile, где docker build после изменения одной строки кода занимает менее пяти секунд.
  3. Объяснить два независимых механизма, из-за которых вывод Python может не попасть в docker logs.
  4. Запустить приложение от non-root user так, чтобы оно могло писать в нужные каталоги.
  5. Показать, что приложение завершает активные запросы при docker stop.
  6. Рассчитать число worker-процессов исходя из заданных CPU и memory limits.
  7. Объяснить, почему venv внутри container иногда всё-таки нужен.

Проверьте себя: Quiz 06. Затем выполните Проект 1. Python CLI.

Что дальше

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

Навигация

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

Markdown на GitHub ↗