Configuration et explication des subroutines de Varnish

Varnish Cache - Subroutines

Cet article fait suite à l’article sur la présentation de Varnish. Dans cette partie je vais détailler le fichier de base de configuration que j’ai mis en place. Il y a plein d’autres exemples sur le net qui traîne. Pour ma part je me suis basé sur une configuration déployé sur nos projets professionnels.

Le fichier est constitué de plusieurs subroutines (fonctions), ayant toutes un rôle différent.

Il y a plusieurs méthodes pour créer votre configuration :

  • Mettre toutes vos règles dans le même fichier à savoir le default.vcl
  • Créer un fichier différent par type de « règle », et pour améliorer la lisibilité de votre code. Soit chaque fichier contient :
    • les mêmes fonctions que le fichier default.vcl à savoir vcl_recv, ….
    • des nouvelles fonctions qui devront être appelé dans le fichier default.vcl via un call nom_de_fonction

De mon point de vue déclarer les mêmes fonctions dans différents fichier peut être source d’erreur.  La raison vient de la compilation du vcl (Merci à Guillaume Quintard pour les explications) :

  • varnish lit le vcl principal (default.vcl)
  • il inclut en place tous les fichiers spécifié par les « include « XXXX »; « , récursivement
  • il inclut le buitin.vcl tout à la fin de ce gros fichier
  • il va ensuite fusionner ensemble tous les vcl_recv, vcl_hash, vcl_hit, etc. en concaténant les différentes sections et en gardant leur ordre relatifs
  • puis traduction en C et compilation
De fait, s’il y a un return dans votre vcl principal ou dans un autre fichier, il se peut que du code défini dans un autre fichier de conf ou même du builtin.vcl ne soit jamais atteint et donc pris en compte. Idem il faut faire attention à l’odre d’inclusions des fichiers.
Je vais expliquer le fonctionnement et l’utilité des fonctions principales de Varnish que j’ai du modifier. Car le code de builtin.vcl contient déjà le grand nécessaire.
Pour certaines fonction je n’ai pas ajouté le code, il n’y a pas d’utilisé de surcharger celui de builtin.vcl.
Voici en image un récap des subroutines existantes
Varnish Cache - Subroutines

Subroutine vcl_init

Dans cette fonction il faut définir la variable vdir. Elle contiendra la liste des backends.
sub vcl_init {
   new vdir = directors.round_robin();
   vdir.add_backend(backend1);
}

Subroutine vcl_recv

Cette fonction reçoit la requête envoyé par le client. Dans cette fonction on va définir si oui on non on va utiliser le fonctionnement de Varnish ou si on le bypass pour envoyé directement la requête au backend.
Contrairement à beaucoup d’exemple sur internet, je n’ai pas mis le bloc concernant le X-Forwarded-Fo r. En Varnish v4.1 ce code est totalement inutile et contre productif car il est déjà présent dans le code source de varnishd. Ce code est appelé avant le vcl_recv.
Pour que le backend puisse récupérer la véritable adresse IP du client il faut utiliser la variable $_SERVER[‘HTTP_X_FORWARDED_FOR’]
On indique sur quel backend la requête devra être envoyé. Ceci est utile si en fonction du nom de domaine, vous souhaitez utiliser un backend différent.
On définit quelles sont les méthodes autorisées à être caché. Tout ne traffic POST ne doit bien évidement par être caché.
On nettoie les cookies inutiles.

La fonction se termine par un return car one ne veut pas utiliser le code du builtin.vcl. Car par défaut Varnish ne cache pas le contenu dès qu’un cookie est présent. Return a utiliser si vous êtes sure vouloir cacher vos pages même s’il a des cookies. Il est par contre possible de bypasser le cache en fonction de la présence de certains cookies (utile si vos utilisateurs sont connectés ou pour du debug), explication dans cet article Configuration avancé de Varnish :

sub vcl_recv {

   set req.backend_hint = vdir.backend();

   # Added security, the "w00tw00t" attacks are pretty annoying so lets block it before it reaches our webserver
   if (req.url ~ "^/w00tw00t"){
      return( synth(403, "not permitted !"));
   }

   # Normalize the header, remove the port (in case you're testing this on various TCP ports)
   set req.http.Host = regsub(req.http.Host, ":[0-9]+", "");

   # Only deal with "normal" types
   if (req.method != "GET" &&
         req.method != "HEAD" &&
         req.method != "PUT" &&
         req.method != "POST" &&
         req.method != "TRACE" &&
         req.method != "OPTIONS" &&
         req.method != "PATCH" &&
         req.method != "DELETE") {
      /* Non-RFC2616 or CONNECT which is weird. */
      set req.http.X-Pass = "pipe";
      return (pipe);
   }

   # removes all cookies named __utm? (utma, utmb...) - tracking thing
   set req.http.Cookie = regsuball(req.http.Cookie, "(^|(?<=; )) *__utm.=[^;]+;? *", "\1");

   if (req.http.Cookie == "") {
      unset req.http.Cookie;
   }

   # Only cache GET or HEAD requests. This makes sure the POST requests are always passed.
   if (req.method != "GET" && req.method != "HEAD") {
      set req.http.X-Pass = "POST request";
      return (pass);
   }

   # Don't cache HTTP authentication
   if (req.http.Authorization) {
      set req.http.X-Pass = "HTTP Auth";
      return (pass);
   }

   # Normalize Accept-Encoding header
   # straight from the manual: https://www.varnish-cache.org/docs/3.0/tutorial/vary.html
   if (req.http.Accept-Encoding) {
      if (req.url ~ "\.(jpg|png|gif|gz|tgz|bz2|tbz|mp3|ogg)$") {
         # No point in compressing these
         unset req.http.Accept-Encoding;
      } elsif (req.http.Accept-Encoding ~ "gzip") {
         set req.http.Accept-Encoding = "gzip";
      } elsif (req.http.Accept-Encoding ~ "deflate") {
         set req.http.Accept-Encoding = "deflate";
      } else {
         # unkown algorithm
         unset req.http.Accept-Encoding;
      }
   }

   # Cache all others requests
   return (hash);
}

 

Subroutine vcl_hash

Cette fonction permet de créer une clé d’identification de l’object (page) à mettre en cache. Par défaut la clé va être composé de l’url et du host ou du l’adresse IP.

Subroutine vcl_hit

Par défaut il n’est pas utile de modifier le code de cette fonction. Je rentrerai plus en détail sur cette fonction dans un autre article. Car elle permet d’utiliser le mode grace de Varnish.

Subroutines vcl_backend_response

Dans cette fonction on va analyser la réponse que le backend a envoyé à Varnish et ainsi étudier le code http de retour.

Dans le premier bloc, on va contrôler la taille du contenu de la page, et si elle est null, alors on va tenter de réinterroger le backend, et ceux 3 fois au maximun. Explication via un graphique du principe de retry.

Varnish Cache - Gestion du retry

On va également définir un ttl (durée de mise en cache dans Varnish) dans le cas où votre application web ne contient aucunes des informations suivantes dans le header. Ces informations sont lues par Varnish et l’informe de la durée pendant laquelle il doit conserver l’objet en cache avant de réinterroger le backend :

  • s-maxage dans le hedaer Cache-Control
  • max-age dans le hedaer Cache-Control
  • header Expires

Pour finir il y a toute une série de condition, qui indique qu’il ne faut pas mettre la page en cache. Notamment lorsqu’il y a un Set cookie, ou qu’il y a l’un des headers indique qu’il ne faut pas mettre en cache : (Surrogate-control: »no-store » ou que Cache-Control contienne soit no-cache soit no-store soit private)

Il y a un return en fin de fonction car il est inutile de prendre en compte le code de builtin.vcl

sub vcl_backend_response {
   if ( beresp.http.Content-Length == "0"
         && bereq.method == "GET"
         && bereq.retries < 3
         && beresp.status != 301
         && beresp.status != 302
         && beresp.status != 404 ) {
      return(retry);
   }

   # To prevent accidental replace, we only filter the 301/302 redirects for now.
   if (beresp.status == 301
      || beresp.status == 302) {
      set beresp.http.Location = regsub(beresp.http.Location, ":[0-9]+", "");
   }

    # Default
    set beresp.keep = 1m;

   # The default_ttl parameter.
   if (!beresp.http.ttl) {
      set beresp.ttl = 1w;
   }

   if (beresp.status == 301
      || beresp.status == 404) {
      set beresp.ttl = 52w;
      set beresp.uncacheable = false;
      return (deliver);
   }

   if (beresp.ttl <= 0s
      || beresp.status == 307
      || beresp.http.Set-Cookie
      || beresp.http.Surrogate-control ~ "no-store"
      || beresp.http.Cache-Control ~ "no-cache|no-store|private"
      || beresp.http.Vary == "*") {
      /*
       * Mark as "Hit-For-Pass" for the next 2 minutes
       */
      set beresp.ttl = 120s;
      set beresp.uncacheable = true;
      return (deliver);
   }

    return (deliver);
}

 

Subroutine vcl_deliver

Cette fonction est appelée avant que l’objet (page) soit délivré au client. C’est dans cette fonction qu’on peut rajouter des headers et du debug.

Subroutine vcl_backend_error

Cette fonction est appelée si le backend n’a pas pu retourner un résultat valide ou si le backend est down. Elle permet par d’exemple de changer le message d’erreur que l’utilisateur verra, en appelant la fonction synthetic.

Dans mon cas j’ai mis en place un système de retry, qui permet d’envoyer la même requête sur un autre backend ou de retenter sur le même. Dans la limite de 3 essaies maximun pour éviter une boucle infinie.

# If backend fetch failed try the request on an other backend
if ( beresp.status == 503
   && bereq.retries < 3
   && bereq.method == "GET" ) {
   # jumps back up to vcl_backend_fetch
   return(retry);
}

 

Vous avez un Varnish pleinement fonctionnel avec un système de retry en cas d’erreur.


Commenter

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.