Applications: "Nginx", PHP-FPM, "NodeJS", "MySQL", "MongoDB", "Memcached", "mSMTP", "inCron", LXC и "Supervisord".
Задача: подготовить выделенный сервер для простого развёртывания площадок web-сайтов, по возможности автоматизировав процедуры создания таковых.
Я стараюсь конфигурировать сервисы строго следуя официальным руководствам разработчиков программного обеспечения, дополненными рекомендациями специалистов, имеющих опыт эксплуатации высоконагруженных информационных систем. Иначе говоря, серверы должны быть настроены таким образом, чтобы другой специалист мог прийти на них и продолжить работу в русле классических подходов.
Последовательность дальнейших действий такова:
1. Подготовка системного окружения.
2. Установка прикладного программного обеспечения.
3. Создание файловой структуры сайтов.
4. Обеспечение SFTP-доступа разработчикам web-сайтов.
5. Настройка PHP-интерпретатора и "пулов" PHP-FPM.
6. Подготовка LXC-контейнеров с "NodeJS".
7. Настройка web-сервера "Nginx" и создание конфигурации для сайта.
8. Настройка спарки сервисов "Supervisord" и "Memcached".
9. Настройка подсистемы отправки почты.
10. Поверхностная настройка СУБД "MySQL".
11. Поверхностная настройка СУБД "MongoDB".
12. Настройка спарки сервисов "inCron" и "Crontab".
13. Автоматизация рутинных процедур.
2. Установка прикладного программного обеспечения.
3. Создание файловой структуры сайтов.
4. Обеспечение SFTP-доступа разработчикам web-сайтов.
5. Настройка PHP-интерпретатора и "пулов" PHP-FPM.
6. Подготовка LXC-контейнеров с "NodeJS".
7. Настройка web-сервера "Nginx" и создание конфигурации для сайта.
8. Настройка спарки сервисов "Supervisord" и "Memcached".
9. Настройка подсистемы отправки почты.
10. Поверхностная настройка СУБД "MySQL".
11. Поверхностная настройка СУБД "MongoDB".
12. Настройка спарки сервисов "inCron" и "Crontab".
13. Автоматизация рутинных процедур.
Подготовка системного окружения.
Обновляем программное обеспечение и устанавливаем набор полезных утилит:
# apt-get update && apt-get upgrade
# apt-get install aptitude coreutils sudo acl psmisc net-tools dnsutils host htop iotop tree findutils mc vim pwgen ntpdate dirmngr nfs-client socat
# apt-get install aptitude coreutils sudo acl psmisc net-tools dnsutils host htop iotop tree findutils mc vim pwgen ntpdate dirmngr nfs-client socat
Удаляем ненужные нам подсистемы:
# aptitude purge snapd lxd lxd-client open-iscsi mdadm
Если устанавливается "Linux Ubuntu", то лучше бы отключить автоматическое обновление компонентов системы - неприятно бывает обнаружить, что через полгода директория "/boot" забита новыми версиями ядра, установка которых явно не разрешалась.
Устанавливаем точное системное время:
# echo "Asia/Novosibirsk" > /etc/timezone
# rm /etc/localtime && ln -sf /usr/share/zoneinfo/Asia/Novosibirsk /etc/localtime
# rm /etc/localtime && ln -sf /usr/share/zoneinfo/Asia/Novosibirsk /etc/localtime
Для корректировки отклонений внутренних часов заведём регулярную (через три часа) сверку с общемировым временем:
# vi /etc/crontab
....
0 */3 * * * root ntpdate asia.pool.ntp.org &
0 */3 * * * root ntpdate asia.pool.ntp.org &
Ненужную нам подсистему синхронизации времени посредством "Systemd" отключаем:
# systemctl disable systemd-timesyncd
# systemctl stop systemd-timesyncd
# systemctl stop systemd-timesyncd
В свежеустановленном Linux-сервере запросто может не оказаться полноценной поддержки "национальной кодировки" (в нашем случае - русской). Обычно на новых серверах я не трачу время на проверки, а сразу перехожу в режим конфигурирования, добавляя нужные кодировки и удаляя невостребованные:
# dpkg-reconfigure locales
Естественно, кроме всего прочего, активируем пункт "ru_RU.utf8". В качестве варианта "по умолчанию", применяемого для приложений не требующих определённой кодировки, я выбираю "en_US.UTF-8".
Удостоверяемся в успешности изменения перечня поддерживаемых кодировок:
# locale -a
C.UTF-8
en_US.utf8
ru_RU.utf8
en_US.utf8
ru_RU.utf8
Защита административного входа.
Сразу после инсталляции базовых компонентов операционной системы запрещаем сетевой вход через SSH для суперпользователя.
# vi /etc/ssh/sshd_config
....
# Отключаем возможность удалённого входа для суперпользователя
PermitRootLogin no
# Запрещаем использование "пустых" паролей при подключении
PermitEmptyPasswords no
# Запрещаем передачу пользовательских переменных окружения
PermitUserEnvironment no
# Запрещаем перенаправление портов пользователя и транзит подключений (это конечный сервер, а не "шлюз")
GatewayPorts no
X11Forwarding no
....
# Отключаем возможность удалённого входа для суперпользователя
PermitRootLogin no
# Запрещаем использование "пустых" паролей при подключении
PermitEmptyPasswords no
# Запрещаем передачу пользовательских переменных окружения
PermitUserEnvironment no
# Запрещаем перенаправление портов пользователя и транзит подключений (это конечный сервер, а не "шлюз")
GatewayPorts no
X11Forwarding no
....
Предварительно проверяем корректность изменений конфигурационного файла:
# sshd -t
Перезапускаем OpenSSH-сервер:
# /etc/init.d/ssh reload
Установка программного обеспечения.
Устанавливаем наборы пакетов задействуемых в дальнейшем приложений. Полезно сделать это заранее, чтобы работать уже с полным набором автоматически созданных инсталляторами необходимых директорий и файлов.
Устанавливаем web-сервер "Nginx":
# aptitude install nginx apache2-utils
Устанавливаем PHP-интерпретатор:
# aptitude install php7.2-fpm php7.2-cgi php7.2-cli php7.2-opcache php7.2-pgsql php7.2-mysqli php-memcache php-mongodb php7.2-mbstring php-pclzip php7.2-xml php-sockets php7.2-json php7.2-gd php7.2-curl php-geoip php7.2-ldap php7.2-zip php7.2-xmlrpc php7.2-soap php7.2-intl php7.2-imap
Устанавливаем утилиты отправки почты через преднастроенный шлюз:
# aptitude install msmtp
Устанавливаем СУБД "MySQL" (в "Linux Ubuntu" это "Percona MySQL"):
# aptitude install mysql-server
Устанавливаем СУБД "MongoDB":
# aptitude install mongodb
Устанавливаем NoSQL-сервер "Memcached":
# aptitude install memcached
Устанавливаем сервис запуска и контроля статуса приложений "Supervisord":
# aptitude install supervisor
Устанавливаем сервис отслеживания изменений файлов и запланированной реакции на них:
# aptitude install incron
Создание опорной файловой структуры сайтов.
Наверняка корень файловой структуры для web-сайтов уже имеется - но не помешает профилактически его обновить:
# mkdir -p /var/www
# chown root:root /var/www && chmod go+rx /var/www && chmod go-w /var/www
# chown root:root /var/www && chmod go+rx /var/www && chmod go-w /var/www
Через "POSIX ACL" предпишем устанавливать всем создаваемым файлам (и директориям) ниже по иерархии разрешения полного доступа как для пользователя, так и группы (в отличии от системных установок "umask 0022", разрешающим на уровне группы только чтение), при этом полностью убираем доступ всем остальным:
# setfacl --set default:user::rwX,default:group::rwX,default:other:--- /var/www
После задания глобальных параметров доступа файловой структуры набора сайтов, для просмотра списка сайтов разрешаем заходить в корень структуры всем (но не глубже - правило не рекурсивное):
# setfacl --modify group::rX,other:rX /var/www
Никогда не мешает проконтролировать результаты предпринятых действий:
# getfacl /var/www
Создание файловой структуры групп сайтов.
Для набора web-сайтов, объединённых какой-то логикой (например особенностями конфигурации PHP, используемыми общими ресурсами или уровнями безопасности), создаём файловые структуры играющие роль сводных площадок (запрещая при этом кому бы то ни было изменять корневую структуру директорий):
# mkdir -p /var/www/group0
# setfacl --modify user::rX,group::rX /var/www/group0
# setfacl --modify user:www-data:X /var/www/group0
# setfacl --modify user::rX,group::rX /var/www/group0
# setfacl --modify user:www-data:X /var/www/group0
Для каждой сводной площадки создаём необходимые служебные директории:
# mkdir -p /var/www/group0/conf
# mkdir -p /var/www/group0/home
# mkdir -p /var/www/group0/log
# mkdir -p /var/www/group0/mnt
# mkdir -p /var/www/group0/tmp
# mkdir -p /var/www/group0/home
# mkdir -p /var/www/group0/log
# mkdir -p /var/www/group0/mnt
# mkdir -p /var/www/group0/tmp
Заранее добавляем опорного пользователя (и одноимённую группу), от имени которого будут работать сайты сводной площадки:
# useradd --system --shell /bin/false --no-create-home --home-dir /var/www/group0/home --user-group www-group0
Передаём файловую структуру "группы сайтов" во владение опорному пользователю:
# chown -R www-group0:www-group0 /var/www/group0
Удовлетворяя требованиям OpenSSH/SFTP-сервера корневую директорию (и только её, не рекурсивно) сводной площадки группы web-ресурсов передаём в собственность суперпользователю:
# chown root:www-group0 /var/www/group0
# chmod g+rx /var/www/group0
# chmod g+rx /var/www/group0
Если этого не сделать, то "SSHd" откажется принимать подключения со следующим уведомлением в журнале событий:
fatal: bad ownership or modes for chroot directory "/var/www/group0"
Создание файловой структуры сайтов.
Добавляем директории web-сайтов как таковых:
# mkdir -p /var/www/group0/site.example.net
# mkdir -p /var/www/group0/site.example.net/nodejs
# mkdir -p /var/www/group0/site.example.net/www
# mkdir -p /var/www/group0/site.example.net/nodejs
# mkdir -p /var/www/group0/site.example.net/www
Переводим файловую структуру сайта во владение опорного пользователя (рекурсивно, для всех поддиректорий):
# chown -R www-group0:www-group0 /var/www/group0/site.example.net
# chmod -R ug+rw /var/www/group0/site.example.net
# setfacl --recursive --modify user::rwX,group::rwX,other:--- /var/www/group0/site.example.net
# chmod -R ug+rw /var/www/group0/site.example.net
# setfacl --recursive --modify user::rwX,group::rwX,other:--- /var/www/group0/site.example.net
Защищаем от изменений структуры корневую директорию сайта, разрешая всем только чтение содержимого:
# setfacl --modify user::rX,group::rX,other:--- /var/www/group0/site.example.net
Предоставляя доступ к файловой структуре сайтов web-серверу "Nginx", разрешаем пользователю, от имени которого он запускается, чтение содержимого предназначенной к публикации директории:
# setfacl --modify user:www-data:X /var/www/group0/site.example.net
# setfacl --recursive --modify user:www-data:rX /var/www/group0/site.example.net/www
# setfacl --recursive --modify default:user:www-data:rX /var/www/group0/site.example.net/www
# setfacl --recursive --modify user:www-data:rX /var/www/group0/site.example.net/www
# setfacl --recursive --modify default:user:www-data:rX /var/www/group0/site.example.net/www
Наладка ротации файлов журналов событий web-сайтов.
Как уже понятно из заготавливаемой файловой структуры, где мы при каждой группе сайтов создаём директорию "/var/www/group0/log", журналы событий предполагается хранить где-то здесь, рядом с сайтами как таковыми и в лёгкой доступности web-разработчикам. Дабы журналы не росли бесконтрольно, настроим их автоматическое урезание и ротацию:
# mkdir -p /etc/logrotate.d
# vi /etc/logrotate.d/web
# vi /etc/logrotate.d/web
/var/www/*/log/*.log {
# обрезаем файл по достижению заданного размера
size 30M
# отсутствие файла не должно вызывать ошибку
missingok
# не отрабатываем пустые файлы
notifempty
# количество хранимых отработанных резервных копий
rotate 5
# сжимаем отрабатываемые резервные копии для экономии места
compress
# не сжимаем первую резервную копию, делая это при повторном проходе
delaycompress
# указываем копировать данные журнала в архивный и зачищать действующий (что не потребует перезапуска приложения для перехода на новый файл)
copytruncate
# указываем пользователя, от имени которого мы манипулируем файлами журналов (при этом сохраняются разрешения исходного файла журнала)
su root root
}
# обрезаем файл по достижению заданного размера
size 30M
# отсутствие файла не должно вызывать ошибку
missingok
# не отрабатываем пустые файлы
notifempty
# количество хранимых отработанных резервных копий
rotate 5
# сжимаем отрабатываемые резервные копии для экономии места
compress
# не сжимаем первую резервную копию, делая это при повторном проходе
delaycompress
# указываем копировать данные журнала в архивный и зачищать действующий (что не потребует перезапуска приложения для перехода на новый файл)
copytruncate
# указываем пользователя, от имени которого мы манипулируем файлами журналов (при этом сохраняются разрешения исходного файла журнала)
su root root
}
Проверяем корректность конфигурационного файла:
# logrotate -d /etc/logrotate.d/web
Обеспечение доступа к файловым ресурсам разработчикам web-сайтов.
Добавляем пользователя, от имени которого будет подключаться web-разработчик сайта (таких может быть несколько, разумеется), обязательно делая его членом опорной группы пользователей целевого сайта:
# useradd --shell /bin/bash --no-create-home --home-dir /var/www/group0/home --gid www-group0 developer0-group0
Для корректного назначения разрешений в файловой системе очень важно, чтобы у подключающегося к серверу для редактирования файлов сайта пользователя основной (первичной, указанной в "/etc/passwd") группой была выделенная для этого сайта. Мы это задали при создании пользователя, но, перестраховываясь, явно указываем пользователю нужную группу:
# usermod --gid www-group0 developer0-group0
Чтобы дать пользователю возможность читать журналы событий web-сервера, вводим его в группу такового:
# usermod --append --groups www-data developer0-group0
Доступ web-разработчиков к ресурсам сайтов будем обеспечивать с помощью SFTP, функции сервера "OpenSSH". Как правило, в абсолютном большинстве случаев, web-разработчикам достаточно возможности правки файлов только их web-ресурса. Мало того, на моей практике программисты PHP/Python способны лишь сломать выстроенную работающую структуру, но никогда не могут её починить или улучшить. Потому, во избежание, замыкаем их в "chroot"-е, предоставляя доступ только к файловой системе их web-ресурсов.
Исходя из того, что сервер "OpenSSH" уже работает и настроен, вносим в конфигурацию лишь необходимые изменения:
# vi /etc/ssh/sshd_config
....
# Запрещаем пользователю передачу своих переменных окружения
PermitUserEnvironment no
# Устанавливаем режим проверки права доступа пользователя к целевым объектам
StrictModes yes
# Включаем дополнительную функциональность SFTP-сервиса в OpenSSH (отключаем перевод на внешнее приложение, активируя встроенные возможности)
# Subsystem sftp /usr/lib/openssh/sftp-server
Subsystem sftp internal-sftp
....
# Запираем пользователей группы "www-group0" в пределах директории их web-ресурсов
Match Group www-group0 User *,!spec0-group0
AllowTcpForwarding no
ChrootDirectory /var/www/group0
ForceCommand internal-sftp -u 0007
# Запрещаем пользователю передачу своих переменных окружения
PermitUserEnvironment no
# Устанавливаем режим проверки права доступа пользователя к целевым объектам
StrictModes yes
# Включаем дополнительную функциональность SFTP-сервиса в OpenSSH (отключаем перевод на внешнее приложение, активируя встроенные возможности)
# Subsystem sftp /usr/lib/openssh/sftp-server
Subsystem sftp internal-sftp
....
# Запираем пользователей группы "www-group0" в пределах директории их web-ресурсов
Match Group www-group0 User *,!spec0-group0
AllowTcpForwarding no
ChrootDirectory /var/www/group0
ForceCommand internal-sftp -u 0007
Набором параметров "Match" все пользователи "группы сайтов" (в примере "www-group0") изолируются с доступом только посредством SFTP, но для ряда специфичных задач могут быть созданы особые пользователи (вроде "spec0-group0"), на которых правило изоляции не должно распространяться - их мы исключением выводим из под действия конструкции "Match".
Важный параметр "-u 0007" команды "ForceCommand" в групповых настройках переопределяет системные установки "umask 0022", разрешающие членам группы только чтение общих файловых ресурсов, расширяя привилегии группы до уровня владельца файла. Этим мы добиваемся гарантированного доступа к разделяемым файлам web-ресурса как для PHP-интерпретатора, так и для web-разработчиков, могущих в разное время перехватывать владение файлами внутри структуры, не ломая при этом логики разрешений доступа, явно запрещая при этом доступ всем остальным (полностью снимая разрешения для "other").
Обращаю особое внимание на то, что конструкция "Match" должна располагаться обязательно после всех остальных определений - после неё может быть или конец файла или другая конструкция "Match". У конструкции "Match" нет определения завершения и всё, что будет ниже неё - будет воспринято как входящее в эту конструкцию.
Проверяем синтаксическую корректность внесённых изменений и применяем их:
# sshd -t && /etc/init.d/ssh reload
Обеспечение разработчикам возможности исполнения команд.
Если вдруг мы решили всё-таки предоставить web-разработчикам помимо доступа к файловым ресурсам (посредством SFTP/FTPS) ещё и возможность работы в командной строке web-сервера - а это плохая идея! - то понадобиться включить для пользователя возможность через SUDO запускать на исполнение команды в окружении привилегий и переменных сводной площадки.
Используя функционал расширения конфигурации SUDO добавляем правило в отдельный файл конфигурации:
# vi /etc/sudoers.d/web-shell-users
....
user1-group0 ALL=(www-group0:www-group0) NOPASSWD: ALL
user2-group0 ALL=(www-group0:www-group0) NOPASSWD: ALL
user1-group0 ALL=(www-group0:www-group0) NOPASSWD: ALL
user2-group0 ALL=(www-group0:www-group0) NOPASSWD: ALL
Обязательно проверяем синтаксическую корректность вносимых изменений (правила как таковые применяются немедленно без явного на то указания):
# visudo -cf /etc/sudoers.d/web-shell-users
Ограничиваем доступ посторонних к файлу конфигурации:
# chown root:root /etc/sudoers.d/web-shell-users
# chmod go-rwx /etc/sudoers.d/web-shell-users
# chmod go-rwx /etc/sudoers.d/web-shell-users
Таким образом, например, можно будет тестировать PHP-скрипты их прямым запуском:
user1-group0:$ sudo -u www-group0 php -q -f ./script.php
Создание служебного пользователя для CI/CD-процедур.
Заранее добавляем в группу "сводной площадки" web-сайтов специализированного пользователя, от имени которого в дальнейшем будут осуществляться автоматизированные запросы к Git-репозитариями хранения, разработки, тестирования и публикации:
# useradd --shell /usr/bin/git-shell --create-home --home-dir /home/git-group0 --gid www-group0 git-group0
Задавать пароль для этого служебного пользователя я считаю нецелесообразным, так как аутентификация между серверами надёжнее реализуется через пары (приватный и публичный) SSH-ключей. Заготовим для них место в файловой системе:
# mkdir -p /home/git-group0/.ssh
# touch /home/git-group0/.ssh/authorized_keys
# touch /home/git-group0/.ssh/authorized_keys
Обязательно полностью закрываем директорию пользователя от доступа посторонних:
# chown -R git-group0:www-group0 /home/git-group0
# chmod -R go-rwx /home/git-group0
# chmod -R go-rwx /home/git-group0
Более в этой заметке о пользователе для фоновых задач "git-group0" упоминать не будем - это заготовка для отдельно рассматриваемой реализации простейшего функционала CI/CD.
Обобщённая настройка PHP.
Настройка PHP-интерпретатора двухэтапна - вначале подкорректируем ряд общих параметров, а потом добавим индивидуальности в описании экземпляров PHP-FPM, запускаемого для каждой группы сайтов отдельно.
В современном "Linux Debian/Ubuntu" пакет PHP-интерпретатора поставляется в виде трёх идеологически разделённых компонентов: PHP-FPM, PHP-CGI и PHP-CLI - каждый из которых настраивается индивидуальными конфигурационными файлами, изначально идентичными. Мне представляется самым простым сконфигурировать один набор параметров и распространить их на все компоненты PHP перезаписью файла (одно время я пытался делать это через указание на "основной" файл настроек символическими ссылками, но практика показала, что отдельные файлы лучше вписываются в идеологию дистрибуции и обновления программного обеспечения):
Копированием произвольного создаём условно "главный" файл конфигурации:
# cp /etc/php/7.2/fpm/php.ini /etc/php/7.2/php-main.ini
Рассматривать здесь полную настройку не вижу смысла и обращу внимание лишь на особенности, необходимые для работы типичного PHP-сайта:
# vi /etc/php/7.2/php-main.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 = 64M
upload_max_filesize = 64M
max_file_uploads = 20
max_input_vars = 1000
....
; Отключаем новый и пока невостребованный функционал 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.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
....
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 = 64M
upload_max_filesize = 64M
max_file_uploads = 20
max_input_vars = 1000
....
; Отключаем новый и пока невостребованный функционал 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.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
....
Всегда есть смысл проверять, какие появились изменения в конфигурационных файлах - вдруг кто-то уже внёс точечные правки:
# diff /etc/php/7.2/php-main.ini /etc/php/7.2/cgi/php.ini
...или:
# vimdiff /etc/php/7.2/php-main.ini /etc/php/7.2/cgi/php.ini
После того, как мы удостоверились, что перезапись конфигурационных файлов сервисам не повредит, делаем это:
# cp /etc/php/7.2/php-main.ini /etc/php/7.2/cgi/php.ini
# cp /etc/php/7.2/php-main.ini /etc/php/7.2/cli/php.ini
# cp /etc/php/7.2/php-main.ini /etc/php/7.2/fpm/php.ini
# cp /etc/php/7.2/php-main.ini /etc/php/7.2/cli/php.ini
# cp /etc/php/7.2/php-main.ini /etc/php/7.2/fpm/php.ini
Если всё-таки потребуется перекрыть какой-то параметр индивидуально компоненту PHP, то иногда это возможно сделать через директивы "php_admin_value" и "php_admin_flag" в настройках "пула" PHP-FPM.
Для обеспечения автоматизации процедур тестирования и публикации нужно информировать запускаемые на сервере приложения о статусе среды, в которой они находятся. Например, в простейшей схеме разработки, тестирования и публикации имеется три сервера: "develop", "testing" и "master" - вот такие значения мы и будем присваивать специальной глобальной переменной, самым простым способом, через несущую операционную систему:
# vi /etc/environment
....
# Информируем приложения о типе площадки
MODE_ENV="master"
# Информируем приложения о типе площадки
MODE_ENV="master"
Проверяем, доступна ли переменная окружения в среде нужного пользователя:
# sudo -u www-group0 php -r 'var_dump(getenv("MODE_ENV"));'
Настройка "пула" PHP-FPM.
Прежде всего удаляем дистрибутивный конфигурационный файл с настройками "пула" PHP-FPM:
# rm /etc/php/7.2/fpm/pool.d/www.conf
Каждой группе сайтов "сводной площадки" будем заводить отдельный экземпляр ("пул") PHP-FPM (их может быть несколько, обслуживающих разные web-сервисы):
# vi /etc/php/7.2/fpm/pool.d/group0.conf
; Блок описания отдельного инстанса PHP-FPM
[group0]
; Указываем запускать инстанс от имени опорного пользователя "сводной площадки"
user = www-group0
group = www-group0
; Задаём точку приёма FCGI-запросов от вышестоящего web-прокси (Nginx в нашем случае)
;listen = 127.0.0.1:9001
;listen.allowed_clients = 127.0.0.1
listen = /var/run/php/php-fpm-group0.sock
; Явно задаём владельца точки входа FCGI-запросов и разрешения для доступа к ней web-прокси
listen.owner = www-data
listen.group = www-data
listen.mode = 0600
; Режим запуска инстанса
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
; Журнал событий доступа к ресурсам (если не объявлен, то журналирование не активизируется)
;access.log = /var/www/group0/log/php-fcgi.access.log
; Очень полезный журнал запросов, блокирующих ресурсы среднестатистически длительное время
slowlog = /var/www/group0/log/php-fcgi.slow.log
; Порог реакции на время (три секунды) исполнения PHP-скрипта, после которого событие запишется в журнал "slowlog"
request_slowlog_timeout = 3s
; Разрешаем чтение системных переменных окружения (default: Yes)
clear_env = No
env[MODE_ENV] = "master"
; Блок переопределения в среде исполнения PHP-FPM глобальных параметров PHP-интерпретатора
php_admin_value[sys_temp_dir] = /var/www/group0/tmp
php_admin_value[post_max_size] = 300M
php_admin_value[upload_max_filesize] = 300M
php_admin_flag[allow_url_fopen] = On
[group0]
; Указываем запускать инстанс от имени опорного пользователя "сводной площадки"
user = www-group0
group = www-group0
; Задаём точку приёма FCGI-запросов от вышестоящего web-прокси (Nginx в нашем случае)
;listen = 127.0.0.1:9001
;listen.allowed_clients = 127.0.0.1
listen = /var/run/php/php-fpm-group0.sock
; Явно задаём владельца точки входа FCGI-запросов и разрешения для доступа к ней web-прокси
listen.owner = www-data
listen.group = www-data
listen.mode = 0600
; Режим запуска инстанса
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
; Журнал событий доступа к ресурсам (если не объявлен, то журналирование не активизируется)
;access.log = /var/www/group0/log/php-fcgi.access.log
; Очень полезный журнал запросов, блокирующих ресурсы среднестатистически длительное время
slowlog = /var/www/group0/log/php-fcgi.slow.log
; Порог реакции на время (три секунды) исполнения PHP-скрипта, после которого событие запишется в журнал "slowlog"
request_slowlog_timeout = 3s
; Разрешаем чтение системных переменных окружения (default: Yes)
clear_env = No
env[MODE_ENV] = "master"
; Блок переопределения в среде исполнения PHP-FPM глобальных параметров PHP-интерпретатора
php_admin_value[sys_temp_dir] = /var/www/group0/tmp
php_admin_value[post_max_size] = 300M
php_admin_value[upload_max_filesize] = 300M
php_admin_flag[allow_url_fopen] = On
Следует иметь в виду, что не все параметры PHP-интерпретатора можно переопределить динамически на уровне "пула" PHP-FPM или командами самого PHP - нужно сверяться с документацией.
Проверяем синтаксическую корректность внесённых изменений и применяем таковые:
# php -e -c /etc/php/7.2/fpm/php.ini -r 'echo "OK\n";';
# php-fpm7.2 -t --fpm-config /etc/php/7.2/fpm/pool.d/group0.conf
# /etc/init.d/php7.2-fpm reload
# php-fpm7.2 -t --fpm-config /etc/php/7.2/fpm/pool.d/group0.conf
# /etc/init.d/php7.2-fpm reload
Оптимизация работы PHP с дисковой подсистемой.
Для интерпретатора PHP, обслуживающего сайты, активно создающие и читающие файлы сессий, выгодно вынести (параметром "tmpdir") эту работу в файловую систему, смонтированную в область памяти ОЗУ.
Место сохранения сессий в 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=2045M,uid=root,gid=root,mode=41733 0 0
....
# Tuning PHP-sessions`s place
tmpfs /var/lib/php/sessions tmpfs rw,nosuid,nodev,size=2045M,uid=root,gid=root,mode=41733 0 0
....
Я бы выделил под эту файловую систему до 10-15% от всей ОЗУ (она не заблокирует всё заявленное место, а будет выбирать блоки памяти по мере появления необходимости).
Учитывая то, что в общей куче сохраняются сессий всех пользователей, ресурсы которых тем не менее не должны смешиваться, необходимо задать директории особый набор разрешений доступа, работающий по следующему принципу:
1. Полный доступ в директорию только у суперпользователя.
2. Все могут только создать файл внутри ("-wx"), но удаление файлов явно запрещено ("--t").
3. PHP-интерпретатор при создании файла разрешает доступ к нему только владельцу.
4. Таким образом, впоследствии PHP-интерпретатор сможет прочитать только файл с известным ему именем, в контексте только того пользователя, который его создал.
2. Все могут только создать файл внутри ("-wx"), но удаление файлов явно запрещено ("--t").
3. PHP-интерпретатор при создании файла разрешает доступ к нему только владельцу.
4. Таким образом, впоследствии PHP-интерпретатор сможет прочитать только файл с известным ему именем, в контексте только того пользователя, который его создал.
Заготавливаем файловую структуру и задаём разрешения доступа к таковой:
# mkdir -p /var/lib/php/sessions
# chown root:root /var/lib/php/sessions
# chmod go-r /var/lib/php/sessions
# chmod g+wx /var/lib/php/sessions
# chmod o+wt /var/lib/php/sessions
# chown root:root /var/lib/php/sessions
# chmod go-r /var/lib/php/sessions
# chmod g+wx /var/lib/php/sessions
# chmod o+wt /var/lib/php/sessions
Сразу настраиваем простейший "сборщик мусора", удаляющий устаревшие файлы сессий - ибо их нарастает действительно много:
# vi /etc/crontab
....
# Garbage collection for old files sessions of PHP (lifetime 31 days; in minutes)
03 */3 * * * root [ -d /var/lib/php/sessions ] && nice find /var/lib/php/sessions -type f -cmin +44640 -exec rm -f {} \; 1>/dev/null &
....
# Garbage collection for old files sessions of PHP (lifetime 31 days; in minutes)
03 */3 * * * root [ -d /var/lib/php/sessions ] && nice find /var/lib/php/sessions -type f -cmin +44640 -exec rm -f {} \; 1>/dev/null &
....
Подготовка LXC-контейнеров с "NodeJS".
Каждому сайту может быть создано по отдельному инстансу LXC, в котором посредством NPM в соответствии с файлом конфигурации проекта "package.json" уже может быть запущена хоть гирлянда JS-скрипт web-приложений.
Для состарившейся "Linux Ubuntu 16 (Xenial)" придётся добавить репозитории более свежего дистрибутива, из которых установятся пара требуемых LXC библиотек:
# vi /etc/apt/source.lists
....
# Additional
deb http://us.archive.ubuntu.com/ubuntu/ bionic main restricted universe multiverse
# deb-src http://us.archive.ubuntu.com/ubuntu/ bionic main restricted universe multiverse
# Additional
deb http://us.archive.ubuntu.com/ubuntu/ bionic main restricted universe multiverse
# deb-src http://us.archive.ubuntu.com/ubuntu/ bionic main restricted universe multiverse
# vi /etc/apt.conf.d/00default
APT::Default-Release "xenial";
Устанавливаем компоненты LXC и проверяем возможность их использования:
# apt-get update
# apt-get --no-install-recommends --no-install-suggests install lxc-utils lxcfs bridge-utils bindfs
# lxc-checkconfig
# apt-get --no-install-recommends --no-install-suggests install lxc-utils lxcfs bridge-utils bindfs
# lxc-checkconfig
По умолчанию конфигурация виртуальной подсистемы LXC вполне удовлетворительна - я бы лишь заменил IP-подсеть на более подходящую для сугубо внутреннего использования:
# vi /etc/default/lxc-net
USE_LXC_BRIDGE="true"
LXC_BRIDGE="lxcbr0"
LXC_ADDR="100.64.0.1"
LXC_NETMASK="255.255.255.0"
LXC_NETWORK="100.64.0.0/24"
LXC_DHCP_RANGE="100.64.0.2,100.64.0.254"
LXC_DHCP_MAX="253"
LXC_BRIDGE="lxcbr0"
LXC_ADDR="100.64.0.1"
LXC_NETMASK="255.255.255.0"
LXC_NETWORK="100.64.0.0/24"
LXC_DHCP_RANGE="100.64.0.2,100.64.0.254"
LXC_DHCP_MAX="253"
Перезапускаем сервис виртуальной сети LXC:
# systemctl restart lxc-net
На основе предлагаемого сообществом разработчиков LXC загруженного шаблона формируем корневую файловую систему контейнера:
# lxc-create -t download -n lxc-npm-site.example.net -- --dist ubuntu --release bionic --arch amd64
Развёртываемый сервис должен быть активным всегда, так что настраиваем автозапуск контейнера:
# vi /var/lib/lxc/lxc-npm-site.example.net/config
....
# Container autostart configuration
lxc.start.auto = 1
lxc.start.delay = 5
# Container autostart configuration
lxc.start.auto = 1
lxc.start.delay = 5
Заранее узнаём идентификаторы пользователя и группы, являющиеся владельцами файловых ресурсов сайта "site.example.net", с которыми будет работать сервис внутри контейнера:
# id www-group0
uid=1005(www-group0) gid=1005(www-group0) groups=1005(www-group0)
Имея в виду, что файлы JS-проектов, для исполнения которых запускается LXC-контейнер, располагаются в директории "/var/www/group0/site.example.net/nodejs" несущей машины, заранее смонтируем посредством специальной подсистемы "BindFS" директорию сайта внутрь файловой системы контейнера:
# mkdir -p /var/lib/lxc/lxc-npm-site.example.net/rootfs/var/www/group0/site.example.net
# vi /etc/fstab
....
# For XLC container "lxc-npm-site.example.net" with NPN/NodeJS application specific configuration
#
/var/www/group0/site.example.net /var/lib/lxc/lxc-npm-site.example.net/rootfs/var/www/group0/site.example.net fuse.bindfs perms=u+rwX:g+rwX:o+X,force-user=www-group0,force-group=www-group0,create-with-perms=u+rwX:g+rwX:o+X,create-for-user=www-group0,create-for-group=www-group0 0 0
# For XLC container "lxc-npm-site.example.net" with NPN/NodeJS application specific configuration
#
/var/www/group0/site.example.net /var/lib/lxc/lxc-npm-site.example.net/rootfs/var/www/group0/site.example.net fuse.bindfs perms=u+rwX:g+rwX:o+X,force-user=www-group0,force-group=www-group0,create-with-perms=u+rwX:g+rwX:o+X,create-for-user=www-group0,create-for-group=www-group0 0 0
Даём команду немедленно смонтировать указанную в "fstab" директорию:
# mount /var/lib/lxc/lxc-npm-site.example.net/rootfs/var/www/group0/site.example.net/nodejs
Запускаем контейнер, проверяем его состояние и входим внутрь для дальнейших работ:
# lxc-start -n lxc-npm-site.example.net
# lxc-ls -f
# lxc-attach -n lxc-npm-site.example.net
# lxc-ls -f
# lxc-attach -n lxc-npm-site.example.net
Создаём внутри контейнера пользователя и группу, совпадающую по именам и идентификаторам с несущей системой (числовые идентификаторы мы выяснили ранее, выше по инструкции):
# groupadd --system --gid 1005 www-group0
# useradd --system --shell /bin/false --no-create-home --home-dir /var/www/group0 --uid 1005 --gid www-group0 www-group0
# useradd --system --shell /bin/false --no-create-home --home-dir /var/www/group0 --uid 1005 --gid www-group0 www-group0
Задаём статичный IP-адрес системе внутри контейнера - в дальнейшем именно по нему будем обращаться к web-приложению "NodeJS":
root@lxc-npm-site:/# vi # vi /etc/netplan/10-lxc.yaml
network:
version: 2
renderer: networkd
ethernets:
eth0:
dhcp4: no
addresses: [100.64.0.10/24]
gateway4: 100.64.0.1
nameservers:
search: [example.net]
addresses: [100.64.0.1]
version: 2
renderer: networkd
ethernets:
eth0:
dhcp4: no
addresses: [100.64.0.10/24]
gateway4: 100.64.0.1
nameservers:
search: [example.net]
addresses: [100.64.0.1]
Применяем новую конфигурацию сетевого интерфейса:
root@lxc-npm-site:/# netplan apply
root@lxc-npm-site:/# ip addr
root@lxc-npm-site:/# ip addr
Ненужную в контейнере подсистему синхронизации времени "Systemd" отключаем:
root@lxc-npm-site:/# systemctl disable systemd-timesyncd && systemctl stop systemd-timesyncd
Также отключаем "Systemd" сервис кеширования результатов DNS-запросов:
root@lxc-npm-site:/# systemctl disable systemd-resolved && systemctl stop systemd-resolved
root@lxc-npm-site:/# rm /etc/resolv.conf && echo -e "nameserver 100.64.0.1\nsearch example.net\n" > /etc/resolv.conf
root@lxc-npm-site:/# rm /etc/resolv.conf && echo -e "nameserver 100.64.0.1\nsearch example.net\n" > /etc/resolv.conf
Добавляем поддержку нужных "национальных кодировок" (в нашем случае - русской):
root@lxc-npm-site:/# dpkg-reconfigure locales
Покидаем LXC-контейнер, останавливаем и запускаем его для применения внесённых изменений, после чего вновь включаемся в контекст его консоли суперпользователя:
root@lxc-npm-site:/# exit
# lxc-stop -n lxc-npm-site.example.net
# lxc-start -n lxc-npm-site.example.net
# lxc-attach -n lxc-npm-site.example.net
# lxc-stop -n lxc-npm-site.example.net
# lxc-start -n lxc-npm-site.example.net
# lxc-attach -n lxc-npm-site.example.net
Прежде всего установим необходимые дополнительные утилиты внутри контейнера:
root@lxc-npm-site:/# apt-get --no-install-recommends --no-install-suggests install wget net-tools
На сайте разработчиков смотрим график релизов. В Ноябре 2019 стартовала LTS-версия "v.12" - её и применим, переходя с предыдущей LTS "v.8".
Воспользуемся готовой сборкой с исполняемыми файлами ELF-LSB, так называемым "бинарным дистрибутивом" - выложенной в разделе загрузки.
Скачиваем архив с программным обеспечением и разворачиваем его в удобном нам месте:
root@lxc-npm-site:/# cd /usr/src && wget https://nodejs.org/dist/v12.14.0/node-v12.14.0-linux-x64.tar.gz
root@lxc-npm-site:/# mkdir -p /usr/local/lib/nodejs
root@lxc-npm-site:/# tar -xzvf node-v12.14.0-linux-x64.tar.gz --strip=1 -C /usr/local/lib/nodejs
root@lxc-npm-site:/# mkdir -p /usr/local/lib/nodejs
root@lxc-npm-site:/# tar -xzvf node-v12.14.0-linux-x64.tar.gz --strip=1 -C /usr/local/lib/nodejs
Выдуманное нами месторасположение исполняемых файлов "NodeJS" несущей операционной системе неизвестно и для обращения к таковым необходимо специфичный путь к исполняемым файлам самого "NodeJS" и его модулей нужно добавить в системную переменную "PATH":
root@lxc-npm-site:/# vi /etc/environment && . /etc/environment
PATH="/usr/local/sbin:...:/usr/local/lib/nodejs/bin"
....
....
Кроме того, для запуска компонентов "NodeJS" из приложений вроде "Systemd" и "Supervisor" очень полезно указать к основным исполняемым файлам короткие пути символическими ссылками:
root@lxc-npm-site:/# ln -s /usr/local/lib/nodejs/bin/node /usr/bin/node
root@lxc-npm-site:/# ln -s /usr/local/lib/nodejs/bin/npm /usr/bin/npm
root@lxc-npm-site:/# ln -s /usr/local/lib/nodejs/bin/npx /usr/bin/npx
root@lxc-npm-site:/# ln -s /usr/local/lib/nodejs/bin/npm /usr/bin/npm
root@lxc-npm-site:/# ln -s /usr/local/lib/nodejs/bin/npx /usr/bin/npx
Сразу после развёртывания приложения "NodeJS" к нему и его компонентам можно обращаться, например за номерами версий:
root@lxc-npm-site:/# node -v
root@lxc-npm-site:/# npm version
root@lxc-npm-site:/# npx -v
root@lxc-npm-site:/# npm version
root@lxc-npm-site:/# npx -v
Пишем демонстрационное web-приложение:
# vi /var/www/group0/site.example.net/nodejs/site.example.net.js
var express = require('express');
var app = express();
app.get('/', function(req, res){
res.send("Success NodeJS for 'site.example.net'.\n");
console.log('NodeJS is talking.\n');
});
app.listen(8000, '0.0.0.0');
var app = express();
app.get('/', function(req, res){
res.send("Success NodeJS for 'site.example.net'.\n");
console.log('NodeJS is talking.\n');
});
app.listen(8000, '0.0.0.0');
Уже сейчас web-приложение можно запустить командой "node ./site.example.net.js", но ради автоматизации мы пойдём дальше и подготовим конфигурационный файл проекта:
# vi /var/www/group0/site.example.net/nodejs/package.json
{
"name": "nodejs_group0_site_example_net_app",
"description": "NodeJS Example App",
"main": "site.example.net.js", "version": "1.0.0",
"author": "NSU, Andrey Narozhniy", "license": "ISC",
"dependencies": {
"express": "^4.17.1"
},
"scripts": {
"start": "npm install && node site.example.net.js"
}
}
"name": "nodejs_group0_site_example_net_app",
"description": "NodeJS Example App",
"main": "site.example.net.js", "version": "1.0.0",
"author": "NSU, Andrey Narozhniy", "license": "ISC",
"dependencies": {
"express": "^4.17.1"
},
"scripts": {
"start": "npm install && node site.example.net.js"
}
}
На этом этапе также можно запустить web-приложение командой "npm start", будучи при этом в директории с файлом конфигурации проекта "package.json", но нам ещё требуется наладить запуск сервера "NodeJS" как сервиса "Systemd":
root@lxc-npm-site:/# vi /etc/systemd/system/npm-nodejs.service
[Unit]
Description=NPM/NodeJS Application
After=network.target
[Service]
Type=simple
User=www-group0
Group=www-group0
WorkingDirectory=/var/www/group0/site.example.net/nodejs
ExecStart=/usr/bin/npm start
Restart=on-failure
[Install]
WantedBy=multi-user.target
Description=NPM/NodeJS Application
After=network.target
[Service]
Type=simple
User=www-group0
Group=www-group0
WorkingDirectory=/var/www/group0/site.example.net/nodejs
ExecStart=/usr/bin/npm start
Restart=on-failure
[Install]
WantedBy=multi-user.target
Указываем "Systemd" перечитать свою конфигурацию, пробуем запустить сервис, проверяем его статус и, если он работает корректно, активируем его автозапуск:
root@lxc-npm-site:/# systemctl daemon-reload
root@lxc-npm-site:/# systemctl start npm-nodejs
root@lxc-npm-site:/# systemctl status npm-nodejs
root@lxc-npm-site:/# systemctl enable npm-nodejs
root@lxc-npm-site:/# systemctl start npm-nodejs
root@lxc-npm-site:/# systemctl status npm-nodejs
root@lxc-npm-site:/# systemctl enable npm-nodejs
При верной настройке сервис "NodeJS" должен успешно запуститься, прослушивая заданный сетевой порт:
root@lxc-npm-site:/# netstat -apn | grep -i listen | grep -i node
tcp ... 0.0.0.0:8000 0.0.0.0:* LISTEN .../node
Проверяем доступность web-приложения элементарным GET-запросом:
$ wget http://100.64.0.10:8000 -nv -O -
Монтирование поверх "tmpfs" в LXC-контейнере.
В случае необходимости завести внутрь контейнера файловые ресурсы вроде unix-сокетов (например для доступа к общедоступной СУБД), располагающихся в динамически формируемой директории "/var/run" мы сталкиваемся в проблемой - их нельзя просто смонтировать на этапе запуска LXC-контейнера, так как tmpfs-ресурсы создаются уже после, в процессе исполнения инструкций "initd|systemd".
Мне оказалось проще сделать это в два этапа: предварительным монтированием unix-сокета в транзитную директорию внутрь контейнера на этапе запуска такового и последующим созданием символической ссылки посредством "Systemd" контексте исполнения контейнера.
Указываем в конфигурационном файле LXC-контейнера монтировать директорию с unix-сокетами (можно и непосредственно одиночный файл) в произвольном месте:
# vi /var/lib/lxc/lxc-npm-site.example.net/config
....
# Container specific mount configuration
lxc.mount.entry = /var/run/mongodb var/opt/run/mongodb none rw,bind,option,create=dir 0 0
lxc.mount.entry = /var/run/app.sock var/opt/run/app.sock none rw,bind,option,create=file 0 0
# Container specific mount configuration
lxc.mount.entry = /var/run/mongodb var/opt/run/mongodb none rw,bind,option,create=dir 0 0
lxc.mount.entry = /var/run/app.sock var/opt/run/app.sock none rw,bind,option,create=file 0 0
Внутри LXC-контейнера регистрируем systemd-сервис, создающий символические ссылки на нужные файловые ресурсы из уже сформированных tmpfs-структур (systemd-unit "systemd-tmpfiles-setup.service" занимается созданием "/run|/var/run" в частности):
# lxc-attach -n lxc-npm-site.example.net
root@lxc-npm-site:/# vi /etc/systemd/system/runtime-helper.service
root@lxc-npm-site:/# vi /etc/systemd/system/runtime-helper.service
[Unit]
Description=Runtime Environment Helper
After=systemd-tmpfiles-setup.service
Requires=systemd-tmpfiles-setup.service
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/bin/sh -c "ln -s /var/opt/run/mongodb /var/run/mongodb"
ExecStart=/bin/sh -c "ln -s /var/opt/run/app.sock /var/run/app.sock"
[Install]
WantedBy=multi-user.target
Description=Runtime Environment Helper
After=systemd-tmpfiles-setup.service
Requires=systemd-tmpfiles-setup.service
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/bin/sh -c "ln -s /var/opt/run/mongodb /var/run/mongodb"
ExecStart=/bin/sh -c "ln -s /var/opt/run/app.sock /var/run/app.sock"
[Install]
WantedBy=multi-user.target
Запускаем сервис в работу:
root@lxc-npm-site:/# systemctl daemon-reload
root@lxc-npm-site:/# systemctl start runtime-helper.service
root@lxc-npm-site:/# systemctl status runtime-helper.service
root@lxc-npm-site:/# systemctl enable runtime-helper.service
root@lxc-npm-site:/# systemctl start runtime-helper.service
root@lxc-npm-site:/# systemctl status runtime-helper.service
root@lxc-npm-site:/# systemctl enable runtime-helper.service
Обобщённая настройка web-сервера "Nginx".
Предупреждая ошибки эпизодических сбоев доступа к локальному файловому сокету в условиях высокой нагрузки, увеличиваем лимиты:
# vi /etc/sysctl.d/30-sl-nginx.conf
# Увеличиваем ограничение на количество открытых соединений к файловым сокетам (default: 128)
net.core.somaxconn=4096
# Увеличиваем ограничение на количество ожидающих запросов открытия соединений к файловым сокетам (default: 1000)
net.core.netdev_max_backlog=2048
net.core.somaxconn=4096
# Увеличиваем ограничение на количество ожидающих запросов открытия соединений к файловым сокетам (default: 1000)
net.core.netdev_max_backlog=2048
Применяем здесь и сейчас параметры конфигурации:
# sysctl -p -f /etc/sysctl.d/30-sl-nginx.conf
Заготовим место для размещения файлов SSL-сертификатов сайтов за "Nginx":
# mkdir -p /etc/ssl/nginx && chmod -R go-rwx /etc/ssl/nginx
Для последующего включения в "Nginx" современного HTTPv2 генерируем DH-сертификат:
# openssl dhparam -out /etc/ssl/nginx/dhparam.2048.pem 2048
Для очень старых операционных систем и браузеров (Windows XP IE6, Java 6) заготовим DH-сертификат попроще:
# openssl dhparam -out /etc/ssl/nginx/dhparam.1024.pem 1024
Слегка дополняем глобальную конфигурацию web-сервера:
# vi /etc/nginx/nginx.conf
# Явно запускаем Nginx в рамках привbлегий пользователя "www-data" и группы "www-data"
user www-data www-data;
....
# Задаём число рабочих процессов (для начала отталкиваемся от количества ядер CPU; default: 1)
worker_processes 24;
# Указываем максимальное число открытых файлов (RLIMIT_NOFILE) для рабочих процессов (default: "ulimit -n" ~ 1024)
# (на каждое обрабатываемое соединение выделяется по два файловых дескриптора)
# (worker_rlimit_nofile = worker_processes * worker_connections * 2)
worker_rlimit_nofile 50000;
....
events {
# Задаём максимальное число соединений, которые одновременно может открыть рабочий процесс (default: 512)
worker_connections 1024;
....
}
http {
....
tcp_nodelay on;
tcp_nopush on;
# Запрещаем web-серверу сообщать о себе подробные данные
server_tokens off;
# Запрещаем просмотр содержимого директории, если не указан целевой файл
autoindex off;
# Слегка увеличиваем размер области памяти для хранения перечня обслуживаемых имён сайтов (default: 32)
server_names_hash_bucket_size 64;
# Отключаем проверку размера тела передаваемого PHP-FPM запроса (default: 1m)
client_max_body_size 0;
# Увеличиваем размер блока данных, обрабатываемого в памяти без сохранения на диск (default: 16K)
client_body_buffer_size 4M;
# Подгоняем под параметры PHP время ожидания ответа от скриптов
fastcgi_send_timeout 300;
fastcgi_read_timeout 300;
....
user www-data www-data;
....
# Задаём число рабочих процессов (для начала отталкиваемся от количества ядер CPU; default: 1)
worker_processes 24;
# Указываем максимальное число открытых файлов (RLIMIT_NOFILE) для рабочих процессов (default: "ulimit -n" ~ 1024)
# (на каждое обрабатываемое соединение выделяется по два файловых дескриптора)
# (worker_rlimit_nofile = worker_processes * worker_connections * 2)
worker_rlimit_nofile 50000;
....
events {
# Задаём максимальное число соединений, которые одновременно может открыть рабочий процесс (default: 512)
worker_connections 1024;
....
}
http {
....
tcp_nodelay on;
tcp_nopush on;
# Запрещаем web-серверу сообщать о себе подробные данные
server_tokens off;
# Запрещаем просмотр содержимого директории, если не указан целевой файл
autoindex off;
# Слегка увеличиваем размер области памяти для хранения перечня обслуживаемых имён сайтов (default: 32)
server_names_hash_bucket_size 64;
# Отключаем проверку размера тела передаваемого PHP-FPM запроса (default: 1m)
client_max_body_size 0;
# Увеличиваем размер блока данных, обрабатываемого в памяти без сохранения на диск (default: 16K)
client_body_buffer_size 4M;
# Подгоняем под параметры PHP время ожидания ответа от скриптов
fastcgi_send_timeout 300;
fastcgi_read_timeout 300;
....
Если для большого количества сайтов используется один "wildcard" SSL-сертификат, то есть смысл вынести блок описания его настроек в файл и подключать его только там, где потребуется:
# vi /etc/nginx/ssl_wildcard.conf
# SSL/TLS Settings
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/ssl/nginx/dhparam.2048.pem;
ssl_certificate /etc/ssl/nginx/wildcard.example.net.crt;
ssl_certificate_key /etc/ssl/nginx/wildcard.example.net.key.decrypt;
# SSL Caching
ssl_session_cache shared:SSL:30m;
ssl_session_timeout 1h;
# SSL Strict Optional
add_header Strict-Transport-Security max-age=15768000;
# SSL Verify Optional
ssl_stapling 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/ssl/nginx/dhparam.2048.pem;
ssl_certificate /etc/ssl/nginx/wildcard.example.net.crt;
ssl_certificate_key /etc/ssl/nginx/wildcard.example.net.key.decrypt;
# SSL Caching
ssl_session_cache shared:SSL:30m;
ssl_session_timeout 1h;
# SSL Strict Optional
add_header Strict-Transport-Security max-age=15768000;
# SSL Verify Optional
ssl_stapling on;
Создание в "Nginx" конфигурации web-сайта.
Прежде всего удаляем дистрибутивный конфигурационный файл с настройками web-сайта "по умолчанию":
# rm /etc/nginx/sites-enabled/default
Создаём конфигурацию нашего первого web-сайта:
# vi /etc/nginx/sites-available/site.example.net.conf
# Блок отлова HTTP-обращений и перенаправления их на HTTPS
server {
listen 80;
server_name www.site.example.net site.example.net;
location / {
rewrite ^ https://site.example.net$request_uri permanent;
}
}
# Блок отлова обращений по префиксу "www." и перенаправления их на "Canonical URL"
server {
listen 443 ssl http2;
server_name www.site.example.net;
ssl on;
include /etc/nginx/ssl_wildcard.conf;
location / {
rewrite ^ https://site.example.net$request_uri permanent;
}
}
# Описание параметров web-сайта как такового
server {
# Указываем сетевые порты и протоколы для приёма клиентских подключений
listen 443 ssl http2;
# Перечисляем обслуживаемые FQDN сайта
server_name site.example.net;
# Принудительно переводим сайт на работу только через SSL
ssl on;
include /etc/nginx/ssl_wildcard.conf;
# Месторасположение файлов журналов событий сайта
access_log /var/www/group0/log/nginx-site.example.net-access.log;
error_log /var/www/group0/log/nginx-site.example.net-error.log;
# Выключаем невостребованную перекодировку контента
charset off;
# Задаём переменную с многократно используемым параметром PHP-FPM
set $php_pass unix:/var/run/php/php-fpm-group0.sock;
root /var/www/group0/site.example.net/www;
index index.html index.htm index.phtml index.php;
# Подставляем свои страницы обработки ошибок
error_page 403 /403.html;
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
# ACL фильтрующий клиентские IP
#allow 10.0.0.0/12;
#allow 172.16.0.0/12;
#allow 192.168.0.0/16;
#deny all;
# Глобальный обработчик запросов
location / {
# Обработчик событий отсутствия запрашиваемого файла
try_files $uri $uri/ =404;
}
# Блокируем доступ к типовым "закрытым" ресурсам
location ~* (/\.ht|/\.hg|/\.git|/\.svn|/\.subversion|/\.inc|/\.sys|/\.local|/\.env|/\.enabled|/\.config|/\.profile) {
deny all;
log_not_found off;
access_log off;
}
# Блокируем доступ к ресурсам, которые часто забывают спрятать
location ~* (/conf|/cnf|/inc|/log/|/tmp/|/temp/|/runtime|/back|/bkp|/bak|/old|/test|/protected|/base|/database|/exchange|/phpshell|/cli|/bin|\.zip|\.gzip|\.gz|\.sql|\.py|\.perl|\.tpl|\.sh|\.bash|\.dist|\.orig|\.back|\.bak|\.conf|phpinfo.php) {
deny all;
log_not_found off;
}
# Блокируем доступ к классически неправильно и опасно именованным файлам (вроде ".php.1")
location ~* \.(phtml|php)(?!(\?|\/|$)) {
deny all;
log_not_found off;
}
# Обработчик прямых обращений к PHP-скриптам
location ~* \.(phtml|php)$ {
try_files $uri =404;
include /etc/nginx/fastcgi_params;
fastcgi_pass $php_pass;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
# Задаём параметры проксирования к серверу "NodeJS"
set $nodejs_pass 100.64.0.10:8000;
location /nodejs {
proxy_pass http://$nodejs_pass;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-NginX-Proxy true;
# (включение поддержки HTTPv1.1 и WebSocket)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_cache_bypass $http_upgrade;
}
# Не реагируем на неинтересные события загрузок
location = /(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 =404;
expires 30d;
access_log off;
}
}
server {
listen 80;
server_name www.site.example.net site.example.net;
location / {
rewrite ^ https://site.example.net$request_uri permanent;
}
}
# Блок отлова обращений по префиксу "www." и перенаправления их на "Canonical URL"
server {
listen 443 ssl http2;
server_name www.site.example.net;
ssl on;
include /etc/nginx/ssl_wildcard.conf;
location / {
rewrite ^ https://site.example.net$request_uri permanent;
}
}
# Описание параметров web-сайта как такового
server {
# Указываем сетевые порты и протоколы для приёма клиентских подключений
listen 443 ssl http2;
# Перечисляем обслуживаемые FQDN сайта
server_name site.example.net;
# Принудительно переводим сайт на работу только через SSL
ssl on;
include /etc/nginx/ssl_wildcard.conf;
# Месторасположение файлов журналов событий сайта
access_log /var/www/group0/log/nginx-site.example.net-access.log;
error_log /var/www/group0/log/nginx-site.example.net-error.log;
# Выключаем невостребованную перекодировку контента
charset off;
# Задаём переменную с многократно используемым параметром PHP-FPM
set $php_pass unix:/var/run/php/php-fpm-group0.sock;
root /var/www/group0/site.example.net/www;
index index.html index.htm index.phtml index.php;
# Подставляем свои страницы обработки ошибок
error_page 403 /403.html;
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
# ACL фильтрующий клиентские IP
#allow 10.0.0.0/12;
#allow 172.16.0.0/12;
#allow 192.168.0.0/16;
#deny all;
# Глобальный обработчик запросов
location / {
# Обработчик событий отсутствия запрашиваемого файла
try_files $uri $uri/ =404;
}
# Блокируем доступ к типовым "закрытым" ресурсам
location ~* (/\.ht|/\.hg|/\.git|/\.svn|/\.subversion|/\.inc|/\.sys|/\.local|/\.env|/\.enabled|/\.config|/\.profile) {
deny all;
log_not_found off;
access_log off;
}
# Блокируем доступ к ресурсам, которые часто забывают спрятать
location ~* (/conf|/cnf|/inc|/log/|/tmp/|/temp/|/runtime|/back|/bkp|/bak|/old|/test|/protected|/base|/database|/exchange|/phpshell|/cli|/bin|\.zip|\.gzip|\.gz|\.sql|\.py|\.perl|\.tpl|\.sh|\.bash|\.dist|\.orig|\.back|\.bak|\.conf|phpinfo.php) {
deny all;
log_not_found off;
}
# Блокируем доступ к классически неправильно и опасно именованным файлам (вроде ".php.1")
location ~* \.(phtml|php)(?!(\?|\/|$)) {
deny all;
log_not_found off;
}
# Обработчик прямых обращений к PHP-скриптам
location ~* \.(phtml|php)$ {
try_files $uri =404;
include /etc/nginx/fastcgi_params;
fastcgi_pass $php_pass;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
# Задаём параметры проксирования к серверу "NodeJS"
set $nodejs_pass 100.64.0.10:8000;
location /nodejs {
proxy_pass http://$nodejs_pass;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-NginX-Proxy true;
# (включение поддержки HTTPv1.1 и WebSocket)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_cache_bypass $http_upgrade;
}
# Не реагируем на неинтересные события загрузок
location = /(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 =404;
expires 30d;
access_log off;
}
}
Включаем конфигурационный файл, проверяем его синтаксическую корректность и применяем:
# ln -s /etc/nginx/sites-available/site.example.net.conf /etc/nginx/sites-enabled/site.example.net.conf
# nginx -t
# /etc/init.d/nginx reload
# nginx -t
# /etc/init.d/nginx reload
Укладываем в корне сайта текстовый файл (если таковой отсутствует) и пробуем его получить через браузер:
# echo "site.example.net" > /var/www/group0/site.example.net/www/index.html
$ wget http://site.example.net/
$ wget http://site.example.net/
У "Nginx" на мой взгляд недоработана подсистема журналирования событий - он сам не создаёт файлы, а ожидает их наличия. Хорошо хоть в случае их отсутствия сервер продолжает работу, просто не сохраняя никуда сообщения о событиях. Файлы журналов создаются скриптовой обёрткой "SysV" или "Systemd" на этапе запуска или перезагрузки конфигурации сервера, но сделано это неудобно - несмотря на то, что рабочие потоки "Nginx" могут действовать от имени специфичного пользователя, журнальные файлы всё равно скорее всего (в зависимости от реализации серверного окружения) будут отданы во владении суперпользователя "root".
Приходится после создания и активации каждого нового сайта переопределять права доступа к журнальным файлам "Nginx":
# chown www-group0:www-group0 /var/www/group0/log/*
# chmod g+rw /var/www/group0/log/*
# chmod g+rw /var/www/group0/log/*
Обобщённая настройка "Memcached".
"Memcached" представляет собой сервис, хранящий в оперативной памяти некоторые данные с заданным временем жизни. Доступ к данным осуществляется по ключу (имени). По сути это "хэш"-таблица. В самом простом случае в нем хранят сессии пользователей, коды "капч" или накапливают перед сбросом в СУБД долговременного хранения сведения о текущих процессах.
Дистрибутивный скрипт запуска "Memcached" откровенно нефункциональный, да ещё и неработающий после перехода на "Systemd", так что его проще заблокировать и воспользоваться для запуска сконфигурированных в нужном нам виде инстансов "Memcached" сервисом "Supervisord" (написан на Python, и проверен на высоконагруженных сервисах).
Блокируем автоматический запуск "Memcached" посредством подсистемы "SysV":
# /etc/init.d/memcached stop && update-rc.d memcached disable
Блокируем автоматический запуск "Memcached" посредством подсистемы "Systemd":
# systemctl stop memcached && systemctl disable memcached
Удаляем дистрибутивный файл конфигурации по умолчанию:
# rm /etc/memcached.conf
По идее унификации локальные файловые "сокеты" надо бы располагать примерно здесь: "/var/run/memcached/*.sock" - однако "Memcached" не умеет вначале стартовать, строить себе окружение, а уже потом уходить в работу от непривилегированного пользователя, так что создать себе ресурс в доступной только суперпользователю директории "/run" он не может. Приходится для простоты посредством "Supervisord" запускать предварительно, с максимальным приоритетом, процедуру создания нужной директории:
# vi /etc/supervisor/conf.d/memcached-preset.conf
[program:memcached-preset]
umask=0000
command=/bin/bash -c 'mkdir -p /var/run/memcached && chmod ugo+rwX /var/run/memcached'
stdout_logfile=/var/log/supervisor/memcached-preset.log
redirect_stderr=true
priority=0
autostart=true
autorestart=false
startsecs=0
startretries=1
umask=0000
command=/bin/bash -c 'mkdir -p /var/run/memcached && chmod ugo+rwX /var/run/memcached'
stdout_logfile=/var/log/supervisor/memcached-preset.log
redirect_stderr=true
priority=0
autostart=true
autorestart=false
startsecs=0
startretries=1
Настройка инстанса "Memcached" в "Supervisord".
Каждой группе сайтов "group0" будем создавать по отдельному инстансу "Memcached", доступному через локальный файловый "сокет" только соответствующему пользователю "www-group0" (так проще всего ограничить доступ):
# vi /etc/supervisor/conf.d/memcached-group0.conf
[program:memcached-group0]
user=www-group0
command=/usr/bin/memcached -m 128 -c 1024 -t 4 -s /var/run/memcached/memcached-group0.sock -a 770 -v
stdout_logfile=/var/log/supervisor/memcached-groupo.log
redirect_stderr=true
autostart=true
autorestart=true
stopsignal=KILL
numprocs=1
user=www-group0
command=/usr/bin/memcached -m 128 -c 1024 -t 4 -s /var/run/memcached/memcached-group0.sock -a 770 -v
stdout_logfile=/var/log/supervisor/memcached-groupo.log
redirect_stderr=true
autostart=true
autorestart=true
stopsignal=KILL
numprocs=1
Перечитываем конфигурацию с последующим перезапуском приложений, для которых обновилась конфигурация и остановкой исключённых из таковой:
# supervisorctl reread
# supervisorctl update
# supervisorctl update
Просматриваем статус запущенных в "Supervisord" приложений:
# supervisorctl status
memcached-preset EXITED ...
memcached-group0 RUNNING ...
memcached-group0 RUNNING ...
Простейшим способом проверяем доступность для выделенного опорного пользователя запущенного инстанса "Memcached":
# echo 'stats' | sudo -u www-group0 nc.openbsd -U /var/run/memcached/memcached-group0.sock
...или:
# echo 'stats' | sudo -u www-group0 socat - UNIX-CONNECT:/var/run/memcached/memcached-group0.sock
Ротация файлов журналов "супервизора" и его дочерних сервисов изначально не настроена вообще - исправляем упущение:
# vi /etc/logrotate.d/supervisor && logrotate -d /etc/logrotate.d/supervisor
/var/log/supervisor/*.log {
size 2M
missingok
notifempty
rotate 3
compress
delaycompress
copytruncate
su root root
}
size 2M
missingok
notifempty
rotate 3
compress
delaycompress
copytruncate
su root root
}
Настройка подсистемы отправки почты.
В самых простых web-сайтах на PHP функцию "mail()" реализуют через системную утилиту "sendmail" или её заменители в MTA. Однако в сложных проектах со множеством профилей транзитных почтовых серверов удобнее использовать легковесного почтового клиента "mSMTP" - с ним можно как просто пересылать сообщения через прозрачный почтовый шлюз, так и подключаться к почтовым провайдерам вроде "GMail" с многоступенчатой авторизацией.
Простенький "mSMTP" не умеет создавать файл журнала событий, так что делаем это за него:
# touch /var/log/msmtp.log && chmod ugo+rw /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-site@example.net
defaults
auth off
tls off
logfile /var/log/msmtp.log
# Default Account
account default
host mx.example.net
port 25
from www-site@example.net
Тестируем работоспособность схемы путём отправки почтового сообщения:
# echo "Test" | msmtp --debug --account=default test@site.example.net
loaded system configuration file /etc/msmtprc
....
<-- 220 mx.example.net ESMTP Exim ...
--> EHLO localhost
....
<-- 250 OK id=...
--> QUIT
<-- 221 mx.example.net closing connection
....
<-- 220 mx.example.net ESMTP Exim ...
--> EHLO localhost
....
<-- 250 OK id=...
--> QUIT
<-- 221 mx.example.net closing connection
Пробуем посредством PHP-интерпретатора отсылать почту через "mSMTP":
# sudo -u www-group0 /usr/bin/php -r "mail('test@site.example.net', 'Test', 'Test');"
Настройка СУБД "MySQL".
Предварительно создадим или обновим параметры прав доступа директории хранения временных файлов - она будет затронута при оптимизации работы СУБД:
# mkdir -p /var/lib/mysql/tmp
# chown -R mysql:mysql /var/lib/mysql/tmp
# chmod -R go-rwx /var/lib/mysql/tmp
# chown -R mysql:mysql /var/lib/mysql/tmp
# chmod -R go-rwx /var/lib/mysql/tmp
Слегка дополняем глобальную конфигурацию СУБД:
# vi /etc/mysql/mysql.cnf
....
[mysql]
# Явно указываем предпочтительную "кодировку" СУБД
default_character_set = utf8
....
[mysqld]
# Для совместимости с грязно-кодными CMS (вроде "Bitrix") выключаем строгое следование SQL-стандартам
sql_mode = ''
innodb_strict_mode = 0
# Явно указываем предпочтительную "кодировку" данных при приёме подключений к серверу
character_set_server = utf8
collation_server = utf8_unicode_ci
#
init_connect='SET collation_connection = utf8_general_ci'
init_connect='SET NAMES utf8'
# Выносим временные файлы в удобное нам место
tmpdir = /var/lib/mysql/tmp
innodb_tmpdir = /var/lib/mysql/tmp
# Включаем работу с клиентами через локальный файловый "сокет"
socket = /var/run/mysqld/mysqld.sock
# Разрешаем подключаться только с "localhost"
bind-address = 127.0.0.1
# Увеличиваем лимит размера временной таблицы, обрабатываемой в ОЗУ, до сброса её в файловую систему
tmp_table_size = 512M
# Увеличиваем лимит размера создаваемых пользователем MEMORY-таблицы, обрабатываемой в ОЗУ
max_heap_table_size = 512M
# Чуть увеличиваем лимиты буферов, до которых блоки данных будут кешироваться
query_cache_limit = 10M
query_cache_size = 256M
key_buffer_size = 512M # (default: 8M; optimal: 20% RAM)
# Увеличиваем размер пакетов данных и время их обработки (для SQL-дампов больших таблиц)
max_allowed_packet = 2048M # (default: 16M)
net_read_timeout = 3600 # (default: 30 seconds)
net_write_timeout = 3600 # (default: 60 seconds)
# Увеличиваем размер буфера запросов и журнала транзакций
innodb_buffer_pool_size = 2G # (default: 128MB; optimal: 60% RAM)
innodb_log_file_size = 512M # (default: 5MB)
innodb_log_buffer_size = 256M # (default: 8MB)
# Указываем записывать журнал транзакций примерно один раз в секунду, а не немедленно после каждого изменения данных (default: 1)
innodb_flush_log_at_trx_commit = 2
# Увеличиваем количество параллельных потоков чтения/записи (default: 4)
innodb_read_io_threads = 16
innodb_write_io_threads = 16
# Включаем поддержку нового формата файлов DB
innodb_file_format = barracuda
# Включаем поддержку длинных индексных ключей
innodb_large_prefix = 1
# Явно указываем создавать для каждой таблицы отдельные файлы описаний (.frm) и данных (.ibd) на диске, а не сваливать всё в один (ibdataX)
innodb_file_per_table = 1
....
[client]
# Явно указываем предпочтительную "кодировку" данных при подключении к серверу
default_character_set = utf8
....
[mysql]
# Явно указываем предпочтительную "кодировку" СУБД
default_character_set = utf8
....
[mysqld]
# Для совместимости с грязно-кодными CMS (вроде "Bitrix") выключаем строгое следование SQL-стандартам
sql_mode = ''
innodb_strict_mode = 0
# Явно указываем предпочтительную "кодировку" данных при приёме подключений к серверу
character_set_server = utf8
collation_server = utf8_unicode_ci
#
init_connect='SET collation_connection = utf8_general_ci'
init_connect='SET NAMES utf8'
# Выносим временные файлы в удобное нам место
tmpdir = /var/lib/mysql/tmp
innodb_tmpdir = /var/lib/mysql/tmp
# Включаем работу с клиентами через локальный файловый "сокет"
socket = /var/run/mysqld/mysqld.sock
# Разрешаем подключаться только с "localhost"
bind-address = 127.0.0.1
# Увеличиваем лимит размера временной таблицы, обрабатываемой в ОЗУ, до сброса её в файловую систему
tmp_table_size = 512M
# Увеличиваем лимит размера создаваемых пользователем MEMORY-таблицы, обрабатываемой в ОЗУ
max_heap_table_size = 512M
# Чуть увеличиваем лимиты буферов, до которых блоки данных будут кешироваться
query_cache_limit = 10M
query_cache_size = 256M
key_buffer_size = 512M # (default: 8M; optimal: 20% RAM)
# Увеличиваем размер пакетов данных и время их обработки (для SQL-дампов больших таблиц)
max_allowed_packet = 2048M # (default: 16M)
net_read_timeout = 3600 # (default: 30 seconds)
net_write_timeout = 3600 # (default: 60 seconds)
# Увеличиваем размер буфера запросов и журнала транзакций
innodb_buffer_pool_size = 2G # (default: 128MB; optimal: 60% RAM)
innodb_log_file_size = 512M # (default: 5MB)
innodb_log_buffer_size = 256M # (default: 8MB)
# Указываем записывать журнал транзакций примерно один раз в секунду, а не немедленно после каждого изменения данных (default: 1)
innodb_flush_log_at_trx_commit = 2
# Увеличиваем количество параллельных потоков чтения/записи (default: 4)
innodb_read_io_threads = 16
innodb_write_io_threads = 16
# Включаем поддержку нового формата файлов DB
innodb_file_format = barracuda
# Включаем поддержку длинных индексных ключей
innodb_large_prefix = 1
# Явно указываем создавать для каждой таблицы отдельные файлы описаний (.frm) и данных (.ibd) на диске, а не сваливать всё в один (ibdataX)
innodb_file_per_table = 1
....
[client]
# Явно указываем предпочтительную "кодировку" данных при подключении к серверу
default_character_set = utf8
....
Параметры СУБД "MySQL" раскиданы по нескольким файлам в директории "/etc/mysql", так что есть смысл убедится, не переопределяются ли они где-то в "/etc/mysql/mysql.conf.d/".
Проверить корректность синтаксиса конфигурационных файлов MySQL-сервера можно следующим способом:
# mysqld --verbose --help 1>/dev/null
... [ERROR] unknown variable 'table_cache=512'
... [ERROR] Aborting
... [ERROR] Aborting
После проверки синтаксической корректности перезапускаем СУБД для применения конфигурации:
# /etc/init.d/mysql restart
Проконтролировать фактическое применение параметров можно посредством 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)
+-------------------+--------------------+
| 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)
Практически все ныне эксплуатируемые сайты хранят данные к кодировке "UTF-8", так что обязательно добиваемся этого для всех параметров:
mysql> SHOW VARIABLES LIKE 'char%';
+--------------------------+--------+
| Variable_name | Value |
+--------------------------+--------+
| character_set_client | utf8 |
| character_set_connection | utf8 |
| character_set_database | utf8 |
| character_set_filesystem | binary |
| character_set_results | utf8 |
| character_set_server | utf8 |
| character_set_system | utf8 |
+--------------------------+--------+
+--------------------------+--------+
| Variable_name | Value |
+--------------------------+--------+
| character_set_client | utf8 |
| character_set_connection | utf8 |
| character_set_database | utf8 |
| character_set_filesystem | binary |
| character_set_results | utf8 |
| character_set_server | utf8 |
| character_set_system | utf8 |
+--------------------------+--------+
Аналогично есть смысл проверить параметры сортировки и сопоставления данных: "SHOW VARIABLES LIKE 'collation%';".
Оптимизация работы "MySQL" с дисковой подсистемой.
Для СУБД, активно создающей и уничтожающей файлы временных таблиц, выгодно вынести (параметром "tmpdir") эту работу в файловую систему, смонтированную в область памяти ОЗУ:
# 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
....
# 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
Я бы выделил под эту файловую систему до 25% от всей ОЗУ (она не заблокирует всё заявленное место, а будет выбирать блоки памяти по мере появления необходимости).
Понижение уровня параноидальности "MySQL".
В современном "MySQL" из соображений заботы о пользователях по умолчанию включён строгий режим создания сложного пароля из символов разного регистра, цифр и спецсимволов. В локальной схеме с парой-тройкой сайтов это может быть избыточным - это просто отключается:
# mysql -u root -p
mysql> SHOW GLOBAL VARIABLES LIKE 'validate_password%';
+--------------------------------------+--------+
| Variable_name | Value |
+--------------------------------------+--------+
| validate_password_check_user_name | OFF |
| validate_password_dictionary_file | |
| validate_password_length | 8 |
| validate_password_mixed_case_count | 1 |
| validate_password_number_count | 1 |
| validate_password_policy | MEDIUM |
| validate_password_special_char_count | 1 |
+--------------------------------------+--------+
mysql>
mysql> uninstall plugin validate_password;
mysql>
mysql> SHOW GLOBAL VARIABLES LIKE 'validate_password%';
Empty set (0.00 sec)
+--------------------------------------+--------+
| Variable_name | Value |
+--------------------------------------+--------+
| validate_password_check_user_name | OFF |
| validate_password_dictionary_file | |
| validate_password_length | 8 |
| validate_password_mixed_case_count | 1 |
| validate_password_number_count | 1 |
| validate_password_policy | MEDIUM |
| validate_password_special_char_count | 1 |
+--------------------------------------+--------+
mysql>
mysql> uninstall plugin validate_password;
mysql>
mysql> SHOW GLOBAL VARIABLES LIKE 'validate_password%';
Empty set (0.00 sec)
Настройка СУБД "MongoDB".
По умолчанию "MongoDB" запускается в режиме полностью свободного доступа, без ограничений на чтение и модификацию данных.
Сразу после первичной инсталляции в "MongoDB" пользователи отсутствуют. Заведём для начала суперпользователя:
$ mongo --quiet
> use admin
> db.createUser( { user: "root", pwd: "rootPassword", roles: [ { role:"root", db:"admin" } ] } )
> exit
> use admin
> db.createUser( { user: "root", pwd: "rootPassword", roles: [ { role:"root", db:"admin" } ] } )
> exit
После создания учётной записи суперпользователя СУБД следует перезапустить "MongoDB" в режиме работы с доступом к данным только после аутентификации и проверки привилегий:
# vi /etc/mongodb.conf
....
# Прослушиваемый сервисом IP-адрес и TCP-порт
# (разрешаем подключаться только с "localhost")
bind_ip = 127.0.0.1
port = 27017
....
# Turn on/off security (default: off)
auth = true
....
# Прослушиваемый сервисом IP-адрес и TCP-порт
# (разрешаем подключаться только с "localhost")
bind_ip = 127.0.0.1
port = 27017
....
# Turn on/off security (default: off)
auth = true
....
# systemctl restart mongodb
Настройка "Crontab" в произвольном месте файловой системы.
Учитывая то, что подключающиеся к web-площадке по SFTP и замкнутые в в "chroot" web-разработчики не имеют доступа к содержимому директории "/var/spool/cron/crontabs/", придётся сделать небольшой финт ушами, предлагая редактировать условный "crontab" в доступном им месте, а по событию изменения этого файла синхронизировать его его содержимое с системным.
Осуществим это через компонент мониторинга изменений файлов "Inotify", входящий в ядро "Linux" начиная с версии "2.6.13".
Включаем мониторинг событий появления конфигурационных файлов в директории "./conf/" и переинициализации при этом правил наблюдения "incrond", а также изменения регламентировано именуемых "./conf/crontab" файлов подставных "crontab"-ов с запуском экспорта содержимого таковых в обслуживаемое системой пользовательское окружение:
# vi /etc/incron.d/web-crontab-export
#InCron_table_does_not_support_comments_with_several_(more_than_one)_words!
#<directory>_<file_change_mask>_<command_or_action>_<options>
/var/www/group0/conf IN_CREATE /etc/init.d/incron reload &
/var/www/group0/conf/crontab IN_MODIFY,IN_CLOSE_WRITE,IN_MOVE crontab -u www-group0 $@
#<directory>_<file_change_mask>_<command_or_action>_<options>
/var/www/group0/conf IN_CREATE /etc/init.d/incron reload &
/var/www/group0/conf/crontab IN_MODIFY,IN_CLOSE_WRITE,IN_MOVE crontab -u www-group0 $@
Параметр "$@" в конфигурации InCron указывает подставить сюда полный путь к объекту файловой системы, событие которого обрабатывается.
Сервис "incrond" мониторит состояние своих конфигурационных файлов и немедленно применяет изменения:
# tail -100 /var/log/syslog
....
... incrond[...]: system table web-crontab-export changed, reloading
... incrond[...]: system table web-crontab-export changed, reloading
Создаём подставной "crontab"-файл из потенциально уже имеющегося системного:
# crontab -u www-group0 -l > /var/www/group0/conf/crontab
# chown www-group0:www-group0 /var/www/group0/conf/crontab
# chmod g+w /var/www/group0/conf/crontab && chmod o-rwx /var/www/group0/conf/crontab
# chown www-group0:www-group0 /var/www/group0/conf/crontab
# chmod g+w /var/www/group0/conf/crontab && chmod o-rwx /var/www/group0/conf/crontab
Если файл "/var/www/group0/conf/crontab" отсутствует или случайно удалён, то достаточно его создать снова, как функциональность будет восстановлена.
Настройка простейшего резервного копирования данных.
Подключаем внешнее файловое хранилище:
# vi /etc/fstab
....
# Full backup file storage
10.20.30.40:/volume1/backup_example.net/ /mnt/backup nfs _netdev,rw 0 0
....
# Full backup file storage
10.20.30.40:/volume1/backup_example.net/ /mnt/backup nfs _netdev,rw 0 0
....
Создаём точку монтирования и подключаем внешнее хранилище в файловую систему:
# mkdir -p /mnt/backup
# mount /mnt/backup
# mkdir -p /mnt/backup/web
# chown -R root:root /mnt/backup/web
# chmod -R go-rwx /mnt/backup/web
# mount /mnt/backup
# mkdir -p /mnt/backup/web
# chown -R root:root /mnt/backup/web
# chmod -R go-rwx /mnt/backup/web
Пишем простейший скрипт, копирующуи и упаковывающий файлы web-сайтов, а также снимающий "бинарный дамп" БД "MySQL" и выгружающий резервную копию всех БД "MongoDB":
# mkdir -p /usr/local/etc/backup
# vi /usr/local/etc/backup/backup-web-full.sh && chmod ugo+x /usr/local/etc/backup/backup-web-full.sh
# vi /usr/local/etc/backup/backup-web-full.sh && chmod ugo+x /usr/local/etc/backup/backup-web-full.sh
#!/bin/bash
# Месторасположение директорий для резервных копий.
DPATH="/var/backups/web"
# Копируем содержимое директорий web-сайтов как таковых, без служебной и дополнительной пользовательской информации.
tar -c -z -f "${DPATH}/example.www.$(date +%Y-%m-%dT%H:%M).tar.gz" /var/www/*/*/www/
# Полный срез баз данных MySQL посредством XtraBackup.
xtrabackup --backup --stream=tar --target-dir="${DPATH}/" | gzip - > "${DPATH}/example.mysql-xtra.$(date +%Y-%m-%dT%H:%M).tar.gz"
# Полный срез баз данных MongoDB.
mongodump --host localhost --port 27017 --username root --password "`cat /root/.mongodb`" --authenticationDatabase=admin --gzip --archive="${DPATH}/example.mongodump.$(date +%Y-%m-%dT%H:%M).gz"
exit ${?}
# Месторасположение директорий для резервных копий.
DPATH="/var/backups/web"
# Копируем содержимое директорий web-сайтов как таковых, без служебной и дополнительной пользовательской информации.
tar -c -z -f "${DPATH}/example.www.$(date +%Y-%m-%dT%H:%M).tar.gz" /var/www/*/*/www/
# Полный срез баз данных MySQL посредством XtraBackup.
xtrabackup --backup --stream=tar --target-dir="${DPATH}/" | gzip - > "${DPATH}/example.mysql-xtra.$(date +%Y-%m-%dT%H:%M).tar.gz"
# Полный срез баз данных MongoDB.
mongodump --host localhost --port 27017 --username root --password "`cat /root/.mongodb`" --authenticationDatabase=admin --gzip --archive="${DPATH}/example.mongodump.$(date +%Y-%m-%dT%H:%M).gz"
exit ${?}
Настраиваем автоматический запуск процедуры, например дважды в неделю, в Понедельник и Четверг:
# vi /etc/crontab
....
# Full web-sites backup
0 2 * * 0,3 root /usr/local/etc/backup/backup-web-full.sh &
# Full web-sites backup
0 2 * * 0,3 root /usr/local/etc/backup/backup-web-full.sh &
Автоматизация рутинных процедур.
В этой публикации мы разобрали пошаговое развёртывание web-сервиса. В следующей заметке приведены примеры скриптов, позволяющих снизить до минимума ручное конфигурирование компонентов web-сервиса при создании новых "сводных площадок" групп сайтов и сайтов как таковых.
6 января 2020 в 02:14
6 января 2020 в 10:53