Обзор
Jails — это нативная технология изоляции FreeBSD, появившаяся в FreeBSD 4.0 (2000 год), задолго до Docker (2013) и даже Linux namespaces/cgroups. Автор — Poul-Henning Kamp, создавший jails для R&D Associates как механизм разделения сервисов в среде shared hosting.
Концептуально jail — это chroot на стероидах: полная изоляция файловой системы, процессов, сети и пользователей. Jailed-процессы не видят процессы за пределами jail, не могут модифицировать ядро, менять сетевую конфигурацию хоста или монтировать файловые системы. При этом все jail'ы разделяют одно ядро хоста — это не виртуализация, а OS-level контейнеризация.
Jails — одна из определяющих технологий FreeBSD. Они используются повсеместно: от изоляции сервисов на production-серверах до сборочных окружений poudriere, от запуска Linux-бинарников до тестирования сетевых конфигураций.
Архитектура и механизм изоляции
Что именно изолируется
Файловая система: jail видит только поддерево файловой системы от указанного корня (path). Процесс внутри jail не может обращаться к файлам выше этого корня — это наследие chroot, но с enforcement на уровне ядра, а не простой подменой корня.
Процессы: jail'ы изолированы друг от друга и от хоста. ps внутри jail показывает только процессы этого jail. PID-пространство общее (ядро одно), но видимость ограничена.
Сеть: в базовом режиме jail привязан к IP-адресу хоста. С VNET — получает полную копию сетевого стека с собственными интерфейсами, таблицей маршрутизации и firewall.
Пользователи: каждый jail имеет собственного root (UID 0), но этот root ограничен рамками jail — не может влиять на хост или другие jail'ы. Ядро внутренне помечает credentials процесса как jailed и проверяет это при каждом системном вызове.
Чего jail НЕ делает
Jail не изолирует ядро — все jail'ы разделяют одно ядро хоста. Jail не ограничивает ресурсы по умолчанию (CPU, RAM) — для этого нужен rctl(8). Jail не изолирует PID-пространство полностью — PID глобальны, но видимость ограничена. Jail не может загружать kernel modules — модули должны быть загружены на хосте.
jail.conf: конфигурация
Начиная с FreeBSD 9.1, конфигурация jail'ов описывается в /etc/jail.conf (или в файлах /etc/jail.conf.d/*.conf). Это декларативный формат с поддержкой переменных, наследования и шаблонов.
Минимальный пример
# Глобальные настройки (применяются ко всем jail'ам)
exec.start = "/bin/sh /etc/rc";
exec.stop = "/bin/sh /etc/rc.shutdown";
exec.clean;
mount.devfs;
# Конкретный jail
www {
host.hostname = "www.example.org";
path = "/usr/local/jails/www";
ip4.addr = "vtnet0|10.0.0.10/24";
}
exec.start и exec.stop — команды, выполняемые при старте и остановке jail. Для "complete" jail запускается /etc/rc, как при обычной загрузке FreeBSD. Для service jail — запуск конкретного демона.
exec.clean — запуск с чистым окружением (без переменных хоста).
mount.devfs — монтирование /dev внутри jail. Без этого большинство программ не работает. devfs_ruleset контролирует, какие устройства видны. Если параметр не задан, mount.devfs применяет набор 4 — devfsrules_jail из /etc/defaults/devfs.rules, минимальное безопасное подмножество. Набор 5 (devfsrules_jail_vnet) отличается тем, что дополнительно открывает /dev/pf, чтобы VNET-jail мог держать собственный firewall.
path — корень файловой системы jail.
Начиная с FreeBSD 15.0 у jail(8) есть параметр zfs.dataset — список ZFS-датасетов, которые ядро прикрепляет к jail само, при старте. Он требует allow.mount.zfs и избавляет от ручного zfs jail в хуке exec.created. В jail(8) из 14.x этого параметра нет.
Переменные и шаблоны
jail.conf поддерживает переменные ($name — имя jail, ${параметр} — подстановка), что позволяет создавать шаблоны:
# Шаблон для всех jail'ов
exec.start = "/bin/sh /etc/rc";
exec.stop = "/bin/sh /etc/rc.shutdown";
exec.consolelog = "/var/log/jail_${name}.log";
exec.clean;
mount.devfs;
devfs_ruleset = 4;
path = "/usr/local/jails/containers/${name}";
host.hostname = "${name}";
# Jail'ы наследуют всё из шаблона, добавляя специфичные настройки
web { ip4.addr = "vtnet0|10.0.0.10/24"; }
db { ip4.addr = "vtnet0|10.0.0.11/24"; }
mail { ip4.addr = "vtnet0|10.0.0.12/24"; }
Три строки — три jail'а. Всё общее вынесено в глобальную секцию.
Типы jail'ов
Thick Jails (полные)
Каждый jail содержит полную копию userland FreeBSD. Это самый простой и наиболее изолированный вариант.
Создание:
# Подготовить каталог
zfs create -o mountpoint=/usr/local/jails zroot/jails
zfs create zroot/jails/www
# Установить base system в jail
bsdinstall jail /usr/local/jails/www
# или вручную:
fetch https://download.freebsd.org/releases/amd64/amd64/15.1-RELEASE/base.txz
tar -xf base.txz -C /usr/local/jails/www
# Базовая настройка
cp /etc/resolv.conf /usr/local/jails/www/etc/
cp /etc/localtime /usr/local/jails/www/etc/
Trade-off: полная независимость и простота обновления каждого jail отдельно, но каждый thick jail занимает ~300-500MB на диске и обновляется индивидуально.
Thin Jails (ZFS clone)
Базируются на ZFS-снепшоте шаблона. Создаются мгновенно, занимают почти ноль места до первых изменений (copy-on-write).
# Создать шаблон
zfs create zroot/jails/templates/15.1-RELEASE
bsdinstall jail /usr/local/jails/templates/15.1-RELEASE
freebsd-update -b /usr/local/jails/templates/15.1-RELEASE fetch install
# Снепшот
zfs snapshot zroot/jails/templates/15.1-RELEASE@base
# Клонировать для каждого jail
zfs clone zroot/jails/templates/15.1-RELEASE@base zroot/jails/containers/web
zfs clone zroot/jails/templates/15.1-RELEASE@base zroot/jails/containers/db
Trade-off: мгновенное создание, минимальный расход диска. Но каждый clone независим — обновление шаблона не влияет на существующие клоны. Для обновления нужно обновлять каждый jail отдельно (или пересоздавать из нового снепшота).
Thin Jails (NullFS)
Base system монтируется read-only через nullfs из единого шаблона. Каждый jail имеет собственные writable-директории (/etc, /var, /usr/local, /tmp).
# Шаблон base (read-only)
zfs create zroot/jails/templates/15.1-RELEASE-base
# ... установить base system ...
# Skeleton — шаблон для writable-частей
zfs create zroot/jails/templates/15.1-RELEASE-skeleton
mkdir -p /usr/local/jails/templates/15.1-RELEASE-skeleton/{etc,var,tmp,usr/local,home,root}
# ... перенести etc, var, tmp и т.д. из base в skeleton ...
# Клонировать skeleton для каждого jail
zfs clone zroot/jails/templates/15.1-RELEASE-skeleton@base zroot/jails/containers/web
# fstab для jail (nullfs-монтирование)
# /usr/local/jails/web.fstab:
/usr/local/jails/templates/15.1-RELEASE-base /usr/local/jails/web/ nullfs ro 0 0
/usr/local/jails/containers/web /usr/local/jails/web/skeleton nullfs rw 0 0
Trade-off: обновление base system — одна операция для всех jail'ов (freebsd-update -b /path/to/base fetch install, затем service jail restart). Наиболее экономичный подход для множества однотипных jail'ов. Но структура сложнее, отладка менее тривиальна.
Какой тип выбрать
Для 2-5 jail'ов на VPS — thick jails на ZFS. Простота важнее экономии диска.
Для 5-20 однотипных jail'ов — thin jails (ZFS clone). Быстрое развёртывание, приемлемая сложность обновления.
Для десятков jail'ов на одном хосте — NullFS thin jails. Экономия диска и централизованное обновление перевешивают сложность настройки.
Сеть: три модели
IP aliasing (базовая)
Jail привязывается к IP-адресу хоста. Простейший вариант, не требующий дополнительной настройки.
www {
ip4.addr = "vtnet0|10.0.0.10/24";
# ...
}
Jail и хост разделяют сетевой стек. Jail может слушать только на своём IP. Ограничения: нет собственного localhost (127.0.0.1 принадлежит хосту), нет собственной таблицы маршрутизации, нет собственного firewall.
ip4 = inherit
Jail наследует все IP-адреса хоста. Самый простой вариант для single-service jail'ов:
www {
ip4 = inherit;
# ...
}
VNET (виртуальный сетевой стек)
Каждый jail получает полную копию сетевого стека: собственные интерфейсы, routing table, ARP/NDP, firewall. VNET включён в GENERIC-ядро начиная с FreeBSD 12.
Механизм: epair(4) — пара виртуальных Ethernet-интерфейсов, работающих как виртуальный патч-корд. Одна сторона (epairXa) остаётся на хосте и подключается к bridge(4), другая (epairXb) передаётся в jail.
# /etc/rc.conf хоста
cloned_interfaces="bridge0"
ifconfig_bridge0="addm vtnet0 up"
ifconfig_vtnet0="up"
# IP назначается bridge0, не vtnet0
ifconfig_bridge0_alias0="inet 10.0.0.1/24"
# /etc/jail.conf
vnet_web {
vnet;
vnet.interface = "${epair}b";
$id = "10";
$ip = "10.0.0.${id}/24";
$gateway = "10.0.0.1";
$bridge = "bridge0";
$epair = "epair${id}";
exec.prestart = "/sbin/ifconfig ${epair} create up";
exec.prestart += "/sbin/ifconfig ${epair}a up";
exec.prestart += "/sbin/ifconfig ${bridge} addm ${epair}a";
exec.start = "/sbin/ifconfig ${epair}b ${ip} up";
exec.start += "/sbin/route add default ${gateway}";
exec.start += "/bin/sh /etc/rc";
exec.stop = "/bin/sh /etc/rc.shutdown";
exec.poststop = "/sbin/ifconfig ${bridge} deletem ${epair}a";
exec.poststop += "/sbin/ifconfig ${epair}a destroy";
path = "/usr/local/jails/containers/${name}";
host.hostname = "${name}";
allow.raw_sockets;
exec.clean;
mount.devfs;
devfs_ruleset = 5;
}
Хуки exec.prestart/exec.poststop создают и уничтожают epair при старте/остановке jail, подключая его к bridge. Внутри jail epairXb выглядит как обычный Ethernet-интерфейс.
VNET jail может запускать собственный PF/IPFW — удобно для тестирования firewall-правил без риска заблокировать доступ к хосту. Если ошибся в правилах — просто jexec из хоста и исправляешь.
Для NAT из jail'ов наружу — правило на хосте:
# /etc/pf.conf хоста
nat on $ext_if from 10.0.0.0/24 to any -> ($ext_if)
Управление jail'ами
Базовые операции
# Включить jails при загрузке
sysrc jail_enable=YES
# Управление через service
service jail start www # запустить
service jail stop www # остановить
service jail restart www # перезапустить
service jail start # запустить все jail'ы
# Просмотр запущенных jail'ов
jls # JID, IP, hostname, path
jls -v # подробный вывод
jls -N # имя jail вместо числового JID
jls -n # параметры в формате name=value
# Выполнение команд внутри jail
jexec www /bin/sh # shell в jail
jexec www pkg update # обновить пакеты
jexec www service nginx restart # перезапустить сервис
# Вход в jail: jexec без команды запускает login-шелл указанного пользователя
jexec -u root www
Отдельной утилиты «войти в jail» в базе нет: jlogin — это подкоманда CBSD, а не команда FreeBSD.
Управление пакетами в jail'ах
Пакеты устанавливаются внутри jail независимо от хоста:
jexec www pkg install -y nginx
jexec www sysrc nginx_enable=YES
service jail restart www
Каждый jail имеет собственную базу pkg в /var/db/pkg/. Пакеты в jail и на хосте полностью независимы.
Обновление jail'ов
Thick jail — для запущенного jail'а удобнее флаг -j, он определяет версию userland самого jail'а; -b берёт версию из uname -r хоста и потому годится в основном для шаблонов, лежащих на диске:
freebsd-update -j www fetch install
service jail restart www
Thin jail (NullFS — обновить шаблон):
freebsd-update -b /usr/local/jails/templates/15.1-RELEASE-base fetch install
service jail restart # перезапустить все jail'ы
Всё это относится к jail'ам, развёрнутым из distribution set — из base.txz или через bsdinstall jail. С FreeBSD 15.0 userland jail'а можно поставить и пакетами базовой системы (bsdinstall умеет цель pkgbase, репозиторий FreeBSD-base в /etc/pkg/FreeBSD.conf по умолчанию выключен); такой jail обновляется через pkg(8), а freebsd-update к нему неприменим. Поддержка distribution sets и freebsd-update сохранится на всё время жизни ветки 15, а удалить её планируют в FreeBSD 16.
Ограничение ресурсов: rctl
По умолчанию jail не ограничен в ресурсах. rctl(8) (Resource Limits) позволяет задать лимиты:
# Требуется в /boot/loader.conf:
kern.racct.enable=1
# Ограничить RAM для jail
rctl -a jail:www:memoryuse:deny=1g
rctl -a jail:www:vmemoryuse:deny=2g
# Ограничить CPU
rctl -a jail:www:pcpu:deny=50 # не более 50% одного CPU
# Ограничить число процессов
rctl -a jail:www:maxproc:deny=100
# Посмотреть текущее потребление
rctl -u jail:www
# Персистентные правила — /etc/rctl.conf
Service Jails
Service jails (описаны подробнее в статье про rc-систему) — лёгкий способ изолировать отдельный rc-сервис в jail без создания отдельной файловой системы. Доступны начиная с FreeBSD 15.0; в ветку 14 их не переносили.
# /etc/rc.conf
nginx_svcj="YES"
nginx_svcj_options="net_basic"
Сервис автоматически запускается в jail, наследующем файловую систему хоста, но с jail-ограничениями на процессы и (опционально) сеть. Не требует создания jail rootfs, настройки jail.conf или установки пакетов в jail — используется установленный на хосте бинарник.
Это компромисс: меньше изоляции, чем у полноценного jail, но значительно проще в настройке.
Linux Jails
FreeBSD поддерживает запуск Linux-бинарников через уровень совместимости Linuxulator. В комбинации с jail'ами это позволяет создавать jail'ы с Linux userland:
# Включить Linux-совместимость
sysrc linux_enable=YES
service linux start
# Установить Linux userland в jail: debootstrap для Debian/Ubuntu,
# порт linux_base (по умолчанию linux_base-rl9, Rocky Linux 9) для RPM-мира
# Далее — конфигурация как обычный jail, но с linux-специфичными настройками
Это не полноценная виртуализация Linux — ядро остаётся FreeBSD, системные вызовы транслируются. Большинство Linux-софта работает, но не всё (Docker внутри FreeBSD jail не работает, ядро-специфичный софт может не работать).
Инструменты управления
Стандартные утилиты FreeBSD (jail, jls, jexec, jail.conf) достаточны для большинства задач. Но существуют обёртки:
bastille (sysutils/bastille) — наиболее активно развиваемый менеджер jail'ов. Автоматизирует создание, шаблоны, сеть, firewall. Хороший выбор для тех, кто хочет Docker-подобный workflow.
cbsd (sysutils/cbsd) — мощный фреймворк для управления jail'ами и bhyve VM. Более сложный, больше возможностей.
pot (sysutils/pot) — менеджер jail'ов с поддержкой nomad/consul.
iocage — исторически популярный, но мёртвый: последний релиз — 2019 год, а в апреле 2024 репозиторий переведён в архивный режим и доступен только на чтение. Для новых установок не вариант.
Рекомендация для изучения: начать со стандартного jail.conf, чтобы понять механику. Перейти на bastille, когда число jail'ов или рутинных операций станет обременительным.
Сравнение с Docker/LXC
Jails — OS-level контейнеризация, как и LXC. Docker добавляет поверх LXC (теперь containerd) image management, registry и оркестрацию.
Jails vs Docker: jails — зрелая, стабильная технология с 26-летней историей; нет встроенной оркестрации типа docker-compose (но pot + nomad); jail'ы "тяжелее" в ощущениях, но фундаментально та же модель — общее ядро, изолированный userland.
Утверждение «у FreeBSD нет OCI-образов и своего Docker Hub» устарело. Начиная с 14.3-RELEASE проект публикует официальные OCI-совместимые образы базовой системы (freebsd-static, freebsd-dynamic, freebsd-runtime, freebsd-notoolchain, freebsd-toolchain) для amd64 и aarch64, в 15.0 они вошли в состав релизных артефактов, а зеркала лежат на Docker Hub и в GitHub Container Registry. Штатный инструментарий — podman (демонless, docker-совместимый CLI), buildah и skopeo из портов; Handbook описывает их отдельной главой «OCI Containers». Под капотом это те же jail'ы — OCI меняет способ упаковки и доставки, а не механизм изоляции.
Jails vs LXC: ближайший аналог. Обе технологии — контейнеризация на уровне ядра. FreeBSD jail появился на 8 лет раньше. VNET jails дают полную изоляцию сетевого стека, аналогичную network namespaces в Linux.
Ключевое преимущество jail'ов: они глубоко интегрированы в систему. ZFS snapshots для мгновенного клонирования, rc-система для управления жизненным циклом, pf для сетевой фильтрации, rctl для ресурсных лимитов — всё это нативные компоненты FreeBSD, работающие вместе без дополнительных слоёв абстракции.
Практический рецепт: nginx в VNET jail на ZFS
# 1. Подготовка хоста
sysrc jail_enable=YES
sysrc cloned_interfaces+="bridge0"
sysrc ifconfig_bridge0="addm vtnet0 inet 10.0.0.1/24 up"
sysrc ifconfig_vtnet0="up"
sysrc gateway_enable=YES
# 2. Создание jail
zfs create -o mountpoint=/usr/local/jails zroot/jails
zfs create zroot/jails/web
bsdinstall jail /usr/local/jails/web
cp /etc/resolv.conf /usr/local/jails/web/etc/
cp /etc/localtime /usr/local/jails/web/etc/
# 3. Настройка rc.conf внутри jail
cat > /usr/local/jails/web/etc/rc.conf << 'EOF'
ifconfig_epair10b="inet 10.0.0.10/24"
defaultrouter="10.0.0.1"
nginx_enable="YES"
EOF
# 4. Установка nginx в jail
pkg -j web install -y nginx # или после первого запуска: jexec web pkg install nginx
# 5. Конфигурация jail.conf — использовать VNET-шаблон из секции выше
# 6. NAT на хосте
# В /etc/pf.conf:
# nat on vtnet0 from 10.0.0.0/24 to any -> (vtnet0)
# pass all
Источники
- FreeBSD Handbook, Chapter 17 "Jails and Containers": docs.freebsd.org/en/books/handbook/jails/
- FreeBSD Handbook, Chapter 18 "OCI Containers": docs.freebsd.org/en/books/handbook/containers/
- FreeBSD Architecture Handbook, "The Jail Subsystem": docs.freebsd.org/en/books/arch-handbook/jail/
- "Jail vnet by Examples", Olivier Cochard-Labbé (FreeBSD Foundation): freebsdfoundation.org/wp-content/uploads/2020/03/Jail-vnet-by-Examples.pdf
- "Virtualize Your Network on FreeBSD with VNET": forums.freebsd.org/threads/virtualize-your-network-on-freebsd-with-vnet.78155/
jail(8): man.freebsd.org/cgi/man.cgi?query=jail&sektion=8jail.conf(5): man.freebsd.org/cgi/man.cgi?query=jail.conf&sektion=5rctl(8): man.freebsd.org/cgi/man.cgi?query=rctl&sektion=8epair(4): man.freebsd.org/cgi/man.cgi?query=epair&sektion=4vnet(9): man.freebsd.org/cgi/man.cgi?query=vnet&sektion=9
Связанные заметки
- FreeBSD — сеть — VNET, bridge/epair и NAT для jail-сетей
- FreeBSD — ZFS — датасеты как основа для jail-хранилища и снепшотов
- FreeBSD VPS: Honeypot — практический сценарий изоляции сервиса в jail
- Linux — контейнеризация — namespaces и cgroups, для сравнения с jail
- FreeBSD — сводный cheatsheet — быстрая справка по jail-командам
- FreeBSD — bhyve: виртуализация после jails — следующий уровень изоляции: своё ядро вместо общего, когда jail принципиально не подходит
- Структура ядра: монолит, микроядро и что между — общее ядро как модель: что даёт вызов функции вместо сообщения и чем за это платят
Из библиотеки института уносят только знания. Читателей здесь не считают: посещение не регистрируется, отметок в издании не остаётся, счётчиков нет ни у одного издания фонда.
Издание пополняется и пересверяется. Текст доступен на условиях CC BY-SA 4.0 — унесённое можно передавать дальше на тех же условиях.