UMGUM.COM (лучше) 

Moodle ( О нюансах развёртывания сайтов под управлением LMS "Moodle". )

12 октября 2017  (обновлено 20 августа 2021)

OS: "Linux Debian 8/9/10", "Linux Ubuntu 16/18/20 LTS".
Applications: "Nginx", PHP-FPM, "MySQL".

Задача: подготовить несущий web-сервер, удовлетворяющий требованиям системы управления процессом преподавания посредством "виртуальной рабочей среды", реализованной в виде комплексного PHP-приложения "Moodle" (от аббревиатуры "Modular Object-Oriented Dynamic Learning Environment", "модульная объектно-ориентированная динамическая обучающая среда"). Попросту говоря - это CMS (вообще такого рода программное обеспечение уже выделяется в отдельный класс LMS - "Learning Management System"), позволяющая действительно просто развёртывать сайты для онлайн-обучения.


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


Предварительная подготовка файловой системы.

Самому классическому web-серверу более чем достаточно 50GB на все его нужды, но объём данных учебных курсов LMS "Moodle" может возрастать самым непредсказуемым образом - в зависимости от аппетитов преподавателей. Удобнее всего завести отдельный виртуальный диск для данных, начальными размером до 200-250 Гигабайт, который можно будет расширять по мере появления необходимости, не затрагивая при этом операционную систему.

Исходя из того, что системный диск уже размечен и введён в работу, добавляем диск (в нашем случае второй, предположительно именованный как "sdb") и файловую систему для хранения данных (разумеется, мы живём с LVM):

# pvcreate /dev/sdb
# vgcreate vg1 /dev/sdb
# lvcreate --zero y -l 100%FREE -n lvmoodledata vg1
# mkfs.ext4 -L moodle-data /dev/vg1/lvmoodledata

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

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

# vi /etc/ssh/sshd_config

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

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

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

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

Предварительно проверяем корректность изменений конфигурационного файла и перезапускаем OpenSSH-сервер:

# sshd -t
# /etc/init.d/ssh reload

Устанавливаем программное обеспечение.

Прежде всего обновим имеющееся ПО и укомплектуем операционную систему утилитами комфорта:

# aptitude update
# aptitude full-upgrade
# aptitude install coreutils sudo acl psmisc net-tools dnsutils host htop iotop tree findutils mc vim pwgen ntpdate dirmngr

Некоторые современные и потому модные подсистемы чаще всего лишние - иногда их есть смысл удалить, чтобы не путались "под ногами":

# aptitude purge snapd lxd lxcfs

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

PHP-интерпретатор:

# aptitude install php-fpm php-cgi php-cli php-opcache php-pgsql php-mysqli php-memcache php-mongodb php-mbstring php-pclzip php-xml php-sockets php-json php-gd php-curl php-geoip php-ldap php-zip php-xmlrpc php-soap php-intl php-pspell

Web-сервер:

# aptitude install nginx apache2-utils

Почтовая утилита:

# aptitude install msmtp

СУБД (для ОС до 2020-го):

# aptitude install mysql-server

СУБД (для ОС после 2020-го):

Скачиваем и применяем PGP-ключ, которым подписано содержимое официального APT-репозитория "MySQL", формируем выделенный конфигурационный файл описания подключения к репозиторию и устанавливаем ПО:

# apt-key adv -v --keyserver pgp.mit.edu --recv-keys 5072E1F5
# echo -e "# Official APT-repository MySQL\ndeb [arch=amd64] deb http://repo.mysql.com/apt/$(. /etc/os-release; echo "$ID") $(lsb_release -cs) mysql-8.0" >> /etc/apt/sources.list.d/mysql.list
# aptitude update && aptitude install -y mysql-server

LMS "Moodle" для работы требуются специфичное ПО - устанавливаем его и проверяем, появились ли нужные утилиты по ожидаемым путям:

# aptitude --without-recommends install texlive-extra-utils
# ls /usr/bin/latex
# ls /usr/bin/dvips
# ls /usr/bin/dvisvgm

# aptitude --without-recommends install aspell aspell-en aspell-ru
# ls /usr/bin/aspell

# aptitude --without-recommends install graphicsmagick-imagemagick-compat
# ls /usr/bin/convert

# aptitude --without-recommends install ghostscript gsfonts
# ls /usr/bin/gs

# aptitude --without-recommends install graphviz
# ls /usr/bin/dot

# aptitude --without-recommends install unoconv
# ls /usr/bin/unoconv

Устанавливаем точное системное время.

Виртуальный сервер синхронизирует свои часы через несущую систему виртуализации, но изначально может потребоваться явно указать желаемый часовой пояс (пример для Новосибирска):

# echo "Asia/Novosibirsk" > /etc/timezone
# rm /etc/localtime
# ln -sf /usr/share/zoneinfo/Asia/Novosibirsk /etc/localtime

Синхронизируемся с каким-нибудь солидным сервером:

# ntpdate 0.asia.pool.ntp.org

Если это аппаратный сервер, то сохраняем полученные показатели времени в постоянную память BIOS:

# hwclock --systohc --utc

Дополняем системный набор национальных кодировок.

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

# dpkg-reconfigure locales

Естественно, кроме всего прочего, активируем пункт "ru_RU.utf8". В качестве варианта "по умолчанию", применяемого для приложений не требующих определённой кодировки, я выбираю "en_US.UTF-8".

Удостоверяемся в успешности изменения перечня поддерживаемых кодировок:

# locale -a

C.UTF-8
en_US.utf8
ru_RU.utf8

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

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

# mkdir -p /var/www
# chown root:root /var/www
# chmod go-w /var/www
# chmod go+rx /var/www

Через расширение POSIX ACL предпишем устанавливать всем создаваемым файлам (и директориям) разрешения полного доступа как для пользователя, так и группы (в отличии от системных установок "umask 0022", разрешающим группе только чтение), при этом полностью убираем доступ всем остальным:

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

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

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

# setfacl --no-mask --modify group::rX,other:rX /var/www

Смотреть результаты - так:

# getfacl /var/www

Добавляем директории ресурсов LMS-сайта как такового:

# mkdir -p /var/www/moodle.example.net
# mkdir -p /var/www/moodle.example.net/moodledata
# mkdir -p /var/www/moodle.example.net/tmp
# mkdir -p /var/www/moodle.example.net/www

Настройка PHP.

Предварительно создадим или обновим параметры прав доступа директории хранения файлов web-сессий - в дальнейшем мы слегка это оптимизируем:

# mkdir -p /var/lib/php/sessions
# chmod go-r /var/lib/php/sessions
# chmod +t /var/lib/php/sessions

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

# vi /etc/php/7.2/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
....
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
....
session.gc_maxlifetime = 864000
....

Обязательно проверяем корректность конфигурации, простейшим тестированием:

# php -e -c /etc/php/7.2/fpm/php.ini -r 'echo "OK\n";';

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

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

; Блок описания отдельного инстанса PHP-FPM (их может быть несколько, обслуживающих разные web-сервисы)
[www]
....
; Указываем пользователя, от имени которого PHP-FPM будет запущен
user = www-data
group = www-data
....
; Явно переключаемся на работу через локальный файловый "сокет", существенно снижая задержки при вызовах (открытие файла всегда быстрее сетевой операции), указывая при этом владельца "сокета"
; listen = 127.0.0.1:9000
listen = /run/php/php-fpm.sock
....
listen.owner = www-data
listen.group = www-data
....
; Режим запуска инстанса
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
....

; Блок переопределения в среде исполнения PHP-FPM глобальных параметров PHP-интерпретатора
php_admin_value[sys_temp_dir] = /var/www/moodle.example.net/tmp
php_admin_value[post_max_size] = 300M
php_admin_value[upload_max_filesize] = 300M
php_admin_flag[allow_url_fopen] = On

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

# php-fpm7.2 -t --fpm-config /etc/php/7.2/fpm/pool.d/www.conf
# /etc/init.d/php7.2-fpm reload

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

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

Место сохранения сессий в 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
....

# mount /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 &
....

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

Слегка корректируем глобальную конфигурацию 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;
....

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

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

# Перехватываем все запросы по протоколу без шифрования и перенаправляем их на HTTPS
server {
  listen       80;
  server_name  www.moodle.example.net moodle.example.net;

  access_log off;
  error_log  off;

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

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

  ssl on;
  include /etc/nginx/ssl_wild_params;

  access_log off;
  error_log  off;

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

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

  # Принудительно переводим сайт на работу только через SSL
  ssl on;
  include /etc/nginx/ssl_wild_params;

  access_log /var/log/nginx/moodle.example.net-access.log;
  error_log /var/log/nginx/moodle.example.net-error.log;

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

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

  root /var/www/moodle.example.net/www;
  index index.html index.htm index.php;

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

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

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

  # Блокируем доступ к классически неправильно и опасно именованным файлам (вроде ".php.1")
  location ~* \.(phtml|php)(?!(\?|\/|$)) {
    deny all;
    log_not_found off;
  }

  # Выносим директорию хранения файлов за пределы сайта
  location /moodledata/ {
    internal;
    # Путь обязательно должен завершаться символом "/"
    alias /var/www/moodle.example.net/moodledata/;
  }

  # Обработчик прямых обращений к PHP-скриптам
  location ~ ^(.+\.php)(.*)$ {
    fastcgi_split_path_info  ^(.+\.php)(.*)$;
    include       /etc/nginx/fastcgi_params;
    fastcgi_pass  $php_pass;
    fastcgi_param PATH_INFO       $fastcgi_path_info;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
  }

  # Не реагируем на неинтересные события загрузок
  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;
  }
}

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

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

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

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

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

Предварительно создадим или обновим параметры прав доступа директории хранения временных файлов - она будет затронута при оптимизации работы СУБД:

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

Основное требование, предъявляемое LMS "Moodle" к "MySQL" - поддержка формата хранения данных в "InnoDB":

# vi /etc/mysql/mysql.conf.d/mysqld.cnf

[mysqld]
....

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

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

# Явно указываем месторасположение директории временных файлов СУБД (позже оптимизируем её работу)
tmpdir = /var/lib/mysql/tmp
innodb_tmpdir = /var/lib/mysql/tmp
....

# * InnoDB
#
# Включаем поддержку нового формата файлов DB
#innodb_file_format = barracuda # (deprecated)

# Включаем поддержку длинных индексных ключей
#innodb_large_prefix = 1 (deprecated)

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

# Указываем записывать журнал транзакций примерно один раз в секунду, а не немедленно после каждого изменения данных
innodb_flush_log_at_trx_commit = 2

# Увеличиваем количество параллельных потоков чтения/записи (по умолчанию: 4)
innodb_read_io_threads = 16
innodb_write_io_threads = 16

# Слегка расширяем объём буфера данных в ОЗУ
innodb_buffer_pool_size = 4G // ~60% RAM

# Увеличиваем размер журнала транзакций
innodb_log_file_size = 256M
....

LMS "Moodle" сильно хочет, чтобы СУБД предоставляла ей возможность сохранять данные в современной кодировке "utf8mb4", поддерживающей "четырёхбайтные символы" (в частности, для особо детализированных "смайликов"). С точки зрения общей функциональности это непринципиально, но в общем неплохо заранее перейти на более современные форматы хранения данных:

# vi /etc/mysql/mysql.conf.d/mysqld.cnf

[client]
default-character-set = utf8mb4
....

[mysql]
default-character-set = utf8mb4
....

[mysqld]
....
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
skip-character-set-client-handshake
....

Проверяем корректность синтаксиса конфигурационных файлов MySQL-сервера и перезапускаем таковой:

# mysqld --verbose --help 1>/dev/null
# /etc/init.d/mysql restart

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

# mysql

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 с дисковой подсистемой.

Для СУБД, активно создающей и уничтожающей файлы для временных таблиц, выгодно вынести (параметрами "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
....

# mount /var/lib/mysql/tmp

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

Создаём БД для Moodle-сайта.

# mysql

MySQL> CREATE DATABASE `moodle_example_net` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
MySQL> CREATE USER 'moodle_example_net'@'localhost' IDENTIFIED BY 'dbPassword';
MySQL> GRANT ALL PRIVILEGES ON `moodle_example_net`.* TO 'moodle_example_net'@'localhost';
MySQL> FLUSH PRIVILEGES;

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

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

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

# 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@moodle.example.net

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

loaded system configuration file /etc/msmtprc
....
<-- 220 mx.example.net ESMTP Exim Thu, 06 Jul 2017 17:22:36 +0700
--> EHLO localhost
....
<-- 250 OK id=1dT3vg-0002Vv-Q4
--> QUIT
<-- 221 mx.example.net closing connection

При желании явно указываем PHP отсылать почту через "mSMTP", но на самом деле мы эту возможность уже реализовали ранее посредством символической ссылки к "sendmail":

# vi /etc/php/7.2/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@example.net', 'Test', 'Test');"

Загрузка дистрибутива.

LMS "Moodle" крупный проект, который естественным образом разделился на две кодовых ветви: "latest" и условный LTS (Long-term support). В частности, нынешний LTS - ветка "3.4.6", обновления безопасности для которой обещают выпускать до Мая 2019 года. Учитывая то, что в государственных бюджетных организациях процессы протекают чрезвычайно медленно (например попытка обновления существующего портала до "latest" версии "3.3" в НГУ длилась так долго, что разработчики за это время успели выкатить новый релиз "3.4"), то удобнее играть роль консерватора и в работе использовать LTS-релизы:


Загрузить дистрибутив можно следующим образом:

# cd /usr/src && wget https://download.moodle.org/download.php/direct/stable34/moodle-3.4.6.tgz

Развёртывание сайта под управлением LMS "Moodle".

Всё просто - разворачиваем дистрибутивный архив в целевую директорию, отрезав от имён извлекаемых файловых ресурсов предваряющее "moodle/":

# tar -xvf /usr/src/moodle-3.4.6.tgz -C /var/www/moodle.example.net/www --strip-components=1

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

# chown -R www-data:www-data /var/www/moodle.example.net
# setfacl --no-mask --modify user::rX,group::rX /var/www/moodle.example.net

Весь процесс инсталляции реализован через web-интерфейс:

https://moodle.example.net/install.php

Разработчики LMS "Moodle" реализовали полное разделение данных и средств управления таковыми: абсолютно всё пользовательское сосредоточено в директории "./moodledata", а файлы LMS могут быть заменены совершенно безбоязненно, за исключением несущего ключевые настройки файла "./www/config.php".

Отсюда следует, что если установка LMS "Moodle" пошла "совсем не так", то для её возобновления нужно всего лишь удалить файл "./www/config.php" (он потом будет воссоздан из "./www/config-dist.php").

По завершению установки лучше бы сразу проверить, получилось ли удовлетворить все требования LMS "Moodle" к программному обеспечению:

https://moodle.example.net/admin/environment.php

Корректировка разрешений доступа к пользовательским ресурсам.

Слишком свободные разрешения доступа для создаваемых LMS "Moodle" файлов мне не нравятся - исправим это (запретив доступ всем не членам группы "www-data"), заодно уточнив в конфигурационном файле ресурсов, там ли директория с данными, где предполагается:

# vi /var/www/moodle.example.net/www/config.php

<?php // Moodle configuration file
....
$CFG->dataroot = '/var/www/moodle.example.net/moodledata';
....
$CFG->directorypermissions = 0770;
....

# chmod -R o-rwx /var/www/moodle.example.net/moodledata

Вынос файлов web-сессий в общепринятое место.

Ранее мы уже создали условия для хранения, быстрого к ним доступа и управления файлами "PHP sessions". LMS "Moodle" по умолчанию хранит таковые в своих директориях, что неэффективно - исправим это (для режима хранения сессий в файлах):

# vi /var/www/moodle.example.net/www/config.php

<?php // Moodle configuration file
....
$CFG->session_handler_class = '\core\session\file';
$CFG->session_file_save_path = '/var/lib/php/sessions';
$CFG->sessiontimeout = 14400;
....

Как выяснилось в эксплуатации, LMS "Moodle" почему-то удаляет сессии, исходя из взятого из конфигурации PHP параметра "session.gc_maxlifetime" в 1440 секунд (по умолчанию). При этом переопределение этого параметра из панели администрирования "Moodle -> Администрирование -> Сервер -> Сессии" не срабатывало, без каких-либо предупреждений. Сайт молчком удалял свои "бездействующие" сеансы через 1440 секунд.

Помогло лишь явное указание длительности таймаута бездействия сессии в конфигурационном файле "Moodle". После этого в административной панели сайта у параметра сессии появилась отметка "Значение определено в файле config.php". Похоже, что это мелкий баг, вот таким образом обходящийся.

Вероятно, этого же эффекта глобально можно было добиться изменением параметра PHP "session.gc_maxlifetime" до больших значений.

Вынос хранилища файлов курсов на иную файловую систему.

LMS "Moodle" сам по себе громоздкий web-проект (164 Мегабайта при первичной установке, для v3.4.6), а уж файлы учебных курсов (по умолчанию располагающиеся в директории "./moodledata/filedir/") так быстро отъедают дисковое пространство на сотни Гигабайт, что весьма скоро сервер может стать немобильным. Потому первым делом стоит озаботится явным отделением тяжёлых данных от логики и легковесного контента, вынеся файловое хранилище на отдельную файловую систему. Я подключаю его по NFS или с иного внешнего СХД, в точку вне самого сайта, чтобы не смешивать сущности:

# vi /etc/fstab

....
# Moodle file storage
/dev/vg1/lvmoodledata /var/www/moodle.example.net/mnt ext4 rw,noatime,nodiratime,nosuid,nodev 0 0
....

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

# mkdir -p /var/www/moodle.example.net/mnt
# mount /var/www/moodle.example.net/mnt

Перемещаем директорию с данными на выделенную файловую систему:

# mkdir -p /var/www/moodle.example.net/mnt/moodledata
# mv /var/www/moodle.example.net/moodledata/filedir /var/www/moodle.example.net/mnt/moodledata/filedir

Для выноса web-ресурсов в пределах файловой структуры доступной PHP вполне достаточно "символической ссылки":

# ln -s /var/www/moodle.example.net/mnt/moodledata/filedir /var/www/moodle.example.net/moodledata/filedir

# chown -R www-data:www-data /var/www/moodle.example.net/mnt/moodledata/filedir
# chmod -R g+w /var/www/moodle.example.net/mnt/moodledata/filedir
# chmod -R o-rwx /var/www/moodle.example.net/mnt/moodledata/filedir

Важно понимать, что целиком выносить ресурс "./moodledata/" на внешнюю СХД нежелательно - в этой директории также расположены "кеши" и временные файлы ("./cache/", "./localcache/", "./temp/" и "./lock/"), постоянно запрашиваемые на чтение и изменение web-сервером. А вот большие и редко запрашиваемые файлы директории "./filedir/" вполне можно сложить на медленной файловой системе - при частом использовании они всё равно будут слегка кешироваться и скорость доступа к ресурсам в целом не снизится.

Обеспечиваем возможность запуска заданий по расписанию.

В LMS "Moodle" встроен функционал исполнения заданий по расписанию. Он опирается на вызовы через системный "crontab":

В нашем случае пользовательский "crontab" расположен в "/var/spool/cron/crontabs/www-data", и в него следует внести соответствующее правило:

# sudo -u www-data crontab -e

....
# Moodle schedule initiator
*/5 *  * * *  /usr/bin/php -q -f /var/www/moodle.example.net/www/admin/cli/cron.php >/dev/null 2>&1 &

Включаем антивирусную проверку (опционально).

При желании можно установить антивирус "ClamAV" - в LMS "Moodle" есть плагин, посредством которого можно наладить предварительное сканирование загружаемых пользователями файлов:

# aptitude install clamav
# ls /usr/bin/clamscan

# mkdir -p /var/quarantine
# chown -R www-data:www-data /var/quarantine

Управление режимом вывода сообщений об ошибках.

По умолчанию в LMS "Moodle" принята политика подавления любых сообщений об ошибках от PHP-интерпретатора - вероятно из соображений как безопасности (сложнее взломать, основываясь на ответах сервиса), так и производительности (снижение нагрузки на дисковую подсистему). На этапе поиска неполадки можно включить отображение детальной информации следующим набором параметров в конфигурационном файле web-приложения:

# vi /var/www/moodle.example.net/www/config.php

<?php // Moodle configuration file
....

// Force a debugging mode regardless the settings in the site administration
@error_reporting(E_ALL);         // NOT FOR PRODUCTION SERVERS!
@ini_set('display_errors', '1'); // NOT FOR PRODUCTION SERVERS!
$CFG->debug = E_ALL;             // NOT FOR PRODUCTION SERVERS!
$CFG->debugdisplay = 1;          // NOT FOR PRODUCTION SERVERS!
....

Очень желательно после исправления проблемы снизить детализацию сообщений интерпретатора, или, как минимум, отключить их показ пользователю (опции "display_errors" и "debugdisplay").

Восстановление доступа к учётной записи администратора.

Довольно часто случается, что пароль к учётной записи web-сервиса LMS "Moodle" оказывается забыт или утрачен. В составе сайта имеется соответствующая утилита, посредством которой пароль для известного пользователя сбрасывается в один проход:

# sudo -u www-data php /var/www/moodle.example.net/www/admin/cli/reset_password.php

== Password reset ==
Enter username (manual authentication only)
: admin
....

На самом дела технически ничто не препятствует заменить "хеш" пароля в БД - но это чуть сложнее и не нужно:

# mysql
MySQL> USE moodle;
MySQL> SELECT * FROM mdl_user WHERE username='%admin%';
MySQL> UPDATE mdl_user SET password=MD5('strongAdminPassword') WHERE username='admin';
MySQL> QUIT;

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

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

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

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

Lifting the server siege...
Transactions:      188316 hits
Availability:      100.00 %
Elapsed time:      599.28 secs
Data transferred: 6127.82 MB
Response time:       0.76 secs
Transaction rate:  314.24 trans/sec
Throughput:         10.23 MB/sec
Concurrency:       238.28
Successful transactions: 185865
Failed transactions:          0
Longest transaction:       2.93
Shortest transaction:      0.00

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


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


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