Varnish Cache - специализированный обратный прокси-кэш, который радикально снижает нагрузку на бэкенд-серверы и ускоряет доставку контента. В отличие от встроенных механизмов кэширования в Nginx или Apache, Varnish работает в памяти, что дает скорость отклика менее 1 мс для кэшированных объектов. Его язык конфигурации VCL предоставляет детальный контроль над тем, что кэшировать и при каких условиях. Это руководство проведет вас от установки до продвинутых сценариев оптимизации, предоставив проверенные конфигурации для production-сред.
Внедрение Varnish позволяет обрабатывать в десятки раз больше запросов в секунду на том же аппаратном обеспечении, перенося нагрузку с генерации контента на его быструю отдачу. Мы разберем интеграцию с Nginx и Apache, решение проблемы кэширования для авторизованных пользователей и методы мониторинга для поддержания стабильности.
Введение в Varnish Cache: Зачем он нужен и как работает
Varnish Cache выступает как обратный прокси между клиентом и вашим веб-сервером. Его основная задача - принимать входящие HTTP-запросы и, если их ответ уже сохранен в кэше, отдавать его немедленно, не нагружая бэкенд. Архитектура Varnish оптимизирована для работы с оперативной памятью, что делает его одним из самых быстрых решений для кэширования контента.
Принцип работы строится вокруг языка VCL (Varnish Configuration Language). Каждый запрос проходит через ряд функций, таких как vcl_recv (получение запроса) и vcl_backend_response (обработка ответа от бэкенда). В этих функциях вы определяете логику: проверять ли кэш, как формировать ключ кэша, какие заголовки учитывать.
Ключевое преимущество Varnish перед proxy_cache Nginx - гибкость. Вы можете создавать сложные условия для кэширования на основе любых параметров запроса, заголовков или cookies. Например, легко настроить кэширование страниц для анонимных пользователей и полное его отключение для авторизованных. Varnish эффективнее использует память за счет собственного хранилища (stevedore) и имеет мощные встроенные инструменты для инвалидации кэша (ban/purge).
Практическая польза измеряется метриками: снижение нагрузки на CPU бэкенда на 70-90%, увеличение RPS (запросов в секунду) для статики и кэшируемых HTML-страниц в 50-100 раз, сокращение времени ответа (TTFB) с сотен миллисекунд до единиц.
Установка Varnish и базовая интеграция с Nginx или Apache
Перед любыми изменениями в production протестируйте конфигурацию в staging-среде. Установка пакетов зависит от дистрибутива. Для Ubuntu/Debian:
sudo apt update
sudo apt install varnish -y
Для CentOS/RHEL и подобных:
sudo yum install epel-release -y
sudo yum install varnish -y
После установки необходимо выбрать архитектуру интеграции. Основных сценария два.
Сценарий 1: Varnish как фронтенд (порт 80)
Это рекомендуемая архитектура для новых проектов. Varnish принимает весь входящий HTTP-трафик на порту 80, а ваш веб-сервер (Nginx/Apache) слушает на внутреннем порту, например, 8080.
- Настройте веб-сервер на прослушивание порта 8080. Для Nginx измените директиву
listenв конфигурации сайта:server { listen 8080; server_name your_domain.com; ... } - Отредактируйте системный файл конфигурации Varnish (обычно
/etc/default/varnishили/etc/varnish/varnish.params), чтобы указать порт прослушивания 80:VARNISH_LISTEN_PORT=80 - Настройте бэкенд в основном файле VCL (
/etc/varnish/default.vcl):vcl 4.1; backend default { .host = "127.0.0.1"; .port = "8080"; } - Перезапустите сервисы:
sudo systemctl restart nginx sudo systemctl restart varnish
Теперь весь трафик идет через Varnish. Проверить это можно по HTTP-заголовку ответа Via: 1.1 varnish (Varnish/7.3).
Сценарий 2: Varnish как дополнительный слой (порт 6081)
Этот подход подходит для постепенного внедрения, когда веб-сервер должен оставаться на стандартном порту 80. Varnish запускается на порту 6081, а веб-сервер проксирует запросы к определенным путям на него.
- Настройте Varnish на прослушивание порта 6081 в его системном конфиге.
- В конфигурации Nginx создайте location, который будет проксировать запросы на Varnish:
Аналогичная настройка для Apache потребует активацииlocation / { 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 $scheme; # Важно: передаем реальный IP через Varnish set_real_ip_from 127.0.0.1; real_ip_header X-Real-IP; }mod_proxyи директивProxyPassиProxyPassReverse.
Плюс этого подхода - возможность кэшировать только часть сайта. Минус - добавление лишнего сетевого хопа, что слегка увеличивает задержку.
Основы VCL: Готовые и проверенные конфигурации для типовых задач
VCL файл имеет четкую структуру. Основные функции, которые вам нужно править: vcl_recv (логика при получении запроса от клиента), vcl_backend_response (логика при получении ответа от бэкенда) и vcl_deliver (финальная обработка перед отправкой ответа клиенту). Ниже - готовые блоки.
Кэширование статики: CSS, JS, изображения, шрифты
Статические файлы - идеальный кандидат для длительного кэширования. Добавьте в sub vcl_recv:
if (req.url ~ "\.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|eot)(\?.*)?$") {
# Отправляем запрос на обработку кэша (lookup)
return (hash);
}
# Не кэшируем POST-запросы (формы, API)
if (req.method == "POST") {
return (pass);
}
Затем в sub vcl_backend_response установите длительный TTL для статики:
if (bereq.url ~ "\.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|eot)(\?.*)?$") {
set beresp.ttl = 30d; # 30 дней
set beresp.http.Cache-Control = "public, max-age=2592000";
# Игнорируем любые Cache-Control от бэкенда для этих файлов
unset beresp.http.Set-Cookie;
}
Базовое кэширование HTML-страниц
Для кэширования HTML нужно быть осторожнее. Базовое правило в vcl_recv:
# Кэшируем GET и HEAD запросы по умолчанию
if (req.method == "GET" || req.method == "HEAD") {
return (hash);
}
# Исключаем административные разделы
if (req.url ~ "^/(wp-admin|admin|backend|manager|api/auth)") {
return (pass);
}
В vcl_backend_response лучше уважать заголовки от бэкенда, но задать значение по умолчанию:
# Если бэкенд не установил TTL, ставим свой (1 час)
if (beresp.ttl <= 0s || beresp.http.Cache-Control ~ "no-cache|no-store|private") {
set beresp.ttl = 1h;
set beresp.uncacheable = true;
return (deliver);
}
# Удаляем Set-Cookie для кэшируемых объектов
unset beresp.http.Set-Cookie;
Продвинутые сценарии: Кэширование с учётом сессий и динамического контента
Главная проблема при внедрении Varnish - как не сломать функциональность для авторизованных пользователей, корзин покупок, персональных данных. Решение - разделять трафик на уровне VCL.
Исключение авторизованных пользователей из кэша по Cookie
Самый надежный метод - проверять наличие сессионной куки. Добавьте в начало sub vcl_recv:
# Список кук, которые означают авторизацию
if (req.http.Cookie ~ "(PHPSESSID|sessionid|wordpress_logged_in|auth_token)") {
# Пропускаем запрос напрямую к бэкенду, минуя кэш
return (pass);
}
# Очищаем куки у анонимных пользователей для улучшения хитрейта
unset req.http.Cookie;
Важно адаптировать регулярное выражение под ваше приложение. Для WordPress это wordpress_logged_in, для Django - sessionid, для Laravel - laravel_session. Правило return (pass); гарантирует, что каждый запрос авторизованного пользователя уйдет на бэкенд, и ответ не будет закэширован.
Использование заголовков Vary для легкого динамического контента
Если контент имеет несколько версий (например, для мобильных и десктопных устройств), используйте заголовок Vary. Бэкенд должен отдавать Vary: User-Agent для таких страниц. Varnish автоматически будет создавать отдельные кэшированные копии для разных значений User-Agent.
В Varnish обычно не требуется дополнительная настройка для обработки Vary, но важно убедиться, что заголовок передается от бэкенда. Вы можете добавить его принудительно в vcl_backend_response:
if (bereq.url ~ "^/dynamic-page/") {
set beresp.http.Vary = "User-Agent";
}
Этот подход элегантно решает задачу кэширования слегка разного контента без полного отказа от кэша.
Интеграция Varnish с Redis для эффективного хранения сессий
Чтобы полностью отделить пользовательские данные от кэша и повысить эффективность обоих систем, перенесите хранение сессий из бэкенда (например, файловой системы или memcached) в Redis. Это архитектурное решение, а не настройка Varnish.
Схема работы становится такой:
- Varnish пропускает запросы авторизованных пользователей (
return (pass);) по наличию сессионной куки. - Бэкенд-приложение (PHP, Python, Node.js) сконфигурировано на использование Redis в качестве хранилища сессий (session handler).
- Все данные сессии (логин, корзина, настройки) хранятся в Redis, который работает отдельно от веб-сервера и Varnish.
- Кэш Varnish остается чистым от персональных данных и содержит только общий, анонимный контент.
Преимущества:
- Высокая скорость доступа к сессиям.
- Возможность масштабирования бэкендов без потери сессионных данных.
- Четкое разделение ответственности: Varnish кэширует контент, Redis хранит состояние пользователей.
Для настройки потребуется изменить конфигурацию вашего фреймворка или приложения, указав Redis в качестве хранилища сессий, и установить соответствующий драйвер (например, phpredis для PHP).
Оптимизация производительности и стабильности для production
После внедрения базовой конфигурации настройте параметры для работы под нагрузкой.
Настройка хранилища и политик очистки
Varnish поддерживает два основных типа хранилища: malloc (только RAM) и file (RAM + диск через mmap). Для production с достаточным объемом ОЗУ используйте malloc.
В параметрах запуска Varnish (например, в /etc/default/varnish) укажите:
VARNISH_STORAGE="malloc,256M"
Размер подбирается эмпирически. Мониторьте использование через varnishstat -1 | grep -i storage. Ориентировочно, 1 ГБ RAM может хранить около 100-200 тыс. средних HTML-страниц.
Для инвалидации кэша используйте бан-листы (ban). Команда через varnishadm:
ban req.url ~ "^/news/"
Эта команда пометит все объекты, URL которых начинается с /news/, как устаревшие. Они будут удалены при следующем обращении или в процессе фоновой очистки.
Мониторинг и отладка работы Varnish
Основные инструменты:
- varnishstat: показывает общую статистику в реальном времени. Ключевые метрики:
MAIN.cache_hitиMAIN.cache_miss- рассчитывайте hit rate (hit/(hit+miss)). Цель - выше 90% для статичных сайтов, 70-80% для динамических.MAIN.backend_conn- количество соединений с бэкендом. Резкий рост указывает на проблему (падение кэша или атаку).MAIN.sess_dropped- отброшенные сессии из-за нехватки ресурсов.
- varnishlog: детальный лог запросов. Для отладки конкретного URL:
varnishlog -g request -q "ReqURL ~ '^/problematic-page/'"
Настройте таймауты соединения с бэкендом в VCL, чтобы Varnish не "висел" на медленном приложении:
backend default {
.host = "127.0.0.1";
.port = "8080";
.connect_timeout = 2s;
.first_byte_timeout = 10s;
.between_bytes_timeout = 5s;
}
Готовый пример конфигурации VCL для типового веб-приложения
Ниже приведен полный, рабочий пример файла default.vcl, объединяющий описанные техники. Замените your_backend и проверьте список cookies для вашего приложения.
vcl 4.1;
backend default {
.host = "127.0.0.1";
.port = "8080";
.connect_timeout = 2s;
.first_byte_timeout = 10s;
}
sub vcl_recv {
# 1. Исключаем авторизованных пользователей по сессионным кукам
if (req.http.Cookie ~ "(PHPSESSID|wordpress_logged_in|laravel_session)") {
return (pass);
}
# 2. Не кэшируем POST, PUT, DELETE
if (req.method != "GET" && req.method != "HEAD") {
return (pass);
}
# 3. Исключаем админку и API
if (req.url ~ "^/(wp-admin|admin|api/private)") {
return (pass);
}
# 4. Кэшируем статику по расширению
if (req.url ~ "\.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2)(\?.*)?$") {
unset req.http.Cookie;
return (hash);
}
# 5. Очищаем куки у анонимов для улучшения хитрейта
unset req.http.Cookie;
return (hash);
}
sub vcl_backend_response {
# Длительный TTL для статики
if (bereq.url ~ "\.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2)(\?.*)?$") {
set beresp.ttl = 30d;
unset beresp.http.Set-Cookie;
}
# Удаляем Set-Cookie для кэшируемых объектов
if (beresp.ttl > 0s) {
unset beresp.http.Set-Cookie;
}
# Если бэкенд "упал", отдаем устаревший контент (grace mode)
set beresp.grace = 1h;
}
sub vcl_deliver {
# Добавляем заголовок для отладки (HIT/MISS)
if (obj.hits > 0) {
set resp.http.X-Cache = "HIT";
set resp.http.X-Cache-Hits = obj.hits;
} else {
set resp.http.X-Cache = "MISS";
}
# Удаляем внутренние заголовки Varnish перед отправкой клиенту
unset resp.http.Via;
unset resp.http.X-Varnish;
}
Эта конфигурация - надежная основа. После ее применения мониторьте hit rate и поведение авторизованных пользователей. Для более сложных сценариев, таких как кэширование AJAX-запросов, изучите наше руководство по динамической загрузке контента и настройке кэширования в Nginx. Если вы только выбираете решение для кэширования, сравнение технологий в статье «Сравнение технологий кэширования для Nginx» поможет принять взвешенное решение.