За счет чего достигается легковесность контейнера в Docker
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
# За счет чего достигается легковесность контейнера в Docker
Краткий ответ
Докер контейнеры легче чем виртуальные машины благодаря использованию общего ядра ОС вместо эмуляции всей ОС. Docker использует Linux kernel features (namespaces и cgroups) для изоляции процессов без полной виртуализации.
Архитектура Docker vs Виртуальные машины
Виртуальная машина (тяжелая)
Хост ОС
├─ Hypervisor (KVM, VirtualBox)
│ ├─ ОС 1 (200MB) + ядро + драйверы
│ │ └─ Приложение
│ ├─ ОС 2 (200MB) + ядро + драйверы
│ │ └─ Приложение
│ └─ ОС 3 (200MB) + ядро + драйверы
│ └─ Приложение
Время запуска: несколько минут
Объем диска: ГБ (100+)
Потребление памяти: ГБ
Docker контейнер (легкий)
Хост ОС (Linux kernel)
├─ Docker Daemon
│ ├─ Контейнер 1 (процесс)
│ │ ├─ Приложение
│ │ └─ Зависимости (МБ)
│ ├─ Контейнер 2 (процесс)
│ │ ├─ Приложение
│ │ └─ Зависимости (МБ)
│ └─ Контейнер 3 (процесс)
│ ├─ Приложение
│ └─ Зависимости (МБ)
Время запуска: несколько миллисекунд
Объем диска: МБ (5-100)
Потребление памяти: МБ
Ключевые технологии Linux Kernel
1. Namespaces (изоляция)
Namespaces позволяют контейнерам иметь независимые виды на системные ресурсы:
# PID namespace — контейнер видит свои процессы
Containers видят разные PID:
Контейнер 1: PID 1 (init), PID 2 (app) → но это PID 100, 101 на хосте
Контейнер 2: PID 1 (init), PID 2 (app) → но это PID 200, 201 на хосте
# Network namespace — отдельная сетевая стек
Каждый контейнер имеет свой:
- IP адрес
- Port space
- Маршруты
- Файерволл
# Mount namespace — отдельная файловая система
Контейнер видит свой корень /
- /bin → слой контейнера
- /lib → базовый образ
- /app → приложение
# User namespace — изоляция прав
Пользователь root в контейнере может быть непривилегированным пользователем на хосте
# IPC namespace — изоляция межпроцессного взаимодействия
Контейнеры не могут видеть shared memory другого контейнера
# UTS namespace — отдельные hostname и domainname
2. Cgroups (ограничение ресурсов)
Control Groups позволяют ограничивать потребление ресурсов:
docker run --memory="512m" nginx
↓
Контейнер не может использовать больше 512MB памяти
docker run --cpus="0.5" nginx
↓
Контейнер использует максимум 50% одного ядра процессора
docker run --pids-limit=10 nginx
↓
Контейнер может создать максимум 10 процессов
Cgroups отслеживают и ограничивают:
- Память (RSS, swap)
- CPU
- I/O дискового ввода-вывода
- Сеть
- Количество процессов
Многоуровневая файловая система (Layers)
Copy-on-Write (CoW)
Докер использует многоуровневую файловую систему, где слои переиспользуются:
FROM ubuntu:20.04 # Слой 1: 72 MB базовая ОС
RUN apt-get install -y java # Слой 2: +200 MB
COPY app.jar . # Слой 3: +50 MB (приложение)
ENTRYPOINT java -jar app.jar
Результат
Образ Java приложения:
├─ Слой 1 (ubuntu базе): 72 MB (переиспользуется всеми образами)
├─ Слой 2 (Java): 200 MB (переиспользуется всеми Java образами)
└─ Слой 3 (приложение): 50 MB (уникально для этого образа)
Общий размер одного образа: 322 MB
Если создать ещё 5 контейнеров: 322 MB + 5*(дельта слоя контейнера)
Copy-on-Write механизм
Базовый образ (read-only):
/usr/bin/java
/lib/libc.so
/app/config.yml
Контейнер A изменяет /app/config.yml:
1. Копируется в слой контейнера (write layer)
2. Остальные файлы остаются в read-only слое
3. Размер слоя = только изменённые файлы
Сравнение размеров
Без Docker
Установка 3 экземпляров Java приложений:
- ОС 1: 2 GB + Java + app
- ОС 2: 2 GB + Java + app
- ОС 3: 2 GB + Java + app
Итого: 6+ GB + Java*3 + app*3
С Docker
Ubuntu образ: 72 MB (общий)
Java слой: 200 MB (переиспользуется)
Приложение слой: 50 MB × 3 контейнера
Итого: 72 + 200 + 150 = 422 MB
Экономия: в 14+ раз меньше места!
Пример: Alpine Linux
# ❌ Большой образ
FROM ubuntu:20.04
RUN apt-get update && apt-get install -y curl
# Размер: 100+ MB
# ✅ Легкий образ
FROM alpine:latest
RUN apk add --no-cache curl
# Размер: 5-10 MB
Alpine базирована на муслинке (миниатюрная libc), а не на полной glibc.
Процесс изоляции в действии
// Java приложение в контейнере
public class DockerApp {
public static void main(String[] args) {
// Это процесс на хосте, но изолирован
System.out.println(ManagementFactory.getRuntimeMXBean().getPid());
// Выведет: 1 (в контейнере)
// На хосте это может быть PID 45678
}
}
# На хосте видно
$ ps aux | grep java
root 45678 99.5 0.5 2563480 45088 ? Sl 10:00 java -jar app.jar
# В контейнере видно
$ ps aux
root 1 99.5 0.5 2563480 45088 ? Sl 10:00 java -jar app.jar
Потребление ресурсов
Память
Типичный контейнер: 5-50 MB (базовая система)
Java приложение: +200-500 MB (JVM + app)
Всего на контейнер: 250-600 MB
Вирт. машина: 2+ GB минимум
Вирт. машина с Java: 3+ GB
Время запуска
Вирт. машина: 30-60 секунд (boot ОС)
Докер контейнер: 100-500 миллисекунд (start process)
Почему контейнеры легче (итоговая таблица)
| Аспект | ВМ | Docker |
|---|---|---|
| Ядро ОС | Свое (эмулируется) | Общее (Linux kernel) |
| ОС | Полная (2+ GB) | Только необходимое (5-100 MB) |
| Файловая система | Цельная копия | Слои с переиспользованием (CoW) |
| Процессы | Эмулируются | Настоящие процессы ОС |
| Память | Выделяется заранее | По требованию |
| Запуск | Boot цикл (минуты) | Fork процесса (мс) |
| Результат | ГБ | МБ |
Заключение
Докер контейнеры легче виртуальных машин потому что:
- Используют общее ядро ОС вместо полной виртуализации (namespaces, cgroups)
- Не требуют полной ОС в образе (только зависимости)
- Используют многоуровневую ФС с переиспользованием слоев (Copy-on-Write)
- Изоляция на уровне процессов, а не эмуляция железа
- Быстрый запуск — просто fork процесса вместо boot ОС
Это достигается на уровне Linux kernel features, что делает контейнеры идеальными для микросервисной архитектуры и CI/CD конвейеров.