Очень шустрый блог на WordPress при помощи связки nginx + PHP-FPM + MariaDB + Varnish

от автора

В данной статье я расскажу о том, как я заставил свой блог на WordPress летать за счёт грамотного кэширования, сжатия и другой оптимизации серверной и клиентской сторон. На момент написания статьи характеристики VDS следующие:

CPU: 1 x 2GHz
HDD: 10Gb
RAM: 512Mb
OS: Debian 8 x64

Схема работы системы выглядит следующим образом:

image

Описание работы схемы

Для посетителей сайта происходит перенаправление на HTTPS, где nginx работает в качестве прокси для Varnish, при этом на выходе nginx помимо реализации HTTPS-соединения происходит gzip-сжатие данных, передаваемых пользователю. Следующим элементом в данной системе является HTTP-акселератор Varnish, ожидающий соединения на 6081 порту. Получая запрос от клиента он выполняет поиск запрашиваемого URL в кэше, и в случае его обнаружения мгновенно отдаёт его фронтенду. Таким образом, при наличии запрашиваемого файла в кэше скорость запроса к страницам сокращается до скорости запроса к статическим данным. Если же запрашиваемого файла в кэше не обнаруживается, Varnish передаёт запрос бэкенду. Так же в Varnish реализована оптимизация клиентской стороны — здесь статическим данным устанавливаются заголовки Cache-Control и Expires, указывающие браузеру на необходимость кэширования этих данных на стороне клиента. Таким образом снижается скорость загрузки сайта и нагрузка на веб-сервер.

В роли бэкенда выступает опять же nginx, ожидающий соединений на 127.0.0.1:81. Интерпретация PHP реализована с помощью FPM. Версия PHP — 5.6 с включенным по умолчанию акселератором OPcache. В качестве СУБД — MariaDB 10, являющаяся одной из лучших по производительности и кушающих в меру оперативную память СУБД среди форков MySQL. В качестве движка таблиц — MyISAM, так как запись производится редко, в основном чтение, для которого данный движок больше оптимизирован. За счёт отключения движка InnoDB реализуется экономия оперативной памяти. Наконец, в качестве CMS функционирует WordPress с установленным плагином Varnish HTTP Purge, отправляющий PURGE-запросы на адреса страниц, на которых были произведены изменения, что приводит к очистке кэша Varnish для данных страниц. Таким образом, пользователь получает всегда актуальную версию сайта. Далее я детально расскажу об установке и настройке данных компонентов, а так же о проблемах, с которыми я столкнулся.

Установка и настройка nginx

Устанавливаем:

apt-get install nginx 

Содержимое основного конфига /etc/nginx/nginx.conf:

# Пользователь и группа, от имени которых будет запущен процесс user                    www-data www-data;  # Число воркеров в новых версиях рекомендовано устанавливать в auto worker_processes        auto;  error_log               /var/log/nginx/error.log; pid                     /var/run/nginx.pid;  events {     # Максимальное количество соединений одного воркера     worker_connections              1024;      # Метод выбора соединений (для FreeBSD будет kqueue)     use                             epoll;      # Принимать максимально возможное количество соединений     multi_accept                    on; }  http {     # Указываем файл с mime-типами и указываем тип данных по-умолчанию     include                         /etc/nginx/mime.types;     default_type                    application/octet-stream;      # Метод отправки данных sendfile эффективнее чем read+write     sendfile                        on;      # Отправлять заголовки и и начало файла в одном пакете     tcp_nopush                      on;     tcp_nodelay                     on;      # Сбрасывать соединение если клиент перестал читать ответ     reset_timedout_connection       on;     # Разрывать соединение по истечению таймаута при получении заголовка и тела запроса     client_header_timeout           3;     client_body_timeout             5;     # Разрывать соединение, если клиент не отвечает в течение 3 секунд     send_timeout                    3;      # Задание буфера для заголовка и тела запроса     client_header_buffer_size       2k;     client_body_buffer_size         256k;     # Ограничение на размер тела запроса     client_max_body_size            12m;      # Отключаем лог доступа     access_log                      off;      # Подключаем дополнительные конфиги     include                         /etc/nginx/conf.d/*.conf; } 

Создадим файл настроек бэкенда /etc/nginx/conf.d/backend.conf:

server {     # Ожидать локального соединения на 81 порту     listen 127.0.0.1:81;      # Корневая директория и индексовый файл     root /var/www/site.ru/public_html;     index index.php;      # Имя хоста     server_name site.ru www.site.ru;      # Запрет на доступ к скрытым файлам     location ~ /\. {         deny all;     }      # Запрет на доступ к загруженным скриптам     location ~* /(?:uploads|files)/.*\.php$ {         deny all;     }      # Поиск запрашиваемого URI по трем путям     location / {         try_files $uri $uri/ /index.php?$args;     }      # Добавление слэша в конце для запросов */wp-admin     rewrite /wp-admin$ $scheme://$host$uri/ permanent;      location ~ \.php$ {         # При ошибке 404 выдавать страницу, сформированную WordPress         try_files $uri =404;          # При обращении к php передавать его на интерпретацию FPM         fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;         include fastcgi_params;         fastcgi_pass unix:/var/run/php5-fpm.sock;     } } 

На тему детального описания настройки HTTPS в nginx рекомендую к прочтению данную статью: habrahabr.ru/post/252821
Создаём файл настроек фронтэнда /etc/nginx/conf.d/frontend.conf:

server {     # Редирект на HTTPS     listen      REAL_IP:80;     server_name site.ru www.site.ru;     return 301 https://$server_name$request_uri; }  server {     listen      93.170.105.102:443 ssl;     server_name site.ru www.site.ru;      # Устанавливать Keep-Alive соединения с посетителями     keepalive_timeout               60 60;      # Включить gzip-сжатие на выходе     gzip                on;     gzip_comp_level     9;     gzip_min_length     512;     gzip_buffers        8 64k;     gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript image/svg+xml;     gzip_proxied        any;      # Отдавать предпочтение шифрам, заданным на сервере     ssl_prefer_server_ciphers on;     # Установка длительности TLS сессии в 2 минуты     ssl_session_cache shared:TLS:2m;     ssl_session_timeout 2m;      # Задание файла, содержащего сертификат сайта и сертификат УЦ     ssl_certificate      /etc/ssl/combined.crt;     # Указание закрытого ключа     ssl_certificate_key  /etc/ssl/3_site.ru.key;      # Файл с параметрами Диффи-Хеллмана     ssl_dhparam /etc/ssl/dh2048.pem;      # Поддерживаемые протоколы     ssl_protocols TLSv1.2 TLSv1.1 TLSv1;      # Наборы шифров, данный набор включает forward secrecy     ssl_ciphers EECDH+ECDSA+AESGCM:EECDH+aRSA+AESGCM:EECDH+ECDSA+SHA512:EECDH+ECDSA+SHA384:EECDH+ECDSA+SHA256:ECDH+AESGCM:ECDH+AES256:DH+AESGCM:DH+AES256:RSA+AESGCM:!aNULL:!eNULL:!LOW:!RC4:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS;      # Передача Strict-Transport-Secutiry заголовка     add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains';      location / {         # Проксирование на Varnish         proxy_pass      http://127.0.0.1:6081/;          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-Proto https;         proxy_set_header    X-Forwarded-Port  443;     } } 

Перечитаем конфиги nginx:

service nginx reload 

Теперь при попытке зайти на сайт увидим ошибку 502. Это нормально, так как Varnish пока не запущен.

Установка и настройка Varnish

Устанавливаем Varnish:

apt-get install varnish 

Файл параметров запуска располагается здесь — /etc/default/varnish. В DAEMON_OPTS задаём следующие параметры:

DAEMON_OPTS="-a :6081 \              -T 127.0.0.1:6082 \              -f /etc/varnish/default.vcl \              -S /etc/varnish/secret \              -s malloc,128m" 

-a — задаёт порт, на котором Varnish будет принимать соединения, в нашем случае от фронтенда — nginx;
-T — здесь крутится админка, подробнее в описании к флагу -S;
-f — файл с конфигурацией VCL — специальном языке, предназначенном для определения правил обработки запросов и кэширования в Varnish;
-S — Varnish имеет панель администрирования. Для входа необходимо выполнить команду varnishadm, при этом пользователь должен иметь права на чтение файла /etc/varnish/secret для прохождения аутентификации;
-s указание места хранения кэша и его размер, в данном случае 128Mб в оперативной памяти.

Как вы уже, наверное, поняли, самое интересное нас ждёт в файле с правилами обработки запросов. Во время старта процесса Varnish’а данный файл компилируется. В VCL используется несколько подразделов-функций, в которых описываются эти правила. Кратко расскажу о них, полное описание рекомендую прочитать на официальном сайте.

sub vcl_recv — данная функция используется когда приходит запрос от клиента;
sub vcl_pass — выполняется, когда запрос клиента необходимо передать напрямую бэкенду, не кэшировать и не искать соответствия в кэше;
sub vcl_hash — определяет правила кэширования, можно использовать несколько хранилищ для одного и того же документа, в зависимости от разных условий, например, поддержки сжатия клиентом, или каких-либо других особенностей клиента. В нашем случае не будет использоваться, так как клиент у нас для Varnish’а один — nginx на фронтенде;
sub vcl_backend_response — данная функция используется когда приходит запрос от бэкенда (nginx);
sub vcl_deliver — используется непосредственно перед отправкой данных клиенту, например, для добавления/изменения заголовков.

Схема работы компонентов VCL может быть представлена следующим образом:

image

Если обращение к бэкенду происходит при этом из функции vcl_miss ответ бэкенда отправляется и в кэш. Сам язык очень похож на C. Приступим к настройке. Открываем файл /etc/varnish/default.vcl и начинаем кодить:

# Сообщаем компилятору о том, что используется новая версия VCL 4 vcl 4.0;  # Настройки бэкенда backend default {     .host = "127.0.0.1";     .port = "81"; }  # Диапазон IP/Хостов, которым разрешено выполнять PURGE-запросы для очистки кэша acl purge {     "localhost";     "127.0.0.1"; }  # Получение запроса от клиента sub vcl_recv {         # Разрешить очистку кэша вышеописанному диапазону         if (req.method == "PURGE") {                 # Если запрос не из списка, то разворачивать                 if (!client.ip ~ purge) {                         return(synth(405, "This IP is not allowed to send PURGE requests."));                 }                 return (purge);         }          # POST-запросы а также страницы с Basic-авторизацией пропускать         if (req.http.Authorization || req.method == "POST") {                 return (pass);         }          # Пропускать админку и страницу входа         if (req.url ~ "wp-(login|admin)" || req.url ~ "preview=true") {                 return (pass);         }          # Пропускать sitemap и файл robots, у меня sitemap генерируется плагином Google XML Sitemaps         if (req.url ~ "sitemap" || req.url ~ "robots") {                 return (pass);         }          # Удаляем cookies, содержащие "has_js" и "__*", добавляемые CloudFlare и Google Analytics, так как Varnish не будет кэшировать запросы, для которых установлены cookies.         set req.http.Cookie = regsuball(req.http.Cookie, "(^|;\s*)(_[_a-z]+|has_js)=[^;]*", "");          # Удаление префикса ";" в cookies, если вдруг будет обнаружен         set req.http.Cookie = regsub(req.http.Cookie, "^;\s*", "");          # Удаляем Quant Capital cookies (добавляются некоторыми плагинами)         set req.http.Cookie = regsuball(req.http.Cookie, "__qc.=[^;]+(; )?", "");         # Удаляем wp-settings-1 cookie         set req.http.Cookie = regsuball(req.http.Cookie, "wp-settings-1=[^;]+(; )?", "");          # Удаляем wp-settings-time-1 cookie         set req.http.Cookie = regsuball(req.http.Cookie, "wp-settings-time-1=[^;]+(; )?", "");          # Удаляем wp test cookie         set req.http.Cookie = regsuball(req.http.Cookie, "wordpress_test_cookie=[^;]+(; )?", "");          # Удаляем cookie, состоящие только из пробелов (или вообще пустые)         if (req.http.cookie ~ "^ *$") {                     unset req.http.cookie;         }          # Для статических документов удаляем все cookies, пусть себе кэшируются          if (req.url ~ "\.(css|js|png|gif|jp(e)?g|swf|ico|woff|svg|htm|html)") {                 unset req.http.cookie;         }          # Если установлены cookies "wordpress_" или "comment_" пропускаем напряиую к бэкенду         if (req.http.Cookie ~ "wordpress_" || req.http.Cookie ~ "comment_") {                 return (pass);         }          # Если cookie не найдено, удаляем данный параметр из пришедшего запроса как таковой         if (!req.http.cookie) {                 unset req.http.cookie;         }          # Не кэшировать запросы с установленными cookies, это уже не касается WordPress         if (req.http.Authorization || req.http.Cookie) {                 # Not cacheable by default                 return (pass);         }          # Кэшировать всё остальное         return (hash); }  sub vcl_pass {         return (fetch); }  sub vcl_hash {         hash_data(req.url);          return (lookup); }  # Приём ответа от бэкенда sub vcl_backend_response {         # Удаляем ненужные заголовки         unset beresp.http.Server;         unset beresp.http.X-Powered-By;          # Не хранить в кэше robots и sitemap         if (bereq.url ~ "sitemap" || bereq.url ~ "robots") {                 set beresp.uncacheable = true;                 set beresp.ttl = 30s;                 return (deliver);         }          # Для статических файлов, которые отдаёт бэкенд...         if (bereq.url ~ "\.(css|js|png|gif|jp(e?)g)|swf|ico|woff|svg|htm|html") {                 # Удаляем все куки                  unset beresp.http.cookie;                 # Устанавливаем срок хранения в кэше - неделю                 set beresp.ttl = 7d;                 # Устанавливаем заголовки Cache-Control и Expires, сообщая браузеру о том, что эти файлы стоит сохранить в кэше клиента и не нагружать лишниий раз наш сервер                 unset beresp.http.Cache-Control;                 set beresp.http.Cache-Control = "public, max-age=604800";                 set beresp.http.Expires = now + beresp.ttl;         }          # Не кэшировать админку и страницу логина         if (bereq.url ~ "wp-(login|admin)" || bereq.url ~ "preview=true") {                 set beresp.uncacheable = true;                 set beresp.ttl = 30s;                 return (deliver);         }          # Разрешить устанавливать куки только при обращении к этим путям, всё остальное будет резаться                 if (!(bereq.url ~ "(wp-login|wp-admin|preview=true)")) {                 unset beresp.http.set-cookie;         }          # Не кэшировать результат ответа на POST-запрос или Basic авторизации         if ( bereq.method == "POST" || bereq.http.Authorization ) {                 set beresp.uncacheable = true;                 set beresp.ttl = 120s;                 return (deliver);         }          # Не кэшировать результаты поиска         if ( bereq.url ~ "\?s=" ){                 set beresp.uncacheable = true;                 set beresp.ttl = 120s;                 return (deliver);         }          # Не кэшировать страницы ошибок, только нужные вещи в кэше!         if ( beresp.status != 200 ) {                 set beresp.uncacheable = true;                 set beresp.ttl = 120s;                 return (deliver);         }           # Хранить в кэше всё прочее на протяжении одного дня         set beresp.ttl = 1d;         # Срок жизни кэша после истечения его TTL         set beresp.grace = 30s;          return (deliver); }  # Действия перед отдачей результата пользователю sub vcl_deliver {         # Удаляем ненужные заголовки         unset resp.http.X-Powered-By;         unset resp.http.Server;         unset resp.http.Via;         unset resp.http.X-Varnish;          return (deliver); } 

После чего выполняем команду:

service varnish restart 

Перейдя теперь в браузере на наш сайт, мы увидим index.php, который нужно предварительно создать.

Проблема Varnish и Debian 8

А что если вы захотите изменить порт, на котором Varnish будет принимать входящие соединения или изменить объём кэша. Судя по официальной документации нужно изменить файл с параметрами запуска Varnish, располагающийся по пути: /etc/default/varnish и перезапустить сервис. Но нет! Ничего не изменится, и если мы зайдём в top и нажмем на клавишу ‘c’, то увидим, что сервис запущен с прежними настройками. А всё дело в том, что в новой версии Debian используется systemd вместо init.d в качестве системы инициализации, и файл и поэтому нужно зайти в файл /lib/systemd/system/varnish.service и прописать там в директиве ExecStart те же параметры запуска:

[Unit] Description=Varnish HTTP accelerator  [Service] Type=forking LimitNOFILE=131072 LimitMEMLOCK=82000 ExecStartPre=/usr/sbin/varnishd -C -f /etc/varnish/default.vcl ExecStart=/usr/sbin/varnishd -a :6081 -T 127.0.0.1:6082 -f /etc/varnish/default.vcl -S /etc/varnish/secret -s malloc,128m ExecReload=/usr/share/varnish/reload-vcl  [Install] WantedBy=multi-user.target 

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

systemctl daemon-reload service varnish restart 

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

Установка и настройка PHP-FPM

Устанавливаем FPM и библиотеку PHP для работы с СУБД:

apt-get install php5-fpm php5-mysqlnd 

Заходим в файл конфигурации /etc/php5/fpm/pool.d/www.conf и меняем директиву:

listen = 127.0.0.1:9000 

На следующее:

listen = /var/run/php5-fpm.sock 

В этом же файле задаём настройки воркеров:

; Динамическое изменение количества воркеров pm = dynamic ; Максимальное число воркеров, создаются под нагрузкой, не может быть меньше pm.max_spare_servers. pm.max_children = 10  ; Сколько воркеров запускать при старте FPM pm.start_servers = 1 ; Минимальное количество запасных воркеров (остаются в памяти при отсутствии нагрузки) pm.min_spare_servers = 1 ; Максимальное количество запасных воркеров (при простое, остальные неиспользуемые будут завершаться) pm.max_spare_servers = 3 ; Максимальное количество запросов, которые выполняет один воркер, прежде чем перезапуститься pm.max_requests = 500 

Меняем несколько директив в /etc/php5/fpm/php.ini

upload_max_filesize = 10M post_max_size = 12M allow_url_fopen = Off 

post_max_size задаём чуть больше, чем upload_max_filesize, так как помимо файла в запросе идут другие данные.
Здесь же директивой allow_url_fopen запрещаем выполнять скрипты, расположенные удаленно (убирая возможность эксплуатации уязвимости удалённого инклуда).

И говорим FPM перечитать конфиг:

service php5-fmp reload 

Теперь создайте файлик, выводящий phpinfo() и обратитесь к нему в браузере, всё должно работать. Не забывайте, что он уже закэшировался в Varnish и если вы будете изменять конфигурацию PHP, то она не будет обновляться в вашем браузере. Можете написать правило на пропуск данного файла в Varnish, либо же на время тестов проксировать не Varnish, а напрямую бэкенд на 81 порту.

Установка и настройка MariaDB

Эту СУБД я выбрал по причине её лучшей производительности и способности выдерживать большие нагрузки, при этом затрачивая меньшее количество оперативной памяти по сравнению с MySQL, а так же её полной совместимостью с WordPress. Установка очень проста, будет запрошен пароль для пользователя root.

apt-get install mariadb-server 

В качестве движка для таблиц я использую MyISAM, по причине того, что запись в таблицу выполняется редко, а на чтении MyISAM показывает лучшие характеристики. Я полностью отключил поддержку InnoDB для освобождения оперативной памяти. Настройки хранятся в файле /etc/mysql/my.cnf. Опишу только те директивы, которые я изменил:

# Кэш для работы с ключами и индексами key_buffer = 64M  # Кэш запросов query_cache_size = 32M  # Установка MyISAM в качестве стандартного движка default-storage-engine=MyISAM  # Отключение движка InnoDB skip-innodb 

После сохранения изменений перезапускаем сервис:

service mysql restart 

Настройка WordPress — плагин «Varnish HTTP Purge»

Устанавливаем в панели администрирования WP плагин «Varnish HTTP Purge». Теперь при обновлении данных на измененные страницы будет отправлен PURGE-запрос, очищающий кэш в Varnish, и для посетителей данные всегда будут обновлёнными.

Дополнительная оптимизация

Для оптимизации клиентской стороны с помощью Varnish мы указываем браузеру на необходимость хранения статических данных в локальном кэше клиента. Но если вы жаждете ещё большей оптимизации, перейдите на страничку developers.google.com/speed/pagespeed/insights и введите URL вашего сайта или даже конкретной страницы. Вам предоставится список рекомендаций, а так же предложат архив со сжатыми версиями ваших css и js стилей. Замените их на своём сайте и получите ещё большую скорость загрузки за счёт уменьшенного объема передаваемых данных, так же уменьшится нагрузка на сервер и место, занимаемое данными файлами в кэше.

Как поступить с документами, запрашиваемыми со сторонних серверов, например, шрифтами или библиотекой jquery? Можно перенести их к себе, и тут за счёт установки соединения только с одним сервером возрастёт скорость загрузки страниц, однако, в то же время, возрастёт список обращений и общая нагрузка. Какой вариант выбрать — решайте сами, в зависимости от загруженности вашего сервера и вашей лени.

ссылка на оригинал статьи https://habrahabr.ru/post/278189/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *