Виртуализация и контейнеры Docker
Отличие контейнеров от виртуальных машин, образы и слои, Dockerfile и его оптимизация, тома и сети, docker compose для многосервисного приложения.
Развёртывание проекта в контейнерах — сильный аргумент на защите: работа запускается у проверяющего одной командой, независимо от того, что установлено на его машине. Разберём, как это устроено.
Виртуальная машина или контейнер
| Признак | Виртуальная машина | Контейнер |
|---|---|---|
| Что виртуализуется | Оборудование | Процессы в рамках ядра хоста |
| Своё ядро ОС | Да | Нет, ядро общее |
| Размер | Гигабайты | Десятки-сотни мегабайт |
| Запуск | Минуты | Секунды |
| Накладные расходы | 5-15 % | Практически нулевые |
| Изоляция | Полная | На уровне пространств имён ядра |
| Разные ОС на одном хосте | Да | Нет, только совместимые с ядром |
Контейнер — это не облегчённая виртуальная машина, а изолированный процесс. Изоляцию обеспечивают механизмы ядра Linux: namespaces разделяют видимость процессов, сети и файловой системы, cgroups ограничивают ресурсы. Отсюда и скорость, и ограничение — ядро одно на всех.
Образы и контейнеры
Образ (image) — неизменяемый шаблон, состоящий из слоёв Контейнер — запущенный экземпляр образа с записываемым слоем сверху Аналогия: образ — класс, контейнер — объект. Из одного образа можно запустить сотню контейнеров, и они разделяют слои образа, не копируя их.
docker images # список образов
docker ps # запущенные контейнеры
docker ps -a # включая остановленные
docker run -d --name web -p 8080:80 nginx # запустить в фоне
docker logs -f web # смотреть вывод
docker exec -it web bash # войти внутрь
docker stop web && docker rm web # остановить и удалить
docker build -t myapp:1.0 . # собрать образ из Dockerfile
docker system prune -a # убрать всё неиспользуемое
Dockerfile
# Многоступенчатая сборка: в финальный образ не попадают инструменты сборки
FROM node:20-alpine AS builder
WORKDIR /app
# Сначала только манифесты — слой с зависимостями закешируется
COPY package*.json ./
RUN npm ci --only=production
# Затем исходники: их изменение не заставит переустанавливать пакеты
COPY . .
FROM node:20-alpine
WORKDIR /app
# Не root: процесс в контейнере не должен иметь лишних прав
RUN addgroup -S app && adduser -S app -G app
COPY --from=builder --chown=app:app /app/node_modules ./node_modules
COPY --chown=app:app . .
USER app
EXPOSE 3000
ENV NODE_ENV=production
HEALTHCHECK --interval=30s --timeout=3s \
CMD wget -qO- http://127.0.0.1:3000/ || exit 1
CMD ["node", "src/server.js"]
Тома и данные
# Записываемый слой контейнера исчезает вместе с ним
docker run -d --name db postgres:16
docker rm -f db # данные потеряны
# Именованный том — данные живут отдельно от контейнера
docker volume create pgdata
docker run -d --name db \
-v pgdata:/var/lib/postgresql/data \
-e POSTGRES_PASSWORD=secret \
postgres:16
# Привязка каталога хоста — удобно при разработке
docker run -d -v $(pwd)/src:/app/src -p 3000:3000 myapp:1.0
docker volume ls
docker volume inspect pgdata
Сети
- bridge — сеть по умолчанию, контейнеры видят друг друга по IP.
- Пользовательская сеть даёт разрешение имён: контейнер обращается к базе по имени db, а не по адресу.
- host — контейнер использует сетевой стек хоста напрямую, изоляции сети нет.
- none — сети нет вовсе, для изолированных вычислений.
- Публикация порта -p 8080:3000 связывает порт хоста с портом контейнера; без неё сервис недоступен снаружи.
Docker Compose
services:
app:
build: .
ports:
- "3000:3000"
environment:
DATABASE_URL: postgres://app:secret@db:5432/appdb
depends_on:
db:
condition: service_healthy
restart: unless-stopped
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: secret
POSTGRES_DB: appdb
volumes:
- pgdata:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app"]
interval: 10s
retries: 5
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
- app
volumes:
pgdata:
docker compose up -d # поднять всё
docker compose logs -f app # логи одного сервиса
docker compose ps # состояние
docker compose exec db psql -U app appdb
docker compose down # остановить, тома сохранятся
docker compose down -v # снести вместе с данными
Частые ошибки
- Секреты записаны прямо в Dockerfile — они остаются в слоях образа навсегда, даже если позже удалены. Передавайте через переменные окружения или файлы секретов.
- Данные базы не вынесены в том — исчезают при пересоздании контейнера.
- Процесс работает от root: получив доступ в контейнер, атакующий получает лишние возможности.
- Использован тег latest — сборка перестаёт быть воспроизводимой, указывайте конкретную версию.
- Отсутствует .dockerignore, и в образ попадают node_modules, .git и файлы окружения.
Что показать в курсовой
- Обоснование выбора контейнеризации: воспроизводимость среды, изоляция, простота развёртывания.
- Схему архитектуры: какие сервисы, как связаны, где хранятся данные.
- Dockerfile с комментариями и объяснением порядка слоёв.
- docker-compose.yml для всего приложения целиком.
- Инструкцию по запуску в две-три команды.
- Сравнение размера образа до и после многоступенчатой сборки — обычно разница кратная.
- Замер времени развёртывания с нуля.
Частые вопросы
Можно ли запустить Windows-контейнер на Linux?
Нет: ядро у контейнера общее с хостом. Windows-контейнеры работают только на Windows, Linux-контейнеры — на Linux. На Windows и macOS Docker поднимает скрытую Linux-виртуальную машину.
Контейнер безопаснее виртуальной машины?
Нет, изоляция слабее — ядро общее, и уязвимость в нём затрагивает все контейнеры. Для ненадёжного кода надёжнее виртуальная машина. Контейнеры выбирают за скорость и удобство, а не за изоляцию.
Зачем нужен образ на alpine?
Он весит около 5 МБ против 100 МБ у обычного дистрибутива, поэтому итоговый образ в разы меньше и содержит меньше потенциально уязвимых пакетов. Минус — musl вместо glibc, из-за чего отдельные библиотеки требуют пересборки.