Docker для дата-инженера
Контейнеры - базовая упаковка пайплайнов и сервисов, и путают их с виртуалками. Собес проверяет, понимаешь ли ты, что это изолированный процесс на общем ядре, и почему данные контейнера эфемерны.
Стержень: контейнер - процесс с namespaces и cgroups, а не VM; образ собирается кэшируемыми слоями, и порядок слоёв решает скорость пересборки.
// Формулировки: «чем контейнер отличается от VM?», «зачем слои образа?», «где хранить данные контейнера?».
Процесс и слои
Контейнер - изолированный процесс со своей файловой системой, сетью и лимитами (namespaces плюс cgroups), а не виртуалка: ядро общее с хостом, старт занимает миллисекунды, а не секунды загрузки ОС.
Образ собирается слоями из Dockerfile, и слои кэшируются сверху вниз. Поэтому редко меняющееся (установку зависимостей) ставят раньше, а часто меняющееся (копирование кода) - позже: тогда пересборка после правки кода занимает секунды, а не переустанавливает зависимости заново.
// Обратный порядок - COPY кода до установки зависимостей - промахивается мимо кэша: каждая правка строчки кода запускает многоминутную переустановку.
- namespaces / cgroups
- изоляция ресурсов / лимиты - основа контейнера
- слой
- кэшируемый шаг сборки; порядок решает скорость
Эфемерность и один процесс
Данные контейнера эфемерны: рестарт даёт чистую файловую систему. Всё, что должно жить, кладут в volumes, а конфигурацию передают через переменные окружения, а не запекают в образ (принцип 12-factor). Хранить данные БД в ФС (файловая система) контейнера - значит стереть прод при рестарте.
Один контейнер - один процесс или сервис: логи в stdout/stderr, а упавшее перезапускает оркестратор. Супервизор внутри контейнера - антипаттерн.
// Секреты в образ (ENV в Dockerfile, вшитые ключи) попадают в каждый слой навсегда - их оттуда не вычистить. Секреты подают в рантайме, а не встраивают в образ.
- volume
- переживающее контейнер хранилище данных
- 12-factor
- конфиг через ENV, а не запечён в образ
Теги, multi-stage, лимиты
Образы тегируют версией или коммитом, а не latest: «latest» вчера и сегодня - разные образы, а воспроизводимость деплоя требует закреплённого тега или digest.
Multi-stage build собирает артефакт в тяжёлом образе и копирует его в тонкий рантайм: меньше размер, меньше поверхность атаки; внутри не работают от root.
// Лимиты памяти и CPU обязательны: контейнер без лимитов съедает ноду целиком. И OOMKilled (exit 137) это не «докер глючит», а превышение выданного лимита памяти; лечится лимитом или профилем потребления, а не рестартом.
- multi-stage build
- сборка в одном образе, рантайм в тонком
- OOMKilled
- убит за превышение лимита памяти (exit 137)
Как отвечать: «Чем контейнер отличается от VM и зачем слои?»
Виртуалка эмулирует железо и несёт целую гостевую ОС со своим ядром, поэтому она тяжёлая и стартует секундами. Контейнер это просто изолированный процесс на ядре хоста: изоляция достигается namespaces, лимиты - cgroups, отдельного ядра нет. Отсюда старт за миллисекунды, маленький размер и высокая плотность на ноду. Про слои: образ собирается по слоям из Dockerfile, и они кэшируются сверху вниз. Смысл в порядке - редко меняющееся, вроде установки зависимостей, ставят раньше, а часто меняющееся, код, позже. Тогда после правки кода пересобирается только верхний слой, за секунды, а тяжёлая установка зависимостей берётся из кэша. Если перепутать и скопировать код до установки зависимостей, кэш промахивается и каждая правка запускает многоминутную пересборку. Плюс помню, что данные контейнера эфемерны - их в volumes, и всегда ставлю лимиты памяти.
Почему это сильный ответ: контейнер отделён от VM через общее ядро (namespaces/cgroups, старт мс), слои объяснены через кэш и порядок с конкретной ошибкой, добавлена эфемерность и лимиты.
На чём валят
- −Хранить данные БД в ФС контейнера - рестарт стёр прод.
- −COPY кода до установки зависимостей - кэш слоёв мимо, пересборка по 10 минут.
- −FROM latest в проде - невоспроизводимый деплой, «вчера работало».
- −Секреты в образ (ENV в Dockerfile, вшитые ключи) - они в каждом слое навсегда.
- −Контейнер от root с проброшенным docker.sock - фактически root на хосте.
Проверьте себя
Пять вопросов из банка по этой подтеме. Всего их 10, остальные разбираются в тренажёре.
- Как порядок инструкций в Dockerfile влияет на скорость пересборки образа?A)Порядок инструкций на скорость пересборки не влияет — Docker собирает образ целиком с нуляB)Код нужно копировать в самом начале Dockerfile, чтобы установка зависимостей попадала в кэшC)Слои кэшируются; редко меняющееся раньше кода → кэш deps не сбрасываетсяD)Docker кэширует последнюю инструкцию образа, поэтому порядок остальных безразличен
показать ответ и разбор
+C)Слои кэшируются; редко меняющееся раньше кода → кэш deps не сбрасывается// разбор: Каждая инструкция Dockerfile создаёт слой, и Docker кэширует их: при пересборке слой берётся из кэша, если он и всё до него не изменились. Как только слой меняется, все последующие пересобираются заново. Отсюда правило: сначала копировать файл зависимостей и ставить их (меняются редко), и только потом копировать исходный код (меняется часто). Тогда правка кода не сбрасывает кэш установки зависимостей — пересборка быстрая. Обратный порядок (COPY всего кода до install) переустанавливает зависимости на каждую правку.
- Для чего служит docker-compose?A)Docker-compose нужен, чтобы собрать один образ из нескольких разных Dockerfile сразуB)Docker-compose распределяет контейнеры по множеству физических узлов кластера для масштабаC)Docker-compose заменяет собой Dockerfile и описывает, как собирается сам образ приложенияD)Описать и поднять многоконтейнерное приложение (например, пайплайн + Postgres + брокер) одной декларацией и командой
показать ответ и разбор
+D)Описать и поднять многоконтейнерное приложение (например, пайплайн + Postgres + брокер) одной декларацией и командой// разбор: docker-compose описывает в одном YAML набор сервисов-контейнеров, их образы, переменные окружения, тома, сети и зависимости, и поднимает весь стек одной командой (up). Удобно для локальной разработки и тестов: поднять пайплайн вместе с его Postgres, Kafka, MinIO в согласованной сети без ручного запуска каждого контейнера. Для продакшена на кластере обычно переходят на Kubernetes (масштаб, самовосстановление, много узлов), а compose остаётся инструментом локального стека и CI.
- Зачем контейнеру монтируют volume (том)?A)Хранить данные вне жизненного цикла контейнера: контейнер эфемерен, а данные в volume переживают его пересозданиеB)Volume нужен, чтобы ускорить работу контейнера, кэшируя его образ в оперативной памяти хостаC)Volume делает контейнер stateful, встраивая всё его состояние прямо внутрь файловой системы контейнераD)Данные, записанные в файловую систему контейнера без volume, сохраняются и после его удаления
показать ответ и разбор
+A)Хранить данные вне жизненного цикла контейнера: контейнер эфемерен, а данные в volume переживают его пересоздание// разбор: Файловая система контейнера эфемерна: удалил/пересоздал контейнер — всё, что писалось внутрь, исчезло. Volume — это хранилище (директория хоста или именованный том), примонтированное в контейнер, живущее независимо от него. Туда кладут данные, которые должны пережить пересоздание: файлы БД, промежуточные результаты, логи. Так контейнер остаётся stateless (легко пересоздать/масштабировать), а состояние вынесено в volume или внешнюю БД. Писать важные данные в саму файловую систему контейнера — частая ошибка: они теряются при рестарте.
- Что даёт multi-stage build в Dockerfile?A)Multi-stage build запускает несколько контейнеров приложения параллельно из одного образаB)Сборка в одном stage, в финал только артефакт — тонкий образ без build-depsC)Он увеличивает финальный образ, включая в него все инструменты сборки ради полноты окруженияD)Multi-stage build нужен, чтобы собрать образ сразу под несколько разных версий приложения
показать ответ и разбор
+B)Сборка в одном stage, в финал только артефакт — тонкий образ без build-deps// разбор: Multi-stage build использует несколько FROM: в первом («build») ставят компиляторы, dev-зависимости, собирают артефакт (бинарь, wheel, статику); в финальном образе с минимальной базой копируют через COPY --from только готовый результат, а весь build-инструментарий остаётся в отброшенном промежуточном образе. Итог — маленький, безопасный runtime-образ без лишних зависимостей и мусора. Это стандартный приём уменьшения размера образа и поверхности атаки: не тащить в прод то, что нужно было только для сборки.
- Почему контейнеры проектируют stateless и эфемерными (принцип 12-factor)?A)Чтобы контейнер хранил всё своё состояние локально и не зависел ни от каких внешних сервисовB)Чтобы каждый контейнер был уникальным и незаменимым, с собственными несохраняемыми даннымиC)Взаимозаменяемость/масштаб/рестарт — состояние во внешних сервисахD)Stateless нужен для экономии оперативной памяти на узле, к масштабу отношения нет
показать ответ и разбор
+C)Взаимозаменяемость/масштаб/рестарт — состояние во внешних сервисах// разбор: Stateless-контейнер не хранит важного состояния локально — оно в БД, объектном хранилище, кэше. Тогда любой экземпляр взаимозаменяем: оркестратор может убить и поднять его на другом узле, запустить десять копий за балансировщиком, обновить без потери данных — контейнеры «как скот, а не питомцы». Это принцип 12-factor app и основа горизонтального масштабирования и самовосстановления в K8s. Если же состояние живёт внутри контейнера, его нельзя безопасно пересоздать или размножить — рестарт теряет данные, копии расходятся.
дальше
Теорию прочитали. Навык ставится повторением
В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, лимит только на количество вопросов в день.