gVisor и Kata: контейнеры с изоляцией уровня ВМ
Если вы запускаете код от чужих пользователей — сборки в CI/CD, ноутбуки в мультитенантной ML-платформе, плагины в SaaS-конструкторе — обычный docker run рано или поздно вызывает вопрос: а что если этот код найдёт уязвимость в ядре хоста? Контейнеры делят одно ядро с хостом и всеми соседями, и это не баг, а архитектура. Два разных проекта — gVisor от Google и Kata Containers — решают эту проблему принципиально разными способами, но оба сохраняют привычный интерфейс: снаружи это всё ещё docker run или под в Kubernetes.
Содержание
Почему обычного контейнера здесь недостаточно
Контейнер — это не виртуальная машина с собственным ядром, а изолированный через namespaces и cgroups процесс на общем ядре хоста. Это лёгкая и быстрая изоляция, но её граница — сам код ядра Linux, а не аппаратный барьер. Если в обработчике системного вызова, файловой системе или сетевом стеке ядра есть уязвимость, процесс внутри контейнера при определённых условиях может её эксплуатировать и выбраться за пределы своего namespace — это называется container breakout. Мы разбирали механику этого заблуждения подробнее в статье про миф о том, что контейнер — это песочница: контейнер прекрасно решает задачи упаковки зависимостей и повторяемости окружения, но как границу безопасности для по-настоящему недоверенного кода использовать его напрямую рискованно.
Отсюда два практических пути. Можно давать каждому недоверенному воркеру полноценную виртуальную машину — так исторически поступали облачные провайдеры и CI-системы, но это дорого по ресурсам, медленно по времени старта (секунды против миллисекунд у контейнера) и плохо ложится на привычный workflow с Docker-образами и Kubernetes-манифестами. А можно взять сам контейнер и добавить ему дополнительный барьер изоляции, не меняя интерфейс запуска. Именно так поступают gVisor и Kata — но добавляют барьер в разных местах стека.
gVisor: userspace-ядро между контейнером и хостом
gVisor — проект Google, изначально созданный для изоляции нагрузок в Google Cloud (в частности, App Engine и Cloud Run). Идея: вместо того чтобы давать процессу контейнера напрямую дёргать системные вызовы настоящего ядра Linux, между процессом и хостом ставится прослойка — компонент под названием Sentry, реализованный полностью в userspace на языке Go. Sentry перехватывает системные вызовы контейнеризованного процесса через ptrace или через более быстрый механизм KVM-перехвата (platform kvm) и обрабатывает их сам: реализует собственную упрощённую реализацию файловой системы, сети, управления памятью и процессами — по сути, urezannoe ядро, написанное не на C и не выполняющееся в кольце 0.
Ключевой эффект: контейнеризованный процесс физически не имеет прямого канала к реальному ядру хоста. Даже если в коде процесса есть эксплойт под конкретную уязвимость ядра Linux, он просто не долетает до настоящего ядра — его перехватывает и переинтерпретирует Sentry. Это резко сокращает поверхность атаки: вместо тысяч системных вызовов ядра Linux, каждый со своей историей CVE, атакующий видит куда более узкий и написанный с нуля с оглядкой на безопасность интерфейс.
Практически gVisor подключается как runtime для containerd/Docker — runsc (run sandboxed container):
# Установка runsc (пример для Debian/Ubuntu)
curl -fsSL https://gvisor.dev/archive.key | sudo gpg --dearmor -o /usr/share/keyrings/gvisor-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/gvisor-archive-keyring.gpg] https://storage.googleapis.com/gvisor/releases release main" | sudo tee /etc/apt/sources.list.d/gvisor.list
sudo apt-get update && sudo apt-get install -y runsc
# Регистрация runtime в Docker
sudo runsc install
sudo systemctl restart docker
# Запуск контейнера в gVisor вместо runc
docker run --runtime=runsc -it ubuntu bash
Внутри такого контейнера uname -a покажет специфичную строку gVisor вместо реальной версии ядра хоста — это и есть видимый след того, что syscalls обслуживает не Linux, а Sentry.
Плата за это — накладные расходы на каждый перехваченный системный вызов: путь удлиняется, добавляется контекстное переключение и обработка в userspace-ядре. Для нагрузок с интенсивным I/O или частыми syscalls (например, база данных с активной записью на диск) это ощутимо; для типичного веб-сервиса, который делает не так много syscalls на запрос, разница часто малозаметна. Точный процент оверхеда сильно зависит от профиля нагрузки и версии gVisor — не берёмся называть конкретные цифры, это стоит измерять на своём коде.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверKata Containers: контейнер внутри лёгкой VM
Kata Containers — проект под эгидой OpenStack Foundation (вырос из слияния Intel Clear Containers и Hyper runV) — решает ту же задачу принципиально иначе. Вместо userspace-прослойки поверх ядра хоста Kata запускает каждый под или контейнер внутри отдельной лёгкой виртуальной машины со своим собственным настоящим ядром гостя. Для этого используется гипервизор — либо облегчённый Firecracker, либо QEMU с KVM, либо Cloud Hypervisor.
Если вы уже разбирались с Firecracker как основой лёгких microVM, то Kata здесь — это ровно тот сценарий, где эта технология применяется не напрямую (как в serverless-платформах), а как runtime внутри контейнерной экосистемы: Kata поднимает microVM, внутри неё — минималистичное гостевое ядро и агент (kata-agent), который принимает от containerd те же команды, что обычно уходят в runc, и выполняет их уже внутри VM.
Разница с gVisor принципиальная: у Kata-контейнера есть настоящее отдельное ядро гостевой VM. Уязвимость в ядре хоста не помогает атакующему напрямую — ему сначала нужно пробить границу гипервизора (аппаратную виртуализацию), а это исторически куда более узкая и реже эксплуатируемая поверхность, чем сам код ядра Linux. Это не userspace-эмуляция системных вызовов — это полноценная, пусть и урезанная, виртуализация с honest-то независимым ядром.
Подключение похоже на gVisor — через containerd/CRI как отдельный runtime:
# Установка Kata Containers (пример через скрипт-установщик проекта)
curl -fsSL https://raw.githubusercontent.com/kata-containers/kata-containers/main/utils/kata-manager.sh | bash -s -- install
# Проверка окружения (виртуализация должна быть доступна: /dev/kvm)
kata-runtime check
# Регистрация в containerd (пример фрагмента config.toml)
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.kata]
runtime_type = "io.containerd.kata.v2"
# Запуск через containerd/crictl с runtime-классом kata
# или напрямую через ctr:
sudo ctr run --runtime io.containerd.kata.v2 -t docker.io/library/ubuntu:22.04 test-kata bash
В Kubernetes это выглядит как отдельный RuntimeClass:
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: kata
handler: kata
---
apiVersion: v1
kind: Pod
metadata:
name: untrusted-workload
spec:
runtimeClassName: kata
containers:
- name: app
image: myregistry/untrusted-app:latest
Важное требование: узел должен поддерживать аппаратную виртуализацию (вложенная виртуализация, если вы уже внутри VM — например, на арендованном VPS — тоже возможна, но требует, чтобы провайдер явно включил nested virtualization и передал /dev/kvm внутрь гостя; на обычном VPS без этой опции Kata просто не запустится).
Оверхед и производительность: практическое сравнение
Общее правило (без точных цифр, которые нужно проверять на своей нагрузке): gVisor обычно легче по накладным расходам, потому что не поднимает полноценную VM — не тратится время и память на загрузку гостевого ядра, инициализацию виртуального устройства и виртуальной сети. Время старта контейнера в gVisor ближе к обычному Docker-контейнеру. Kata платит за более сильную изоляцию временем на загрузку microVM (обычно доли секунды у Firecracker-backend, заметно больше у QEMU) и дополнительным потреблением памяти на гостевое ядро и его сервисы — это фиксированный "налог" на каждый под, который нужно закладывать в capacity planning отдельно от самого приложения.
| Критерий | gVisor | Kata Containers |
|---|---|---|
| Механизм изоляции | Userspace-ядро (Sentry) перехватывает syscalls | Отдельная лёгкая VM (Firecracker/QEMU/KVM) с своим ядром |
| Реальное ядро хоста | Общее, но контейнер не имеет к нему прямого доступа | Изолировано границей гипервизора |
| Оверхед на syscall-интенсивных нагрузках | Заметный (каждый syscall перехватывается) | Минимальный внутри VM (обычные syscalls к своему ядру) |
| Оверхед на старте / памяти | Низкий, близко к обычному контейнеру | Выше: загрузка гостевого ядра и агента |
| Требования к хосту | Работает без аппаратной виртуализации (ptrace-режим) или с ней (kvm-режим) | Требует KVM / аппаратную виртуализацию (/dev/kvm) |
| Совместимость приложений | Ограничена набором поддерживаемых syscalls | Полная — внутри настоящее Linux-ядро |
| Типичный кейс | Serverless-платформы, sandboxing одиночных функций | CI-раннеры, мультитенантный Kubernetes, недоверенные поды |
Ни один из вариантов не бесплатен — оба меняют часть выигрыша контейнеров (лёгкость, скорость старта) на изоляцию. Вопрос всегда в том, какую часть вы готовы отдать под конкретную нагрузку.
Совместимость и грабли: где ломается
У gVisor основная грабля — не все системные вызовы Linux реализованы или реализованы не на 100% идентично настоящему ядру. Sentry поддерживает подавляющее большинство распространённых syscalls, но приложения, которые лезут в редкие углы API ядра — специфичные ioctl, некоторые режимы io_uring, отдельные сетевые опции, прямую работу с определёнными файловыми системами или модулями ядра — могут падать с ENOSYS или вести себя иначе, чем на обычном runc. Практический совет: прежде чем переводить прод-нагрузку на gVisor, прогоните её тестовый набор именно под --runtime=runsc — если приложение использует что-то экзотическое, это вскроется быстро.
У Kata грабля другого рода — не про совместимость системных вызовов (там настоящее ядро, всё работает как обычно), а про инфраструктурные требования. Нужна аппаратная виртуализация на хосте, что на некоторых бюджетных VPS без вложенной виртуализации просто недоступно — арендуя сервер под Kata, уточняйте у провайдера поддержку nested virtualization заранее. Вторая грабля — проброс GPU и других PCI-устройств внутрь Kata-контейнера сложнее, чем в обычный Docker: нужен VFIO-passthrough, который работает не со всяким железом. Третья — сетевая модель Kata сложнее (виртуальные NIC внутри microVM плюс фильтрация на уровне хоста), из-за чего некоторые CNI-плагины Kubernetes требуют отдельной поддержки Kata — проверяйте совместимость конкретного плагина перед миграцией прод-кластера.
Общая для обоих подходов грабля — мониторинг и отладка становятся не такими прозрачными: strace на хосте ведёт себя иначе против gVisor-контейнера (там уже есть своя прослойка перехвата), а внутрь Kata-контейнера классические host-side инструменты профилирования просто не видят, потому что процесс живёт в отдельной VM — нужны средства, которые понимают эту границу, либо диагностика изнутри самого контейнера.
Где это реально нужно на практике
Обе технологии решают одну и ту же бизнес-задачу: запуск кода, которому вы не доверяете полностью, но которому по-прежнему хочется дать интерфейс "обычный контейнер", а не "выделенная VM на каждый чих". Типичные сценарии:
- Мультитенантные SaaS-платформы, где клиенты загружают свой код (плагины, скрипты, кастомную бизнес-логику) и он выполняется на общей инфраструктуре рядом с кодом других клиентов.
- CI/CD-раннеры, выполняющие пул-реквесты от внешних контрибьюторов или сборки с произвольными скриптами — классический вектор атаки на GitHub Actions self-hosted раннеры и аналогичные системы. Если у вас уже есть VPS под разработку и CI/CD, усиление изоляции раннеров — логичный следующий шаг, когда среди задач появляются сборки от внешних участников.
- Serverless и FaaS-платформы, где одна физическая машина обслуживает функции множества разных клиентов, а холодный старт должен оставаться быстрым — именно этот кейс и был исходной мотивацией для gVisor в Google Cloud.
- Kubernetes-кластеры с недоверенными подами — например, внутренняя PaaS-платформа, где разработчики сами описывают манифесты, а платформа не может гарантировать, что там нет случайного или намеренного эксплойта.
Если вы уже задумывались про Docker или LXC для сервера или настраивали изоляцию сервисов через Docker для безопасности, gVisor и Kata — следующий уровень того же вопроса: они не заменяют базовую гигиену (непривилегированные пользователи в контейнере, seccomp-профили, минимальные образы), а добавляют границу поверх неё именно там, где угроза — недоверенный код, а не собственное неаккуратно написанное приложение.
Важно и обратное: если весь код в контейнерах — ваш собственный, проверенный, без внешних загрузок произвольных данных для исполнения, дополнительная изоляция часто избыточна — она добавляет операционную сложность (два runtime вместо одного, отдельный мониторинг, отдельное тестирование совместимости) без реальной пользы. gVisor и Kata — это инструмент под конкретную угрозу, а не универсальное "давайте на всякий случай".
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Можно ли использовать gVisor и Kata одновременно на одном кластере?
Да, Kubernetes поддерживает несколько RuntimeClass на узле — можно держать runc для доверенных подов и gVisor/Kata для недоверенных, выбирая нужный через runtimeClassName.
Что проще внедрить в существующую инфраструктуру на Docker?
gVisor обычно проще — не требует аппаратной виртуализации в базовом ptrace-режиме. Kata требует поддержки KVM и аккуратной проверки сетевого стека и CNI-плагинов перед миграцией.
Теряется ли изоляция полностью, если атакующий найдёт баг в самом Sentry или в гипервизоре Kata?
Полной гарантии не даёт ни один барьер — у обоих проектов были находки CVE в собственном коде. Смысл не в абсолютной неуязвимости, а в том, что поверхность атаки меньше и написана с прицелом на безопасность, а не унаследована от ядра общего назначения.
Подходит ли gVisor или Kata для баз данных и других I/O-интенсивных сервисов?
Технически да, но именно там оверхед обеих технологий ощущается сильнее всего — стоит тестировать конкретную нагрузку заранее.
Нужна ли вложенная виртуализация, если инфраструктура уже работает на VPS, а не на bare metal?
Для Kata — да, обязательно: без неё /dev/kvm внутри гостевой VPS недоступен. Для gVisor в ptrace-режиме не требуется, но опциональный kvm-режим gVisor тоже её просит.
Что выбрать для одиночного проекта без мультитенантности и чужого кода?
В большинстве таких случаев ни то ни другое не нужно — обычных практик изоляции контейнера достаточно, а gVisor/Kata стоит держать в уме на случай, если появится сценарий с недоверенным кодом.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →