lukaszstaniszewski.pl

blog programistyczny

lukaszstaniszewski.pl

blog programistyczny

Varnish – podstawowy cache – cz. 2

W tym poście chce Ci przybliżyć podstawy Varnisha. Przejdziesz przez konfigurację lokalną Dockera oraz najważniejszą część tj. samą konfigurację Varnisha.
Wiedza ta na pewno Ci jeszcze nie pozwoli postawić Varnisha na produkcji ani poprawnie skonfigurować środowiska dla produkcji ale będzie to dobry wstęp do tego aby zrozumieć jak to wszystko mniej więcej działa.

Wpis jest częścią serii o Varnish’u.

Cały kod możesz znaleźć [TUTAJ] oraz [TUTAJ]

Środowisko lokalne

Zacznijmy od konfiguracji Dockera. Będzie ona banalnie prosta. Będzie w niej FPM, Nginx oraz temat naszego wpisu tj. Varnish.

version: '3'

services:
  nginx:
    image: nginx:latest
    ports:
      - '80:80'
    volumes:
      - './:/app'
      - '.docker/site.conf:/etc/nginx/conf.d/default.conf'
    depends_on:
      - fpm
    links:
      - fpm

  fpm:
    image: php:8.1-fpm
    volumes:
      - './:/app'

  varnish:
    image: varnish:latest
    depends_on:
      - nginx
    volumes:
      - .docker/varnish.vcl:/etc/varnish/default.vcl
    links:
      - nginx
    ports:
      - '5000:80'

Nie będę się tutaj rozpisywać odnośnie całej konfiguracji dockera bo wykracza to poza ramę wpisu ale postaram się kiedyś zrobić serię na ten temat. 😉

Dla nas kluczowymi informacjami jest to że pod linkiem  http://localhost/ znajdziesz odpowiedź bez cache tj. od razu z Nginx a pod linkiem http://localhost:5000/ już z obsługą Varnisha.

Zacznijmy od najprostszej konfiguracji Varnisha

vcl 4.1;

backend default {
    .host = "nginx";
    .port = "80";
}

Powyższa konfiguracja to tak naprawdę przekazanie Varnishowi jedynie hosta oraz portu do którego ma stukać w przypadku braku danych.

Mając konfiguracje musimy przygotować sobie kilka przykładowych endpointów dzięki którym będziemy widzieć jak się zachowuje Varnish.
Aby było nam wygodniej patrzeć jak się zachowuje cache każdy endpoint będzie zwracał nam aktualną data + godzinę.

<?php

declare(strict_types=1);

namespace App\Controller;

use DateTime;
use DateTimeZone;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class HomeController extends AbstractController
{
    #[Route('/')]
    #[Cache(maxage: 30, public: true, mustRevalidate: false)]
    public function index(): Response
    {
        return $this->generateResponse();
    }

    #[Route('/revalidate')]
    #[Cache(maxage: 20, public: true, mustRevalidate: true, staleWhileRevalidate: 5, staleIfError: 10)]
    public function revalidate(): Response
    {
        return $this->generateResponse();
    }

    #[Route('/without-cache')]
    public function withoutCache(): Response
    {
        return $this->generateResponse();
    }

    private function generateResponse(): Response
    {
        return new Response(
            (new DateTime())
                ->setTimezone(new DateTimeZone('Europe/Warsaw'))
                ->format('Y-m-d H:i:s:v')
        );
    }
}

 

Pierwszym miejscem jest nasza strona główna. Ma zostać ona zacachowana na 20 sekund a w przypadku braku odpowiedzi nie musi być rewalidowana.

Następny punkt jest dostępny pod adresem localhost:5000/revalidateGdy cache wygaśnie musi on zostać zrewalidowany. Zapewne zauważyłeś dwa ciekawe argumenty przekazane w atrybucie Cache tj. staleWhileRevalidate czyli nagłówek HTTP stale-while-revalidate oraz staleIfError tj. nagłówek stale-if-error.
Pierwszy nagłówek odpowiada za czas przez który można wykorzystywać wygaśnięty cache zaś stale-if-error odpowiada za czas przez który można wykorzystywać wygaśnięty cache w przypadku błędu serwera np. braku jego dostępności.

Ostatnia strona jest dostępna pod linkiem localhost:5000/without-cache. Serwuje ona nam zawsze świeżą odpowiedź.

Co zrobi Varnish gdy Nginx padnie

docker-compose -f docker-compose.varnish.yml stop nginx

W momencie gdy padnie nam nginx, fpm itd. Varnish będzie odpowiadał do momentu aż cache wygaśnie. Po wygaśnięciu podejmie próbę pobrania świeżej wartości ale zakończy się to rzuceniem 503. 504 itp. dla użytkownika bo nasz serwer nie odpowiada. Gdy nasza padnięta usługa wstanie i serwer zwróci poprawną odpowiedź Varnish od razu rozpocznie jej serwowanie użytkownikowi.

Ważna informacją też jest to że Varnish nie będzie zapisywał do cache niczego co zwróci kod błędu np. 504.

Dodajmy healtcheck

vcl 4.1;

backend default {
    .host = "nginx";
    .port = "80";

    .probe = {
        .url = "/health";
        .timeout = 3s;
        .interval = 10s;
        .window = 5;
        .threshold = 3;
    }
}
<?php

declare(strict_types=1);

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class HealthCheckController extends AbstractController
{
    #[Route('health')]
    public function index(): Response
    {
        return new JsonResponse(['status' => 'ok']);
    }
}

Jak widzisz HealthCheckController jest banalnie prosty, zresztą konfiguracja tak samo.

W tym przykładzie Varnish będzie sprawdzał zdrowie serwera (w configu: probe.inverval) co 10 sekund, z timeoutem (w configu: probe.timeout) ustawionym na 3 sekundy. Każda próba będzie wysyłała ​​request pod adres (w configu: probe.url) /health. W przypadku gdy 3 z ostatnich 5 prób się powiedzie, uznajemy serwer za zdrowy. W momencie gdy będzie 2/5, 1/5 lub 0/5 prób udanych, serwer zostanie oznaczony jako chory.

Jakie korzyści da Varnish z używania health checka

W porównaniu do poprzedniego przypadku jak przywrócisz Nginx do żywych zapewne zauważysz że Varnish od razu nie zwróci nam odpowiedzi. Zwróci ją nam dopiero gdy serwer zostanie oznaczony jako zdrowy.
Jest to bardzo dobre rozwiązanie ponieważ gdyby udało nam się podnieść usługę na dosłownie kilka sekund Varnish mógłby wrzucić w cache coś czego byśmy nie chcieli np. jakiś błąd pod 200-tką zwróconą itd. Takie zachowanie mogłoby również zrobić w przysłowiowego konia samego użytkownika że już wszystko działa.

Podsumowanie

Mam nadzieję że ten wpis dobrze wprowadził Cię w podstawy koncept Varnisha. W dalszych wpisach będziemy poszerzać tą wiedzę tak aby zrozumieć większość podstawowej wiedzy. 🙂

Następny wpis z serii Varnishu możesz znaleźć [KLIKNIJ TUTAJ]

Varnish – podstawowy cache – cz. 2
Przewiń na górę