UMGUM.COM 

Birtix + Nginx ( О нюансах перевода сайтов под управлением CMS "Битрикс" на работу через легковесную спарку "Nginx + PHP-FPM". )

28 июня 2017  (обновлено 2 декабря 2017)

OS: "Linux Debian 9", "Linux Ubuntu 16 LTS".

Расскажу здесь о некоторых особенностях настройки площадки для запуска сайтов под управлением CMS "Bitrix". Эта система управления контентом продвигается в продажах как средство легко и непринуждённо создавать сайты. В инструкции от разработчиков сайты под управлением движка "Битрикс" рекомендуется разворачивать в среде исполнения "Apache + modPHP", даже распространяют готовую сборку в этой конфигурации под названием "VMBitrix" на базе "CentOS/Ubuntu", но мне такой подход сильно не нравится и здесь будет изложена последовательность действий, которые необходимо предпринять для корректной работы CMS "Bitrix" с "Nginx + PHP-FPM".



Первое действие - установка нескольких утилит, работа без которых на сервере некомфортна:

# apt-get install aptitude sudo acl psmisc host telnet htop iotop mc vim pwgen

Виртуализация.

Уже лет десять как прошло с того момента, как стало странно запускать сервисы непосредственно на аппаратуре физического сервера, так что да, мы готовим для web-сервера виртуальную площадку, соблюдая определённые условия.

В KVM-Qemu для виртуального диска и сетевой карты обязательно всегда использовать VirtIO-драйверы:

# lspsi

....
00:03.0 Ethernet controller: Red Hat, Inc Virtio network device
00:09.0 SCSI storage controller: Red Hat, Inc Virtio block device

В VMware следует обязательно использовать драйверы "VMware Paravirtual (PVSCSI)" для виртуального HDD и "VMXNET 2/3" для виртуальных NIC:

# lspci

....
03:00.0 Ethernet controller: VMware VMXNET3 Ethernet Controller (rev 01)
0b:00.0 Serial Attached SCSI controller: VMware PVSCSI SCSI Controller (rev 02)

Тест производительности дисковой подсистемы.

Прежде всего тестируем производительность файловой системы - "Bitrix" весьма охоч до неё и на медленных дисках будет еле ворочаться.

Тест записи, по возможности в обход "кеша":

# dd if=/dev/zero of=/var/test bs=2G count=5 oflag=direct

10737397760 bytes (11 GB, 10 GiB) copied, 19.5367 s, 550 MB/s

# dd if=/dev/zero of=/var/test bs=2G count=5 oflag=direct

10737397760 bytes (11 GB, 10 GiB) copied, 22.7542 s, 472 MB/s

# dd if=/dev/zero of=/mnt/var/test bs=2G count=5 oflag=direct

10737397760 bytes (11 GB, 10 GiB) copied, 61.6648 s, 174 MB/s

Выше приведены показатели тестирования вначале дискового массива "NetApp" из SSD, а следом SSD и обычного SATA2 дисков моей рабочей станции.

Надо понимать, что в тесте выше мы "заливали" однотипные данные одним потоком, а в реальной работе запись будет осуществляться часто и помалу, занимая ресурсы дискового контроллера, так что красивые тестовые "550 MBps" для активно работающего стека web-приложений в лучшем случае окажутся на уровне "100 MBps" - но это уже хорошо - а "174 MBps" снизятся до "30-40 MBps", что уже маловато и на количестве одновременных пользователей за сотню-другую дисковая подсистема сервера не будет справляться, выстраивая задачи Disk-IO во всё более разрастающуюся очередь. В общем, если наш простейший тест утилитой "dd" покажет скорость записи менее "100 MBps", то есть смысл серьёзно подумать об использовании другой, более производительной площадки.

Для сведения: скорости операций чтения и записи "случайных" участков множества небольших блоков данных применительно непосредственно к физическим дискам SATA2/3 колеблются в диапазонах от 30 до 80 MBps - "Bitrix"-у этого мало. Показатели для аналогичных операций у дисков SAS получше - до 120 MBps. Максимум покажет SSD, конечно - 400 MBps запросто выдают даже недорогие экземпляры.

Защищаем административный вход.

Сразу после инсталляции базовых компонентов операционной системы запрещаем сетевой вход через SSH для суперпользователя.

# vi /etc/ssh/sshd_config

....
# Отключаем возможность удалённого входа для суперпользователя
PermitRootLogin no

# Запрещаем использование "пустых" паролей при подключении
PermitEmptyPasswords no

# Запрещаем передачу пользовательских переменных окружения
PermitUserEnvironment no

# Запрещаем перенаправление портов пользователя и транзит подключений (это конечный сервер, а не "шлюз")
GatewayPorts no
X11Forwarding no
....

# /etc/init.d/ssh reload

Добавляем аккаунты web-разработчиков.

По хорошему web-разработчику нечего делать на web-сервере и проекты должны выгружаться в рамках процессов CI/CD, но реальность такова, что на мелких сайтах работа ведётся с кодом напрямую. Потому добавляем нужных пользователей:

# useradd --shell /bin/bash --create-home userOne
# useradd --shell /bin/bash --create-home userTwo

Явно включаем пользователя в группу, от имени которой работает web-сервер:

# usermod --append --groups www-data userOne
# usermod --append --groups www-data userTwo

Включаем для пользователя web-разработчика возможность через SUDO работать в окружении привелегий и переменных "www-data":

# visudo

....
userOne ALL=(www-data:www-data) NOPASSWD: ALL
userTwo ALL=(www-data:www-data) NOPASSWD: ALL

Таким образом, например, web-разработчику можно будет изменить параметры запуска задач по расписанию:

userOne@one:~$ sudo -u www-data crontab -l
userOne@one:~$ sudo -u www-data crontab -e

Файловая структура сайтов.

Обозначу сразу незаметную многим начинающим специалистам особенность: в Linux файлы и директории по умолчанию создаются с групповыми разрешениями доступа "только чтение" (общесистемная установка "umask 0022"). Отсюда истекает проблема, когда файл созданный web-сервером или PHP-интерпретатором оказывается недоступным для изменения web-разработчику этого сервера. По мере развития Linux это решалось разными способами, но на данный момент проще всего переопределить политику доступа применительно к структуре директорий с помощью надстройки над подсистемой регулирования доступа "POSIX ACL".

Предпишем устанавливать всем создаваемым впредь файлам (и директориям) разрешения полного доступа как для владельца, так и группы, при этом ограничиваем чтение и запись всем остальным:

# setfacl --no-mask --set default:user::rwX,default:group::rwX,default:other:--X /var/www

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

Проверить установки можно следующим способом:

# getfacl /var/www

Исполняемое ядро CMS "Bitrix" может быть использовано для обслуживания нескольких сайтов. В простейшем случае программное ядро и директория загрузки объектов группы сайтов выносятся отдельно от таковых и подключаются через символические ссылки.

Разделяемый между сайтами "one.example.net" и "two.example.net" движок "Bitrix" (впоследствии к этим двум сайтам можно "прилеплять" дополнительные, по отработанной схеме):

# mkdir -p /var/www/shared/bitrix

Разделяемая между сайтами "one.example.net" и "two.example.net" директория для загрузок файлов:

# mkdir -p /var/www/shared/upload

Разделяемая между сайтами директория временных файлов:

# mkdir -p /var/www/tmp

И файловая структура поддержки сайтов:

# mkdir -p /var/www/one.example.net/www
# mkdir -p /var/www/one.example.net/log

# mkdir -p /var/www/two.example.net/www
# mkdir -p /var/www/two.example.net/log

Символическими ссылками подключаем директории разделяемых ресурсов внутрь сайтов:

# ln -s /var/www/shared/bitrix /var/www/one.example.net/www/bitrix
# ln -s /var/www/shared/bitrix /var/www/two.example.net/www/bitrix

# ln -s /var/www/shared/upload /var/www/one.example.net/www/upload
# ln -s /var/www/shared/upload /var/www/two.example.net/www/upload

Конечно же, закрываем доступ к данным сайтов всем посторонним:

# chown -R www-data:www-data /var/www
# chmod -R ug+rw /var/www/
# chmod -R o-rw /var/www/

Настройка PHP.

# aptitude install php7.0-fpm php7.0-cgi php7.0-cli php7.0-opcache php7.0-mysqli php-memcache php7.0-mbstring php-pclzip php7.0-xml php7.0-sockets php7.0-mcrypt php7.0-json php7.0-gd php7.0-curl php-geoip php7.0-ldap php7.0-soap

Рассматривать здесь полную настройку PHP-интерпретатора не вижу смысла и обращу внимание лишь на особенности, необходимые для работы.

В настройках FPM-пула изменим ряд параметров для достижения лучшей производительности:

# vi /etc/php/7.0/fpm/pool.d/www.conf

....
; Блок описания отдельного инстанса PHP-FPM (их может быть несколько, обслуживающих разные web-сервисы)
[www]
....
; Явно переключаемся на работу через локальный файловый "сокет", существенно снижая задержки при вызовах (открытие файла всегда быстрее сетевой операции)
; listen = 127.0.0.1:9000
listen = /run/php/php7.0-fpm.sock
....
; Режим запуска инстанса
pm = dynamic
; Количество процессов, запускаемых при старте PHP-FPM
pm.start_servers = 20
; Максимальное количество процессов, которые могут быть запущены для обработки запросов
pm.max_children = 512
; Параметры количества запущенных неактивных процессов (находящихся в ожидании запросов)
pm.min_spare_servers = 5
pm.max_spare_servers = 20
; Количество запросов, после которого процесс будет перезапущен (для компенсации "утечек памяти" в скриптах)
pm.max_requests = 4096
....

CMS "Bitrix" требует от PHP явной установки ряда параметров. Здесь я обращаю внимание на то, что в современном Linux пакет интерпретатора PHP поставляется в виде трёх идеологически разделённых компонентов: PHP-FPM, PHP-CGI и PHP-CLI - каждый из которых настраивается индивидуальными конфигурационными файлами, изначально идентичными. Мне представляется наиболее простым сконфигурировать один набор параметров и распространить их на все компоненты PHP перезаписью файла (одно время я пытался делать это через указание на "основной" файл настроек символическими ссылками, но практика показала, что отдельные файлы лучше вписываются в идеологию дистрибуции и обновления).

Возьмём за основу конфигурационный файл PHP-FPM (как наиболее активно используемый):

# vi /etc/php/7.0/fpm/php.ini

....
cgi.fix_pathinfo = 0
allow_url_fopen = Off
....
short_open_tag = On
....
date.timezone = Asia/Novosibirsk
....
memory_limit = 1024M
....
max_execution_time = 300
max_input_time = 300
post_max_size = 200M
upload_max_filesize = 200M
max_file_uploads = 100
max_input_vars = 10000
....
; Отключаем новый и пока невостребованный функционал PHPv7, некстати перекрывающее привычные настройки PCRE
pcre.jit = 0
....
pcre.backtrack_limit = 100000
pcre.recursion_limit = 100000
....
mbstring.default_charset = UTF-8
mbstring.internal_encoding = UTF-8
mbstring.detect_order = "UTF-8"
mbstring.encoding_translation = on
mbstring.func_overload = 2
mbstring.strict_detection = on
....
opcache.enable = 1
opcache.memory_consumption = 256
opcache.max_accelerated_files = 500000
opcache.validate_timestamps = 1
opcache.use_cwd = 1
opcache.revalidate_path = 1
opcache.revalidate_freq = 0
....
session.gc_maxlifetime = 864000
....

Всегда есть смысл проверять, какие появились изменения в конфигурационных файлах - вдруг кто-то уже внёс точечные правки:

# diff /etc/php/7.0/fpm/php.ini /etc/php/7.0/cgi/php.ini

...или:

# vimdiff /etc/php/7.0/fpm/php.ini /etc/php/7.0/cgi/php.ini

После того, как мы удостоверились, что перезапись конфигурационных файлов сервисам не повредит, делаем это:

# cp /etc/php/7.0/fpm/php.ini /etc/php/7.0/cgi/php.ini
# cp /etc/php/7.0/fpm/php.ini /etc/php/7.0/cli/php.ini

Если всё-таки потребуется перекрыть какой-то параметр индивидуально компоненту PHP, то для этого есть директории динамически подключаемых конфигурационных файлов, перекрывающих основной - такие как "/etc/php/7.0/cgi/conf.d/".

Перезапускаем сервис или велим ему принять новую конфигурацию, по ситуации:

# /etc/init.d/php7.0-fpm restart
# /etc/init.d/php7.0-fpm reload

Удовлетворяем ресурсные аппетиты Bitrix-сайтов.

CMS "Bitrix" даже в представлениях его разработчиков настолько прожорлив и неоптимизирован, что для удовлетворения требованиям PCRE-функциональности теста "самопроверки" (site_checker.php) размер доступного PHP "стека" приходится увеличивать в десять тысяч (!!!) раз.

Это делается двумя путями - просто для "эпохи до Systemd" и неудобно для "после Systemd".

Прежде всего узнаём текущее значение размера стека (в Байтах), хотя бы и для того, чтобы потом зафиксировать изменения:

# su www-data --shell /bin/bash --command "ulimit -s"

В рамках классического "unix-way" задаём новые ограничения пользователю, от имени которого запускаются наши web-приложения:

# vi /etc/security/limits.conf

# <domain> <type> <item> <value>
....
www-data   -      stack  81920000

Для приложений, запускаемых через игнорирующий системные лимиты "Systemd" придётся создавать индивидуальные конфигурации.

Уточняем наименование сервиса, в рамках которого запущен PHP-FPM:

# systemctl list-units --type=service | grep -i php

php7.0-fpm.service  loaded active running The PHP 7.0 FastCGI Process Manager

Просматриваем параметры текущей конфигурации сервиса:

# systemctl cat php7.0-fpm.service

Теперь можно воспользоваться встроенной функциональностью "systemctl edit php7.0-fpm.service" для дополнения существующей конфигурации, но я предпочитаю вручную создать в соответствующем месте директорию для автоматически включаемых сервисом дополнительных файлов конфигурации с удобным мне именем файла:

# mkdir -p /etc/systemd/system/php7.0-fpm.service.d

# vi /etc/systemd/system/php7.0-fpm.service.d/ulimit.conf

[Service]
LimitSTACK=81920000:81920000

Указываем "Systemd" перечитать и принять новый набор конфигураций, после чего перезапускаем сервис PHP-FPM:

# systemctl daemon-reload
# systemctl restart php7.0-fpm

Особо одарённые стремлением курочить системные настройки могут внести свои параметры лимитирования прямо в файлы конфигурации сервиса "/etc/init.d/php7.0-fpm" или "/lib/systemd/system/php7.0-fpm.service", но это "фу-фу"-путь.

Чтобы убедиться, что лимиты настроены верно и применяются к целевым приложениям, можно проверить параметры лимитов прямо из PHP-скрипта:

<?php
  echo shell_exec('ulimit -s');
?>

Оптимизация работы PHP с дисковой подсистемой.

Забежим немного вперёд. Почти всегда, когда на web-сервере появляется "Memcached" (мы его будем применять позже для оптимизации кеширования на уровне web-сайта), сразу заходит речь об его использовании в качестве скоростного хранилища сессий пользователя. Да, это быстрее, чем хранение файлов на традиционной файловой системе, но есть проблема - "Memcached" предназначен именно для кеширования и в нём не предусмотрена блокировка записей на момент её изменения. Это критично, на самом деле, и если в "движке" сайта не предусмотрено своих средств контроля актуальности сессии, то лучше бы "Memcached" для этого не использовать (а CMS "Bitrix" в файлах сессий хранит не только её идентификатор, но и некоторую сопутствующую информацию, до десятков килобайт в объёме). Есть более простой способ - вынести директорию файлов PHP-сессий в специально созданную директорию, смонтированную в область ОЗУ.

Место сохранения сессий в PHP определяется параметров "session.save_path" и по умолчанию оно располагается в директории "/var/lib/php/sessions". Точнее всего это выявляется через вывод функции "php_info()". Мне представляется самым простым смонтировать поверх этой директории кусочек "tmpfs":

# vi /etc/fstab

....
# Tuning PHP-sessions`s place
tmpfs  /var/lib/php/sessions  tmpfs  rw,nosuid,nodev,size=1024M,uid=www-data,gid=www-data,mode=0750  0  0
....

Монтируем или перемонтируем (если вносились изменения в уже существующую конфигурацию) "tmpfs"-файловую систему:

# mount /var/lib/php/sessions
# mount -o remount /var/lib/php/sessions

Настраиваем Nginx.

# aptitude install nginx apache2-utils

Прежде всего, для включения поддержки Nginx протокола HTTPv2 генерируем DH-сертификат:

# mkdir -p /etc/nginx/ssl/
# openssl dhparam -out /etc/nginx/ssl/dhparam.2048.pem 2048

Для очень старых ОС и браузеров (Windows XP IE6, Java 6) придётся снизить размер DH до 1024:

# openssl dhparam -out /etc/nginx/ssl/dhparam.1024.pem 1024

Конечно же защищаем директорию SSL-сертификатов от посторонних:

# chmod -R go-rwx /etc/nginx/ssl*

Слегка корректируем глобальную конфигурацию web-сервиса:

# vi /etc/nginx/nginx.conf

# Явно запускаем Nginx в рамках привилегий пользователя "www-data" и такой же группы "www-data"
user www-data www-data;
....
worker_processes 24;
....
events {
  worker_connections 1024;
  ....
}

http {
  ....
  tcp_nodelay on;
  tcp_nopush on;

  # Запрещаем web-серверу сообщать о себе подробные данные
  server_tokens off;

  # Запрещаем просмотр содержимого директории, если не указан целевой файл
  autoindex off;

  # Отключаем проверку размера тела передаваемого PHP-FPM запроса
  client_max_body_size 0;

  client_body_buffer_size 4M;
....

Описываем конфигурацию типового web-сайта под управлением CMS "Bitrix":

# vi /etc/nginx/sites-available/one.example.net.conf

# Перехватываем все запросы по протоколу без шифрования и перенаправляем их на HTTPS
server {
  listen 80 default_server;
  server_name one.example.net www.one.example.net;
  access_log off;
  error_log  off;
  location / {
    rewrite ^ https://one.example.net$request_uri permanent;
  }
}

# Перехватываем запросы к имени нежелательного формата и перенаправляем к нужному
server {
  listen      443 ssl http2;
  server_name www.one.example.net;

  access_log off;
  error_log  off;

  ssl on;
  ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
  ssl_prefer_server_ciphers on;
  ssl_dhparam         /etc/nginx/ssl/dhparam.2048.pem;
  ssl_certificate     /etc/nginx/ssl/one.example.net.crt;
  ssl_certificate_key /etc/nginx/ssl/one.example.net.key.decrypt;
  ssl_session_cache shared:SSL:30m;
  ssl_session_timeout 1h;
  ssl_stapling on;
  add_header Strict-Transport-Security max-age=15768000;

  location / {
    rewrite ^ https://one.example.net$request_uri permanent;
  }
}

# Описывем рабочее окружение web-сайта как такового
server {
  listen      443 ssl http2 default_server;
  server_name one.example.net;

  access_log /var/www/one.example.net/log/access.log;
  error_log /var/www/one.example.net/error.log;

  # SSL Configuration
  ssl on;
  ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
  # Old backward compatibility (Windows XP IE6, Java 6)
  ssl_ciphers HIGH:SEED:AES128-SHA:AES256-SHA:DES-CBC3-SHA:RC4-SHA:RC4-MD5:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!RSAPSK:!aDH:!aECDH;
  ssl_prefer_server_ciphers on;
  ssl_dhparam         /etc/nginx/ssl/dhparam.2048.pem;
  ssl_certificate     /etc/nginx/ssl/one.example.net.crt;
  ssl_certificate_key /etc/nginx/ssl/one.example.net.key.decrypt;
  # SSL Caching
  ssl_session_cache shared:SSL:30m;
  ssl_session_timeout 1h;
  # SSL Verify Optional
  ssl_stapling on;
  # SSL Strict Optional
  add_header Strict-Transport-Security max-age=15768000;

  # Выключаем невостребованную обычно перекодировку контента
  charset off;

  # Задаём переменную с многократно используемым параметром PHP-FPM
  set $php_pass unix:/run/php/php7.0-fpm.sock;

  # Явно указываем корень файловой структуры сайта, выше которой web-сервер не должен выходить
  root /var/www/one.example.net/www;

  # Задаём перечень файлов, которые web-сервер должен выдать в отсутствии явного указания со стороны клиента
  index index.html index.htm index.php;

  # Подставляем свою страницу обработки некоторых ошибок сервиса
  error_page 500 502 503 504 /maintenance.html;
  error_page 403 /403.html;

  # Глобальный обработчик событий отсутствия запрашиваемого файла
  location / {
    try_files $uri $uri/ @bitrix;
  }

  # Блокируем доступ к типовым "закрытым" ресурсам
  location ~* (/\.ht|/\.hg|/\.svn|/\.git|/\.enabled|/\.config) {
    deny all;
    log_not_found off;
    access_log off;
  }

  # Блокируем доступ извне к внутренностям CMS "Bitrix"
  location ~* /(bitrix/modules|bitrix/managed_cache|bitrix/local_cache|bitrix/stack_cache|bitrix/backup|bitrix/tmp|upload/support/not_image|bitrix/php_interface|local/php_interface) {
    deny all;
    log_not_found off;
    access_log off;
  }

  # Разрешаем доступ в административную панель Bitrix только избранным
  location ~* ^/bitrix/admin/.* {

    # Разрешаем доступ только с определённого перечня IP-адресов
    satisfy all;
    allow 1.2.3.4/24;
    deny  all;

    # Обрабатываем PHP-скрипты, доступные только после успешной аутентификации
    location ~* \.php$ {
      try_files     $uri @bitrix;
      include       /etc/nginx/fastcgi_params;
      fastcgi_pass  $php_pass;
      fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }

    # Необработанные запросы передаём разборщику ошибок Bitrix
    try_files $uri $uri/ @bitrix;
  }

  # Включаем "HTTP Basic Authentication" для доступа к REST-API
  location ~* ^/(rest|web_client)/.* {

    # Разрешаем доступ только успешно аутентифицированным
    satisfy all;
    auth_basic           "Restricted";
    auth_basic_user_file $document_root/.htpasswd;

    # Обрабатываем PHP-скрипты, доступные только после успешной аутентификации
    location ~* \.php$ {
      try_files     $uri @bitrix;
      include       /etc/nginx/fastcgi_params;
      fastcgi_pass  $php_pass;
      fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }

    # Без дополнительной обработки отдаём чистый код ошибки (это API, и путать его перенаправлениями неправильно)
    try_files $uri $uri/ =404;
  }

  # Обработчик прямых обращений к PHP-скриптам
  location ~* \.php$ {
    try_files     $uri @bitrix;
    include       /etc/nginx/fastcgi_params;
    fastcgi_pass  $php_pass;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
  }

  # Обработчик ошибок при доступе к ресурсам (таким образом в "Битрикс" реализована поддержка ЧПУ)
  location @bitrix {

    # Специфичная для Bitrix SEO-процедура подстановки к URL завершающего "/"
    rewrite ^/(.+[^/])$ /$1/ permanent;

    include       /etc/nginx/fastcgi_params;
    fastcgi_pass  $php_pass;
    fastcgi_param SCRIPT_FILENAME $document_root/bitrix/urlrewrite.php;
  }

  # Не реагируем на неинтересные события загрузок
  location = /(404.html|favicon.ico|robots.txt|sitemap.xml) {
    log_not_found off;
    access_log off;
  }

  # Напрямую отдаём "статические" данные, предлагая браузеру сохранить их в своём "кеше", и не фиксируем в журнале эти события
  location ~* ^.+\.(jpg|jpeg|gif|png|svg|js|css|mp3|ogg|mpe?g|avi|zip|gz|bz2?|rar|swf|xml|txt)$ {
    try_files  $uri @bitrix;
    expires    30d;
    access_log off;
  }
}

Очевидно, что конфигурация Nginx для другого сайта будет отличатся лишь в части параметров включающих имя сайта "one.example.net", которое следует заменить на нужное.

Активируем конфигурацию:

# rm /etc/nginx/sites-enabled/default
# ln -s /etc/nginx/sites-available/one.example.net.conf /etc/nginx/sites-enabled/one.example.net.conf
# ln -s /etc/nginx/sites-available/two.example.net.conf /etc/nginx/sites-enabled/two.example.net.conf

Перед принятием в работу новой конфигурации обязательно просим Nginx проверить её синтаксическую корректность:

# nginx -t
# /etc/init.d/nginx reload

Старые клиентские системы и новые требования к SSL/TLS.

После перехода на современное серверное программное обеспечение Nginx и OpenSSL работающие на "MS Windows XP" клиентские web-браузеры могут потерять возможность пройти этап согласования SSL/TLS-подключения, так как они пытаются использовать методы шифрования уже признанные уязвимыми и всячески блокируемые на стороне web-сервиса, а клиенты в свою очередь могут ещё не знать о предлагаемых web-сервисом методах шифрования - в итоге подключение может не состоятся. При этом обновление клиентского ПО может как улучшить ситуацию с доступностью, так и ухудшить её, в зависимости от того, как изменится комбинация возможностей поддержки технологий шифрования операционной системы и web-браузера. Вопросы клиент-серверной совместимости рассматриваются по ссылке ниже, но и после исполнения рекомендаций работа старых web-браузеров с современными web-серверами через SSL/TLS всё равно не гарантируется:


Наладка "HTTP Basic Authentication" посредством Nginx.

Если осуществляется полный переход с web-сервера "Apache" на "Nginx", то наверняка потребуется воспроизвести логику файлов ".htaccess", ".htpasswd" и ".htsecure" в части аутентификации пользователей при обращении к закрытым ресурсам.

Прежде всего ищем файлы точечной настройки и изучаем их функционал:

# find /var/www -name ".htaccess" -print0 | xargs --null -I {} echo {}
# find /var/www -name ".htpasswd" -print0 | xargs --null -I {} echo {}
# find /var/www -name ".htsecure" -print0 | xargs --null -I {} echo {}

Особо важно не упустить блоков аутентификации средствами web-сервера при доступе к ресурсам, что-то вроде такого:

# find /var/www -name ".htaccess" -print0 | xargs --null -I {} grep --with-filename --line-number -i ".htpasswd" {}

/var/www/one.example.net/www/rest/api/.htaccess:3:AuthUserFile /var/www/one.example.net/www/.htpasswd

Подсистема "HTTP Basic Authentication" использует шифрование паролей на грани минимально допустимого с точки зрения устойчивости к взлому, но иногда приходится поддерживать совместимость со старыми протоколами, так что генерируем из желаемого пароля специфичного типа "хеш" (MD5-based password algorithm, Apache variant):

# openssl passwd -apr1 [password]

Полученный "хеш" подкладываем в файл с перечнем пользователей, доступ которым разрешается:

# vi ./.htpasswd

# <userName>:<apache-MD5-Hash-of-userPassword>:<Comment>
userOne:$...:REST-API
....

Настройка СУБД.

Подойдёт как MySQL, так и её "форк" - MariaDB:

# aptitude install mariadb-server

Рекомендую сразу запустить утилиту конфигурирования уровня безопасности СУБД:

# /usr/bin/mysql_secure_installation

....
Remove anonymous users? [Y/n] Y
... Success!
....
Remove test database and access to it? [Y/n] Y
- Dropping test database...
... Success!
- Removing privileges on test database...
... Success!
....
Reload privilege tables now? [Y/n] Y
... Success!

Как и с PHP-интерпретатором "Битрикс" хочет от MySQL определённого. Предоставляем ему это:

# vi /etc/mysql/mariadb.cnf

....
[mysqld]
....
tmpdir = /var/lib/mysql/tmp
innodb_tmpdir = /var/lib/mysql/tmp

# Обязательно включаем приём подключений через файловый "сокет", существенно снижая операционные задержки
socket = /var/run/mysqld/mysqld.sock

# Разрешаем подключаться только с "localhost"
bind-address = 127.0.0.1

# Читаем документацию и подбираем подходящие параметры работы с доступной памятью
table_cache = 512
....
tmp_table_size = 512M
max_heap_table_size = 512M
....
query_cache_limit = 10M
query_cache_size = 256M
key_buffer_size = 512M // ~20% RAM
....
innodb_buffer_pool_size = 1G // ~60% RAM
innodb_log_file_size = 256M
innodb_flush_log_at_trx_commit = 2
....
innodb_read_io_threads = 16
innodb_write_io_threads = 16

# Явно указываем создавать для каждой таблицы отдельные файлы описаний (.frm) и данных (.ibd) на диске, а не сваливать всё в один (по умолчанию "ibdataX")
innodb_file_per_table = 1
....

Параметры MySQL/MariaDB раскиданы по нескольким файлам в директории "/etc/mysql", так что есть смысл убедится, не переопределяются ли они где-то в "/etc/mysql/mariadb.conf.d/".

Для применения изменений перезапускаем СУБД или даём команду перечитать изменения, по необходимости:

# /etc/init.d/mysql restart
# /etc/init.d/mysql reload

Проконтролировать фактическое применение параметров можно через SQL-запросы:

# mysql -u root -p

MySQL> SHOW VARIABLES WHERE `Variable_name` LIKE '%tmpdir%';
+-------------------+--------------------+
| Variable_name     | Value              |
+-------------------+--------------------+
| innodb_tmpdir     | /var/lib/mysql/tmp |
| slave_load_tmpdir | /var/lib/mysql/tmp |
| tmpdir            | /var/lib/mysql/tmp |
+-------------------+--------------------+
3 rows in set (0.00 sec)

Оптимизация работы MySQL с дисковой подсистемой.

Для СУБД MySQL, активно создающей и уничтожающей файлы для временных таблиц, выгодно вынести (параметром "tmpdir") эту работу в файловую систему, смонтированную в область памяти ОЗУ:

# mkdir -p /var/lib/mysql/tmp
# chown -R mysql:mysql /var/lib/mysql/tmp
# chmod -R go-rwx /var/lib/mysql/tmp

Добавляем в системный перечень монтируемых файловых систем нашу:

# vi /etc/fstab

....
# Tuning the location of MySQL temporary files
tmpfs  /var/lib/mysql/tmp  tmpfs  rw,nosuid,nodev,size=2G,uid=mysql,gid=mysql,mode=0750  0  0
....

Монтируем или перемонтируем (если вносились изменения в уже существующую конфигурацию) нашу файловую систему:

# mount /var/lib/mysql/tmp
# mount -o remount /var/lib/mysql/tmp

Я бы выделил под эту файловую систему до 25% от объёма ОЗУ (она не заблокирует всё заявленное место, а будет выбирать блоки памяти по мере появления необходимости).

Разворачиваем БД для сайта.

Прежде всего потребуется создать "базу данных" и пользователя, от имени которого сайт будет обращаться к таковой:

# mysql -u root -p

MySQL> CREATE DATABASE `one_example_net` CHARACTER SET utf8 COLLATE utf8_unicode_ci;
MySQL> CREATE USER 'user_one'@'localhost' IDENTIFIED BY 'site_db_password';
MySQL> GRANT ALL PRIVILEGES ON one_example_net.* TO 'user_one'@'localhost';

Снимаем "дамп базы" MySQL исходного сервера (откуда осуществляется перенос сайта) и применяем его на новом сервере СУБД:

# mysqldump -u user_one -p one_example.net > /path/to/file/dump-one.example.net.sql
# mysql -u user_one -p one_example_net < /path/to/dump-one.example.net.sql

Возможная проблема с кодировкой.

В новых инсталляциях на "Linux Debian 8/9" или "Linux Ubuntu 15/16/17" может случится так, что кодировка базы, таблиц или отдельных колонок установится в неподходящую для CMS "Bitrix" кодировку "utf8mb4" - в таком случае следует явно заменить её на обычный "utf8".

Для базы в целом (будет наследоваться после при создании новых таблиц):

MySQL> ALTER DATABASE database_name CHARACTER SET = utf8 COLLATE = utf8_unicode_ci;

Для таблицы (будет наследоваться при создании новых колонок):

MySQL> ALTER TABLE table_name CHARACTER SET utf8 COLLATE utf8_unicode_ci;

Если таблица имеет внутри колонку в неверной кодировке, то можно это исправить за один проход, воздействуя на всю таблицу:

MySQL> ALTER TABLE table_name CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;

...или воздействовать точечно на нужную колонку таблицы:

MySQL> ALTER TABLE table_name CHANGE column_name column_name VARCHAR(45) CHARACTER SET utf8 COLLATE utf8_unicode_ci;

Настраиваем "Memcached".

CMS "Bitrix" весьма запутанная система, с массой подключаемых модулей, генерирующих большое количество блоков данных, используемых при генерации итоговых страниц из шаблонов. Вся эта неоптимальная деятельность сильно сказывается на отзывчивости сайта, терзая дисковую подсистему записью и считыванием информационного мусора, тормозя работу всех сервисов сервера в целом. Хорошо хоть часть этого промежуточного материала можно сохранять в кеширующем сервисе вроде "memcached" или "Redis":

# aptitude install memcached

# vi /etc/memcached.conf

....
# Имя пользователя, для которого запускается сервис
-u www-data

# Количество одновременных подключений (по умолчанию 1024)
-c 1024

# Объем выделяемой памяти для кеша (по умолчанию 64MB)
-m 1024m

# Количество потоков Memcached (по умолчанию 4)
-t 8

# Выключаем прослушивание сетевого TCP-порта
#-l 127.0.0.01
#-p 11211

# Включаем работу через локальный файловый "сокет"
-s /tmp/memcached.sock
....

По идее унификации точку включения в "сокет" надо бы расположить примерно здесь: "/run/memcached.sock" - однако "Memcached" не умеет вначале стартовать, строить себе окружение, а уже потом уходить в работу от непривелегированного пользователя, так что создать себе ресурс в "/run" он не может. Приходится для простоты оставлять "сокет" в доступной всем и вся директории временных файлов.

# /etc/init.d/memcached restart

Настраиваем подсистему отправки почты.

В самых простых сайтах функцию "mail()" PHP реализуют через системную утилиту "sendmail" или её заменители в MTA. Однако в сложных проектах со множеством профилей транзитных почтовых серверов удобнее использовать легковесного почтового клиента "mSMTP" - с ним можно как просто пересылать сообщения через прозрачный почтовый шлюз, так и подключаться к почтовым провайдерам вроде "GMail" с многоступенчатой авторизацией.

# aptitude install msmtp

Журнал регистрации событий утилита сама создавать не умеет, так что помогаем ей в этом:

# touch /var/log/msmtp.log
# chown www-data:www-data /var/log/msmtp.log

Утилита "msmtp" способна полностью эмулировать классический "sendmail", так что для совместимости с уже настроенным по умолчанию программным обеспечением есть смысл сделать символическую ссылку, направляющую обращения в нужное место:

# ln -s /usr/bin/msmtp /usr/sbin/sendmail

Для начала настроим один профиль, используемый по умолчанию:

# vi /etc/msmtprc

# Set default values for all following accounts.
defaults
auth     off
tls      off
logfile  /var/log/msmtp.log

# Default Account
account  default
host     mx.example.net
port     25
from     www-data@one.example.net

Проверяем, работает ли это непосредственно из CLI сервера:

# echo "Test" | msmtp --debug --account=default test@one.example.net

loaded system configuration file /etc/msmtprc
....
<-- 220 mx.example.net ESMTP Exim
--> EHLO localhost
....
<-- 250 OK id=1dT3vg-0002Vv-Q4
--> QUIT
<-- 221 mx.example.net closing connection

Явно указываем PHP отсылать почту через "mSMTP" (хотя в этом нет необходимости, если конфигурация адаптирована к "sendmail"):

# vi /etc/php/7.0/fpm/php.ini

....
; sendmail_path = "/usr/sbin/sendmail -t -i"
sendmail_path = "/usr/bin/msmtp -t -i"
....

Теперь можно проверить, отправится ли письмо посредством PHP-интерпретатора:

# sudo -u www-data /usr/bin/php -r "mail('test@one.example.net', 'Test', 'Test');"

Настройка подключения CMS "Bitrix" к БД.

В ядре CMS v14.5.2 добавлена полная поддержка современного драйвера "mysqli". Переводим сайт на "mysqli", заодно заменяя способ сетевого подключения на более скоростной - через локальный файловый "сокет". Делать это нужно отдельно для старого и нового ядер "Битрикс" (да, забавно, что они эксплуатируются одновременно - для обратной совместимости):

# vi ./bitrix/php_interface/dbconn.php

....
# Указываем не разрывать соединение после завершения запроса, используя его повторно
define("DELAY_DB_CONNECT", true);
....
# Переводим работу с СУБД на драйвер "mysqli"
$DBType = "mysql";
define("BX_USE_MYSQLI", true);
....
# Указываем точку подключения к СУБД (через локальный файловый "сокет")
// $DBHost = "localhost:3306";
$DBHost = ":/var/run/mysqld/mysqld.sock";

$DBLogin = "user_one";
$DBPassword = "user_password";
$DBName = "one_example_net";
$DBDebug = false;
$DBDebugToFile = false;
....

# vi ./bitrix/.settings.php

....
'connections' =>
  array (
    'value' =>
    array (
      'default' =>
      array (
//        'className' => '\\Bitrix\\Main\\DB\\MysqlConnection',
        'className' => '\\Bitrix\\Main\\DB\\MysqliConnection',
//        'host' => 'localhost:3306',
        'host' => ':/var/run/mysqld/mysqld.sock',
        'database' => 'one_example_net',
        'login' => 'user_one',
        'password' => 'user_password',
        'options' => 2,
      ),
    ),
    'readonly' => true,
  ),
....

Кто заглядывал в PHP-код ядра "Битрикс", тот не удивится, зачем разработчики этой CMS требуют выключения режима строгого следования SQL-стандартам при формировании запросов к БД. В официальной инструкции предлагается сделать это глобально, для всей СУБД, но я предпочитаю всем остальным сайтам предоставить возможность работы в нормальных условиях, а "Битриксу" дать битриксово:

# vi ./bitrix/php_interface/after_connect.php

<?
....
$DB->Query("SET sql_mode = ''");
$DB->Query("SET innodb_strict_mode = 0");
?>

# vi ./bitrix/php_interface/after_connect_d7.php

<?
$connection = \Bitrix\Main\Application::getConnection();
....
$connection->queryExecute("SET sql_mode = ''");
$connection->queryExecute("SET innodb_strict_mode = 0");
?>

Замена пароля подключения к БД.

CMS "Bitrix" хочет, чтобы "всё было безопасно" и требует пароль подключения к СУБД состоящий из символов разного регистра и включающий в себя цифры со спецсимволами. Удовлетворяем его пожелания.

Генерируем кучу паролей и выбираем понравившийся:

# pwgen --capitalize --symbols 12

В файлах "./bitrix/php_interface/dbconn.php" и "./bitrix/.settings.php" заменяем пароли в секциях подключения к СУБД, не сохраняя пока изменения - потом ниже приведёнными командами меняем пароль непосредственно в СУБД и сразу же сохраняем изменения в конфигурационных файлах, чтобы минимизировать время простоя сайтов:

# mysql -h localhost -u root -p

MySQL> USE mysql;
MySQL> SET PASSWORD FOR 'user_one'@'localhost' = PASSWORD('user_password');
MySQL> FLUSH PRIVILEGES;
MySQL> QUIT;

Настройка кеширования.

Включение возможности кеширования через "Memcached" в CMS "Bitrix" сводится к редактированию пары файлов - для старой версии движка и его нового варианта:

# vi ./bitrix/php_interface/dbconn.php

define("BX_CACHE_TYPE", "memcache");
define("BX_CACHE_SID", $_SERVER["DOCUMENT_ROOT"]."#s1");
define("BX_MEMCACHE_HOST", "unix:///tmp/memcached.sock");
define("BX_MEMCACHE_PORT", "0");

# vi ./bitrix/.settings.php

....
'cache' => array(
  'value' => array (
    'type' => 'memcache',
    'sid' => $_SERVER["DOCUMENT_ROOT"]."#s1",
    'memcache' => array(
      'host' => 'unix:///tmp/memcached.sock',
      'port' => '0',
    ),
  ),
  'readonly' => false,
),
....

Всё. Теперь в панели производительности Битрикс (http://site.com/bitrix/admin/perfmon_panel.php) должны увидеть подтверждение возможности использования этого типа кеша.

Особенность настройки FQDN.

CMS "Bitrix" активно использует для "самотестирования" обращения из своих скриптов к самим себе же, через обычные HTTP-запросы. Так вот, если в файле конфигурации локального "резольвинга" завязать доменные имена обслуживаемых сайтов на локальный IP (что часто случается на этапе первоначально наколенной разработки), то "самодиагностика" не состоится из-за сбоя авторизации при обращении к административной части сайта.

# vi /etc/hosts

127.0.0.1  localhost
....
# 127.0.0.1  one.example.net one - BAD WAY!

Адресация файловых систем.

Для работы CMS "Bitrix" в настройках сайтов (доступных через web-панель администрирования) необходимо явно указывать месторасположение файлов сайтов - без этого таковые "не взлетают".

Уборка мусора.

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

# find /var/www -name "web.config" -print0 | xargs --null -I {} rm {}
# find /var/www -name ".DS_Store" -print0 | xargs --null -I {} rm {}

Обновление "движка" CMS "Bitrix".

Отлично отрабатывается через web-интерфейс. Неоднократно обновлялись, например в последний раз с v16.5.11 до 17.0.8. Правда, при этом часто ломается функциональность модулей, но это нормально для "Битрикс"-а - пара недель переписки со службой поддержки и возможно вам пришлют заплатку.

Нагрузочное тестирование.

После запуска web-сайта на новой площадке возможно захочется протестировать, как скоро он сломается под возрастающей нагрузкой. Для этого можно воспользоваться утилиткой "Siege".

Вот так очень просто протестировать сервер в сценарии "250 пользователей 10 минут упорно ходят по сайту":

$ siege https://one.example.net -c250 -t10m

Ради интереса я запускал тестирование одновременно с нескольких рабочих станций, суммарно эмулирующих хождение по сайту пары тысяч пользователей - оно показало узкое место в недостаточном количестве не успевающих отрабатывать запросы потоков PHP-FPM - но в целом web-сервер под нагрузкой спокойно себя чувствует, даже будучи под завязку занят.


Заметки и комментарии к публикации:


Оставьте свой комментарий ( выразите мнение относительно публикации, поделитесь дополнительными сведениями или укажите на ошибку )