Varnish i WordPress

Najskuteczniejszym sposobem zapewnienia szybkiego działania stron WWW, w tym także WordPress jest zastosowanie cache. Oczywistym w tej sytuacji wyborem wydaje się Varnish, jednak jego dobra współpraca z WordPress wymaga stworzenia ekosystemu wykorzystującego kilka aplikacji, które razem pozwalają zachować wydajność i stabilność naszego rozwiązania.

Varnish jest jednym z najlepszych wyborów w dziedzinie rozwiązań software-cache, na jakie możemy się zdecydować. Jego użycie znacznie poprawia komfort pracy z WordPress od strony użytkownika odwiedzającego nasze blogi skracając czas ładowania strony kilkanaście bądź nawet kilkadziesiąt razy.

Niestety, wygodne używanie Varnish w standardzie wysokiej dostępności z WordPress bez pracy developerskiej wymaga nieco zachodu i wiedzy, nie da się bowiem zrobić tego korzystając wyłącznie z wtyczek do CMS. 

Konfiguracja WordPress

Choć samo użycie Varnish jako cache nie wymaga oczywiście komunikacji z WordPress, to dla wygody użytkowania warto poświęcić chwilę na instalację wtyczek, które ułatwią nam współpracę z cache np. poprzez automatyczne wysyłanie zapytań typu PURGE w predefiniowanych sytuacjach.

Jak pokazuje analiza repozytorium pluginów WordPress najpopularniejszym rozwiązaniem w tej kwestii jest wtyczka Varnish HTTP Purge . Sam plugin poza instalacją nie wymaga dodatkowej konfiguracji.

Rozwiązanie jest proste i – co najważniejsze – skuteczne. Wtyczka dba o wysyłanie zapytań PURGE dla stron wpisów, kategorii itp. w przypadku dodania albo aktualizacji posta na naszym blogu. Całość działa bardzo dobrze, jedynym potencjalnym minusem (wg moich obserwacji) jest fakt, iż wszystkie zapytania kierowane są przez HTTP, nawet jeżeli blog jest konfigurowany do pracy przez protokół HTTPS 🙁

Konfiguracja Varnish

Sposobom konfiguracji Varnish oraz dobrym praktykom w przygotowywaniu plików vcl trzeba byłoby poświęcić osobny obszerny artykuł. Ja skupię się tutaj wyłącznie na elementach przydatnych dla cacheWordPress oraz pewnych trikach, które mogą ułatwić pracę z Varnish.

Na początek warto zabezpieczyć się przed zapytaniami, które nie mieszczą się w RFC. Aby zapewnić takie zabezpieczenie w sekcji vcl_recv naszej konfiguracji podajemy:

if (req.method != "GET"
  req.method != "HEAD"
  req.method != "PUT"
  req.method != "POST"
  req.method != "TRACE"
  req.method != "PURGE"
  req.method != "OPTIONS"
  req.method != "PATCH"
  req.method != "DELETE") {
    /* Non-RFC2616 */
    return (pipe);
}

Zastosowanie powyższej reguły sprawi, że w przypadku zapytania przekazanego metodą niezgodną z RFC Varnish odpowie pipe. Czym jest pipe? Specyficznym sposobem odpowiedzi, w którym cache zwraca klientowi jego zapytanie uzupełnione o nagłówek Connection: close. W praktyce oznacza to, że dla zapytań niezgodnych z RFC nie zwracamy żadnych danych z naszego systemu 🙂

Kolejną rzeczą, o którą warto zadbać jest serwowanie z pominięciem cache stron administracyjnych WordPress. Można to zrobić na dwa sposoby – używając cache tylko dla klientów bez sesji logowania albo wskazując adresy dla których cache nie będzie serwowany. Zdecydowałem się na drugie rozwiązanie gdyż moim zdaniem w bardziej adekwatny sposób określa gdzie pomijany ma być cache. Zatem w sekcji vcl_recv dodałem następującą regułę:

# --- WordPress specific configuration
# Did not cache the admin and login pages
if (req.url ~ "wp-(login|admin)" || req.url ~ "preview=true") {
  return (pass);
}

Jak widzimy strony logowania i administracyjne, oraz podgląd wpisu, nie są odczytywane z cache a bezpośrednio z zaplecza.

Warto również zadbać o zrobienie „porządków” w nagłówkach. Reguły vcl Varnish pozwalają w łatwy sposób sterować tym jakie nagłówki będziemy zwracać klientowi. Można to zrobić między innymi poprzez odpowiednie wpisy w sekcji vcl_deliver:

sub vcl_deliver {
  if (obj.hits > 0) {
    set resp.http.X-Cache = "HIT";
  } else {
    set resp.http.X-Cache = "MISS";
  }

  unset resp.http.X-Varnish;
  unset resp.http.Via;
  unset resp.http.Age;
  unset resp.http.X-Powered-By;
}

Jak widać konfiguracja jest prosta, czytelna i właściwie samoopisująca 🙂

Opróżnianie cache

Do skonfigurowania pozostaje zachowanie Varnish przy akceptowaniu żądań opróżnienia cache. Domyślnie zapytanie metodą PURGE doprowadzi do usunięcia z cache zawartości url dla którego wykonano zapytanie np.:

curl -X PURGE http://localhost/uri

Musimy jednak pamiętać, że pozostawienie używania metody PURGE bez kontroli może doprowadzić do nieoczekiwanego czyszczenia cache przez boty lub wręcz ataku polegającego na nagłym usunięciu całego cache, co może z kolei doprowadzić do przeciążenia naszej infrastruktury. Zacznijmy więc od zdefiniowania acl zawierającego listę adresów, które będą mogły wykonywać zapytania czyszczące cache:

acl purge {
 "localhost";
 "127.0.0.1";
 "192.168.0.0/24";
}

po zdefiniowaniu listy, w sekcji vcl_recv musimy dodać fragment, który będzie odpowiedzialny za odrzucanie zapytań PURGE od adresów spoza listy. Tutaj sytuacja się jednak komplikuje. W najprostszym przypadku – kiedy Varnish jest pierwszym elementem naszej infrastruktury możemy skorzystać z wbudowanego obiektu client.ip. Wówczas implementacja wygląda następująco:

# Allow / disallow purging
if (req.method == "PURGE") {
  if (!client.ip ~ purge) {
    return (synth(405, "This IP is not allowed to send PURGE requests."));
  }
  return (purge);
}

jak widzimy w przypadku zapytań spoza listy zwracamy kod odpowiedzi 405 wraz z komunikatem. Nie następuje natomiast skasowanie cache.

Niestety, client.ip zwraca nam adres elementu infrastruktury bezpośrednio przekazującego zapytanie do cache. W związku z tym jeżeli naszej Varnish stoją za load-balancerem lub proxy, każde zapytanie zostanie potraktowane jako zaufane.

Sposoby obejścia tego problemu są dwa. Możemy wprowadzić odpowiednie reguły na load-balancerze lub zmienić konfigurację reguł Varnish. W tym celu jednak będziemy musieli odczytać adres klienta z nagłówka np. X-Forwarded-For. Dodatkowo będziemy musieli skorzystać z wbudowanej metody std.ip zawartej w Varnish Standard Module a szczegółowo opisanej w dokumentacji producenta.

Na początku musimy do naszego vcl zaimportować odpowiedni moduł:

import std;

a następnie zmieniamy naszą regułę tak, by z nagłówka X-Forwarded-For wyciągnąć pierwszy adres, zamienić go na obiekt przy pomocy metody std.ip i porównać z listą acl tak, jak w przypadku client.ip:

# Allow / disallow purging
if (req.method == "PURGE") {
  if (!std.ip(regsub(req.http.X-Forwarded-For, "[, ].*$", ""), client.ip) ~ purge) {
    return (synth(405, "This IP is not allowed to send PURGE requests."));
  }
  return (purge);
}

Translacja przy pomocy metody std.ip jest niezbędna, gdyż w przeciwnym wypadku Varnish nie będzie wstanie skonfrontować podanego ciągu znaków z listą.

Load balancing – HA Varnish

Na opisanych wyżej krokach można byłoby zakończyć integrację VarnishWordPress. Niestety taka konfiguracja nie zapewnia wysokiej dostępności rozwiązania, ponieważ dotyczy tylko jednej instancji cache. Poniżej pokażę jak stworzyć niewielki – dwuinstancyjny – ekosystem złożony z Varnish i HAproxy.

Podobnie jak w przypadku konfiguracji cache, samemu zagadnieniu load – balancingu należałoby poświęcić osobny obszerny wpis. Wymienię tutaj więc tylko zagadnienia specyficzne i przydatne w kontekście omawianego tematu.

Rozpoczniemy od zdefiniowania w sekcji frontend dwóch acl:

frontend web
  [...]
  acl varnish_dynamic hdr(host) -i mydomain.com myseconddomain.com
  acl dynamic_cache_not_healthy nbsrv(varnish_dynamic) lt 1

pomogą nam one określić dla jakich domen mamy używać cache oraz zdefiniować czy wykonać fallback na pulę zwykłych serwerów WWW jeżeli oba Varnish będą niedostępne (oczywiście w przypadku większej liczby maszyn cache taki fallback będzie zbędny lub nawet niewskazany 🙂 ).

Następnie zdefiniować musimy logikę używania backend:

frontend web
  [...]
  use_backend web_dynamic if varnish_dynamic dynamic_cache_not_healthy
  use_backend varnish_dynamic if varnish_dynamic

pierwsze zaplecze (pula serwerów WWW) zostanie użyte dla domen podlegających cache w przypadku, kiedy zaplecze Varnish nie będzie miało dostępnych node. Jeśli nie zostaną spełnione oba warunki zapytanie zostanie przekierowane do właściwego zaplecza cache.

Same zaplecza dla cache, co do zasady można tworzyć według standardowych reguł, nie będę więc tego omawiał szczegółowo.

Purge wielu Varnish

Maszyny Varnish nie komunikują się między sobą. Oznacza to, że chcąc skasować cache musimy wysłać zapytanie PURGE na N serwerów.  Niestety, wtyczka Varnish HTTP Purge nie implementuje takiej funkcjonalności, a w przypadku load – balancingu round robin dla zaplecza Varnish powstanie sytuacja, w której cache zostanie skasowany tylko na jednej z maszyn.

Rozwiązaniem tej sytuacji mogłyby być oczywiście poprawki wprowadzone w kodzie wtyczki. Jeżeli jednak chcemy uniknąć pracy deweloperskiej musimy rozszerzyć nasz ekosystem o oprogramowanie duplikujące ruch.

Ja zdecydowałem się na użycie teeproxy. Jest to niewielkie proxy napisane w Go, którego głównym zadaniem jest właśnie przekierowanie ruchu na serwer A i duplikowanie go na serwer B.

Uruchomiłem więc teeproxy:

./teeproxy -l :8888 -a 192.168.1.1:9000 -b 192.168.1.2:9001

a następnie uzupełniłem konfigurację HAproxy  o odpowiednie reguły umożliwiające przekierowanie zapytań PURGE  na backend, którego memberem jest właśnie wspomniane wyżej proxy:

frontend web
  acl purge method PURGE
  use_backend purge if purge varnish_dynamic

Należy pamiętać, że teeproxy to mały projekt i jego wydajność oraz stabilność nie kwalifikują go do użycia w dużym ruchu, bądź większych klastrach cache 🙂

Podsumowanie

Wprowadzenie Varnish dla blogów opartych o WordPress znacząco poprawia jakość odbioru naszych blogów. Cache eliminuje wiele mankamentów związanych z szybkością działania stron – zarówno tych wynikających z wydajności maszyn, jak i tych które dotykają samego CMS.

Niestety konfiguracja całego rozwiązania wymaga dość dużo zachodu i nie jest tak bezproblemowa jak wydawać by się mogło na pierwszy rzut oka 🙂

Podziel się:

Mikołaj Niedbała

I'm a Poland based IT administrator, linux administrator and IT engineer creating professional IT infrastructure solutions based on Linux and virtual environments.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *