Docker для приложения
Docker убирает «у меня работало», но только если ты понимаешь, как устроено кэширование слоёв и почему секреты нельзя зашивать в образ. Собес проверяет именно эти инженерные детали - по ним видно, писал ли человек Dockerfile для прода или копировал из туториала.
Типовые формулировки: «зачем COPY requirements отдельно от кода?», «что даёт multi-stage build?», «как прокинуть секреты в контейнер».
Образ и кэш слоёв
Образ фиксирует код, зависимости, версию Python и системные библиотеки в ОДНОМ артефакте - одинаковом на ноутбуке, в CI и на проде. Это и лечит «у меня работало»: среда едет вместе с кодом.
Ключевой приём Dockerfile - порядок слоёв: сначала COPY requirements и install, и только ПОТОМ COPY кода. Тогда тяжёлый слой с зависимостями берётся из кэша, пока requirements не менялся, и пересобирается лишь при смене зависимостей. Скопируешь код до install - любая правка исходника инвалидирует слой зависимостей, и pip install гоняется на каждую мелкую правку.
// .dockerignore держит лишнее (венвы, .git, кэши) вне контекста сборки - меньше контекст, быстрее и чище образ.
COPY requirements.txt .
RUN pip install -r requirements.txt # слой кэшируется
COPY . . # правки кода не ломают слой выше- образ
- код + зависимости + рантайм в фиксированном виде
- кэш слоёв
- неизменные инструкции берутся из кэша при пересборке
Multi-stage и секреты снаружи
Multi-stage build отделяет стадию сборки (компиляторы, кэш pip, dev-инструменты) от финального образа, где остаётся только рантайм. Итог - меньше размер и меньше поверхность атаки: в проде не лежат компиляторы, которых там быть не должно. Тащить билд-инструменты в финальный образ вместо multi-stage - раздувать его и множить риски.
Секреты и конфиг среды в образ НЕ зашивают: образ один на все среды, и зашитый секрет утечёт вместе с ним. Их прокидывают переменными окружения при запуске (или через секрет-менеджер) - тогда один и тот же образ безопасно едет в dev, stage и prod.
// Правило 12-factor тут и проявляется: артефакт неизменен, различия сред задаются снаружи конфигом, а не пересборкой.
- multi-stage
- отделить сборку от тонкого рантайм-образа
- .dockerignore
- исключить лишнее из контекста сборки
Как отвечать: «Зачем COPY requirements отдельно, до COPY кода?»
Ради кэша слоёв. Docker кэширует каждый слой и переиспользует его, пока инструкция и её входные данные не изменились. Если я сначала копирую requirements и ставлю зависимости, а код копирую отдельным шагом после, то тяжёлый слой с pip install остаётся в кэше при любой правке исходника - пересобирается только когда реально поменялся requirements. А если скопировать весь код до install одной командой, то любое изменение файла инвалидирует слой, и pip install прогоняется заново каждый раз - сборки становятся мучительно долгими. То есть это про то, чтобы дорогой шаг зависел только от того, что редко меняется.
Объяснён механизм кэша, показан контраст обоих порядков и назван эффект на скорость сборки - практическая деталь, которую знают только те, кто реально оптимизировал образы.
На чём валят
- −COPY кода до install: любая правка исходника инвалидирует слой зависимостей - install каждый раз.
- −Зашивать секреты в образ: они утекут вместе с ним и ломают «один образ на все среды».
- −Тащить билд-инструменты в финальный образ вместо multi-stage - размер и поверхность атаки.
- −Забыть .dockerignore: в контекст сборки уезжают венвы, .git и кэши - медленно и грязно.
Проверьте себя
Пять вопросов из банка по этой подтеме. Всего их 12, остальные разбираются в тренажёре.
- Почему в Dockerfile сначала COPY requirements.txt и install, а COPY кода — позже?A)Слой с зависимостями кэшируется, пока requirements не менялсяB)Python требует, чтобы зависимости лежали строго выше исходниковC)Это уменьшает итоговый размер образа за счёт объединения слоёвD)Иначе pip не найдёт файл requirements и установка просто упадёт
показать ответ и разбор
+A)Слой с зависимостями кэшируется, пока requirements не менялся// разбор: Docker кэширует слои: если requirements.txt не изменился, слой с pip install берётся из кэша, а переустанавливается только при правке зависимостей. Скопируй код раньше — любое изменение исходника инвалидирует последующий install и пересобирает зависимости каждый раз. Порядок инструкций = скорость пересборки.
- Зачем для Python-сервиса multi-stage build?A)Чтобы запускать несколько версий Python в одном образе параллельноB)Собрать зависимости в одном слое, а в финальный взять только нужноеC)Это обязательный синтаксис — без него Dockerfile не соберётсяD)Чтобы каждый этап сборки работал в отдельном запущенном контейнере
показать ответ и разбор
+B)Собрать зависимости в одном слое, а в финальный взять только нужное// разбор: Multi-stage build отделяет этап сборки (компиляторы, dev-заголовки, кэш pip) от финального образа, куда копируют лишь готовые артефакты и рантайм-зависимости. Итог — образ меньше и с меньшей поверхностью атаки: билд-инструментов в проде нет. Особенно заметно, когда есть пакеты с C-расширениями.
- Что не стоит зашивать внутрь Docker-образа приложения?A)Сам код приложения — его правильнее монтировать томом на продеB)Файл requirements.txt со списком зависимостей проектаC)Точку входа (CMD), запускающую сервер приложения при стартеD)Секреты и конфиг среды — их прокидывают при запуске
показать ответ и разбор
+D)Секреты и конфиг среды — их прокидывают при запуске// разбор: Секреты (пароли БД, API-ключи) и специфичный для среды конфиг не зашивают в образ: образ версионируется и раздаётся, а вместе с ним утекут и секреты, к тому же один образ должен работать на всех средах. Их прокидывают переменными окружения или секрет-менеджером при запуске. .dockerignore держит лишнее вне контекста сборки.
- Почему в Dockerfile COPY requirements и install ставят ДО COPY кода приложения?A)Слой с зависимостями кешируется и не пересобирается при каждой правке кодаB)Это уменьшает итоговый размер образа за счёт сжатия слоя с зависимостямиC)Иначе pip не найдёт файл requirements и установка зависимостей завершится ошибкойD)Так требует синтаксис Dockerfile: install обязан идти раньше COPY исходников
показать ответ и разбор
+A)Слой с зависимостями кешируется и не пересобирается при каждой правке кода// разбор: Docker кеширует слои: слой пересобирается, только если изменились его входные данные. Скопировав сначала requirements и установив зависимости, а КОД — позже, вы делаете так, что правка исходника инвалидирует лишь верхние дешёвые слои, а тяжёлый install берётся из кеша, пока requirements не менялся. Обратный порядок пересобирает зависимости на каждую правку кода.
- Что фиксирует Docker-образ приложения?A)Настройки конкретной среды (адреса, ключи), вшитые в образ на этапе его сборкиB)Только исходный код приложения, а зависимости докачиваются при каждом запуске контейнераC)Код, зависимости, версию Python и системные библиотеки в одном артефактеD)Работающий процесс вместе с его текущим состоянием оперативной памяти на момент сборки
показать ответ и разбор
+C)Код, зависимости, версию Python и системные библиотеки в одном артефакте// разбор: Образ упаковывает всё нужное для запуска: код, зависимости, версию интерпретатора, системные библиотеки — в один неизменяемый артефакт. Один и тот же образ одинаково работает на ноутбуке, в CI и на проде — лечит «у меня работало». Конфиг среды в образ не зашивают (иначе образ перестанет быть единым для всех сред) — его передают переменными окружения при запуске.
дальше
Теорию прочитали. Навык ставится повторением
В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, лимит только на количество вопросов в день.