Пример конфигурационного файла Varnish
Как обещал в докладе выкладываю пример и описание рабочего конфига для Варниша. Чтобы узнать подробнее о том, что такое Варниш и для чего он нужен ознакомьтесь с разделом Pressflow + Varnish.
Сразу сделаю важный комментарий. Прежде чем запускать Варниш на боевом сервере разработчик должен детально изучить его документацию, понять основные принципы его работы и настройки. Слепое копирование чужих конфигов, без понимания того, что делает та или иная инструкция, может привести к плачевным результатам.
В этом примере рассматривается конфиг для Варниша третьей версии (на данный момент это последняя стабильная версия). Обратите внимание, у Варниша с версии 2.1.0 поменялся движок обработки регулярных выражений, по этому некоторые примеры конфигов, доступные в интернете, могут работать некорректно. Луллаботы, например, обновили свой туториал и предлагают сразу несколько вариантов конфига для разных версий Варниша.
Задача
Варниш должен отдавать анонимам данные из своего кеша, а запросы от авторизованных пользователей передавать бэкенду. Отличать анонимного пользователя от зарегистрированного Варниш будет по наличию/отсутствию куки SESS*. В случае падения бэкенда Варниш должен отдавать кеш в том числе и для авторизованных пользователей.
Скачать мой конфиг вы можете здесь, ниже описание самых интересных и важных его частей.
Бэкенд
Указываем Варнишу с каким веб-сервером нужно работать. Параметры в блоке probe означают, что каждые 5 секунд на бэкенде нужно дергать файл status.php. Если в течение 1 секунды от бэкенда не будет получен ответ с кодом 200, то проверка будет считаться провалившейся. Если провалено 3 проверки из 5, бэкенд помечается “упавшим”.
backend web1 {
.host = "10.10.10.10";
.port = "80";
.probe = {
.url = "/status.php";
.interval = 5s;
.timeout = 1s;
.window = 5;
.threshold = 3;
}
Таким образом, уже через 15 секунд после возникновения проблем на бэкенде, Варниш пометит его упавшим и, в зависимости от настроек, либо начнет передавать запросы другому бэкенду, либо начнет обслуживать запросы зарегистрированных пользователей из своего кеша.
Несколько бэкендов могут быть объединены в логическую группу — director, в случае использования директоров при выходе из строя одного из бэкендов запросы будут автоматически перенаправлены на живые бэкенды из текущего директора. В нашем случае балансировка делается на уровне nginx и эта возможность Варниша не используется.
Status.php мы позаимствовали у Луллаботов, скачать его можно тут. В этом файле осуществляется проверка доступности серверов БД и Мемкеша.
ACL
Создаем списки айпишников, на основе которых позже зададим ограничение доступа к cron.php и очистке кеша Варниша:
# ACL for purging cache
acl purge {
"127.0.0.1";
"localhost";
"10.1.0.0"/16;
}
# ACL for access to cron.php
acl internal {
"127.0.0.1";
"localhost";
"10.10.0.0"/16;
}
vcl_recv
Процедура vcl_recv содержит инструкции, которые будут выполняться Варнишем при пролучении запроса от клиента. В ней содержится основная магия. В основном эта процедура должна возвращать одно из двух значений: pass — передать запрос бэкенду или lookup — попытаться найти закешированную версию страницы в кеше, в случае отсутствия кеша, запрос будет передан бэкенду, а ответ закеширован. Полный список возможных значений можно найти тут: https://www.varnish-cache.org/docs/3.0/reference/vcl.html#subroutines, а здесь: https://www.varnish-cache.org/docs/3.0/faq/configuration.html дано описание различий значений pass и pipe.
Для начала отключаем обработку Варнишем некоторых служебных адресов Друпала:
// list of URLS which shouldn't be cached
if (req.url ~ "^/status\.php$" ||
req.url ~ "^/update\.php$" ||
req.url ~ "^/info/.*$" ||
req.url ~ "^/flag/.*$" ||
req.url ~ "^.*/ajax/.*$" ||
req.url ~ "^.*/ahah/.*$") {
return (pass);
}
Доступ
Далее, ограничиваем доступ к операции очистки кеша и cron.php на основе ранее определенных списков ip-адресов.
# Clear cache entry
if (req.request == "PURGE") {
if (!client.ip ~ purge) {
error 405 "Not allowed.";
}
return (lookup);
}
# Do not allow outside access to cron.php or install.php.
if (req.url ~ "^/(cron|install)\.php$" && !client.ip ~ internal) {
# Have Varnish throw the error directly.
error 404 "Page not found.";
# Use a custom error page that you've defined in Drupal at the path "404".
#set req.url = "/404.html";
}
Задаем максимальное время жизни кеша 6 часов, а также указываем, что если сервер помечен как “упавший” (опция probe в настройках бэкенда), то у пользователя нужно отрезать все куки. В дальнейшем, для пользователей без кук мы будем отдавать кеш и эта настройка позволит Варнишу отдавать кеш авторизованным юзерам.
set req.grace = 6h;
if (!req.backend.healthy) {
unset req.http.Cookie;
}
cookies
Далее анализируем куки, полученные от пользователя, и удаляем ненужные. Дело в том, что куки могут быть установлены как серверным софтом, так и клиентским, через java-script. Разного рода счетчики и коды баннеров ставят куки для своих целей и эти куки совершенно не интересны серверу, кроме того они мешают нам отличить анонимного посетителя от зарегистрированного. Мы удаляем все куки кроме SESS*, если после такого удаления у нас осталась пустая строка, значит куки SESS* нет и мы считаем пользователя анонимным и можем отдать ему данные из кеша. Если кука SESS* существует, то мы передаем запрос бэкенду.
// clean cookies. Pass only cookies SESS[a-z0-9], xyz
if (req.http.Cookie) {
set req.http.Cookie = regsub(req.http.Cookie, "^(.*)$", "; \1");
set req.http.Cookie = regsuball(req.http.Cookie, "; +", ";");
set req.http.Cookie = regsuball(req.http.Cookie, ";(SESS[a-z0-9]|xyz+)=", "; \1=");
set req.http.Cookie = regsuball(req.http.Cookie, ";[^ ][^;]*", "");
set req.http.Cookie = regsuball(req.http.Cookie, "^[; ]+|[; ]+$", "");
if (req.http.Cookie == "") {
unset req.http.Cookie;
}
else {
return (pass);
}
}
Код выше требует некоторого пояснения.
Куки представляют из себя текстовую строку вида “ключ=значение” разделенные точкой с запятой. Для примера рассмотрм строку name1=val1; name2=val2; name3=val3;
. Предположим, мы хотим пропустить далее только куку name2.
Приведенный выше код делает следующее:
- сначала, для служебных целей, добавляет точку с запятой перед этой строкой. Наша тестовая строка примет вид
;name1=val1; name2=val2; name3=val3;
. - Затем удаляет пробелы после всех точек с запятой (
;
заменяет на;
). Тестовая строка примет вид:;name1=val1;name2=val2;name3=val3;
. - Далее перед разрешенными куками добавляет пробел. Если быть точнее, то пробел добавляется между именем разрешенной куки и точкой с запятой идущей перед ним, именно для этого на первом шаге в начало строки добавлена точка с запятой, чтобы если разрешенная кука идет на первом месте, чтобы можно было перед ней воткнуть пробел. Теперь вся строка имеет вид
;name1=val1; name2=val2;name3=val3;
(перед разрешенной кукой name2 добавлен пробел). - Теперь удаляются все записи, которые начинаются не с
;
. Вот это регулярное выражение:;[^ ][^;]*
можно словами озвучить так: все строки первый символ в которых “точка с запятой”, второй символ не пробел, затем идет любое число символов кроме точек с запятой нужно заменить на ``. - Последняя строка удаляет лидирующую точку с запятой, добавленную на первом шаге.
В итоге, если после таких преобразований у нас осталась не пустая строка, то значит кука SESS* у пользователя есть и его запрос надо передать бэкенду. Как я уже говорил выше, в случае, если бэкенд отмечен как упавший, мы удаляем из запроса пользователя информацию о куках и любой запрос, даже от авторизованного юзера, будет обслуживаться как анонимный.
Accept-Encoding
Далее приводим к общему виду заголовок Accept-Encoding
. Этот заголовок используется браузером, для того чтобы сообщить серверу какие типы сжатия он поддерживает. Веб-сервер, получив такой заголовок, может (и, по хорошему, должен) заархивировать отдаваемые данные тем алгоритмом, который поддерживается браузером.
Если одна и та же страница запрашивается разными браузерами, передающими разный заголовок “Accept-Encoding” Варниш должен в своем кеше создать несколько копий одной и той же страницы, иначе, если, например, браузер поддерживает только алгоритм сжатия deflate, а данные в кеше Варниша сжаты алгоритмом gzip, браузер, получив такие данные, не сможет их распаковать.
Проблема в том, что разные браузеры, поддерживающие один и тот же алгоритм сжатия, сообщают о нем серверу по разному, например, Хром это делает так: Accept-Encoding:gzip,deflate,sdch
, а Firefox так: Accept-Encoding: gzip, deflate
. По умолчанию, для этих двух заголовков Варниш создал бы разные копии кеша одной и той же страницы. Код ниже, приводит подобные заголовки к универсальному виду и предотвращает излишний расход памяти под кеш.
if (req.http.Accept-Encoding) {
if (req.http.Accept-Encoding ~ "gzip") {
# If the browser supports it, we'll use gzip.
set req.http.Accept-Encoding = "gzip";
}
else if (req.http.Accept-Encoding ~ "deflate") {
# Next, try deflate if it is supported.
set req.http.Accept-Encoding = "deflate";
}
else {
# Unknown algorithm. Remove it and send unencoded.
unset req.http.Accept-Encoding;
}
}
Вот и всё. Конфиг содержит больше настроек, чем я описал в этом тексте, но остальные не такие интересные и, думаю, их смысл должен быть интуитивно понятен.