Предлагаю вашему вниманию инструкцию по развертыванию Drupal 7 на VDS/VPS с применением веб-сервера Caddy. Предполагается, что серверу доступно примерно 500 мегабайт оперативки и предустановлена Rocky Linux 8 (скорее всего аналогично будет и в Alma с Oracle). 9-е семейство RHEL для такой ситуации по-моему уже слишком тяжеловесное.
Поскольку речь идет о VDS/VPS, консольные команды скорее всего будут выполняться от имени root, поэтому в начале будет стоять решетка. Если не root, то большинство (если не все) команды придется предварять sudo
.
Подготовка ОС
Файл подкачки
Обычно на VDS подкачки нет (проверить, например, free -m
). В этом случае создадим файл подкачки на 512 “метров”:
# fallocate -l 512M /swapfile # chmod 600 /swapfile # mkswap /swapfile # swapon /swapfile
Сейчас скорее всего можно установить mc
.
В /etc/fstab добавить:
/swapfile none swap defaults 0 0
в /etc/sysctl.conf добавить (якобы чтобы поменьше "свопился"):
vm.swappiness=10
Перезагрузить для пущей важности. Вот теперь можно dnf update
.
fail2ban
# dnf install fail2ban
Должен подтянуться и fail2ban-firewalld.
Файл /etc/fail2ban/jail.d/01-sshd.conf
[sshd] enabled = true bantime = 12h maxretry = 3
Перезапустить сервис:
# systemctl restart fail2ban
Проверить статус:
# fail2ban-client status
firewalld
Проверить список правил:
# firewall-cmd --list-all
Например, помимо прочего:
services: cockpit dhcpv6-client ssh
Удалить cockpit, добавить http и https:
# firewall-cmd --permanent --remove-service=cockpit # firewall-cmd --permanent --add-service=http # firewall-cmd --permanent --add-service=https # firewall-cmd --reload
kdump
Обычно включен, но не нужен.
Проверка состояния сервиса и отключение:
# systemctl status kdump # systemctl disable kdump
Проверка наличия параметра (аргумента) ядра:
# dir /boot/vmlinuz* # grubby --info /boot/vmlinuz-4.18.0-553.22.1.el8_10.x86_64 | grep args
(подставить нужный файл из вывода dir). Пример вывода:
args="ro crashkernel=auto rhgb quiet $tuned_params"
Удалить:
# grubby --remove-args="crashkernel" --update=ALL
Хотя особой разницы что-то не заметил. Подозреваю, что crashkernel=auto и так не резервировал память, видя менее 500 мегабайт.
MariaDB
Установим какую-нибудь не самую новую, например 10.6. Более привычная система сохранилась на mariadb.org. Более новая:
https://mariadb.com/docs/server/deploy/deployment-methods/repo/#Configur...
Почему бы и нет.
# curl -LsSO https://r.mariadb.com/downloads/mariadb_repo_setup # chmod +x mariadb_repo_setup # ./mariadb_repo_setup --mariadb-server-version=10.6
https://mariadb.com/docs/server/deploy/topologies/single-node/community-...
Устанавливаем:
# dnf install MariaDB-server
Включаем:
# systemctl enable --now mysql
Защищаем установку:
# mariadb-secure-installation
Холивар по поводу авторизации по сокету vs пароль root. Лично я предпочитаю сокет, чтобы не добавлять параметры -u
и -p
в команды. Поэтому везде ответ по умолчанию, кроме изменения пароля root:
Enter current password for root (enter for none): Switch to unix_socket authentication [Y/n] Change the root password? [Y/n] n Remove anonymous users? [Y/n] Disallow root login remotely? [Y/n] Remove test database and access to it? [Y/n] Reload privilege tables now? [Y/n]
Заходим в командную строку MariaDB:
# mysql
Запросом SHOW VARIABLES LIKE 'performance_schema';
проверяем, что Performance Schema выключена (не хватало ее еще на 500 метрах оперативки):
MariaDB [(none)]> SHOW VARIABLES LIKE 'performance_schema'; +--------------------+-------+ | Variable_name | Value | +--------------------+-------+ | performance_schema | OFF | +--------------------+-------+
Серией запросов создаем БД drupal
, пользователя drupal
(пароль лучше назначить более надежный) и назначаем привилегии:
CREATE DATABASE drupal; CREATE USER drupal@localhost IDENTIFIED BY 'drupal_password'; GRANT ALL PRIVILEGES ON drupal.* to drupal@localhost; FLUSH PRIVILEGES;
Выходим exit
и загружаем дамп (если он есть) (удобно же с сокетом?):
# zcat drupal.sql.gz | mysql drupal
PHP-FPM
https://docs.rockylinux.org/ru/guides/web/php/
epel-release скорее всего уже каким-то образом установлен, но на всякий случай:
# dnf install epel-release
Подключаем remi:
# dnf install https://rpms.remirepo.net/enterprise/remi-release-8.rpm
Смотрим, какие версии доступны:
# dnf module list php
https://www.drupal.org/docs/7/system-requirements/php-requirements-for-d...
Drupal 7, конечно, история больше для 7-го же PHP, так что можно было бы взять 7.2 (по умолчанию) из AppStream, но актуальная версия PHP 8.3, которой в AppStream нет. В зависимости от проекта может понадобиться минимум правок (или они вообще не понадобятся). С другой стороны, рекомендуют 8.1 или 8.2, они есть в AppStream. Для определенности - я все-таки взял 8.3 из remi.
# dnf module enable php:remi-8.3
Устанавливаем php-fpm и командную строку (“просто” php
не нужен, чтобы не тянуть Apache) и минимальный набор расширений.
# dnf install php-fpm php-cli php-gd php-xml php-mbstring php-mysqlnd
С пользователями и группами разберемся позже, а пока немного “приструним” пул процессов.
В /etc/php-fpm.d/www.conf правим (уменьшаем) параметры, например как в образе Докера:
pm.max_children = 5 pm.start_servers = 2 pm.min_spare_servers = 1 pm.max_spare_servers = 3
Если и этого будет недостаточно, то можно отрегулировать следующие параметры в файле /etc/php-fpm.conf, например:
emergency_restart_threshold = 10 emergency_restart_interval = 1m process_control_timeout = 10s
Включаем сервис:
# systemctl enable --now php-fpm
Caddy
https://docs.rockylinux.org/ru/guides/web/caddy/
Раз у нас и так есть EPEL, то установим Caddy из него:
# dnf install caddy
Справедливости ради, он там не самый новый, ну да ладно.
Включим сервис:
# systemctl enable --now caddy
И посмотрим, что сервер как-то отзывается:
Теперь, если проанализировать определение сервиса, то мы заметим, что Caddy запускается под одноименным пользователем, в то время как php-fpm - от имени apache. Заменим nginx на caddy в параметре listen.acl_users
файла /etc/php-fpm.d/www.conf:
listen.acl_users = apache,caddy
На данном этапе не обязательно, так что по желанию:
# systemctl restart php-fpm
Drupal
Проверка
Разворачиваем “исходники” в /var/www/html, меняем владельца на apache (в соответствии с php-fpm).
# chown -R apache:apache /var/www/html
Так как по умолчанию права доступа 755 / 644, то веб-сервер (пользователь caddy) также сможет читать файлы. Корректируем, при необходимости, sites/default/settings.php.
Далее или исправляем основную конфигурацию Caddy (/etc/caddy/Caddyfile), или добавляем файл в /etc/caddy/Caddyfile.d. Во втором варианте при заходе на сервер “по IP” или неизвестным именем хоста будет отображаться заглушка, что скорее всего неправильно.
Итак, номинально (ВНИМАНИЕ! НЕ БЕЗОПАСНО!) правим Caddyfile:
# The Caddyfile is an easy way to configure your Caddy web server. # https://caddyserver.com/docs/caddyfile # The configuration below serves a welcome page over HTTP on port 80. To use # your own domain name with automatic HTTPS, ensure your A/AAAA DNS record is # pointing to this machine's public IP, then replace `http://` with your domain # name. Refer to the documentation for full instructions on the address # specification. # # https://caddyserver.com/docs/caddyfile/concepts#addresses http:// { # Set this path to your site's directory. #root * /usr/share/caddy root * /var/www/html # Enable the static file server. file_server # Another common task is to set up a reverse proxy: # reverse_proxy localhost:8080 # Or serve a PHP site through php-fpm: # php_fastcgi localhost:9000 php_fastcgi unix//run/php-fpm/www.sock # Refer to the directive documentation for more options. # https://caddyserver.com/docs/caddyfile/directives } # As an alternative to editing the above site block, you can add your own site # block files in the Caddyfile.d directory, and they will be included as long # as they use the .caddyfile extension. import Caddyfile.d/*.caddyfile
Перезапускаем сервисы (кстати можно делать caddy reload
вместо systemctl restart caddy
), проверяем. В основном должно работать, а всякими нюансами займемся далее.
Права на запись
Необходимо обеспечить права на запись в sites/default/files. Вспоминаем, что у нас Rocky Linux, а следовательно включен SELinux (проверка - sestatus
).
[root@vm876632 ~]# sestatus SELinux status: enabled SELinuxfs mount: /sys/fs/selinux SELinux root directory: /etc/selinux Loaded policy name: targeted Current mode: enforcing Mode from config file: enforcing Policy MLS status: enabled Policy deny_unknown status: allowed Memory protection checking: actual (secure) Max kernel policy version: 33 [root@vm876632 ~]# cd /var/www/html/sites/default/files [root@vm876632 files]# ls -Z unconfined_u:object_r:httpd_sys_content_t:s0 bootstrap unconfined_u:object_r:httpd_sys_content_t:s0 css unconfined_u:object_r:httpd_sys_content_t:s0 css_injector unconfined_u:object_r:httpd_sys_content_t:s0 ctools unconfined_u:object_r:httpd_sys_content_t:s0 imagecache unconfined_u:object_r:httpd_sys_content_t:s0 imagecache_sample.png unconfined_u:object_r:httpd_sys_content_t:s0 imagefield_default_images unconfined_u:object_r:httpd_sys_content_t:s0 imagefield_thumbs unconfined_u:object_r:httpd_sys_content_t:s0 images unconfined_u:object_r:httpd_sys_content_t:s0 imports unconfined_u:object_r:httpd_sys_content_t:s0 js unconfined_u:object_r:httpd_sys_content_t:s0 languages unconfined_u:object_r:httpd_sys_content_t:s0 module unconfined_u:object_r:httpd_sys_content_t:s0 products unconfined_u:object_r:httpd_sys_content_t:s0 styles unconfined_u:object_r:httpd_sys_content_t:s0 xmlsitemap
Как видим, контекст httpd_sys_content_t, в то время как нужен httpd_sys_rw_content_t.
По идее fail2ban подтянул пакет policycoreutils-python-utils, из которого нам нужны команды semanage
и restorecon
. Посему:
# semanage fcontext -a -t httpd_sys_rw_content_t "/var/www/html/sites/default/files(/.*)?"
Установим этот же контекст и на каталог sites/all для работы функций обновления.
# semanage fcontext -a -t httpd_sys_rw_content_t "/var/www/html/sites/all(/.*)?"
Восстанавливаем (применяем) контекст:
# restorecon -r /var/www/html/sites
На всякий случай стоит проверить эту настройку SELinux:
# getsebool httpd_can_network_connect
Если она выключена, то функции обновления Drupal скорее всего не будут работать. Включить (-P
- на постоянной основе):
# setsebool -P httpd_can_network_connect on
Конфигурация PHP
Мы не трогали /etc/php.ini, а в нем желательно бы кое-что подправить. Согласно все той же странице системных требований, вносим изменения:
expose_php = off allow_url_fopen = off
Остальные перечисленные параметры или совпадают с рекомендованными значениями, либо вовсе отсутствуют.
По желанию можно также скорректировать настройки ограничения памяти, размера POST-запроса, а также количества и размера загружаемых файлов (ниже приведены значения из php.ini).
memory_limit = 128M post_max_size = 8M upload_max_filesize = 2M max_file_uploads = 20
После внесения изменений в конфигурацию необходимо перезапустить сервис php-fpm.
Конфигурация Caddy
Поскольку в Drupal 7 index.php располагается прямо в корне, во избежание всяких неприятностей он поставляется с огромным файлом .htaccess (для Apache). С этой точки зрения, конечно, лучше Drupal так и использовать с Apache, но теперь наша задача попытаться все это дело воспроизвести.
Первый важный для нас фрагмент:
# Protect files and directories from prying eyes. <FilesMatch "\.(engine|inc|info|install|make|module|profile|test|po|sh|.*sql|theme|tpl(\.php)?|xtmpl)(~|\.sw[op]|\.bak|\.orig|\.save)?$|^(\.(?!well-known).*|Entries.*|Repository|Root|Tag|Template|composer\.(json|lock)|web\.config)$|^#.*#$|\.php(~|\.sw[op]|\.bak|\.orig|\.save)$"> <IfModule mod_authz_core.c> Require all denied </IfModule> <IfModule !mod_authz_core.c> Order allow,deny </IfModule> </FilesMatch>
Лютейшая регулярка, можно воспользоваться сервисом regex101.com, чтобы все равно ничего в ней не понять (хотя вообще-то она проверяет различные расширения файлов или некоторые файлы и директории в корне). Да еще и несовместимая с языком RE2, используемом в Caddy - (?!well-known)
поддерживается в PCRE, но не в RE2. Смысл в том, чтобы файл (путь) начинался с точки, кроме .well-known. В принципе у нас такие URL (пока?) не используются, так что предлагаю просто исключить несовместимый фрагмент из выражения. Еще надо добавить /
(для Caddy не надо экранировать) в случае, когда регулярка сравнивает начало строки.
https://caddyserver.com/docs/caddyfile/matchers#path-regexp
# Protect files and directories from prying eyes. @protect_files { path_regexp \.(engine|inc|info|install|make|module|profile|test|po|sh|.*sql|theme|tpl(\.php)?|xtmpl)(~|\.sw[op]|\.bak|\.orig|\.save)?$|^/(\..*|Entries.*|Repository|Root|Tag|Template|composer\.(json|lock)|web\.config)$|^/#.*#$|\.php(~|\.sw[op]|\.bak|\.orig|\.save)$ } error @protect_files 403
Честно говоря, я бы еще дополнительно поставил запрет на php-файлы (они иногда попадаются, помимо корня).
# Deny acces to .php files outside root directory @outer_php { path_regexp ^/.*/.*\.php$ } error @outer_php 403
Честно говоря, а дальше-то уже объявлениями file_server
и php_fastcgi
все сделано. В принципе можно насыпать “ведро компрессии”:
encode gzip
Стоит ли “конвертировать” mod_expires? Казалось бы аналогичный not path_regexp \.php$
работает некорректно (появился двойной заголовок Cache-Control у самих документов). Можно явно перечислить расширения статических файлов:
https://caddy.community/t/correct-way-to-set-expires-on-caddy-2/7914/8
# Cache static files for 2 weeks after access @static { path_regexp \.(ico|css|js|gif|jpg|jpeg|png|svg|woff)$ } header @static Cache-Control max-age=1209600
Но в документации предлагается другой способ, который я и оставлю:
header ?Cache-Control "max-age=1209600"
Смысл в том, чтобы добавить этот заголовок, если “бэкенд” его не установил. А для документов (страниц) его устанавливает Drupal - что и требуется. Честно говоря, я бы уменьшил срок с двух недель до 1-2 часов, хотя тут уже надо смотреть на посещаемость и частоту обновления сайта.
Заголовок X-Content-Type-Options nosniff
вроде и так есть, насчет unset Proxy…
header -Proxy
Вот только как проверить? Заодно можно исключить Server (чтобы никто не догадался, что у нас Caddy):
header -Server
Итого конфигурация:
http:// { # Set this path to your site's directory. root * /var/www/html header -Proxy header -Server header ?Cache-Control "max-age=1209600" # Protect files and directories from prying eyes. @protect_files { path_regexp \.(engine|inc|info|install|make|module|profile|test|po|sh|.*sql|theme|tpl(\.php)?|xtmpl)(~|\.sw[op]|\.bak|\.orig|\.save)?$|^/(\..*|Entries.*|Repository|Root|Tag|Template|composer\.(json|lock)|web\.config)$|^/#.*#$|\.php(~|\.sw[op]|\.bak|\.orig|\.save)$ } error @protect_files 403 # Deny acces to .php files outside root directory @outer_php { path_regexp ^/.*/.*\.php$ } error @outer_php 403 # Enable the static file server. encode gzip file_server # Serve a PHP site through php-fpm php_fastcgi unix//run/php-fpm/www.sock }
Let’s Encrypt
После всех проверок можно воспользоваться функцией автоматического https. Для этого в начало /etc/caddy/Caddyfile добавляем глобальный блок с указанием электронной почты:
{ email admin@example.com }
После чего вместо http:// указываем домен сайта. Все :)
API
По умолчанию у Caddy есть API на 2019-м порту. Благодаря этому, в частности, работает caddy reload
. Поскольку у нас работает firewalld, в принципе можно так все и оставить (порт извне недоступен). Но если ничего такого нет, то лучше переключить прослушивание API на сокет или отключить вовсе.
В глобальном блоке, для прослушивания сокета:
admin unix//run/caddy-admin.sock
Для полного отключения:
admin off
Но в этом случае после изменения конфигурации надо будет именно перезапускать сервис.
OPcache
Посмотрим, удастся ли выжать больше производительности.
Установим расширение:
# dnf install php-opcache
В документации Rocky Linux рекомендуют скорректировать параметры в /etc/php.d/10-opcache.ini: потребление памяти - общее и буфер неких “интернированных” строк, и количество ускоряемых файлов. Последнее значение предложено сопоставить с общим количеством php-файлов, но из-за специфической структуры Drupal подсчитать их довольно сложно. Вот значения по умолчанию:
opcache.memory_consumption=128 opcache.interned_strings_buffer=8 opcache.max_accelerated_files=10000
Я бы только убавил первый параметр, все-таки оперативки у нас “ограничено очень”.
Честно говоря, по ощущениям не то, чтобы ускорилось - основной “тормоз” все же СУБД. Можно было бы пойти дальше и включить JIT - для этого надо установить размер соответствующего буфера (например 8 мегабайт):
; The amount of shared memory to reserve for compiled JIT code. A zero value disables the JIT. ; When an int is used, the value is measured in bytes. Shorthand notation may also be used. opcache.jit_buffer_size=8M
Да вот только сайт после этого перестает работать (502 Bad Gateway). На всякий случай оставил для потомков. https://sergeymukhin.com/blog/php-8-kak-vklyucit-jit
Как обычно, после изменения конфигурации systemctl restart php-fpm
.
Доступ по FTP
Возможно заливать обновления самого Drupal или какие-то правки будет удобнее по FTP. Установим “стандартный” сервер ProFTPD (из EPEL):
# dnf install proftpd proftpd-utils
Создадим FTP-пользователя www-data, соответствующего системному пользователю apache (чтобы всех запутать). Сначала выясним uid и gid системного пользователя - это 48.
[root@vm876632 ~]# cat /etc/passwd | grep apache apache:x:48:48:Apache:/usr/share/httpd:/sbin/nologin
Подставим соответствующие параметры в команду:
# ftpasswd --passwd --file=/etc/proftpd/ftpd.passwd --name=www-data --uid=48 --gid=48 --home=/var/www/html --shell=/bin/false
Поскольку указали параметр --passwd, будет запрошен ввод пароля.
Правим основной файл /etc/proftpd.conf.
Существующие параметры закомментировать/удалить:
- AuthPAMConfig
- AuthOrder
“Рядом” добавить:
AuthUserFile /etc/proftpd/ftpd.passwd AuthPAM off AuthOrder mod_auth_file.c
Тем самым переключаем режим аутентификации на пользователей из файла. В секцию <Global> добавить:
RequireValidShell off
Выше мы указали “неправильную” оболочку /bin/false, так что теперь придется ее разрешить.
Добавляем FTP в firewall:
# firewall-cmd --permanent --add-service=ftp # firewall-cmd --reload
Разрешаем доступ к файлам сервису FTP на уровне SELinux:
# setsebool -P ftpd_full_access on
И только теперь можем стартовать:
# systemctl enable --now proftpd
По хорошему надо бы еще настроить пассивный режим, но уже и так что-то с FTP провозились. Так что ну его.
Заключение
В принципе, и на 500 “метрах” оперативки жить можно. Если страница есть в кэше Друпала, то она даже довольно быстро “выплевывается”. Под пользователем или “на холодную” это все-таки в районе секунды “±километр”.
Дальнейшие направления оптимизации - использование более легковесной ОС (например популярной в Docker-среде Alpine Linux) или перевод Drupal на SQLite. Или не на Друпал. :)