Gymterview
senior

Как работает изоляция контейнеров на уровне ядра Linux?

Контейнеры — это не виртуальные машины. Они не содержат собственного ядра операционной системы. Все контейнеры на хосте разделяют одно ядро Linux, а изоляция обеспечивается тремя основными механизмами ядра: namespaces (изоляция видимости), cgroups (ограничение ресурсов) и union filesystems (послойная файловая система). Понимание этих механизмов необходимо для оценки реальной степени изоляции и осознанного выбора дополнительных мер защиты.

1. Linux Namespaces (изоляция видимости ресурсов):

Namespaces создают для процесса внутри контейнера иллюзию собственной изолированной операционной системы. Каждый namespace изолирует определённый тип системных ресурсов:

Namespace Что изолирует Флаг clone() Описание
PID Процессы CLONE_NEWPID Контейнер видит только свои процессы. Процесс entrypoint получает PID 1 внутри контейнера, хотя на хосте у него другой PID
NET Сеть CLONE_NEWNET Собственный сетевой стек: IP-адрес, интерфейсы, таблица маршрутизации, порты
MNT Файловая система CLONE_NEWNS Собственные точки монтирования, изолированные от хоста
UTS Hostname CLONE_NEWUTS Собственное имя хоста (hostname), независимое от реального хоста
IPC Межпроцессное взаимодействие CLONE_NEWIPC Собственные семафоры, очереди сообщений, разделяемая память
USER Пользователи CLONE_NEWUSER Собственная таблица UID/GID. Root (UID 0) внутри контейнера может маппиться на непривилегированного пользователя на хосте
Cgroup Cgroup-иерархия CLONE_NEWCGROUP Контейнер видит собственную иерархию cgroups, а не всю иерархию хоста
Пример
# Посмотреть namespaces контейнера по PID процесса
docker inspect --format '{{.State.Pid}}' my-container
# Например, PID = 12345

ls -la /proc/12345/ns/
# lrwxrwxrwx 1 root root 0 ... cgroup -> cgroup:[4026532234]
# lrwxrwxrwx 1 root root 0 ... ipc -> ipc:[4026532232]
# lrwxrwxrwx 1 root root 0 ... mnt -> mnt:[4026532230]
# lrwxrwxrwx 1 root root 0 ... net -> net:[4026532235]
# lrwxrwxrwx 1 root root 0 ... pid -> pid:[4026532233]
# lrwxrwxrwx 1 root root 0 ... user -> user:[4026531837]
# lrwxrwxrwx 1 root root 0 ... uts -> uts:[4026532231]

Каждый namespace идентифицируется числовым inode (числа в квадратных скобках). Контейнеры с одинаковыми inode делят один namespace. Например, если USER namespace совпадает с хостовым — значит, user namespace remapping не включён.

2. Cgroups (Control Groups — ограничение ресурсов):

Если namespaces отвечают за то, что контейнер видит, то cgroups отвечают за то, сколько ресурсов он может потребить:

Подсистема Что ограничивает
cpu Процессорное время (CPU shares, quotas)
memory Оперативная память (limits, swap)
blkio Disk I/O (пропускная способность, IOPS)
pids Количество процессов (защита от fork bomb)
cpuset Привязка к конкретным ядрам CPU (CPU pinning)
devices Доступ к устройствам хоста (/dev/*)
Пример
# Просмотр cgroup-лимитов контейнера
cat /sys/fs/cgroup/memory/docker/<container_id>/memory.limit_in_bytes
cat /sys/fs/cgroup/cpu/docker/<container_id>/cpu.cfs_quota_us
cat /sys/fs/cgroup/pids/docker/<container_id>/pids.max

Для Java особенно важно: начиная с Java 10, JVM по умолчанию включает -XX:+UseContainerSupport и корректно читает cgroup-лимиты для автоматической настройки размера heap и количества потоков GC. Без этой поддержки JVM может попытаться использовать всю RAM хоста, что приведёт к OOMKill.

3. Seccomp (Secure Computing — фильтрация системных вызовов):

Seccomp позволяет фильтровать системные вызовы (syscalls), доступные процессу. Docker по умолчанию применяет профиль, блокирующий примерно 44 системных вызова из более чем 300, включая: reboot (перезагрузка хоста), mount (монтирование файловых систем), swapon (управление swap), clock_settime (изменение системного времени), bpf (загрузка BPF-программ) и другие потенциально опасные вызовы.

4. Linux Capabilities:

Традиционная модель привилегий Linux бинарна: процесс либо root (всемогущий), либо обычный пользователь. Capabilities дробят привилегии root на примерно 40 отдельных единиц, позволяя дать процессу только необходимые:

Пример
# Capabilities контейнера по умолчанию
docker run --rm alpine cat /proc/1/status | grep Cap
# CapPrm: 00000000a80425fb
# Включает: CHOWN, DAC_OVERRIDE, FSETID, FOWNER, NET_RAW и др.

Docker по умолчанию оставляет минимальный набор capabilities, но для максимальной безопасности рекомендуется --cap-drop=ALL с добавлением только действительно нужных.

5. Ограничения изоляции контейнеров:

Поскольку контейнеры разделяют ядро Linux с хостом, существуют принципиальные ограничения изоляции:

  • Уязвимость ядра = потенциальный побег из контейнера (container escape). Эксплоит уязвимости ядра может дать злоумышленнику доступ к хостовой системе.
  • /proc и /sys частично доступны — некоторые файлы в этих виртуальных файловых системах содержат информацию о хосте.
  • Флаг --privileged полностью отключает изоляцию — контейнер получает доступ ко всем устройствам хоста и capabilities root.
  • Время (clock) является общим для всех контейнеров и хоста — нет способа изолировать системные часы.

6. Усиление изоляции (для высокочувствительных систем):

Когда стандартной контейнерной изоляции недостаточно, существуют технологии, добавляющие дополнительный уровень:

Технология Описание Накладные расходы
gVisor (Google) Виртуальное ядро в user-space, перехватывающее все syscalls контейнера Средние (замедление syscall-heavy нагрузок)
Kata Containers Лёгкие виртуальные машины с собственным ядром, OCI-совместимые Низкие (быстрый старт, но больше RAM)
Firecracker (AWS) microVM с минимальным VMM, используется в AWS Lambda и Fargate Минимальные (старт за ~125 мс)
Пример
# Использование gVisor как runtime class в Kubernetes
apiVersion: v1
kind: Pod
spec:
  runtimeClassName: gvisor  # Указать runtime class
  containers:
  - name: banking-app
    image: registry.bank.local/banking-service:1.0.0

gVisor перехватывает все системные вызовы контейнера и выполняет их в собственном ядре (Sentry), работающем в user-space. Даже при наличии уязвимости в «ядре» gVisor атакующий не получит доступ к реальному ядру хоста. Для банковских систем с обработкой платёжных данных стоит рассмотреть Kata Containers или gVisor как дополнительный уровень изоляции.