Карта решений
Таблицы «задача → подход → альтернативы → цена». Используется при проектировании: помогает выбрать осознанно и увидеть, чем платишь.
Правило чтения: колонка «цена» не менее важна, чем «подход». Решение без названной цены — не решение, а предпочтение.
Содержание
- Нужна ли контейнеризация
- Базовый образ
- Зависимости
- Конфигурация
- Состояние и данные
- Сеть
- Запуск и процессы
- Наблюдаемость
- Тестирование
- Доставка
- Масштаб
Нужна ли контейнеризация
Первое решение, и единственное, которое стоит принимать до всех остальных.
| Признак задачи | Подход | Альтернатива | Цена выбора |
|---|---|---|---|
| Зависимости не ставятся из репозитория дистрибутива | Container | Пакет дистрибутива | Инфраструктура сборки навсегда |
| Несколько машин, окружения должны совпадать | Container | Управление конфигурацией | То же |
| Одна машина, один сервис, зависимости в дистрибутиве | systemd-unit | Container | Нет одинаковости между машинами |
| Однократный скрипт | Виртуальное окружение | Container | Зависимости из системы |
| Статический сайт | Хостинг или веб-сервер из пакета | Container | Практически ничем |
| Жёсткие требования к задержкам | bare metal | Container | Нет одинаковости окружения |
| Работа с GPU или устройством | Container или пакет | — | Образ привязан к драйверу хоста |
| Недоверенный код | Микро-ВМ или отдельная машина | Container | Стоимость на порядок выше |
Главный вопрос: что перестанет работать, если убрать Docker? Ответ «ничего, кроме привычки» означает, что решаемой проблемы нет (урок 19.3).
Базовый образ
| Задача | Подход | Альтернативы | Цена |
|---|---|---|---|
| Обычный сервис на Python | python:X.Y-slim | alpine, полный | — |
| Критичен размер | alpine | slim, distroless | Сборка колёс из исходников; различия musl |
| Минимум площади атаки | distroless | slim | Нет оболочки — отладка сложнее |
| Нужны системные библиотеки | slim плюс apt-get | Полный образ | Слой пакетов и его обновление |
| Требуется воспроизводимость | Закрепление по digest | Тег | Обновление вручную |
| Много образов в организации | Свой базовый образ | Публичные | Его нужно поддерживать |
Умолчание: slim с конкретным тегом. Отклонение от него требует довода.
Зависимости
| Задача | Подход | Альтернативы | Цена |
|---|---|---|---|
| Приложение на Python | Файл блокировки | requirements.txt с == | Обновление через инструмент |
| Быстрая установка | uv | pip | Ещё один инструмент в образе сборки |
| Разделение сред | requirements-dev.txt | Группы в pyproject.toml | — |
| Изоляция от системы | Виртуальное окружение в /opt/venv | Установка в систему | Одна лишняя инструкция |
| Ускорение повторных сборок | Cache mount | Слой с кэшем | Не переносится в CI |
| Перенос кэша в CI | cache-to с mode=max | Без кэша | Место в registry |
Ловушка: --mount=type=cache вместе с --no-cache-dir — взаимоисключающие. Второй отключает ровно то, что кэшируется.
Конфигурация
| Задача | Подход | Альтернативы | Цена |
|---|---|---|---|
| Значения, различные по средам | Переменные окружения | Файл в образе | Нужна проверка при старте |
| Много значений | Файл, монтируемый снаружи | Много переменных | Ещё одна сущность |
| Секреты | Файл через secrets | Переменная | Путь вместо значения в коде |
| Секреты при сборке | --mount=type=secret | ARG | Требует BuildKit |
| Проверка значений | При старте, с отказом | При использовании | Падение вместо тихой работы |
| Обнаружение опечаток в именах | Явная сверка с полями | extra="forbid" | Восемь строк кода |
Правило: один образ для всех сред. Конфигурация в образе означает отдельный образ на среду — то есть в эксплуатацию едет не то, что проверяли.
Состояние и данные
| Задача | Подход | Альтернативы | Цена |
|---|---|---|---|
| Данные эксплуатации | Именованный том | Bind mount | Процедуры копирования — ваши |
| Конфигурация снаружи | Bind mount только для чтения | Том | Привязка к машине |
Временные файлы при read_only | tmpfs | Том | Исчезают при перезапуске |
| Код при разработке | Bind mount подкаталога | Пересборка | Расхождение с эксплуатацией |
| База данных | Управляемая вне container'а | Container с томом | Стоимость сервиса |
| База в container'е неизбежна | Том плюс шесть процедур | — | Копирование, восстановление, миграции — ваши |
| Разделяемое состояние нескольких экземпляров | Внешнее хранилище | Общий том | Ещё один компонент |
Шесть вопросов до того, как помещать базу в container: как делается копия; как проверяется восстановление; как восстановить на момент времени; что с правами; как переносить между машинами; как обновлять версию СУБД. Docker не отвечает ни на один.
Сеть
| Задача | Подход | Альтернативы | Цена |
|---|---|---|---|
| Связь сервисов | Пользовательская сеть | Сеть по умолчанию | — |
| Изоляция данных | Отдельная сеть с internal: true | Одна сеть | Ещё одна сущность |
| Доступ снаружи | Публикация порта только у входного сервиса | Публикация у всех | — |
| Разработка | Публикация на 127.0.0.1 | На всех адресах | — |
| Производительность сети критична | --network host | Bridge | Изоляция снята целиком |
| Обращение к хосту | host.docker.internal | Адрес шлюза | Различия между платформами |
| Отладка без утилит в образе | --network container:имя | Установка утилит в образ | — |
Про --network host. Прежде чем брать — измерить, действительно ли сеть узкое место. Обычно нет.
Запуск и процессы
| Задача | Подход | Альтернативы | Цена |
|---|---|---|---|
| Точка входа | Exec-форма | Shell-форма | — |
| Аргументы снаружи | ENTRYPOINT плюс CMD | Только CMD | — |
| Нужна подстановка оболочки | Явный sh -c | Shell-форма | Требуется exec внутри |
| Сборщик зомби-процессов | init: true | Свой init в образе | — |
| Несколько процессов | Несколько container'ов | Супервизор внутри | Отказ второго не виден снаружи |
| Число рабочих процессов | Переменная окружения | От числа ядер | os.cpu_count() видит ядра хоста |
| Непривилегированный запуск | USER числом | Именем | Kubernetes не запустит по имени |
| Мягкое завершение | Обработчик плюс ограничение по времени | Только обработчик | Зависший запрос блокирует выход |
Наблюдаемость
| Задача | Подход | Альтернативы | Цена |
|---|---|---|---|
| Логи | stdout в JSON | Файл в container'е | Настройка формата |
| Настройка формата | При импорте модуля | В lifespan | Первые строки уйдут другим форматом |
| Ротация | max-size и max-file | Без ротации | — |
| Связывание записей | Идентификатор запроса | Без него | Проброс через контекст |
| «Жив ли процесс» | /healthz без зависимостей | Общая проверка | Ещё один эндпоинт |
| «Готов ли обслуживать» | /readyz с зависимостями | Общая проверка | Ещё один эндпоинт |
| Медленный старт | /startupz | Большая начальная задержка | Ещё один эндпоинт |
| Ресурсы | Чтение cgroup изнутри | docker stats | — |
| Давление памяти | memory.events | Только текущее потребление | — |
Про пятый и шестой пункты. Слитые в одну проверку, они приводят к тому, что отказ зависимости перезапускает все экземпляры — а после перезапуска зависимость по-прежнему недоступна.
Тестирование
| Задача | Подход | Альтернативы | Цена |
|---|---|---|---|
| Логика без зависимостей | Unit-тесты | Только интеграционные | — |
| Взаимодействие с базой | Настоящая база | Подделка | Медленнее; нужна база в CI |
| Управление зависимостями в тестах | Testcontainers | Compose | Ещё одна зависимость |
| То же без новых зависимостей | Compose плюс --wait | Testcontainers | Управление жизненным циклом вручную |
| Проверка образа | Отдельные проверки собранного образа | Только тесты исходников | — |
| Тесты в конвейере | Стадия test плюс явный вызов | Только стадия | Стадия без ссылок не выполняется |
| Отсутствие зависимости | Отказ, а не пропуск | Пропуск | Переключатель для локальной работы |
Про последний пункт. «33 skipped» в отчёте выглядит как успех. В конвейере пропуск должен быть отказом.
Доставка
| Задача | Подход | Альтернативы | Цена |
|---|---|---|---|
| Именование образа | Тег по SHA коммита | latest, prod | Нужен шаг обновления ссылки |
| Развёртывание | По digest | По тегу | Шаг подстановки digest |
| Откат | Записанный предыдущий digest | «Предыдущая версия» | Хранение состояния |
| Логика конвейера | Скрипты ci/*.sh | Шаги YAML | — |
| Порядок этапов | По время / вероятность отказа | Произвольный | — |
| Сканирование | Три кода возврата | Два | Немного кода |
| Состав образа | SBOM инструментом | Из файла зависимостей | Ещё один инструмент |
| Кэш между запусками | Registry, mode=max | Без кэша | Место в registry |
| Публикация | Только при коде 0 | При «не провалено» | — |
Масштаб
| Признак | Подход | Альтернативы | Цена |
|---|---|---|---|
| Одна машина, до десяти сервисов | Compose | Оркестратор | Нет автоматического восстановления |
| Одна машина, нужна доступность | Вторая машина плюс балансировщик | Кластер | Автоматики нет |
| Несколько машин, нагрузка растёт | Управляемый Kubernetes | Свой кластер | Знания всё равно нужны |
| Несколько команд, общая инфраструктура | Kubernetes | Compose на машину команде | Постоянная стоимость |
| Оркестрация проще Kubernetes | Nomad | Kubernetes | Меньше экосистема |
| Нет узлов вовсе | Serverless-платформа | Кластер | Ограничения и привязка |
Проверка перед переходом: чек-лист из урока 18.4. Отрицательные признаки в нём весят больше положительных намеренно — они отсекают решения, принятые по инерции.
Как пользоваться картой при проектировании
- Пройти сверху вниз, отмечая выбранное.
- Для каждого выбора выписать цену из последней колонки.
- Сложить цены и посмотреть на список целиком.
Третий шаг даёт то, ради чего карта существует. Каждое решение по отдельности выглядит разумным; сумма показывает, во что обошлась совокупность — и иногда заставляет пересмотреть первое решение из первой таблицы.
Навигация
Вернуться к справочникам
Каталог антипаттернов
Production checklist
План дальнейшего развития
Главное оглавление