FreeBSD 15 Jails + ZFS + Bastille: полное руководство по контейнеризации сервисов

Автор: Raven2000 , 14 апреля 2026
ZFS

FreeBSD 15 Jails + ZFS + Bastille: полное руководство по контейнеризации сервисов

Как изолировать веб-сервер, базу данных и реверс-прокси в отдельных jail-контейнерах на FreeBSD 15 с помощью Bastille и ZFS — с реальными примерами конфигурации.

Введение

Контейнеризация — один из ключевых подходов к построению надёжной серверной инфраструктуры. В мире Linux для этого чаще всего используют Docker, но во FreeBSD есть собственная технология, которая появилась задолго до Docker — Jails.

FreeBSD Jails — это механизм изоляции на уровне ОС, впервые представленный ещё в FreeBSD 4.0 (2000 год). В сочетании с файловой системой ZFS и менеджером контейнеров Bastille мы получаем мощную, безопасную и удобную среду для развёртывания сервисов. В этой статье я покажу, как на практике настроить три jail-контейнера для типичного веб-проекта:

FreeBSD Jails
JailIP-адресНазначение
db10.0.0.12MySQL 8 — база данных
web10.0.0.11Nginx + PHP-FPM + Drupal
proxy10.0.0.10Caddy — 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-адреса jail
  • bastille_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 start

Caddy автоматически получит и обновит 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_update

Jail возвращается к точному состоянию на момент снапшота. Все изменения после снапшота отменяются.

Удаление снапшота

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

Поток запроса:

  1. Пользователь обращается к example.com (порт 443)
  2. PF перенаправляет трафик в jail proxy (10.0.0.12)
  3. Caddy терминирует TLS и проксирует запрос в jail web (10.0.0.11:80)
  4. Nginx + PHP-FPM обрабатывает Drupal, обращается к db (10.0.0.10:5432)
  5. Ответ возвращается по цепочке

Полезные команды на каждый день

Управление 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

Рекомендации по безопасности

  1. Минимизируйте сервисы в каждом jail. Один jail — одна задача. База данных отдельно, веб-сервер отдельно, прокси отдельно.
  2. Не открывайте SSH внутри jail без необходимости. Используйте bastille console для администрирования. Если SSH нужен для отладки — отключайте после.
  3. Делайте снапшоты перед каждым изменением. Это бесплатно по ресурсам и спасает при ошибках.
  4. Ограничивайте сетевой доступ через PF. PostgreSQL должен принимать подключения только с IP веб-сервера (10.0.0.11), а не со всей сети.
  5. Регулярно обновляйте пакеты внутри jail:

    bastille pkg ALL upgrade
  6. Настройте репликацию 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 в набор простых и понятных команд.

Такая архитектура отлично подходит для:

  • Веб-проектов с разделением компонентов (БД, приложение, прокси)
  • Хостинга нескольких сайтов с изоляцией
  • Серверов, где важна безопасность и предсказуемость
  • Инфраструктуры малого и среднего бизнеса

Автор: IgNix.ru — консалтинг и инфраструктура на базе BSD-систем

Комментарии