Здесь вам не Докер, а клетки (jails) - будем говорить, это контейнеры FreeBSD, когда это еще не было мейнстримом (на минуточку, они появились еще во FreeBSD 4.x - 2000 год). Практический смысл в моем случае - неким образом изолированно использовать разные версии PHP, ну и чуть ближе познакомиться с технологией, с которой я уже сталкивался при обзоре TrueNAS. Основано, как говорится, на реальных событиях - я переносил сайты на Drupal 7.x и Yii с сервера на Linux.

Предполагается, что вся работа ведется под root, если явно не указано иное.

Подготовка

Как обычно, на хостинге предустановлена очень древняя ОС (13.1 в моем случае), так что ее сначала придется долго и упорно обновлять. По большому счету, этот раздел повторяет предыдущую статью, разве что теперь конфигурация виртуального сервера включает целый гигабайт оперативки.

Для начала - "прокачаем" текущий релиз:

freebsd-update fetch
freebsd-update install

При работе первой команды выдаются различные предупреждения - о локально измененных файлах, а также удаляемых, добавляемых и обновляемых в рамках обновления. Нажимаем q.

После обновления reboot для пущей важности.

Вообще во FreeBSD редактор по умолчанию vi, причем еще и работает он как-то странно, что ли, даже для самого себя. Возможно, прежде чем выполнять обновление релиза, стоит сначала установить какой-нибудь nano и сделать его редактором по умолчанию - потребуется для разрешения конфликтов:

pkg install nano
nano /.cshrc

Изменить:

setenv  EDITOR  nano

Сохранить. Еще, наверное, оболочку стоит поменять, если вдруг у вас /bin/sh (у меня уже была установлена csh, но видимо это самодеятельность хостера):

chsh -s /bin/csh root
exit

Ча, ща пишем с буквой А!

В прошлый раз я сразу обновился до 14.1. На этот раз я пожалуй все же сначала сделаю промежуточное обновление до 13.4, и только потом уже до вышедшей недавно 14.2.

freebsd-update -r 13.4-RELEASE upgrade

С этой точки зрения do-release-upgrade в Debian/Ubuntu выглядит как-то проще, а RHEL'ы вообще долгожители (с другой стороны, Oracle Linux я в свое время побоялся с 8 до 9 обновить).

Как и в прошлый раз, приходится вручную разрешать конфликты - фактически оставить локальные версии:

  • /etc/hosts
  • /etc/master.passwd

В случае чего, если все-таки запускается vi, D - удалить всю строку (dN - сразу несколько от курсора).

И автоматически (y):

  • /etc/login.conf
  • /etc/ssh/sshd_config
  • /etc/sysctl.conf

Далее, как и при freebsd-update fetch, нажимаем q после просмотра сведений об удаляемых и т.д. файлах. Выполняем freebsd-update install первый раз (обновление ядра), перезагружаемся второй раз (для всего остального), и обновляем менеджер пакетов:

pkg-static upgrade -f

Я обычно устанавливаю Midnight Commander, хотя он не особо дружит с "альтернативными" оболочками.

pkg install mc
Message from mc-4.8.32:

--
Midnight Commander was built with subshell support, which works with most
popular shells, e.g. bash(1), dash(1), tcsh(1), zsh(1), but not sh(1) due
to its lack of "precmd" or equivalent hooks which mc(1) needs to read the
subshell's current directory.

  $ env SHELL=/bin/sh mc
  common.c: unimplemented subshell type 1
  read (subshell_pty...): No such file or directory (2)

Please either use more advanced interactive shell, or start mc(1) with -u
(--nosubshell) switch if you're confined to /bin/sh for some reason.

В принципе можно было бы формально поменять оболочку (csh в моем случае на tcsh), вот только это одинаковые файлы. Накатывать ли bash - большой вопрос, хотя в принципе он есть в пакетах. Мне кажется это не совсем в стиле FreeBSD (но другой хостер с 12-й таки предустановил bash).

Ну что, reboot для пущей важности и все по новой, только теперь до 14.2:

freebsd-update -r 14.2-RELEASE upgrade

На этот раз после второго вызова freebsd-update install явно была озвучена просьба переустановить дополнительное ПО (pkg-static upgrade -f) и, таким образом, после этого вызвать freebsd-update install в третий раз. Фух.

Перезагружаемся и на всякий пожарный добавляем файл подкачки (традиционно VDS/VPS инициализируют без оной). При 1 гигабайте ОЗУ установщик FreeBSD предлагает раздел подкачки на 819 мегабайт. Немного странная цифра, но возьмем ее за основу (только все же 820 метров):

dd if=/dev/zero of=/usr/swap0 bs=1m count=820
chmod 0600 /usr/swap0

В /etc/fstab добавляем:

md none swap sw,file=/usr/swap0,late 0 0

В очередной раз перезагружаемся, после чего проверить использование подкачки можно, например, top'ом.

Осталось защитить SSH - ключом или fail2ban'ом на ваше усмотрение. В первом варианте все как обычно, даже конфиги находится в /etc/ssh (а не, к примеру, /usr/local/etc…), а второй вариант я описывал ранее. Лично я на сей раз предпочел сделать ключ.

Создание клеток

Я, пожалуй, не буду пользоваться каким-либо менеджером, хотя теоретически можно было бы посмотреть к примеру CBSD. Вроде у меня не особо сложный случай намечается (хе-хе)… devil

Итак, создадим две так называемые "тонкие" клетки с VNET, т.е. каждая будет обладать своим локальным IP (сетевым стеком).

Поехали - практически по инструкции. Включаем сервис и говорим, чтобы клетки стартовали в фоне:

sysrc jail_enable="YES"
sysrc jail_parallel_start="YES"

Формируем структуру директорий (напоминаю про UFS):

mkdir /usr/local/jails
cd /usr/local/jails
mkdir media
mkdir templates
mkdir containers

Скачиваем и распаковываем, эээ, базовый образ ОС? Так, наверное, стоит понимать userland:

mkdir templates/14.2-RELEASE-base
fetch https://download.freebsd.org/ftp/releases/amd64/amd64/14.2-RELEASE/base.txz -o media/14.2-RELEASE-base.txz
tar -xf media/14.2-RELEASE-base.txz -C templates/14.2-RELEASE-base --unlink

В общем случае версии ОС хоста и шаблона по идее не обязаны совпадать, но в данном случае непонятно, зачем могло бы понадобиться использовать какую-то другую версию. Также стоит сказать, что распакованный шаблон занимает почти 4 гигабайта. Н-да, Docker куда как меньше места требует, да и компрессию, в отличие от ZFS, нельзя включить.

Вообще возможно зря директории будем называть с точностью до релиза - при выходе нового, получается, придется все делать заново (или переименовывать, что тоже не особо радует). Ладно, продолжим. Согласно инструкции, копируем два файла (DNS и настройки локального времени) с хоста в этот самый шаблон, образ, пользователяндию, …

cp /etc/resolv.conf templates/14.2-RELEASE-base/etc/resolv.conf
cp /etc/localtime templates/14.2-RELEASE-base/etc/localtime

И обновляем (хотя в моем случае ничего обновлять в итоге не потребовалось):

freebsd-update -b /usr/local/jails/templates/14.2-RELEASE-base/ fetch install

Далее производим манипуляции, цель которых - сформировать каркас для создания клеток, чтобы не "тянуть" туда всю FreeBSD. Начиная с этого момента, я отступаю от официальной инструкции, поскольку ее авторы не учли маааленькую деталь - а как они собирались монтировать /dev на файловую систему только для чтения? Поэтому дальше применяем схему, характерную для ezjail (отчасти жаль, что они давно не обновлялись - пользоваться вроде и можно, но в целом там уже многое устарело).

Итак, создаем директории и переносим кое-что из дистрибутива (удобнее, конечно, воспользоваться mc вместо ввода команд):

cd templates
mkdir 14.2-RELEASE-skeleton
mkdir 14.2-RELEASE-skeleton/baseos
mkdir 14.2-RELEASE-skeleton/usr
mv 14.2-RELEASE-base/dev 14.2-RELEASE-skeleton/dev
mv 14.2-RELEASE-base/etc 14.2-RELEASE-skeleton/etc
mv 14.2-RELEASE-base/media 14.2-RELEASE-skeleton/media
mv 14.2-RELEASE-base/mnt 14.2-RELEASE-skeleton/mnt
mv 14.2-RELEASE-base/net 14.2-RELEASE-skeleton/net
mv 14.2-RELEASE-base/proc 14.2-RELEASE-skeleton/proc
mv 14.2-RELEASE-base/root 14.2-RELEASE-skeleton/root
mv 14.2-RELEASE-base/tmp 14.2-RELEASE-skeleton/tmp
mv 14.2-RELEASE-base/usr/local 14.2-RELEASE-skeleton/usr/local
mv 14.2-RELEASE-base/usr/obj 14.2-RELEASE-skeleton/usr/obj
mv 14.2-RELEASE-base/var 14.2-RELEASE-skeleton/var
mv 14.2-RELEASE-base/.cshrc 14.2-RELEASE-skeleton/.cshrc
mv 14.2-RELEASE-base/.profile 14.2-RELEASE-skeleton/.profile

Некоторое сомнение вызывает перенос usr/obj, но теоретически может пригодиться, если собирать что-то из исходников. Желательно также установить права 777 на 14.2-RELEASE-skeleton/tmp - они там почему-то 755, что в дальнейшем приводит, например, к ошибке установки MariaDB. Я об этом напишу в соответствующем разделе. И, в отличие от ezjail, я решил назвать каталог для монтирования ОС baseos, а не basejail.

А сейчас придется изрядно потрудиться, создавая 100500 ссылок:

cd 14.2-RELEASE-skeleton
ln -s baseos/bin bin
ln -s baseos/boot boot
ln -s baseos/lib lib
ln -s baseos/libexec libexec
ln -s baseos/rescue rescue
ln -s baseos/sbin sbin
ln -s usr/src/sys sys
cd usr
ln -s ../baseos/usr/bin bin
ln -s ../baseos/usr/include include
ln -s ../baseos/usr/lib lib
ln -s ../baseos/usr/lib32 lib32
ln -s ../baseos/usr/libdata libdata
ln -s ../baseos/usr/libexec libexec
ln -s ../baseos/usr/sbin sbin
ln -s ../baseos/usr/share share
ln -s ../baseos/usr/src src
ln -s ../baseos/usr/tests tests

Уфф. Пожалуй, стоит упомянуть, что если бы нас устраивали "толстые" клетки (что, вообще говоря, крайне расточительно с точки зрения ресурсов), то можно было бы воспользоваться bsdinstall вместо вот этого вот всего. Либо вновь сожалеть об отсутствии ZFS.

Итак, уже почти!.. Создаем контейнеры на основе каркаса. Я их назову drupal и yii в соответствии с предназначением. Опять же напоминаю про UFS, так что это банальная cp:

cd /usr/local/jails
cp -R templates/14.2-RELEASE-skeleton containers/drupal
cp -R templates/14.2-RELEASE-skeleton containers/yii

Возможно cp не копирует права доступа к tmp, на всякий случай перепроверьте и исправьте при необходимости.

Создаем сетевой мост. Вызовом ifconfig выясняем основной сетевой интерфейс - у меня это eth0 (в то время как в инструкциях обычно фигурирует em0) и добавляем следующие строки в /etc/rc.conf:

cloned_interfaces="bridge0"
ifconfig_bridge0="inet 192.168.56.1/24 addm eth0 up"

Тем самым мы создаем мост с собственным адресом 192.168.56.1 - я позволю себе раздавать IP-адреса в стиле VirtualBox. Сразу перезагрузился, чтобы проверить, что сетевой мост появляется в ifconfig.

Настройку файрволла выполним чуть позже, а пока создадим конфигурационные файлы клеток, которые будут отличаться друг от друга лишь парой переменных.

/etc/jail.conf.d/drupal.conf:

drupal {
  # STARTUP/LOGGING
  exec.start = "/bin/sh /etc/rc";
  exec.stop = "/bin/sh /etc/rc.shutdown";
  exec.consolelog = "/var/log/jail_console_${name}.log";

  # PERMISSIONS
  allow.raw_sockets;
  exec.clean;
  mount.devfs;

  # HOSTNAME/PATH
  host.hostname = "${name}.jail";
  path = "/usr/local/jails/containers/${name}";

  # NETWORKS/INTERFACES
  $id = "10";
  $ip = "192.168.56.${id}/24";
  $gateway = "192.168.56.1";
  $bridge = "bridge0";
  $epair = "epair${id}";

  # VNET/VIMAGE
  vnet;
  vnet.interface = "${epair}b";

  # ADD TO bridge INTERFACE
  exec.prestart  = "/sbin/ifconfig ${epair} create up";
  exec.prestart += "/sbin/ifconfig ${epair}a up descr jail:${name}";
  exec.prestart += "/sbin/ifconfig ${bridge} addm ${epair}a up";
  exec.start    += "/sbin/ifconfig ${epair}b ${ip} up";
  exec.start    += "/sbin/route add default ${gateway}";
  exec.poststop  = "/sbin/ifconfig ${bridge} deletem ${epair}a";
  exec.poststop += "/sbin/ifconfig ${epair}a destroy";

  # MOUNT
  exec.prepare = "/sbin/mount_nullfs -o ro /usr/local/jails/templates/14.2-RELEASE-base /usr/local/jails/containers/${name}/baseos";
  exec.release = "/sbin/umount /usr/local/jails/containers/${name}/baseos";
}

В начале идут стандартные команды запуска, завершения и логирования. По поводу allow.raw_sockets есть некоторые сомнения, нужно ли оно, но пусть будет. Написано, что это позволяет работать таким командам, как ping и traceroute "изнутри" - может пригодиться. exec.clean - запуск команд в "чистом" окружении. mount.devfs, соответственно, монтирует файловую систему devfs на /dev клетки. В различных инструкциях предлагается дополнительно прописывать devfs_ruleset, но вроде бы и со значением по умолчанию все работает.

С хостом и путем, думаю, все понятно, а с монтированием базового образа ОС возникли нюансы. В инструкции предлагалось создать файлы и подключать их (mount.fstab), однако возникла неожиданная проблема - при запуске клетки mount ругался на слишком длинное имя файла!

mount: /usr/local/jails/templates/14.2-RELEASE-base: File name too long

Поэтому пришлось мудрить с exec.prepare = "/sbin/mount_nullfs …".

Основное веселье, конечно, заключается в настройках сети - это довольно длинный кусок кода, но суть его сводится к созданию виртуального сетевого устройства, точнее даже пары устройств (epair) - устройство a для хоста и b для контейнера, и его подключению к нашему сетевому мосту.

Файл /etc/jail.conf.d/yii.conf отличается именем клетки и id. В принципе большую часть конфига можно было бы разместить прямо в /etc/jail.conf, поскольку все максимально шаблонизировано, но для пущей важности пусть все же будут отдельные файлы.

Наконец-то (ура!) можно запускать клетки:

service jail start drupal
service jail start yii

Справедливости ради, клетки вопреки инструкции все равно не стартуют после перезагрузки, поэтому пришлось добавить "недокументированный" параметр в /etc/rc.conf:

jail_list = "drupal yii"

Возник нюанс - с хоста клетки пингуются, сами клетки пингуют друг друга и хост, а вот "наружу" пинг не идет. Это, в свою очередь, выливается в невозможность установить пакеты в клетки. Оказывается, необходим еще и файрволл, чтобы скрыть клетки за NAT и работать как врата Балдура гейт. Хотя вроде мост и так должен был все это объединить. Позволю себе предположить, что это сделано специально ради безопасности. Казалось бы, зачем тогда основной сетевой интерфейс среди "членов" - а без него почему-то скорость интернета в контейнере крайне мала.

PF

Не совсем понятно, заработает ли вся эта система с иным файрволлом, но с этим работает точно, да и настраивается он относительно просто.

Включаем сервис и режим гейта:

sysrc pf_enable=yes
sysrc gateway_enable=yes

Файл /etc/pf.conf:

tcp_services = "{ ssh, http, https }"
udp_services = "{ domain }"
icmp_types = "echoreq"
ext_if = "eth0"    # macro for external interface
int_if = "bridge0" # macro for internal interface
localnet = $int_if:network

# ext_if IP address could be dynamic, hence ($ext_if)
nat on $ext_if from $localnet to any -> ($ext_if)

block all
pass proto tcp to any port $tcp_services keep state
pass proto udp to any port $udp_services keep state
pass from { lo0, $localnet } to any keep state
pass inet proto icmp all icmp-type $icmp_types keep state

Я некоторым образом объединил примеры правил из инструкции - открыл нужные порты (здесь главное не выстрелить себе в ногу и не отрубить SSH - при невозможности доступа к консоли сервера придется все переустанавливать, будьте особенно внимательны и осторожны!), настроил гейт/NAT для локальных адресов и разрешил ping (но не стандартный режим traceroute).

Как обычно - service pf start или перезагрузка. Логгер не стал активировать (хотя теоретически он мог бы помочь при отладке).

Drupal

В этой клетке мы будем разворачивать сайт на базе Drupal 7.x. Если клетка вдруг не запущена, то запускаем, но устанавливать софт предлагается с хоста. Для начала - самый минимум:

pkg -j drupal install apache24 php82

Apache

Включить "индейца" можно или напрямую отредактировав /usr/local/jails/containers/drupal/etc/rc.conf, или через jexec:

jexec drupal service apache24 enable

Правим конфиги - на хосте это аж /usr/local/jails/containers/drupal/usr/local/etc/apache24/httpd.conf. Эээ, что-то я наверное переборщил с двумя usr/local, но все-таки это стиль, принятый во FreeBSD.

Кстати, наверное, стоит пояснить - в отличие от Докера, где контейнеры "короткоживущие" (эфемерные, как они говорят) и необходимо принимать меры для сохранения состояния (пробрасывать директории, использовать тома), не говоря уже о том, что ковыряние непосредственно в дебрях /var/lib/docker карается высшей мерой, клетки FreeBSD наоборот, "живее всех живых".

Итак, предлагаю поменять порт (Listen 8080), поскольку Апач не будет "отсвечивать" непосредственно в интернете (хотя теоретически можно через pf что-нибудь намутить), а работать бэкендом для реверс-прокси на хосте.

В немалой степени здесь я повторяюсь относительно Arch Linux - включаем модули:

  • proxy_module
  • proxy_fcgi_module
  • expires_module
  • rewrite_module

Первые два отвечают за интеграцию Apache с PHP FPM, а вторые два нужны самому Drupal.

Выключаем autoindex_module.

По умолчанию каталог документов определен как /usr/local/www/apache24/data (это уже с точки зрения контейнера) - не совсем очевидно, но пусть живет (кстати там уже размещена заглушка), однако настройки надо подправить - как минимум разрешить переопределение (AllowOverride All) в этой директории через .htaccess (вместе с Drupal таковой поставляется, причем весьма внушительного размера).

Для обработки PHP создадим Includes/fcgi.conf (путь относительно расположения httpd.conf):

<FilesMatch \.php$>
    SetHandler "proxy:unix:/var/run/www.sock|fcgi://localhost/"
</FilesMatch>

PHP FPM

Давайте пути я уже относительно контейнера буду писать. Для начала скопируем /usr/local/etc/php.ini-production в php.ini. "Тюнинг" я описывал неоднократно, да и на данном этапе он не особо нужен. Правим php-fpm.d/www.conf - переключаем на сокет. В отличие от Linux, похоже нужно явно прописать владельца и группу:

listen = /var/run/www.sock
listen.owner = www
listen.group = www

Для проверки создаем файл /usr/local/www/apache24/data/info.php:

<?php phpinfo(); ?>

Включаем сервис php_fpm и запускаем оба сервиса (на хосте):

jexec drupal service php_fpm enable
jexec drupal service php_fpm start
jexec drupal service apache24 start

Что-то произошло, хотя Apache ругается на ServerName - наверное в дальнейшем его можно будет и установить. В браузере, конечно, пока ничего нельзя посмотреть, так что:

fetch http://192.168.56.100:8080/index.html -o -
fetch http://192.168.56.100:8080/info.php -o -

Хм, а давайте не теоретически, а практически "приоткроем" доступ к веб-серверу через интернет - добавим правила в /etc/pf.conf (хоста). Эту часть после объявления nat:

#Redirect web traffic to the jail.
web="192.168.56.100"
rdr on $ext_if proto tcp from any to any port http -> $web port 8080

А это правило - в конец:

pass proto tcp from any to $web port 8080 keep state

Перезагружаем конфигурацию файрволла:

pfctl -f /etc/pf.conf

По идее теперь можно "заходить по IP" через браузер.

MariaDB

Установка практически стандартная, за исключением нюанса с клеткой. На хосте:

pkg -j drupal install mariadb106-server

Я как-то не очень люблю более новые версии, так что пока есть 10.6, ставлю ее. Переходим в контейнер - jexec drupal - и все как обычно…

service mysql-server enable
service mysql-server start

Или нет:

Installing MariaDB/MySQL system tables in '/var/db/mysql' ...

Installation of system tables failed!  Examine the logs in
/var/log/mysql/mysqld.err or /var/db/mysql for more information.

The problem could be conflicting information in an external
my.cnf files. You can ignore these by doing:

    shell> /usr/local/bin/mariadb-install-db --defaults-file=~/.my.cnf

You can also try to start the mariadbd daemon with:

    shell> /usr/local/libexec/mariadbd --skip-grant-tables --general-log &

and use the command line tool /usr/local/bin/mariadb
to connect to the mysql database and look at the grant tables:

    shell> /usr/local/bin/mariadb -u root mysql
    MariaDB> show tables;

Try '/usr/local/libexec/mariadbd --help' if you have problems with paths.  Using
--general-log gives you a log in /var/db/mysql that may be helpful.

Оказывается, проблема в доступе к /tmp, и действительно - права внутри контейнера 755 для root:wheel.

[ERROR] mariadbd: Can't create/write to file '/tmp/ibc3ctNk' (Errcode: 13 "Permission denied")

К счастью, это легко исправить (внутри контейнера):

chmod 777 /tmp

Стартуем сервис заново и защищаем установку - mariadb-secure-installation. Надеюсь, постоянные читатели помнят, что я "топлю" за сокет-аутентификацию. smile

И наши любимые команды по созданию БД, которые кочуют из статьи в статью (не забудьте подставить сгенерированный пароль):

CREATE DATABASE drupal;
CREATE USER drupal@localhost IDENTIFIED BY 'very_secure_password';
GRANT ALL PRIVILEGES ON drupal.* to drupal@localhost;
FLUSH PRIVILEGES;

Осталось загрузить дамп - zcat drupal.sql.gz | mysql drupal - проблема лишь в том, как этот самый дамп "затащить" на сервер. Хотя проблемы-то и нет - scp есть "из коробки", или тот же sFTP в помощь.

Завершение настройки

Возвращаемся на хост и устанавливаем необходимые расширения PHP. Как я уже писал ранее, некоторым недостатком FreeBSD является то, что PHP в ней "мелко нарезан", поэтому список всех необходимых расширений сформировался далеко не с первой попытки:

pkg -j drupal install php82-ctype php82-curl php82-gd php82-dom php82-filter php82-mbstring php82-opcache php82-pecl-uploadprogress php82-pdo_mysql php82-session php82-simplexml php82-xml php82-xmlwriter php82-zlib

Только сейчас узнал, что в Drupal 7 уже добавили поддержку PHP 8.3, по крайней мере в ядро. Это, вообще говоря, ставит под сомнение необходимость мудриться с клетками и прочим. Вопрос, конечно, в модулях… Ну и, допустим, в будущем можно будет сделать пару из 8.4 и 8.3 в клетках.

Перезапускаем сервис:

jexec drupal service php_fpm restart

Разворачиваем исходники, например сочетанием scp, tar и mc. Рекурсивно меняем владельца (на хосте):

chown -R www:www /usr/local/jails/containers/drupal/usr/local/www/apache24/data

К счастью это "предопределенный" пользователь, поэтому результат должен быть одинаковым при вызове как с хоста, так и из контейнера (с поправкой на путь в этом случае).

Yii

Здесь у нас уже более современный стек - работа идет через git и composer. Напоминаю про хост, имя клетки и PHP 8.3. Отдельная СУБД не нужна, поскольку в этом случае я пользуюсь sqlite.

pkg -j yii install git php83-composer

Благодаря зависимостям установится сам PHP и несколько его расширений, а также, к примеру, curl.

Установим другие необходимые расширения PHP:

pkg -j yii install php83-gd php83-dom php83-fileinfo php83-opcache php83-pcntl php83-pdo_sqlite php83-session php83-zlib

Как обычно, копируем php.ini-production в php.ini, а PHP FPM на сей раз настроим иначе - пусть слушает порт, но ограничим прослушивание любого IPv4 адреса перечнем допустимых клиентов.

listen = 0.0.0.0:9000
listen.allowed_clients = 192.168.56.1

Это сделано на случай состояния гонки, когда PHP FPM запускается, но IP-адрес клетки еще не успел активироваться. Я с такой ситуацией столкнулся после одной из перезагрузок, когда первоначально просто сделал прослушивание на адресе клетки.

Включаем и запускаем сервис:

jexec yii service php_fpm enable
jexec yii service php_fpm start

Теперь момент с пользователем. PHP FPM фактически работает под www - собственно говоря поэтому мы делали chown в случае с Drupal. Но если работать с git под root (а других доступных пользователей у нас на данный момент нет), то если потом менять владельца на www, начнется ругань. Предлагаю в рамках клетки разрешить вход пользователю (или по крайней мере переключение в) www - в общем случае, наверное, это не самая лучшая идея, но для контейнера сделаем исключение.

Входим в контейнер:

jexec yii

Меняем оболочку пользователю www - пусть будет простейшая (sh):

chsh -s /bin/sh www

Теперь можно делать su www, но с хоста jexec -u www yii работать не будет (this account is currently not available). Ну и ладно, хотя при деплое тогда придется вводить пару лишних команд.

Собственно к развертыванию (по прежнему в контейнере):

mkdir /usr/local/www
chown www:www /usr/local/www
su www
cd /usr/local/www
git clone https://git.example.com/user/yii.git
cd yii
composer install

Решил для ясности пометить, какие команды выполняются под root (контейнера), а какие - под пользователем. URL репозитория я, понятное дело, слегка зашифровал. Осталось "занести" всякого рода заразу контент. Скорее всего без chown не обойтись!

Caddy

Возвращаемся на хост и устанавливаем веб-сервер:

pkg install caddy

Тем самым убиваем сразу несколько зайцев - это и простота настройки, и решение проблем с "сертификацией". По крайней мере в отличие от nginx при настройке мы делом занимаемся, а не задаем безумное количество параметров для работы с fcgi (да и ssl) без всякой уверенности, что они вообще говоря правильные и безопасные. Это, конечно, шутка, но только лишь с долей шутки…

Как и в прошлый раз, выполняем инструкцию для запуска Caddy под пользователем www:

pkg install security/portacl-rc
sysrc portacl_users+=www
sysrc portacl_user_www_tcp="http https"
sysrc portacl_user_www_udp="https"
service portacl enable
service portacl start

Ставим сервис в автозагрузку:

sysrc caddy_user=www caddy_group=www
service caddy enable

Убираем проброс http-трафика в контейнер с Drupal - т.е. удаляем (комментируем) добавленные ранее по этому поводу строки в /etc/pf.conf и перечитываем конфигурацию:

pfctl -f /etc/pf.conf

Сначала проверим работу Yii "по IP". Правим /usr/local/etc/caddy/Caddyfile - заменяем блок localhost:

http:// {
    # Set this path to your site's directory:
    root * /usr/local/jails/containers/yii/usr/local/www/yii/web

    # Deny access to .htaccess or similar files:
    error /.ht* 403

    # Cache static files for an hour:
    header ?Cache-Control "max-age=3600"

    # Enable the static file server:
    encode zstd gzip
    file_server

    # Serve a PHP site through php-fpm:
    php_fastcgi 192.168.56.20:9000 {
        root /usr/local/www/yii/web
    }

    # Show error code and status instead of blank screen:
    handle_errors {
        respond "{err.status_code} {err.status_text}"
    }
}

Здесь мы делаем "финт ушами". Поскольку Caddy запущен на хосте, для обслуживания статики указываем большой, длинный, необрезанный путь (а заодно кэширование). Но PHP-FPM работает внутри контейнера, поэтому для него корневой каталог нужно переопределить. Эти самые пути, если что, надо будет актуализировать (здесь они для примера). "Обнаружение сервисов", конечно же, ручное.

Некоторым недостатком Caddy является отсутствие стандартной обработки ошибок, т.е. в общем случае это будет просто "пустой экран". Поэтому я добавил номинальный блок handle_errors.

Запускаем:

service caddy start

И по идее все должно заработать.

Завершение настройки

Что ж, похоже уже можно менять DNS-записи - перенацеливать сайты на новый сервер. Тем временем, доводим до ума конфигурацию Caddy. Заводим глобальный блок:

{
    admin off
    email admin@example.com
}

Довольно спорным образом отключаю администрирование, но что-то там менять на сокет или открывать порт в pf не особо хочется (так получилось, что 2019 порт даже localhost'а оказался заблокированным). Насколько часто вы меняете конфигурацию веб-сервера, и при этом нельзя отключить веб-сервер ни на секунду? В случае чего перезапущу сервис и не буду париться.

Также рекомендуется указать свой e-mail для Let's Encrypt.

Мне кажется не совсем правильным "засорять" основной Caddyfile, особенно если сайтов несколько. Поэтому предлагаю добавить туда ранее анонсированный обратный прокси к Drupal'у, а настройки Yii вынести в отдельный файл. Настройка прокси:

drupal.example.com {
    header -Server
    reverse_proxy 192.168.56.10:8080
}

Здесь я принудительно убрал заголовок Server, иначе их оказывается два - от Апача и самого (самой?) Caddy. Еще оказалось, что Drupal'у за обратным прокси живется как-то не очень хорошо и нужно править sites/default/settings.php:

$base_url = 'https://drupal.example.com';
$conf['reverse_proxy'] = TRUE;
$conf['reverse_proxy_addresses'] = array('192.168.56.1');

Возвращаемся к Caddyfile - в конец добавляем импорт к примеру файлов .conf из поддиректории conf.d (разумеется, вместо этого можно сделать какие-нибудь sites-enabled и файлы без расширений - как угодно):

import /usr/local/etc/caddy/conf.d/*.conf

Соответственно:

mkdir /usr/local/etc/caddy/conf.d

И выносим ранее сделанную настройку Yii в файл - например conf.d/yii.conf с заменой http:// на домен сайта.

Для пущей важности во всех php.ini можно запретить раскрывать версию PHP (expose_php = Off) - не забудьте перезапустить сервисы. В принципе можно убрать и заголовок Server, но тогда придется добавлять header -Server в каждый сайт в настройках Caddy. Особого смысла в этом наверное нет.

По готовности перезапускаем сервис:

service caddy restart

И вроде как я обещал задать ServerName drupal.example.com в настройках Apache (а заодно можно и ServerAdmin). Самое время это сделать и, соответственно, перезапустить Апач.

Обслуживание клеток

Теперь осталось понять, как эту всю "красоту" обслуживать. Кое-что, впрочем, уже встречались ранее - например управление пакетами внутри клетки (pkg -j) и выполнение команд (jexec). Еще я кажется ранее не упоминал команду вывода списка клеток - jls.

Соответственно для обновления клетки, с одной стороны, надо обновить ее пакеты, например:

pkg -j drupal upgrade

А с другой - обновить наш базовый образ системы для клеток (в принципе мы так уже делали ранее при создании) и перезапустить сервис этих самых клеток (а может и сервер целиком для пущей важности):

freebsd-update -b /usr/local/jails/templates/14.2-RELEASE-base/ fetch install
service jail restart

Хотя прежде чем обновлять базовый образ, логично обновить саму ОС на хосте. Опять же, с этого мы и начинали.

Удаление клетки не совсем очевидно - согласно инструкции, по соображениям безопасности нельзя просто так взять и удалить каталог контейнера даже под root. Допустим, мы хотим удалить клетку drupal:

# останавливаем ее
service jail stop drupal
# очищаем флаги файлов
chflags -R 0 /usr/local/jails/containers/drupal
# теперь можем удалить каталог
rm -rf /usr/local/jails/containers/drupal

Наконец, удаляем само определение клетки из /etc/jail.conf.d.

Заключение

Да уж, неудивительно, что Docker взлетел - настраивать контейнеры в соответствии с вышеизложенным захочет только настоящий энтузиаст. Даже менеджер типа CBSD вряд ли сильно все упростит. Особенно неудачной, как мне кажется, является настройка сети (особенно непонятно, как это делалось до VNET и вообще работало ли). И это не говоря уже о CI/CD, чьи перспективы в связке с клетками крайне туманные (лично мне представляются разве что какие-то самописные скрипты).

Тем не менее, я по прежнему считаю FreeBSD более цельной системой по сравнению с "зоопарком" Linux, да и работает все это весьма шустро. Если развертывание достаточно простое (примерно как git pull / composer install), то привычный стек вполне реально поднять под "фряхой".


Категория: Программирование, веб | Опубликовано 01.06.2025 | Редакция от 05.07.2025

Похожие материалы

FreeBSD на VPS

Продолжаю устанавливать что-нибудь этакое на VPS. На сей раз решил, так сказать, вернуться к истокам - ведь когда-то многие веб-сервера были на фряхе, а также посмотреть, насколько она компактна сама по себе и в плане ресурсоемкости.


Комментарии, обсуждение