Раздел 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-фреймворки).
Материалы
-
Выбор base image
python:X.Y,-slim,-alpine,distroless. Debian Trixie и Bookworm.glibcпротивmuslи почему это ломает установку научных пакетов. Измеримое сравнение размера и времени сборки. Выбор версии Python и стратегия обновления. -
Управление зависимостями
requirements.txt,pyproject.toml,Poetry,uv,pip-tools. Что такое lock file и почему он важнее, чем кажется. Wheels против сборки из исходников,--no-cache-dir, системные зависимости для компиляции. Нужен лиvenvвнутри container — три сценария с разными ответами. -
Python Dockerfile
Пошаговое построение правильногоDockerfile: от наивной версии к оптимизированной. Установка зависимостей до копирования кода. Multi-stage сuv. Измерение результата на каждом шаге. -
Environment variables
PYTHONUNBUFFERED,PYTHONDONTWRITEBYTECODE,PYTHONPATH,PIP_NO_CACHE_DIR,PYTHONFAULTHANDLER,PYTHONHASHSEED. Что делает каждая, когда нужна и когда вредна. Конфигурация приложения через environment: подход и границы. -
Сигналы и PID 1 в Python
Почему приложение не получаетSIGTERM. Обработчики сигналов в Python:signal.signal, особенностиasyncio, поведение под Gunicorn и Uvicorn. Реализация graceful shutdown для трёх типов приложений. Когда нуженtini. -
Logging
Буферизация stdout и почемуprint()пропадает. Настройкаloggingдля контейнеризованного приложения. Structured logging в JSON. stdout против stderr. Почему не нужно писать логи в файл внутри container. Интеграция с логами Uvicorn и Gunicorn. -
Non-root user и permissions
Создание пользователя в образе,USER, фиксированные UID/GID. Права на каталоги приложения, кэш, временные файлы. Проблема записи в примонтированный volume. Решения безchmod 777. -
CLI-приложение
Контейнеризация CLI:ENTRYPOINTпротивCMD, передача аргументов, чтение конфигурации, работа со stdin, корректные exit codes, вывод в stdout и диагностика в stderr. Рабочий пример. -
Flask
Почемуflask run— не production server. Gunicorn с синхронными worker-процессами, привязка к0.0.0.0, healthcheck endpoint, разница development и production конфигурации. Рабочий пример. -
FastAPI
Uvicorn как ASGI-сервер,fastapi runкак официальный способ запуска и почему Gunicorn сUvicornWorkerбольше не рекомендуется,--reloadтолько для разработки,lifespanи graceful shutdown, раздельные liveness и readiness, валидация конфигурации при старте. Рабочий пример. -
Worker processes
Расчёт числа worker-процессов,--workersи--threads, влияние CPU limits, память на worker, preload и copy-on-write. Background worker: очередь на Redis, обработкаSIGTERMв цикле, идемпотентность. Scheduled jobs. Рабочий пример. -
Тестирование в container
Запускpytestвнутри образа, отдельная стадия для тестов, тестовые зависимости без раздувания production-образа, exit code тестов как результат сборки. Связь с разделом 15. -
Resource limits и память Python
--memory,--cpus,--pids-limit. Почему Python не «видит» лимит и как это меняет поведение.os.cpu_count()против доступных CPU. Поведение аллокатора, фрагментация, OOM killer и exit code137. Практический подбор лимитов. -
Практические задания
Лабораторные задания раздела с проверкой результата.
Рекомендуемый порядок чтения
Последовательный: 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 с uv | resources/examples/multistage-uv/ | 02, 03 |
| Flask под Gunicorn | resources/examples/flask-basic/ | 09 |
| FastAPI под Uvicorn | resources/examples/fastapi-basic/ | 10 |
| Background worker | resources/examples/worker-redis/ | 11 |
| Тесты в container | resources/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. Найти причину и подобрать лимит | диаг. |
| 10 | Gunicorn с --workers 9 на машине с --cpus=2: объяснить проблему и рассчитать корректное значение | ★ |
Полные формулировки — в exercises.md.
Критерии завершения раздела
Раздел пройден, когда учащийся может без подсказок:
- Обосновать выбор base image для конкретного Python-проекта, включая случай с научными пакетами.
- Написать
Dockerfile, гдеdocker buildпосле изменения одной строки кода занимает менее пяти секунд. - Объяснить два независимых механизма, из-за которых вывод Python может не попасть в
docker logs. - Запустить приложение от non-root user так, чтобы оно могло писать в нужные каталоги.
- Показать, что приложение завершает активные запросы при
docker stop. - Рассчитать число worker-процессов исходя из заданных CPU и memory limits.
- Объяснить, почему
venvвнутри container иногда всё-таки нужен.
Проверьте себя: Quiz 06. Затем выполните Проект 1. Python CLI.
Что дальше
Приложение собрано и запускается. Следующий раздел решает проблему, которая возникает сразу после: данные исчезают при пересоздании container.
Навигация
← Предыдущий раздел: Dockerfile
Вернуться к главному оглавлению
Следующий раздел: Storage →