Материал открыт для чтения

Войдите, чтобы сохранять прогресс и результаты.

Войти
Docker: Команды, контейнеры и сборка образов

Контейнеры vs VM

чтение ~12 минУрок 1 из 17

Контейнеры vs виртуальные машины

module docker basics Image01

Проблема: «У меня работает, у тебя — нет»

Классическая история в командах разработки: разработчик пишет код на MacBook, всё работает. Передаёт тестировщику — падает. Деплоит на сервер — снова падает. Причина почти всегда одна: разные окружения. Разные версии Python, Node.js, разные системные библиотеки, разные переменные окружения.

До Docker проблему решали по-разному. Виртуальные машины — тяжёлый, но надёжный способ. Скрипты настройки окружения типа bootstrap.sh — хрупко и ненадёжно. Инструменты вроде Vagrant — лучше, но всё ещё громоздко.

Docker предложил принципиально иной подход: упаковать приложение вместе с его окружением.

Важная идея: контейнер решает не только задачу запуска процесса, а задачу повторяемого окружения для этого процесса.

Когда приложение запускается из образа (image), вместе с ним приезжают нужные библиотеки, файлы и настройки запуска. Поэтому меньше зависит от того, что случайно установлено на сервере руками.


Виртуальные машины: как они работают

Виртуальная машина (VM) — это полная симуляция компьютера. Она содержит:

  • Виртуальное железо (CPU, RAM, диск, сетевую карту)
  • Гипервизор — программу, которая управляет этим виртуальным железом
  • Полноценную гостевую ОС с ядром (Guest OS)
  • Все системные службы этой ОС
  • Ваше приложение поверх всего этого
┌─────────────────────────────────────────────┐
│ Host OS                                     │
│ ┌─────────────────────────────────────────┐ │
│ │ Гипервизор (VMware/KVM)                 │ │
│ │ ┌─────────────────┐ ┌─────────────────┐ │ │
│ │ │ VM 1            │ │ VM 2            │ │ │
│ │ │ Guest OS        │ │ Guest OS        │ │ │
│ │ │ App A           │ │ App B           │ │ │
│ │ └─────────────────┘ └─────────────────┘ │ │
│ └─────────────────────────────────────────┘ │
└─────────────────────────────────────────────┘

Плюсы VM:
- Полная изоляция — разные ОС на одном железе
- Можно запустить Windows на Linux-хосте и наоборот
- Хорошая безопасность: атака из VM не затронет хост

Минусы VM:
- Большой размер: образ Ubuntu занимает несколько гигабайт
- Медленный старт: VM загружается минуты
- Большое потребление ресурсов: каждая VM резервирует RAM и CPU
- Накладные расходы (overhead): ядро гостевой ОС работает поверх виртуального железа


Контейнеры: другой подход к изоляции

Контейнеры используют совершенно другую идею. Они не создают виртуальное железо и не загружают отдельное ядро ОС. Вместо этого они используют механизмы самого Linux-ядра для изоляции процессов.

┌─────────────────────────────────────────────┐
│ Host OS                                     │
│ ┌─────────────────────────────────────────┐ │
│ │ Docker Engine (daemon)                  │ │
│ └─────────────────────────────────────────┘ │
│ ┌─────────────────┐ ┌─────────────────┐     │
│ │ Контейнер 1     │ │ Контейнер 2     │     │
│ │ ┌─────────────┐ │ │ ┌─────────────┐ │     │
│ │ │ App A       │ │ │ │ App B       │ │     │
│ │ ├─────────────┤ │ │ ├─────────────┤ │     │
│ │ │ libs A      │ │ │ │ libs B      │ │     │
│ │ └─────────────┘ │ │ └─────────────┘ │     │
│ └─────────────────┘ └─────────────────┘     │
│ Одно ядро Linux для всех контейнеров        │
└─────────────────────────────────────────────┘

Два ключевых механизма ядра Linux, которые делают контейнеры возможными:

Пространства имён (namespaces) — изоляция

Пространства имён создают изолированный взгляд на разные ресурсы:

  • пространство процессов (pid namespace) — каждый контейнер видит свои процессы со своими PID. Процесс с PID 1 внутри контейнера — это не init системы, а ваше приложение.
  • сетевое пространство (net namespace) — у каждого контейнера своя сетевая карта, свой IP, свои порты. Два контейнера могут оба слушать порт 80 без конфликта.
  • пространство монтирования (mnt namespace) — у каждого контейнера своя файловая система. Контейнер не видит файлы хоста, если вы явно не подключили их внутрь.
  • пространство имени хоста (uts namespace) — у каждого контейнера свой hostname.
  • пространство пользователей (user namespace) — сопоставление пользователей и групп (uid/gid).
  • пространство межпроцессного взаимодействия (ipc namespace) — изоляция обмена данными между процессами.

Контрольные группы (cgroups) — контроль ресурсов

Контрольные группы (Control Groups, cgroups) позволяют ограничивать ресурсы для группы процессов:

  • Максимальное использование CPU (например, не более 0.5 ядра)
  • Максимальное использование RAM (например, не более 256 MB)
  • Дисковый I/O
  • Пропускная способность сети

Если контейнер превысит лимит по памяти — ядро убьёт его процессы. Это защищает соседние контейнеры.

Если совсем коротко:

  • пространства имён (namespaces) отвечают за то, что процесс видит вокруг себя: процессы, сеть, hostname, точки монтирования;
  • контрольные группы (cgroups) отвечают за то, сколько ресурсов процесс может потреблять: CPU, RAM, I/O.

Контейнер получается из сочетания этих механизмов: обычный Linux-процесс запускается с отдельными пространствами имён, попадает в свою контрольную группу и получает свою файловую систему из образа. Docker не изобрёл изоляцию на уровне ядра, а собрал эти возможности в удобный инструмент: docker run, образы, реестры, сети и тома.

Важная идея: контейнер - это не маленькая виртуальная машина, а изолированный процесс на общем ядре хоста.

Из-за этого контейнеры быстро стартуют и занимают меньше ресурсов, но их изоляция отличается от VM. У контейнера нет своего отдельного ядра ОС; он использует ядро хоста через Linux namespaces и cgroups.

Не путай Linux namespace с namespace в имени образа. В registry.company.local/dev/app:v1 слово dev тоже могут называть namespace, но это уже часть имени образа в реестре, а не механизм изоляции Linux.


Сравнение в цифрах

Характеристика Виртуальная машина Контейнер
Время запуска 30 секунд — несколько минут Секунды или миллисекунды
Размер образа Гигабайты (полная ОС) Мегабайты (только нужные слои)
Потребление RAM Гигабайты (резервируется) Только то, что реально нужно
Плотность на хосте Единицы VM Сотни контейнеров
Изоляция ядра Полная (своё ядро) Общее ядро хоста
Безопасность Выше (полная изоляция) Ниже (общее ядро, shared kernel)

Когда что использовать

Виртуальные машины подходят, когда:
- Нужна разная ОС (Windows на Linux-хосте)
- Требуется максимальная безопасность для недоверенного кода (untrusted code)
- Нужно своё ядро с особыми настройками
- Требования комплаенса (compliance) требуют полной изоляции

Контейнеры подходят, когда:
- Все приложения работают на Linux
- Нужна быстрая разработка и деплой
- Важна плотность размещения (много сервисов на одном хосте)
- Используется микросервисная архитектура

На практике в DevOps используют и то, и другое: контейнеры запускаются внутри виртуальных машин. Это даёт и изоляцию VM, и эффективность контейнеров.


Docker и OCI

Docker — это не просто контейнеры. Это экосистема, включающая:

  • Docker Engine — демон, управляющий контейнерами
  • Docker Image — образ, неизменяемый шаблон для создания контейнеров
  • Docker Registry — реестр, хранилище образов (Docker Hub, Nexus, Harbor)
  • Docker Compose — инструмент для запуска нескольких контейнеров вместе
  • Docker CLI — командная строка для управления

В 2015 году Docker передал спецификацию контейнеров в OCI (Open Container Initiative). Теперь существуют и другие container runtimes (среды запуска контейнеров): containerd, CRI-O, podman. Kubernetes использует containerd под капотом. Но образы, собранные Docker, работают везде, потому что все следуют одной спецификации.


Итого

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

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

В первых лабораторных важно думать о контейнере не как о “маленькой VM”, а как о процессе с настройками изоляции.

Главная связка такая:

образ (image) -> контейнер (container) -> процесс (process)

Образ (image) — это шаблон. Контейнер (container) — экземпляр этого шаблона. Процесс (process) — программа, которая реально выполняется внутри контейнера. Если главный процесс завершился, контейнер тоже остановился. Подробные команды запуска, просмотра логов и входа внутрь контейнера будут в статье про жизненный цикл.

Что сохраняется, а что нет

Образ неизменяемый. Контейнер имеет свой записываемый слой (writable layer) поверх образа. Если записать файл внутрь контейнера и удалить контейнер, файл пропадёт вместе с этим слоем.

Для данных, которые должны жить дольше контейнера, нужны volumes (тома) или bind mounts (привязки директорий хоста). Это появится в сетевых и volume-лабах, но базовую идею лучше держать сразу:

Где данные Что будет при docker rm
внутри записываемого слоя контейнера удалятся
named volume (именованный том) сохранятся
bind mount (директория хоста) останутся на хосте

Контейнеры и безопасность

Изоляция контейнера полезна, но не абсолютна. Контейнеры используют ядро хоста, поэтому root внутри контейнера и небезопасные capabilities могут быть проблемой.

Capabilities — это отдельные привилегии ядра Linux, которые можно дать процессу вместо полного root-доступа. Например, одна capability может разрешать менять сетевые настройки, другая — выполнять операции администрирования системы. Чем больше таких привилегий у контейнера, тем шире возможный ущерб при ошибке или взломе.

В начальном курсе мы не уходим глубоко в настройку безопасности контейнеров, но хорошая привычка уже здесь: не запускать лишнее, не хранить секреты в образе, не использовать --privileged без причины, проверять опубликованные порты.

Почему контейнеры быстро стартуют

VM должна загрузить отдельную гостевую ОС: ядро, init, системные сервисы. Контейнер использует уже работающее ядро хоста и стартует только нужный процесс. Поэтому docker run nginx обычно занимает секунды, а создание VM - минуты.

Но скорость не означает, что контейнер “проще” операционно. У контейнера всё равно есть образ, сеть, тома, логи, лимиты ресурсов и жизненный цикл. Docker просто делает эти вещи более лёгкими и повторяемыми.

Что значит stateless

Stateless-сервис — это сервис без локального состояния. Он не хранит критичные данные внутри файловой системы контейнера, поэтому его можно удалить и создать заново без потери данных. Примеры: frontend, API, worker, который читает задачи из очереди.

Stateful-сервис хранит состояние: база данных, брокер сообщений, файловое хранилище. Для него обязательно нужно думать о volumes, backup и восстановлении.

В этом курсе это решается просто: nginx-контейнер можно удалить и запустить заново — контейнер будет точно такой же, а данные PostgreSQL или пользовательские файлы должны быть в volume, иначе при удалении контейнера все данные будут потеряны.

Образ против контейнера

Один образ может породить много контейнеров:

docker run -d --name web1 nginx:alpine
docker run -d --name web2 nginx:alpine

web1 и web2 используют один образ, но имеют разные container layers, имена, IP и lifecycle. Удаление одного контейнера не удаляет образ и не останавливает другой контейнер.

Почему “зайти в контейнер и поправить” плохо

Можно выполнить:

docker exec -it web sh

и вручную изменить файл внутри контейнера. Для диагностики это нормально. Для постоянного исправления - плохо: изменение живёт только в записываемом слое конкретного контейнера и пропадёт при пересоздании.

Правильный путь зависит от типа изменения:

Изменение Где фиксировать
код приложения в Git и новом образе
системная зависимость в Dockerfile
конфигурация запуска env/volume/Compose/Kubernetes
данные приложения volume или внешнее хранилище

Так контейнер остаётся воспроизводимым.

Материал изучен?

Войдите, чтобы сохранить прогресс и перейти к практической лаборатории.

Войти и продолжить