% Copyright (c) 2004-2010 Evolix <info@evolix.fr>
%  Permission is granted to copy, distribute and/or modify this document
%  under the terms of the GNU Free Documentation License, Version 1.2
%  or any later version published by the Free Software Foundation;
%  with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
%  A copy of the license is included at http://www.gcolpart.com/howto/fdl.html

\chapter{Varnish}

Site officiel: \url{https://www.varnish-cache.org/}

\section{Présentation}

Varnish est un reverse proxy HTTP dans le but premier est la mise en cache de
contenu. Il est également capable de gérer plusieurs backend, avec
répartition de charge et détection de panne.

Varnish, développé en C, se concentre principalement sur la performance sur des infrastructures
à haut et très haut trafic.

Un autre point fort est son langage de configuration, qui permet de paramétrer
finement le comportement de Varnish aux différentes étapes du traitement de la
requête.

Le développement de Varnish a commencé en 2005, et il est distribué sous licence
BSD.

\section{Installation}

Varnish est disponible dans les dépôts de Debian Squeeze en version 2.1.3. Il
existe également un backport du paquet de Wheezy, qui fournit la version 3.0.2.
Cette version apporte de nombreuses amélioration et fonctionnalité dans la
gestion du load-balancing entre les backends.

Installation du paquet:
\begin{verbatim}
# aptitude install varnish
\end{verbatim}

\section{Configuration}

Les possibilités offertes pour la configuration de Varnish sont assez vastes,
elles seront abordées par grands thèmes.

\subsection{Paramétrage de base}

Tout d'abord, il est nécessaire de renseigner quelques informations de base au
démon \texttt{varnishd}. Cette configuration se passe dans le fichier
\texttt{/etc/default/varnish}. Plusieurs cas de figure sont proposés à titre
d'exemple dans ce fichier, en voici un autre avec quelques optimisations
supplémentaires:
\begin{verbatim}
DAEMON_OPTS="-a 192.0.2.1:80 \
             -T localhost:6082 \
             -f /etc/varnish/default.vcl \
             -S /etc/varnish/secret \
             -s malloc,3G
             -s file,/var/lib/varnish/$INSTANCE/varnish_storage.bin,10G
             -p thread_pools=<Number of CPU cores>
             -p thread_pool_add_delay=2
             -p thread_pool_max=5000"
umask 022
\end{verbatim}

Et voici quelques explications sur les paramètres:
\begin{description}
  \item[\texttt{-a 192.0.2.1:80}] \hfill \\
    Il s'agit du couple IP,port sur lequel Varnish attendra les requêtes HTTP à
    traiter.
  \item[\texttt{-T localhost:6082}] \hfill \\
    Il s'agit du couple IP,port sur lequel sera accessible l'interface
    d'administration de Varnish (traité plus loin dans ce chapitre).
  \item[\texttt{-f /etc/varnish/default.vcl}] \hfill \\
    Cette option indique le fichier de configuration à utiliser.
  \item[\texttt{-S /etc/varnish/secret}] \hfill \\
  \item[\texttt{-s malloc,3G}]
  \item[\texttt{-s file,/var/lib/varnish/\$INSTANCE/varnish\_storage.bin,10G}] \hfill \\
    On indique ici qu'une partie du cache sera stocké en mémoire 3~Go, ainsi que
    dans un fichier plat sur le disque, qui sera limité à 10~Go.
  \item[\texttt{-p thread\_pools=<Number of CPU cores>}]
  \item[\texttt{-p thread\_pool\_add\_delay=2}]
  \item[\texttt{-p thread\_pool\_max=5000}] \hfill \\
    L'option \texttt{-p} permet de modifier différents paramètres d'exécution.
    De nombreux paramètres peuvent être modifiés, la liste complète avec leur
    description se trouve ici:
    \url{https://www.varnish-cache.org/docs/2.1/reference/varnishd.html}.

    \texttt{thread\_pools} indique le nombre de groupe de threads à lancer. Cette
    valeur ne devrait pas dépasser le nombre de c\oe{}ur disponibles sur le système
    (pour des raisons de performance). Pour \texttt{threa\_poo\_ad\_delay}, il
    s'agit du temps en milisecondes à attendre avant la création d'un nouveau
    thread. Et enfin \texttt{threa\_poo\_max} représente le nombre total de
    thread maximum à ne pas dépasser, tout pool confondus.
  \item[\texttt{umask 022}] \hfill \\
    Varnish s'attend à avoir un umask à 022 pour s'exécuter
    correctement. \'Etant donné qu'il n'est pas forcé dans le script d'init,
    nous le plaçons ici manuellement.
\end{description}

\subsection{Aperçu de la syntaxe du langage VCL}

La syntaxe VCL est complexe mais puissante. On découpe un fichier VCL en
plusieurs sous-routines dans lesquelles on définit des actions/comportements en
fonction de certaines conditions.

Concrètement, 99~\% des règles sont faites dans les 2 sous-routines
\texttt{vcl\_recv} et \texttt{vcl\_fetch}.

\begin{itemize}
  \item \texttt{vcl\_recv} est appelé AVANT le début de la requête au backend.
    On peut donc choisir vers quel backend renvoyer la requête. On peut aussi
    de modifier la requête (modifier des entêtes HTTP, supprimer des demandes
    de cookies, etc\dots). Seules les actions \texttt{set req.} sont possibles.
  \item \texttt{vcl\_fetch} est appelé APRÈS la réception de la réponse du
    backend. Les actions \texttt{set req.} sont possibles, mais aussi \texttt{set
    beresp.} (pour \emph{backend response}).
\end{itemize}

Voici donc des règles typiques:
\begin{verbatim}
sub vcl_recv {

    if (req.http.host ~ "(www\.example\.com|example\.com)") {
        set req.backend = default;
    }

    if (req.url ~ "^/images") {
        unset req.http.cookie;
    }
}

sub vcl_fetch {
   if (req.url ~ "\.(png|gif|jpg)$") {
     unset beresp.http.set-cookie;
     set beresp.ttl = 3600s;
  }
}
\end{verbatim}

Voici un certain nombre de \emph{conditions} possibles:
\begin{verbatim}
# Condition sur l'entête HTTP Host:
if (req.http.host ~ "^regex$")
# Présence d'un cookie
if (req.http.cookie) {
# Condition sur l'URL demandée
if (req.url ~ "^/regex$")
# Si le backend est accessible
if (req.backend.healthy)
# Présence entête Accept-Encoding
if (req.http.Accept-Encoding)
# Condition sur la requête faite 
if (req.request != "GET" && req.request != "HEAD")
# Présence de l'entête X-Forwarded-For
if (req.http.x-forwarded-for)
# Condition sur les entêtes envoyés
if (req.http.Authorization || req.http.Cookie)
# Condition 
if (req.http.Cache-Control ~ "no-cache")
# Si la réponse du backend permet la mise en cache
if (beresp.cacheable)
# Condition sur le temps de mise (Cache-Control: max-age a priori)
if (beresp.ttl < 120s)
# Condition sur le statut des réponses
if (obj.status == 404 || obj.status == 503 || obj.status == 500)
\end{verbatim}

Voici un certain nombre d'\emph{actions} possibles:
\begin{verbatim}
# Renvoyer vers un backend
set req.backend = baz;
# Supprimer les cookies dans la requête
unset req.http.cookie;
remove req.http.cookie;
# Supprimer un certain nombre d'entêtes HTTP
remove req.http.X-Forwarded-For;
remove req.http.Accept-Encoding;
# Positionner un certain nombre d'entêtes HTTP pour la requête
set req.http.X-Forwarded-For = client.ip;
set req.http.Accept-Encoding = "gzip";
set req.http.Accept-Encoding = "deflate";
# Positionner un certain nombre d'entêtes HTTP pour la réponse
set obj.http.expires = "Mon, 1 Jan 2007 00:00:01 GMT";
set obj.http.X-foo = "bar";
# Renvoyer une erreur HTTP
error 404 "Page not found";
\end{verbatim}

Enfin, voici les \emph{comportements} possibles:
\begin{verbatim}
# Renvoie vers le backend (pas de cache)
return (pass);
# Renvoie la version en cache (si possible)
return (lookup);
return (deliver);
# Renvoie "directement" vers le backend sans inspection du contenu
return (pipe)
# Redémarre la demande au backend (et incrémente le compteur de restarts)
return (restart);
\end{verbatim}

\subsection{Gestion du cache}

En se positionnant entre le client et le serveur applicatif, Varnish permet de
lire et surcharger si besoin les entêtes HTTP de contrôle du cache. Par défaut,
ceux ci sont lus et pris en compte, mais on peut redéfinir le comportement
dans la configuration.

Voici quelques exemples d'utilisation typique:

\paragraph{Forcer le TTL pour certains contenus}
\begin{verbatim}
sub vcl_fetch {
    if (req.url ~ "\.(png|gif|jpg)$") {
        set beresp.ttl = 5d;
        set beresp.http.magicmarker = "1";
    }
}

sub vcl_deliver {
    if (resp.http.magicmarker) {
        unset resp.http.magicmarker;
        set resp.http.Age = "0";
      }
}
\end{verbatim}
\texttt{beresp.http.magicmarker} permet de marquer l'objet pour pouvoir ensuite
remettre son age à 0 (dans \texttt{vcl\_deliver}.

Pour que le changement de TTL le soit également coté client, on réécrit le
header HTTP \emph{Cache-Control} en ajoutant (dans le premier \texttt{if}:
\begin{verbatim}
set beresp.http.cache-control = ``max-age=432000'';
\end{verbatim}

\paragraph{Indiquer si un objet provient du cache ou pas dans les headers HTTP}
Dans un but de debugage, il peut être intéressant d'indiquer si un contenu
provient du cache de Varnish ou non. Cela se fait simplement comme ceci:
\begin{verbatim}
sub vcl_deliver {
    if (obj.hits > 0) {
        set resp.http.X-Cache = "HIT";
    } else {
        set resp.http.X-Cache = "MISS";
    }
}
\end{verbatim}

\subsection{Gestion du load-balancing}

Tout d'abord, il faut définir au moins un backend pour que l'ensemble puisse
fonctionner correctement. Cela se fait à l'aide de la directive
\texttt{backend}, comme ceci:
\begin{verbatim}
backend www00 {
    .host = "192.0.2.8";
    .port = "80";
}

backend www01 {
    .host = "192.0.2.14";
    .port = "80";
}
\end{verbatim}

Il est ensuite possible de grouper ces backends dans un cluster, appelé
\emph{director} dans le langage de Varnish:
\begin{verbatim}
director baz round-robin {
    { .backend = www00; }
    { .backend = www01; }
}
\end{verbatim}

Et enfin, on indique dans quel cas il sera utilisé (dans l'exemple il sera
utilisé dans tous les cas, pas de condition):
\begin{verbatim}
sub vcl_recv {
    set req.backend = baz;
}
\end{verbatim}

Il s'agit ici de la configuration la plus simple possible. Maintenant, il peut
être intéressant d'ajuster certains paramètres:
\begin{itemize}
  \item Dans l'exemple ci dessus, le director est en mode round-robin. Le trafic
    est alors réparti équitablement entre les backend. On peut définir un
    «poids» pour chacun des backends, afin de jouer sur la répartition du trafic
    entre eux:
    \begin{verbatim}
director baz random {
    {
        .backend = www00;
        .weight = 6;
    }
    {
        .backend = www01;
        .weight = 4;
    }
}
    \end{verbatim}
    Pour cela, on change le mode du director pour \emph{random}.
  \item Une directive importante est \texttt{.max\_connections}. Elle permet de
    limiter le nombre de connexions concurrentes envoyées sur un backend. En en
    positionnant une sur chacun des backends, Varnish saura qu'il devra ignorer
    le backend saturé et en choisir un autre, afin de ne pas le surcharger.
    \begin{verbatim}
backend www00 {
    .host = "192.0.2.8";
    .port = "80";
    .max_connections = 80;
}
\end{verbatim}

  \item Il est possible également de répartir les requêtes sur les backends
    suivant des critères sur la requête. Le mode du director à utiliser est
    alors \emph{client}:
    \begin{verbatim}
director baz client {
    { .backend = www00; }
    { .backend = www01; }
}

sub vcl_recv {
   set req.backend = baz;
   set client.identity = req.ip;
}
\end{verbatim}
Dans l'exemple ci-dessus, le critère utilisé est l'IP du client
(\texttt{client.identity = req.ip}. Les autres critères possibles sont le
user-agent (\texttt{req.http.user-agent}), l'URL (\texttt{client.url}) ou encore
un cookie de session (\texttt{req.http.cookie}).

\end{itemize}

\subsection{Gestion du failover}

Nous avons vu que Varnish était capable de gérer plusieurs backend en contrôlant
la répartition des requêtes sur chacun d'eux. Il est aussi capable de détecter
une panne sur un backend, et de prendre en compte cet évènement pour modifier
son comportement: utiliser un autre backend, ou renvoyer ces fichiers en cache.

\subsubsection{Détection lors de la requête}

Il est possible d'ajuster différents paramètres indiquant le temps d'attente
maximum toléré par Varnish lors de l'interrogation d'un backend:
\begin{verbatim}
backend www00 {
    .host = "192.0.2.6";
    .port = "80";
    .connect_timeout = 1s;
    .first_byte_timeout = 3s;
    .between_bytes_timeout = 2s;
}
\end{verbatim}
Les directives sont assez explicites. Passé ce délai, Varnish ira interroger le
backend suivant.

\subsubsection{Surveillance périodique des backends}

La méthode précédente est essentielle, mais pas suffisante en elle-même; en
effet, même si Varnish basculera sur un autre backend en cas de saturation du
premier, le traitement de la requête sera ralenti par l'expiration des délais
d'attente. D'autre part, une erreur HTTP 5xx par exemple sur un backend
n'empêchera pas Varnish de continuer à lui envoyer des requêtes.

Varnish offre la possibilité de surveiller régulièrement ses backends et les
marquer éventuellement comme «down». Pour cela, il est nécessaire de lui
indiquer comment les surveiller, avec la directive
\texttt{.probe}:
\begin{verbatim}
backend www00 {
    .host = "192.0.2.6";
    .port = "80";
    .probe = {
     .request = "GET / HTTP/1.1"
             "Host: www.example.com"
             "User-Agent: test Varnish"
             "Connection: close"
             "Accept-Encoding: text/html" ;
     .timeout = 1s;
     .interval = 5s;
     .window = 8;
     .threshold = 6;
   }
}
\end{verbatim}
On définit la requête HTTP que Varnish devra envoyer au backend, ainsi que
diverses directives. Le backend devra obligatoirement retourné un code HTTP 200,
il sera considéré comme «down» si ce n'est pas le cas.

Dans l'exemple ci-dessus, les checks sont fait sur un intervalle de 5 secondes,
et s'attend à avoir une réponse en moins d'une seconde. Les directives
\texttt{.window} et \texttt{.threshold} permettent de définir un cycle d'hystérésis:
pour que le backend soit vu comme étant «down», il faut que, sur une quantité de
8, 6 checks aient échoué. Et inversement, pour qu'il repasse «up», il faut que 6
checks sur les 8 aient réussi.

\subsubsection{Saint mode}

Dans le cas où tous les backends sont HS, il est possible de faire en sorte que
Varnish utilise sont cache pour répondre aux requêtes (sous réserve bien sûr
qu'elle y soit). Il est donc possible d'éteindre temporairement son unique
backend, et que Varnish continu à délivrer son contenu.
Au niveau de la configuration, cela se configure comme ceci:
\begin{verbatim}
sub vcl_fetch {
  if (beresp.status == 500) {
    set beresp.saintmode = 10s;
    restart;
  }
}
\end{verbatim}
Le \texttt{beresp.saintmode} spécifie de ne plus retenter d'interroger le
backend en cas d'erreur 500 avant un certain temps (ici 10 semonces). En effet,
autant laisser le backend tranquille, une erreur 500 se résolvant rarement
«magiquement» sans untervention manuelle.

\subsubsection{Backend de spare}

Il est également possible de déclarer un backend de spare, qui sera utilisé
automatiquement (et seulement) si plus aucun backend n'est disponible:
\begin{verbatim}
backend wwwspare {
    .host = "192.0.2.32";
    .port = "80";
}
sub vcl_recv {
    if (!req.backend.healthy) {
        set req.backend = wwwspare;
    }
}
\end{verbatim}

\section{Administration}

Dans la première partie de ce chapitre, un couple IP, port avait été défini pour
faire écouter une interface d'administration de Varnish. Cette interface permet
d'envoyer un certain nombre de commande d'administration au démon varnishd.
Pour s'y connecter, une authentification est également nécessaire. Par défaut
lors de l'installation du paquet Debian, le fichier \texttt{/etc/varnish/secret}
est créé contenant la clé permettant de s'authentifier. On peut se connecter en
utilisant \texttt{telnet}, mais la commande dédiée \texttt{varnishadm} est plus
adaptée:
\begin{verbatim}
# varnishadm -T localhost:6082 -S /etc/varnish/secret
200 154
-----------------------------
Varnish HTTP accelerator CLI.
-----------------------------
Type 'help' for command list.
Type 'quit' to close CLI session.
\end{verbatim}

On est alors dans un mode interactif, où l'on peut exécuter les commandes
Varnish disponibles. Il est également possible de passer une commande
directement en argument de \texttt{varnishtop}.

Par exemple, pour pouvoir vider la totalité du cache Varnish:
\begin{verbatim}
# varnishadm -T localhost:6082 -S /etc/varnish/secret purge.url ".*"
\end{verbatim}

On peut également vérifier l'état du démon, via la commande \texttt{status}:
\begin{verbatim}
# varnishadm -T localhost:6082 -S /etc/varnish/secret status
\end{verbatim}

Ainsi que d'autres opérations, comme l'arrêt et le redémarrage du processus, le
chargement de nouvelles règles VCL à chaud, etc\dots


\section{Gestion des logs}

Varnish permet de loguer de nombreuses informations, notamment très utiles pour
analyser son fonctionnement.

Par défaut, Varnish n'écrit pas ses logs dans un fichier, mais dans un segment
mémoire, ce qui permet d'augmenter grandement les performances. Quand l'espace
est plein, Varnish réécrit par dessus en repartant de l'origine, ce qui fait
que la mémoire allouée pour les logs n'augmente pas.

Plusieurs outils permettent ensuite de récupérer les lignes de log en mémoire, et
de les exploiter, soit pour les visualiser en direct, soit pour les écrire dans
un fichier de log.

L'outil \texttt{varnishtop} permet d'afficher les logs en temps réel:
\begin{verbatim}
# varnishtop
\end{verbatim}

\texttt{varnishlog} et \texttt{varnishncsa} permettent également de lire le
segment mémoire dans lequel varnish écrit ces logs, mais ils peuvent également
écrire ce contenu dans des fichiers, afin d'en conserver une trace. Ils peuvent
donc être utilisés manuellement, mais des scripts d'init sont fournis pour
permettre de les lancer au démarrage en mode démon.\\
\texttt{varnishncsa}, comme son nom l'indique, permet d'avoir les logs d'accès
HTTP au format NCSA (comme ceux générés par les serveurs web tel que Apache).

Pour les activer, il faut éditer respectivement les fichiers de configuration
\texttt{/etc/default/varnishlog} et \texttt{/etc/default/varnishncsa}:
\begin{verbatim}
VARNISHLOG_ENABLED=1
\end{verbatim}
\begin{verbatim}
VARNISHNCSA_ENABLED=1
\end{verbatim}

Puis lancer les démons:
\begin{verbatim}
# /etc/init.d/varnishlog start
# /etc/init.d/varnishncsa start
\end{verbatim}
