Пример конфигурационного файла Varnish

Submitted by Ромка on Пнд, 25/06/2012 - 16:11

Ромка аватар

Как обещал в докладе выкладываю пример и описание рабочего конфига для Варниша. Чтобы узнать подробнее о том, что такое Варниш и для чего он нужен ознакомьтесь с разделом Pressflow + Varnish.

Сразу сделаю важный комментарий. Прежде чем запускать Варниш на боевом сервере разработчик должен детально изучить его документацию, понять основные принципы его работы и настройки. Слепое копирование чужих конфигов, без понимания того, что делает та или иная инструкция, может привести к плачевным результатам.

В этом примере рассматривается конфиг для Варниша третьей версии (на данный момент это последняя стабильная версия). Обратите внимание, у Варниша с версии 2.1.0 поменялся движок обработки регулярных выражений, по этому некоторые примеры конфигов, доступные в интернете, могут работать некорректно. Луллаботы, например, обновили свой туториал и предлагают сразу несколько вариантов конфига для разных версий Варниша.

Задача

Варниш должен отдавать анонимам данные из своего кеша, а запросы от авторизованных пользователей передавать бэкенду. Отличать анонимного пользователя от зарегистрированного Варниш будет по наличию/отсутствию куки SESS*. В случае падения бэкенда Варниш должен отдавать кеш в том числе и для авторизованных пользователей.

Скачать мой конфиг вы можете здесь, ниже описание самых интересных и важных его частей.

Бэкенд

Указываем Варнишу с каким веб-сервером нужно работать. Параметры в блоке probe означают, что каждые 5 секунд на бэкенде нужно дергать файл status.php. Если в течение 1 секунды от бэкенда не будет получен ответ с кодом 200, то проверка будет считаться провалившейся. Если провалено 3 проверки из 5, бэкенд помечается "упавшим".

  1. backend web1 {
  2. .host = "10.10.10.10";
  3. .port = "80";
  4. .probe = {
  5. .url = "/status.php";
  6. .interval = 5s;
  7. .timeout = 1s;
  8. .window = 5;
  9. .threshold = 3;
  10. }
  11. }

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

Несколько бэкендов могут быть объединены в логическую группу — director, в случае использования директоров при выходе из строя одного из бэкендов запросы будут автоматически перенаправлены на живые бэкенды из текущего директора. В нашем случае балансировка делается на уровне nginx и эта возможность Варниша не используется.

Status.php мы позаимствовали у Луллаботов, скачать его можно тут. В этом файле осуществляется проверка доступности серверов БД и Мемкеша.

ACL

Создаем списки айпишников, на основе которых позже зададим ограничение доступа к cron.php и очистке кеша Варниша:

  1. # ACL for purging cache
  2. acl purge {
  3. "127.0.0.1";
  4. "localhost";
  5. "10.1.0.0"/16;
  6. }
  7.  
  8. # ACL for access to cron.php
  9. acl internal {
  10. "127.0.0.1";
  11. "localhost";
  12. "10.10.0.0"/16;
  13. }

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.

Для начала отключаем обработку Варнишем некоторых служебных адресов Друпала:

  1. // list of URLS which shouldn't be cached
  2. if (req.url ~ "^/status\.php$" ||
  3. req.url ~ "^/update\.php$" ||
  4. req.url ~ "^/info/.*$" ||
  5. req.url ~ "^/flag/.*$" ||
  6. req.url ~ "^.*/ajax/.*$" ||
  7. req.url ~ "^.*/ahah/.*$") {
  8. return (pass);
  9. }

Доступ

Далее, ограничиваем доступ к операции очистки кеша и cron.php на основе ранее определенных списков ip-адресов.

  1. # Clear cache entry
  2. if (req.request == "PURGE") {
  3. if (!client.ip ~ purge) {
  4. error 405 "Not allowed.";
  5. }
  6. return (lookup);
  7. }
  8.  
  9. # Do not allow outside access to cron.php or install.php.
  10. if (req.url ~ "^/(cron|install)\.php$" && !client.ip ~ internal) {
  11. # Have Varnish throw the error directly.
  12. error 404 "Page not found.";
  13. # Use a custom error page that you've defined in Drupal at the path "404".
  14. #set req.url = "/404.html";
  15. }

Задаем максимальное время жизни кеша 6 часов, а также указываем, что если сервер помечен как "упавший" (опция probe в настройках бэкенда), то у пользователя нужно отрезать все куки. В дальнейшем, для пользователей без кук мы будем отдавать кеш и эта настройка позволит Варнишу отдавать кеш авторизованным юзерам.

  1. set req.grace = 6h;
  2.  
  3. if (!req.backend.healthy) {
  4. unset req.http.Cookie;
  5. }

cookies

Далее анализируем куки, полученные от пользователя, и удаляем ненужные. Дело в том, что куки могут быть установлены как серверным софтом, так и клиентским, через java-script. Разного рода счетчики и коды баннеров ставят куки для своих целей и эти куки совершенно не интересны серверу, кроме того они мешают нам отличить анонимного посетителя от зарегистрированного. Мы удаляем все куки кроме SESS*, если после такого удаления у нас осталась пустая строка, значит куки SESS* нет и мы считаем пользователя анонимным и можем отдать ему данные из кеша. Если кука SESS* существует, то мы передаем запрос бэкенду.

  1. // clean cookies. Pass only cookies SESS[a-z0-9], xyz
  2. if (req.http.Cookie) {
  3. set req.http.Cookie = regsub(req.http.Cookie, "^(.*)$", "; \1");
  4. set req.http.Cookie = regsuball(req.http.Cookie, "; +", ";");
  5. set req.http.Cookie = regsuball(req.http.Cookie, ";(SESS[a-z0-9]|xyz+)=", "; \1=");
  6. set req.http.Cookie = regsuball(req.http.Cookie, ";[^ ][^;]*", "");
  7. set req.http.Cookie = regsuball(req.http.Cookie, "^[; ]+|[; ]+$", "");
  8.  
  9. if (req.http.Cookie == "") {
  10. unset req.http.Cookie;
  11. }
  12. else {
  13. return (pass);
  14. }
  15. }

Код выше требует некоторого пояснения.

Куки представляют из себя текстовую строку вида "ключ=значение" разделенные точкой с запятой. Для примера рассмотрм строку "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". По умолчанию, для этих двух заголовков Варниш создал бы разные копии кеша одной и той же страницы. Код ниже, приводит подобные заголовки к универсальному виду и предотвращает излишний расход памяти под кеш.

  1. if (req.http.Accept-Encoding) {
  2. if (req.http.Accept-Encoding ~ "gzip") {
  3. # If the browser supports it, we'll use gzip.
  4. set req.http.Accept-Encoding = "gzip";
  5. }
  6. else if (req.http.Accept-Encoding ~ "deflate") {
  7. # Next, try deflate if it is supported.
  8. set req.http.Accept-Encoding = "deflate";
  9. }
  10. else {
  11. # Unknown algorithm. Remove it and send unencoded.
  12. unset req.http.Accept-Encoding;
  13. }
  14. }

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

ВложениеРазмер
Plain text icon varnish30.vcl_.txt5.74 KB
Plain text icon status.php_.txt2.37 KB

1 Comment