FreeBSD 15 Jails + ZFS + Bastille: полное руководство по контейнеризации сервисов
Введение
Контейнеризация — один из ключевых подходов к построению надёжной серверной инфраструктуры. В мире Linux для этого чаще всего используют Docker, но во FreeBSD есть собственная технология, которая появилась задолго до Docker — Jails.
FreeBSD Jails — это механизм изоляции на уровне ОС, впервые представленный ещё в FreeBSD 4.0 (2000 год). В сочетании с файловой системой ZFS и менеджером контейнеров Bastille мы получаем мощную, безопасную и удобную среду для развёртывания сервисов. В этой статье я покажу, как на практике настроить три jail-контейнера для типичного веб-проекта:
| Jail | IP-адрес | Назначение |
|---|---|---|
| db | 10.0.0.12 | MySQL 8 — база данных |
| web | 10.0.0.11 | Nginx + PHP-FPM + Drupal |
| proxy | 10.0.0.10 | Caddy — HTTPS-прокси с Let's Encrypt |
Почему Jails, а не Docker?
Прежде чем перейти к настройке, разберёмся, почему FreeBSD Jails — это отличный выбор для серверной инфраструктуры.
Преимущества Jails
- Нативная изоляция на уровне ядра. Jails — часть ядра FreeBSD, а не надстройка. Каждый jail работает в собственном пространстве процессов, файловой системы и сети. Никаких дополнительных слоёв абстракции.
- Минимальный оверхед. В отличие от виртуальных машин, jail разделяет ядро с хост-системой. Это означает практически нулевые потери производительности.
- Безопасность. Процессы внутри jail не видят процессы хоста и других jail. Компрометация одного контейнера не даёт доступ к остальным.
- Простота управления. С помощью Bastille создание, запуск, остановка, клонирование и удаление jail выполняется одной командой.
- Интеграция с ZFS. Каждый jail получает собственный ZFS-датасет. Это даёт мгновенные снапшоты, откат, клонирование и эффективное резервное копирование.
Jails + ZFS: синергия
ZFS привносит в работу с jail возможности, недоступные при использовании обычных файловых систем:
| Возможность | Что это даёт |
|---|---|
| Снапшоты | Мгновенный снимок состояния jail перед обновлением или изменением конфигурации |
| Откат (rollback) | Возврат jail к предыдущему состоянию за секунды |
| Клонирование | Создание копии jail из снапшота для тестирования |
| Компрессия | Экономия дискового пространства (lz4, zstd) |
| ZFS send/receive | Репликация jail на удалённый сервер для бэкапов |
| Квоты | Ограничение дискового пространства для каждого jail |
Подготовка хост-системы
Требования
- FreeBSD 15.0-RELEASE
- ZFS-пул (рекомендуется создать при установке)
- Установленный Bastille
Установка Bastille
pkg install bastilleНастройка /etc/rc.conf
Добавляем необходимые параметры для работы PF (пакетный фильтр), сетевого интерфейса для jail и самого Bastille:
# vi /etc/rc.conf
# PF + Bastille
pf_enable="YES"
pflog_enable="YES"
if_bridge_load="YES"
bastille_enable="YES"
cloned_interfaces="lo1"
ifconfig_lo1_name="bastille0"Что здесь происходит:
pf_enable— включаем пакетный фильтр PF для управления трафикомcloned_interfaces="lo1"— создаём дополнительный loopback-интерфейсifconfig_lo1_name="bastille0"— переименовываем его вbastille0— именно к этому интерфейсу будут привязаны IP-адреса jailbastille_enable— автозапуск всех jail при загрузке системы
Настройка Bastille для работы с ZFS
Редактируем /usr/local/etc/bastille/bastille.conf:
bastille_zfs_enable="YES"
bastille_zfs_zpool="zroot"Важно: укажите имя вашего ZFS-пула. Обычно это
zroot, если пул был создан при установке системы.
При включённом ZFS Bastille автоматически создаёт отдельный датасет для каждого jail. Это означает, что каждый контейнер живёт в собственной изолированной файловой системе.
Загрузка базовой системы
Перед созданием jail нужно скачать базовый образ FreeBSD:
bastille bootstrap 15.0-RELEASEПроверяем:
bastille list releasesНастройка PF (Packet Filter)
PF — это файрвол FreeBSD, который будет управлять сетевым трафиком между jail и внешним миром.
Конфигурация /etc/pf.conf
ext_if="vtnet0"
set block-policy return
scrub in on $ext_if all fragment reassemble
set skip on lo
table <jails> persist
nat on $ext_if from <jails> to any -> ($ext_if:0)
rdr-anchor "rdr/*"
block in all
pass out quick keep state
antispoof for $ext_if inet
pass in inet proto tcp from any to any port ssh flags S/SA keep stateРазбор правил
| Правило | Назначение |
|---|---|
ext_if="vtnet0" | Определяем внешний сетевой интерфейс |
set block-policy return | При блокировке — отправляем RST, а не молча дропаем |
scrub in on $ext_if | Нормализация входящих пакетов (защита от фрагментационных атак) |
set skip on lo | Не фильтруем трафик на loopback (иначе jail не смогут общаться) |
table <jails> persist | Таблица IP-адресов jail (Bastille заполняет автоматически) |
nat on $ext_if from <jails> | NAT для jail — они получают доступ в интернет через внешний IP хоста |
rdr-anchor "rdr/*" | Якорь для проброса портов (Bastille добавляет правила через bastille rdr) |
block in all | По умолчанию блокируем весь входящий трафик |
pass out quick keep state | Разрешаем весь исходящий трафик |
antispoof for $ext_if | Защита от подмены IP-адреса источника |
pass in ... port ssh | Разрешаем входящие SSH-подключения |
Применение правил
# Проверяем синтаксис (без применения)
pfctl -nf /etc/pf.conf
# Загружаем правила
pfctl -f /etc/pf.conf
# Проверяем загруженные правила
pfctl -s rules | head -20
# Проверяем NAT
pfctl -s natСоздание Jail-контейнеров
Jail db — MySQL 8
# bastille create db 15.0-RELEASE 10.0.0.12Устанавливаем PostgreSQL:
# bastille console db
# pkg update
# pkg install mysql80-server mysql80-clientВходим в jail и инициализируем БД:
# Внутри jail:
sysrc postgresql_enable=YES
service mysql-server start
# Запускаем безопасную установку
mysql_secure_installation
Enter current password for root: — нажмите Enter (пароля пока нет)
Change the root password? — y и задайте пароль
Remove anonymous users? — y
Disallow root login remotely? — n (если нужен внешний доступ для root)
Remove test database and access to it? — y
Reload privilege tables now? — y
# vi /usr/local/etc/mysql/my.cnf
[client]
port = 3306
socket = /tmp/mysql.sock
default-character-set = utf8mb4
[mysqld]
# Базовые настройки
bind-address = 0.0.0.0
port = 3306
socket = /tmp/mysql.sock
# Кодировка
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
# Логи
log-error = /var/log/mysql/error.log
pid-file = /var/run/mysql/mysql.pid
# Размеры и лимиты
max_allowed_packet = 256M
max_connections = 200
innodb_buffer_pool_size = 256M
# Таймауты
wait_timeout = 600
interactive_timeout = 600
connect_timeout = 60
# mkdir -p /var/log/mysql
# chown mysql:mysql /var/log/mysql
# service mysql-server restartДля информации
# Для Информации -- Создаем пользователя, который может подключаться с любого IP
# CREATE USER 'username'@'%' IDENTIFIED BY 'your_strong_password';
# -- Или с конкретного IP (безопаснее)
CREATE USER 'username'@'192.168.1.100' IDENTIFIED BY 'your_strong_password';
# -- Создаем пользователя для доступа из локальной сети
CREATE USER 'username'@'10.0.0.%' IDENTIFIED BY 'your_strong_password';
# -- Создаем пользователя для доступа с вашего внешнего IP
# CREATE USER 'username'@'31.132.209.20' IDENTIFIED BY 'your_strong_password';
# -- Даем полный доступ ко всем базам
# GRANT ALL PRIVILEGES ON *.* TO 'username'@'%' WITH GRANT OPTION;
# -- Или только к конкретной базе
GRANT ALL PRIVILEGES ON mydatabase.* TO 'username'@'%';Создаём базу данных и пользователя для Drupal:
## Создадим DB и пользователя
# mysql -u root -p XXX
CREATE DATABASE drupal_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'drupal_user'@'10.0.0.11' IDENTIFIED BY 'PASS';
GRANT ALL PRIVILEGES ON drupal_db.* TO 'drupal_user'@'10.0.0.11';
FLUSH PRIVILEGES;
SELECT user, host FROM mysql.user; #Проверить Пользователей
EXIT;Jail web — Nginx + PHP-FPM + Drupal
bastille create web 15.0-RELEASE 10.0.0.11Устанавливаем необходимые пакеты:
# bastille pkg web install nginx php84 php84-fpm php84-pdo_pgsql \
php84-gd php84-mbstring php84-xml php84-curl php84-zip php84-opcacheВходим и настраиваем:
## bastille console web
# pkg install nginx
# sysrc nginx_enable=YES
# sysrc php_fpm_enable=YES
# service nginx start
# service php-fpm startНастраиваем Ngnix. Drupal размещается в /www/drupal внутри jail.
# vi /usr/local/etc/nginx/nginx.conf
error_log /var/log/nginx/error.log;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
server {
listen 80;
server_name localhost;
access_log /var/log/nginx/ignix.access.log main;
location / {
root /usr/local/www/drupal;
index index.php index.html;
try_files $uri $uri/ /index.php?$query_string; # Важно для чистых URL Drupal
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/local/www/nginx-dist;
}
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /usr/local/www/drupal11$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\.ht {
deny all;
access_log off;
log_not_found off;
}
}
PHP-FM
Устанавливаем PHP 8.4 и расширения
pkg install php84 php84-dom php84-filter php84-gd php84-mbstring php84-opcache php84-pdo php84-pdo_mysql php84-session php84-simplexml php84-tokenizer php84-xml php84-zlib# Для кеширования
pkg install php84-pecl-APCu# Настройка PHP
vi /usr/local/etc/php.ini-production
memory_limit = 256M
upload_max_filesize = 64M
post_max_size = 64M
realpath_cache_size = 256K
realpath_cache_ttl = 300 # или выше, до 3600 секунд для редко меняющихся файлов
opcache.save_comments = 1## Тестирование и перезапуск сервисов
# php-fpm -t
[31-Mar-2026 13:10:33] NOTICE: configuration file /usr/local/etc/php-fpm.conf test is successful
# service nginx start
# service php_fpm start
## Вне клетки привязываем внешний порт на внутренний и можно тестировать
# bastille rdr web tcp 80 80
Jail proxy — Caddy (HTTPS)
# bastille create proxy 15.0-RELEASE 10.0.0.10Устанавливаем Caddy:
# bastille pkg proxy install caddyНастраиваем как реверс-прокси к web-jail:
# bastille console proxy
sysrc caddy_enable=YESПример Caddyfile:
vi /usr/local/etc/caddy/Caddyfile
example.com {
reverse_proxy 10.0.0.11:80
}
service caddy enable
service caddy startCaddy автоматически получит и обновит TLS-сертификат через Let's Encrypt.
Для работы Caddy с внешним трафиком пробрасываем порты (вне клетки):
# bastille rdr web reset ИЛИ bastille rdr web clear
bastille rdr proxy tcp 80 80
bastille rdr proxy tcp 443 443Проброс портов и тестирование
Bastille позволяет легко пробрасывать порты с хоста в jail через PF.
Синтаксис
bastille rdr <jail> <proto> <host_port> <jail_port>Пример: проброс SSH для тестирования
# Пробрасываем порт 222 хоста на порт 22 jail web (или 80 на web / 443 на proxy)
bastille rdr web tcp 222 22
# Включаем SSH внутри jail
bastille sysrc web sshd_enable=YES
bastille service web sshd startТеперь можно подключиться:
ssh -p 222 user@91.227.18.ХХПроверяем список jail и проброшенные порты:
bastille listВывод:
JID Name Boot Prio State Type IP Address Published Ports Release Tags
1 web on 99 Up thin 10.0.0.11 tcp/222:22 15.0-RELEASE -Не забудьте убрать тестовый проброс после проверки:
bastille rdr web resetСовет по безопасности: мониторьте логи на попытки подбора паролей. Пример из /usr/local/bastille/jails/web/root/var/log/messages:
sshd-session: error: PAM: Authentication error for illegal user from 31.132.209.ХХРекомендуется отключать SSH внутри jail после тестирования и использовать bastille console для доступа.
Работа со снапшотами ZFS через Bastille
Одно из главных преимуществ связки Bastille + ZFS — управление снапшотами прямо из командной строки.
Создание снапшота
Перед любым изменением (установка модуля, обновление пакетов, правка конфигурации) создавайте снапшот:
bastille zfs web snapshot before_updateЭто мгновенная операция — ZFS фиксирует текущее состояние файловой системы jail без копирования данных.
Просмотр снапшотов
bastille list snapshotОткат к снапшоту
Если что-то пошло не так:
bastille zfs web rollback before_updateJail возвращается к точному состоянию на момент снапшота. Все изменения после снапшота отменяются.
Удаление снапшота
bastille zfs web destroy before_updateКлонирование jail из снапшота
Нужна тестовая копия? Создаём клон:
bastille zfs web snapshot for_clone
bastille clone web_for_clone web_testЭто создаст новый jail web_test с точной копией данных — идеально для тестирования обновлений.
Практический пример: безопасная установка модуля Drupal
# 1. Создаём снапшот
bastille zfs web snapshot add_drupal
# 2. Входим в jail
bastille console web
# 3. Устанавливаем модуль автоматической установки Drupal и зависимостей
pkg install php84-composer
# composer --version
Composer version 2.9.3 2025-12-30 13:40:17
PHP version 8.4.16 (/usr/local/bin/php)
# Установим Drupal
cd /usr/local/www/
composer create-project drupal/recommended-project drupal
mkdir chmod web/sites/default/files
chmod 775 web/sites/default/files
cp web/sites/default/default.settings.php web/sites/default/settings.php
chmod 644 web/sites/default/settings.php
chown -R www:www drupal/
# service nginx restart
# service php_fpm restart
## Установим модуль для Drupal
https://www.drupal.org/project/pathauto
cd /usr/local/www/drupal
composer require 'drupal/ebt_timeline:^1.4'
## Обновим CMS и модули
# cd /usr/local/www/drupal/
composer update "drupal/core-*" --with-all-dependencies
# 4. Тестируем. Если модуль не зашел то:
exit
bastille zfs web rollback add_mod
# Клетка снова работает как до установки модуляАрхитектура сети
Схема взаимодействия компонентов:
Интернет
│
▼
┌─────────────────┐
│ PF Firewall │
│ (vtnet0) │
│ 91.227.18.ХХ │
└───────┬─────────┘
│ NAT + rdr
▼
┌─────────────────┐
│ bastille0 │
│ (loopback) │
└───┬───┬───┬─────┘
│ │ │
┌────────┘ │ └────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ proxy │ │ web │ │ db │
│10.0.0.12 │ │10.0.0.11 │ │10.0.0.10 │
│ Caddy │ │Nginx+PHP │ │ MySQL 8 │
│ HTTPS │─│ Drupal │─│ drupal_db│
└──────────┘ └──────────┘ └──────────┘
:443 :80 :5432Поток запроса:
- Пользователь обращается к
example.com(порт 443) - PF перенаправляет трафик в jail
proxy(10.0.0.12) - Caddy терминирует TLS и проксирует запрос в jail
web(10.0.0.11:80) - Nginx + PHP-FPM обрабатывает Drupal, обращается к
db(10.0.0.10:5432) - Ответ возвращается по цепочке
Полезные команды на каждый день
Управление jail
# Список всех jail
bastille list
# Запуск / остановка / перезапуск
bastille start web
bastille stop web
bastille restart web
# Вход в консоль jail
bastille console web
# Удаление jail (осторожно!)
bastille destroy webУправление пакетами
# Установка пакета в jail
bastille pkg web install nginx
# Обновление списка пакетов
bastille pkg web update
# Обновление всех пакетов
bastille pkg web upgradeСнапшоты и бэкапы
# Создать снапшот
bastille zfs web snapshot mytag
# Список снапшотов
bastille list snapshot
# Откат
bastille zfs web rollback mytag
# Удалить снапшот
bastille zfs web destroy mytagМониторинг
# Логи jail с хоста
tail -f /usr/local/bastille/jails/web/root/var/log/messages
# Проверка правил PF
pfctl -s rules | head -20
pfctl -s nat
# Проверка ZFS
zfs list | grep bastilleРекомендации по безопасности
- Минимизируйте сервисы в каждом jail. Один jail — одна задача. База данных отдельно, веб-сервер отдельно, прокси отдельно.
- Не открывайте SSH внутри jail без необходимости. Используйте
bastille consoleдля администрирования. Если SSH нужен для отладки — отключайте после. - Делайте снапшоты перед каждым изменением. Это бесплатно по ресурсам и спасает при ошибках.
- Ограничивайте сетевой доступ через PF. PostgreSQL должен принимать подключения только с IP веб-сервера (10.0.0.11), а не со всей сети.
Регулярно обновляйте пакеты внутри jail:
bastille pkg ALL upgradeНастройте репликацию ZFS на удалённый сервер для резервного копирования:
zfs send zroot/bastille/jails/web@backup | ssh backup-server zfs receive tank/backup/web
Заключение
Связка FreeBSD Jails + ZFS + Bastille — это промышленный подход к контейнеризации, который сочетает безопасность, производительность и удобство управления. В отличие от Docker, здесь нет дополнительных слоёв абстракции — jail работают на уровне ядра ОС.
ZFS добавляет мгновенные снапшоты, откат и клонирование, а Bastille превращает управление jail в набор простых и понятных команд.
Такая архитектура отлично подходит для:
- Веб-проектов с разделением компонентов (БД, приложение, прокси)
- Хостинга нескольких сайтов с изоляцией
- Серверов, где важна безопасность и предсказуемость
- Инфраструктуры малого и среднего бизнеса
Комментарии