Service de cache HTTP avec Varnish

Varnish est un service de reverse-proxy-cache (mandataire inversé avec cache) HTTP, autrement dit un accélérateur de sites web.

Varnish reçoit les requêtes HTTP des visiteurs :

  • s’il dispose de la réponse à la requête en cache, celle-ci est renvoyée directement au client depuis la mémoire du serveur,

  • s’il ne dispose pas de la réponse, Varnish s’adresse alors au serveur web. Il lui transmet la requête, récupère la réponse, la stocke dans son cache, et répond au client.

Fournir la réponse depuis le cache en mémoire permet d’améliorer les temps de réponse aux clients. En effet, dans ce cas, il n’y a pas d’accès aux disques physiques.

Par défaut, Varnish écoute sur le port 6081 et utilise le langage VCL (Varnish Configuration Language) pour sa configuration. Grâce au langage VCL, il est possible de décider ce qui doit ou ne doit pas être transmit au client, ce qui doit être stocké en cache, depuis quel site et comment la réponse peut être modifiée.

Varnish est extensible par l’utilisation de modules VMOD (Varnish Modules).

Principe de fonctionnement

Dans un fonctionnement basique d’un service Web, le client communique en TCP sur le port 80 directement avec le service.

001 Varnish
Figure 1. Fonctionnement d’un site web standard

Pour profiter du cache, le client doit communiquer avec le service web sur le port par défaut de Varnish 6081.

002 Varnish
Figure 2. Fonctionnement par défaut de Varnish

Pour rendre le service transparent au client, il faudra changer le port d’écoute par défaut de Varnish et des vhosts du service web.

003 Varnish
Figure 3. Mise en place transparente pour le client

Installation de Varnish

  • Sous Debian :

# apt-get install varnish
  • Sous RHEL :

# yum install varnish

Configuration du démon varnishd

Configuration du démon

La configuration du démon se fait dans le fichier /etc/varnish/varnish.params sur RedHat et dans /etc/default/varnish sous Debian :

  1. Le fichier /etc/varnish/varnish.params sous RedHat

# Set this to 1 to make systemd reload try to switch VCL without restart.
RELOAD_VCL=1

# Main configuration file. You probably want to change it.
VARNISH_VCL_CONF=/etc/varnish/default.vcl

# Default address and port to bind to. Blank address means all IPv4
# and IPv6 interfaces, otherwise specify a host name, an IPv4 dotted
# quad, or an IPv6 address in brackets.
# VARNISH_LISTEN_ADDRESS=192.168.1.5
VARNISH_LISTEN_PORT=6081

# Admin interface listen address and port
VARNISH_ADMIN_LISTEN_ADDRESS=127.0.0.1
VARNISH_ADMIN_LISTEN_PORT=6082

# Shared secret file for admin interface
VARNISH_SECRET_FILE=/etc/varnish/secret

# Backend storage specification, see Storage Types in the varnishd(5)
# man page for details.
VARNISH_STORAGE="malloc,256M"

# User and group for the varnishd worker processes
VARNISH_USER=varnish
VARNISH_GROUP=varnish

# Other options, see the man page varnishd(1)
#DAEMON_OPTS="-p thread_pool_min=5 -p thread_pool_max=500 -p thread_pool_timeout=300"
Le fichier /etc/default/varnish sous Debian
DAEMON_OPTS="-a :6081 \
             -T localhost:6082 \
             -f /etc/varnish/default.vcl \
             -S /etc/varnish/secret \
             -s default,256m"

Depuis systemctl, changer les valeurs par défaut se fait en créant le fichier /etc/systemd/system/varnish.service.d/customexec.conf :

Le ficher /etc/systemd/system/varnish.service.d/customexec.conf
[Service]
ExecStart=
ExecStart=/usr/sbin/varnishd -a :80 -T localhost:6082 -f /etc/varnish/default.vcl -S /etc/varnish/secret -s default,256m

Cela aura pour effet de surcharger les valeurs par défaut dans la partie ExecStart fournies avec Varnish.

Il faut ensuite relancer systemctl daemon-reload pour s’assurer que systemd prenne en compte les modifications avant de relancer Varnish.

Table 1. Les options du démon varnish
Option Observation

-a addr[:port]

Ecoute les requêtes clientes sur les adresses IP et les ports spécifiés. Par défaut : toutes les adresses et sur le port 80.

-T addr[:port]

Interface de gestion

-f file

Fichier de configuration

-S

Fichier contenant le secret permettant l’authentification sur le port de gestion

-s

Spécifier un backend de stockage du cache. L’option peut être spécifiée plusieurs fois. Les types de stockage possibles sont malloc (cache en mémoire puis si besoin dans la swap), ou file (création d’un fichier sur le disque puis mapping en mémoire). Les tailles sont exprimées en K/M/G/T (kilobytes, megabytes, …​)

-C

Compile la VCL en langage C et l’affiche à l’écran.

Test de la configuration et relance

Il est conseillé de vérifier la syntaxe de la VCL avant de relancer le démon varnishd.

Il s’agit de compiler en langage C le fichier de configuration VCL. Le service peut être redémarré si la compilation fonctionne et qu’aucune alarme n’est affichée.

Vérification de la syntaxe varnishd
varnishd -C -f /etc/varnish/default.vcl

ou utilisation du script init :

# /etc/init.d/varnish configtest
[...]
    .hit_func = VGC_function_vcl_hit,
    .init_func = VGC_function_vcl_init,
    .miss_func = VGC_function_vcl_miss,
    .pass_func = VGC_function_vcl_pass,
    .pipe_func = VGC_function_vcl_pipe,
    .purge_func = VGC_function_vcl_purge,
    .recv_func = VGC_function_vcl_recv,
    .synth_func = VGC_function_vcl_synth,
};

Puis purge du cache et rechargement de la configuration : (si RELOAD_VCL=1) :

Rechargement de la configuration
systemctl reload varnishd

ou

Redémarrage complet
systemctl restart varnishd

Il est possible de vérifier qu’une page provient du cache varnish depuis les en-têtes de la réponse HTTP :

004 varnish
Figure 4. En-tête varnish et cache hit

Le langage VCL

Les sous-routines

Varnish utilise des fichiers VCL, segmentés en sous-routines comportant les actions a exécuter. Ces sous-routines sont exécutées uniquement dans les cas spécifiques qu’elles définissent. Dans le fichier par défaut /etc/varnish/default.vcl, nous trouvons les routines vcl_recv, vcl_backend_response et vcl_deliver.

# vim /etc/varnish/default.vcl
cat /etc/varnish/default.vcl
#
# This is an example VCL file for Varnish.
#
# It does not do anything by default, delegating control to the
# builtin VCL. The builtin VCL is called when there is no explicit
# return statement.
#
# See the VCL chapters in the Users Guide at https://www.varnish-cache.org/docs/
# and http://varnish-cache.org/trac/wiki/VCLExamples for more examples.

# Marker to tell the VCL compiler that this VCL has been adapted to the
# new 4.0 format.
vcl 4.0;

# Default backend definition. Set this to point to your content server.
backend default {
    .host = "127.0.0.1";
    .port = "8080";
}

sub vcl_recv {

}

sub vcl_backend_response {

}

sub vcl_deliver {

}
Table 2. Les sous-routines VCL
Sous-routine Action

vcl_recv

Cette routine est appelée avant l’envoi de la requête vers le backend. Dans cette routine, il est possible de modifier les en-têtes HTTP, cookies, choisir le backend, etc. Voir actions set req..

vcl_backend_response

Cette routine est appelée après la réception de la réponse du backend (beresp = BackEnd RESPonse). Voir actions set bereq. et set beresp..

vcl_deliver

Cette routine est utile pour modifier la sortie de Varnish. Si besoin de modifier l’objet final (ajouter ou supprimer un en-tête, …​), il est possible de le faire dans vcl_deliver.

Les opérateurs VCL

Table 3. Les opérateurs VCL
Opérateur Action

=

assignation

==

comparaison

~

comparaison en association avec une expression régulière et des ACL

!

négation

&&

et logique

||

ou logique

Les objets Varnish

beresp = BackEnd RESPonse

Table 4. Les différents objets Varnish
Objet Observation

req

l’objet requête. Quand Varnish reçoit la requête, req est créé. La plupart du travail dans la sous-routine vcl_recv touche à cet objet.

bereq

l’objet requête à destination du serveur web. Varnish créé cet objet à partir de req.

beresp

l’objet réponse du serveur web. Il contient les entêtes de l’objet en provenance de l’application. Il est possible de modifier la réponse du serveur dans la sous-routine vcl_backend_response.

resp

la réponse HTTP qui va être envoyée au client. Cet objet est modifié dans la sous-routine vcl_deliver.

obj

l’objet tel que sauvegardé en cache. En lecture seule.

Les actions varnish

Table 5. Les actions les plus fréquentes
Action Observation

pass

Quand pass est retourné, la requête et la réponse qui en suivront viendront du serveur d’application. Il n’y aura pas de cache appliqué. pass est retourné depuis la sous-routine vcl_recv.

hash

Quand hash est retourné depuis vcl_recv, Varnish servira le contenu depuis le cache même si la requête est configurée pour passer sans cache.

pipe

Cette action sert à gérer les flux. Varnish dans ce cas n’inspectera plus chaque requête mais laisse passer tous les bytes au serveur. pipe est utilisé par exemple par les websockets ou la gestion des flux vidéos.

deliver

Sert l’objet au client. En général depuis la sous-routine vcl_backend_response.

restart

Recommence le processus de traitement de la requête. Les modifications de l’objet req sont conservées.

retry

La requête est de nouveau transférée au serveur d’application. Utilisé depuis vcl_backend_response ou vcl_backend_error si la réponse de l’application n’est pas satisfaisante.

En résumé, les interactions possibles entre les sous-routines et les actions sont illustrées dans le schéma ci-dessous :

005 varnish
Figure 5. Fonctionnement simplifié de varnish

Configuration des backends

Varnish utilise le terme backend pour les vhosts qu’il doit mandater.

Plusieurs backend peuvent être défini sur le même serveur Varnish.

La configuration des backends se fait dans le fichier /etc/varnish/default.vcl.

Gestion des ACL

# ACL de deny
acl deny {
"10.10.0.10"/32;
"192.168.1.0"/24;
}

Appliquer l’ACL :

# Bloquer les IP de l'ACL deny
if (client.ip ~ forbidden) {
  error 403 "Acces interdit";
}

Ne pas cacher certaines pages :

# Ne pas mettre en cache les pages login et admin
if (req.url ~ "/(login|admin)") {
  return (pass);
}

Paramètres POST et cookies

Varnish ne met jamais en cache les requêtes HTTP de type POST ou celle contenant des cookies (qu’ils proviennent du client ou du backend).

Si le backend utilise des cookies, alors aucun contenu ne sera mis en cache.

Pour corriger ce comportement, il est possible de déréferrencer les cookies de nos requêtes :

sub vcl_recv {
    unset req.http.cookie;
}

sub vcl_backend_response {
    unset beresp.http.set-cookie;
}

Répartir les requêtes sur différents backend

Dans le cas de l’hébergement de plusieurs sites, comme par exemple un serveur de document (doc.formatux.fr) et un blog (blog.formatux.fr), il est possible de répartir les requêtes vers le bon backend.

Déclaration des backends
backend docs {
    .host = "127.0.0.1";
    .port = "8080";
}

backend blog {
    .host = "127.0.0.1";
    .port = "8081";
}

L’objet req.backend est modifié en fonction de l’hôte appelé dans la requête HTTP dans la sous-routine vcl_recv :

Sélection du backend
sub vcl_recv {
    if (req.http.host ~ "^doc.formatux.fr$") {
        set req.backend = nginx;
    }

    if (req.http.host ~ "^blog.formatux.fr$") {
        set req.backend = ghost;
    }
}

Répartir la charge

Varnish est capable de gérer la répartition de charge via des backends spécifiques appelés directors.

  • Le director round-robin distribue les requêtes aux backends en round-robin (alternativement). Il est possible d’affecter une pondération à chaque backend.

  • Le director client distribue les requêtes en fonction d’une affinité de session (sticky session) sur n’importe quel élément de l’en-tête (par exemple avec un cookie de session). Un client est dans ce cas toujours renvoyé vers le même backend.

Déclaration des backends
backend docs1 {
    .host = "10.10.11.10";
    .port = "8080";
}

backend docs2 {
    .host = "10.10.11.11";
    .port = "8080";
}

Le director permet d’associer les 2 backends définis :

Déclaration du director
director docs_director round-robin {
    { .backend = docs1; }
    { .backend = docs2; }
}

Reste à définir le director comme backend aux requêtes :

Association des requêtes au director
sub vcl_recv {
    set req.backend = docs_director;
}

Configuration du système

Configuration du port 80 et 443

Modifier, sous debian le fichier /etc/default/varnish :

DAEMON_OPTS="-a :80
-T localhost:6082
-f /etc/varnish/default.vcl
-S /etc/varnish/secret
...

Configuration d’un cache

Configuration d’un cache sur disque d'1G.

Modifier, sous debian le fichier /etc/default/varnish :

DAEMON_OPTS="-a :80
-T localhost:6082
-f /etc/varnish/default.vcl
-S /etc/varnish/secret
...
-s file,/var/lib/varnish/$INSTANCE/varnish_storage.bin,1G"

Adaptation d’Apache

Changement de ports réseau

Si le service Varnish est localisé sur le même serveur que le service Web (Apache ou Nginx), les deux services ne pourront plus écouter en même temps les ports par défaut 80 et 443.

Dans ce cas, il est d’usage de faire écouter le service web sur un port 8080, 8081, 8082 etc. et de laisser le port par défaut à Varnish.

#Listen 80
Listen 8080

Modification du LogFormat

Le service http étant reverse proxifié, le serveur web n’aura plus accès aux adresses IP du client mais à celui du service Varnish.

Pour prendre en compte le reverse proxy dans les logs Apache, modifier dans le fichier de configuration du serveur le format du journal d’évènement :

LogFormat "%{X-Forwarded-For}i %l %u %t "%r" %>s %b "%{Referer}i" "%{User-Agent}i"" varnishcombined

et prendre en compte ce nouveau format dans le vhost du site web :

CustomLog /var/log/httpd/www-access.log.formatux.fr varnishcombined

et rendre Varnish compatible :

# Compatibility with Apache format log

if (req.restarts == 0) {
  if (req.http.x-forwarded-for) {
    set req.http.X-Forwarded-For = req.http.X-Forwarded-For + ", " + client.ip;
  } else {
   set req.http.X-Forwarded-For = client.ip;
  }
}

Purge du cache

Le cache peut être purgé :

  • en ligne de commande :

# varnishadm 'ban req.url ~ .'
  • en utilisant un secret et un port différent du port par défaut :

# varnishadm -S /etc/varnish/secret -T 127.0.0.1:6082 'ban req.url ~ .'
  • avec l’interface CLI:

# varnishadm

varnish> ban req.url ~ ".css$"
200

varnish> ban req.http.host == docs.formatux.fr
200

varnish> ban req.http.host ~ .
200
  • via une requête HTTP PURGE :

$ curl -X PURGE http://www.example.org/foo.txt

Varnish doit être configuré pour accepter cette requête :

acl local {
    "localhost";
    "10.10.1.50";
}

sub vcl_recv {
    # directive a placer en premier,
    # sinon une autre directive risque de matcher avant
    # et la purge ne sera jamais effectuée
    if (req.method == "PURGE") {
        if (client.ip ~ local) {
            return(purge);
        }
    }
}

Gérer les backends par CLI

Les backends peuvent être marqués comme sick ou healthy en fonction des besoins d’administration ou de maintenance. Cette action permet de sortir un noeud du pool sans avoir à modifier la configuration du serveur Varnish (et donc sans le relancer) ou sans stopper le service du backend.

Visualiser le status du backend

La commande backend.list affiche l’ensemble des backends, même les backends qui ne disposent pas de healthcheck (probe).

$ varnishadm backend.list
Backend name                   Admin      Probe
site.default                   probe      Healthy (no probe)
site.front01                   probe      Healthy 5/5
site.front02                   probe      Healthy 5/5

Basculer un backend en status sick

La commande backend.set_health va permettre de basculer un backend d’un état à l’autre :

$ varnishadm backend.set_health site.front01 sick

Le backend est sorti du pool et ne reçoit plus de trafic :

$ varnishadm backend.list
Backend name                   Admin      Probe
site.default                   probe      Healthy (no probe)
site.front01                   sick       Sick 0/5
site.front02                   probe      Healthy 5/5

Notez que dans la colonne Admin, le backend a été marqué explicitement comme sick.

Rebasculer un backend en status healthy

De la même manière, le status peut être rebasculé en healthy, ce qui n’est toutefois pas encore le même status qu' auto (voir paragraphe suivant).

$ varnishadm backend.set_health site.front01 healthy

Le backend est de retour dans le pool, il reçoit à nouveau du trafic.

Retour à la normal en mode auto

Pour laisser décider varnish de l’état de ses backends, il faut impérativement rebasculer en mode auto les backends qui auraient étés basculés en sick ou healthy manuellement.

$ varnishadm backend.set_health site.front01 auto

La gestion des journaux

Varnish écrit ses journaux en mémoire et en binaire afin de ne pas pénaliser ses performances. Lorsqu’il ne dispose plus d’espace en mémoire, il réécrit les nouveaux enregistrements par dessus les anciens, en repartant du début de son espace mémoire.

Les journaux peuvent être consultés avec les outils varnishstat (statistiques), varnishtop (top pour Varnish), varnishlog (journalisation verbeuse) ou varnishnsca (journaux au format NCSA comme Apache) :

# varnishstat
# varnishtop -i ReqURL
# varnishlog
# varnishnsca

Filtrer les journaux

L’option -q permet d’appliquer des filtres sur les journaux via les commandes précédentes :

# varnishlog -q 'TxHeader eq MISS' -q "ReqHeader ~ '^Host: formatux\.fr$'"
# varnishncsa -q "ReqHeader eq 'X-Cache: MISS'"

Enregistrer les journaux sur disques

L’enregistrement des journaux sur disque est effectué par les démons varnishlog et varnishnsca en toute indépendance du démon varnishd. Le démon varnishd continue a renseigner ses journaux en mémoire sans pénaliser ses performances vers les clients, puis les autres démons se charge de copier les enregistrements sur disque.

Commandes Varnish

  • varnishlog: Affichage du log du daemon Varnish.

  • varnishstat: Affichage des statistiques d’utilisation de Varnish.

  • varnishhist: Affiche un historique sous forme de graphe des requêtes faites à votre serveur Varnish.

  • varnishadm: une interface d’administration locale de Varnish