Образ, контейнер и чем это не виртуальная машина

Вопрос, с которого начинается блок про контейнеры почти на каждом собеседовании.

Вопрос: «Чем образ отличается от контейнера? И чем контейнер отличается от виртуальной машины?»

Что на самом деле проверяет интервьюер

Первая часть — терминологическая гигиена: человек, который говорит «залил контейнер в registry», почти наверняка использовал Docker поверхностно. Вторая часть — понимание, что контейнер это не маленькая виртуалка, а обычный процесс хоста с ограничениями. Из этого понимания вытекает всё остальное: почему контейнеры стартуют за миллисекунды, почему нельзя запустить контейнер с другим ядром и почему изоляция контейнера слабее, чем у ВМ.

Развёрнутый ответ: образ и контейнер

Образ — это неизменяемый (immutable) шаблон: набор слоёв файловой системы плюс метаданные (какую команду запускать, какие переменные окружения, какой рабочий каталог). Образ лежит в registry и одинаков у всех.

Контейнер — это запущенный экземпляр образа: те же read-only слои плюс тонкий writable-слой сверху, куда попадают все изменения, сделанные во время работы. Из одного образа можно поднять сто контейнеров, у каждого будет свой writable-слой, и все они будут переиспользовать общие read-only слои.

Аналогия, которая обычно нравится интервьюеру: образ — это класс, контейнер — экземпляр класса. Или: образ — установочный ISO, контейнер — работающая с него система.

docker images        # список ОБРАЗОВ (шаблонов)
docker ps -a         # список КОНТЕЙНЕРОВ (экземпляров)

docker run --name a -d nginx
docker run --name b -d nginx    # два контейнера из одного образа

docker diff a        # что изменилось в writable-слое контейнера a

Важное следствие: удалили контейнер — потеряли writable-слой вместе со всем, что в него записали. Это подводит к теме volume, разбираем её в третьем уроке раздела.

Развёрнутый ответ: контейнер против виртуальной машины

Виртуальная машина эмулирует железо: гипервизор даёт гостю виртуальные диск, сетевую карту и процессор, гость грузит собственное ядро и полноценную ОС. Контейнер ничего не эмулирует — это процесс, запущенный на ядре хоста, которому ядро подсунуло изолированное представление о мире.

Изоляция стоит на трёх механизмах ядра Linux, и назвать их — признак сильного кандидата:

  • namespaces — что процесс видит. Отдельные пространства имён для PID (внутри контейнера ваш процесс — PID 1), для сети (свой сетевой стек и интерфейсы), для точек монтирования, для пользователей, для hostname.
  • cgroups — сколько процесс может потребить: лимиты CPU, памяти, дискового ввода-вывода. На них же построены requests/limits в Kubernetes.
  • union filesystem (overlay2) — как несколько read-only слоёв и один writable собираются в единое дерево файлов.
КритерийКонтейнерВиртуальная машина
Ядрообщее с хостомсобственное
Стартмиллисекундыдесятки секунд
Накладные расходыпочти нулевыегостевая ОС целиком
Изоляцияна уровне ядра, слабееаппаратная, сильнее
Разные ОС на одном хостенет (только то же ядро)да

Проверить общее ядро легко: команда ниже покажет одну и ту же версию на хосте и внутри контейнера, даже если контейнер собран из Alpine, а хост — Ubuntu.

uname -r
# 6.1.0-18-amd64

docker run --rm alpine uname -r
# 6.1.0-18-amd64   ← ядро то же самое

# а вот дистрибутив внутри действительно другой
docker run --rm alpine cat /etc/os-release | head -1
# NAME="Alpine Linux"

Практические следствия, которые стоит проговорить

  • Нельзя запустить Windows-контейнер на Linux-хосте и наоборот: ядро одно. Docker Desktop на macOS и Windows поэтому втихую поднимает Linux-ВМ.
  • Уязвимость ядра — общая для всех контейнеров. Container escape реален, поэтому для недоверенного кода берут gVisor, Kata Containers или обычные ВМ.
  • Контейнер видит ядро хоста и его параметры, но не видит его процессы — благодаря PID namespace.
  • Плотность. На одном сервере спокойно живут сотни контейнеров, потому что не нужно платить за сотни копий ОС.

Типичные ошибки кандидатов

  • «Контейнер — это лёгкая виртуалка». Формулировка допустима как метафора для менеджера, но на техническом собеседовании её попросят уточнить, и без namespaces/cgroups ответ рассыпется.
  • Путают образ и контейнер в речи: «пересобрал контейнер», «запушил контейнер».
  • Считают, что контейнер изолирован так же надёжно, как ВМ — и не могут объяснить, почему в мультитенантных облаках всё равно используют виртуализацию.
  • Не знают, что writable-слой умирает вместе с контейнером, и хранят в нём данные.
  • Думают, что Alpine-контейнер «приносит своё ядро Linux». Он приносит только userspace: библиотеки и утилиты.

Как ответить кратко (20–30 секунд)

«Образ — неизменяемый шаблон из слоёв файловой системы и метаданных, он лежит в registry. Контейнер — запущенный экземпляр образа: те же слои плюс тонкий writable-слой, который умирает вместе с контейнером. От виртуалки контейнер отличается тем, что не эмулирует железо и не грузит своё ядро: это процесс на ядре хоста, которому namespaces ограничивают видимость, а cgroups — потребление ресурсов. Отсюда старт за миллисекунды и почти нулевой оверхед, но и более слабая изоляция, чем у ВМ.»

Проверьте себя
1. Какие два механизма ядра Linux обеспечивают изоляцию контейнера?
Achroot и SELinux
Bnamespaces (что процесс видит) и cgroups (сколько ресурсов может потребить)
Csystemd и journald
Diptables и AppArmor
2. Можно ли на хосте с Linux запустить контейнер с ядром Windows?
AДа, Docker подгрузит нужное ядро из образа
BНет: контейнер использует ядро хоста, поэтому Windows-контейнеры требуют Windows-хоста или прослойки в виде ВМ
CДа, если включить режим privileged
DДа, но только для образов на базе Alpine
3. Что произойдёт с файлом, записанным внутри контейнера в /tmp, после docker rm этого контейнера?
AФайл сохранится в образе и появится в новых контейнерах
BФайл исчезнет вместе с writable-слоем контейнера
CФайл автоматически переедет в именованный volume
DФайл останется в кэше сборки