Библиотека — FreeBSD — Jails: контейнеризация по-BSD [X]
Шифр
freebsd-jails
Разделы
freebsd tooling
Сверено
2 сентября — в этот день утверждения статьи сверялись с первоисточниками, а не правился текст
Объём
19 934 знака · 28 разделов · чтения около 20 минут

FreeBSD — Jails: контейнеризация по-BSD


Обзор

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

Источники


Связанные заметки

Из библиотеки института уносят только знания. Читателей здесь не считают: посещение не регистрируется, отметок в издании не остаётся, счётчиков нет ни у одного издания фонда.

Издание пополняется и пересверяется. Текст доступен на условиях CC BY-SA 4.0 — унесённое можно передавать дальше на тех же условиях.