Caching reverse-proxy HTTP avec Varnish Cache

Varnish Cache est un reverse-proxy HTTP qui a pour fonction principale d'accélérer le rendu des contenus Web. Il est typiquement :

  • placé en frontal d'un serveur HTTP
  • configuré pour la mise en cache de contenus Web (document HTML ou PDF, ressources Javascript, CSS ou images ... etc).

Son mode de configuration s'appuie sur un langage de script, appelé VCL (Varnish Configuration Language). Ci-dessous, un script VCL classique, avec mise en cache des ressources de type image (JPEG, PNG et GIF) :

# $hika  
# Last-Modified: 2012-12-03  
#  
# VCL configuration file for varnish. See the vcl(7)  
# man page for details on VCL syntax and semantics.  
#   
# Default backend definition.  Set this to point to your content  
# server.  
#   
 backend default {  
     .host = "127.0.0.1";  
     .port = "8080";  
     .max_connections = 250;  
 }  

 backend lighttpd {  
     .host = "127.0.0.1";  
     .port = "8001";  
     .max_connections = 100;  
 }  

 acl purge {  
     "localhost";  
 }  

#   
# Below is a commented-out copy of the default VCL logic.  If you  
# redefine any of these subroutines, the built-in logic will be  
# appended to your code.  
 sub vcl_recv {  
     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;  
#        }  

         set req.http.X-ClientIP-From-Varnish = client.ip;  
     }  
     if (req.request == "PURGE")  
     {  
         if (!client.ip ~ purge)  
         {  
             error 405 "Not allowed !";  
         }  
         return (lookup);  
     }  

     if (req.request != "GET" &&  
       req.request != "HEAD" &&  
       req.request != "PUT" &&  
       req.request != "POST" &&  
       req.request != "TRACE" &&  
       req.request != "OPTIONS" &&  
       req.request != "DELETE")  
     {  
         # Non-RFC2616 or CONNECT which is weird.  
         return (pipe);  
     }  

#    if (req.url ~ "\.(jpeg|jpg|png|gif)$")  
#    {  
         # Remove the cookie and make the request static  
#        unset req.http.cookie;  
#        return (lookup);  
#    }  

     if (req.request != "GET" && req.request != "HEAD")  
     {  
         # We only deal with GET and HEAD by default  
         return (pass);  
     }  
#    if (req.http.Authorization || req.http.Cookie)  
#    {  
#        # Not cacheable by default  
#        return (pass);  
#    }  

     if (req.http.host == "video.mondomain.fr"  
     && req.url ~ "^/video/")  
     {  
         set req.url = regsub(req.url, "^/video/(.+)", "\1");  
         set req.backend = lighttpd;  
     }  

     # Default, lookup on cache.  
     return (lookup);  
 }  
#   
# sub vcl_pipe {  
#    # Note that only the first request to the backend will have  
#    # X-Forwarded-For set.  If you use X-Forwarded-For and want to  
#    # have it set for all requests, make sure to have:  
#    # set bereq.http.connection = "close";  
#    # here.  It is not set by default as it might break some broken web  
#    # applications, like IIS with NTLM authentication.  
#    return (pipe);  
# }  
#   
# sub vcl_pass {  
#    return (pass);  
# }  
#   
# sub vcl_hash {  
#    hash_data(req.url);  
#    if (req.http.host) {  
#        hash_data(req.http.host);  
#    } else {  
#        hash_data(server.ip);  
#    }  
#    return (hash);  
# }  
#   
 sub vcl_hit {  
     if (req.request == "PURGE")  
     {  
         purge;  
         error 200 "Purged.";  
     }  
     return (deliver);  
 }  

 sub vcl_miss {  
     if (req.request == "PURGE")  
     {  
         purge;  
         error 404 "Not purged (Cache missed).";  
     }  
     return (fetch);  
 }  

 sub vcl_fetch {  
     if (req.http.cache-control ~ "(no-cache|private)"  
     || req.http.pragma == "no-cache"  
     || beresp.status >= 300)  
     {  
         # Do not use cache  
         set beresp.ttl = 0s;  
     }  
     # If the request is static  
#    elseif (req.url ~ "\.(jpeg|jpg|png|gif)$")  
#    {  
#        # Cache it, and make it last 5 minutes  
#        set beresp.ttl = 300s;  
#        # Make the request static by removing any cookies set by those static files  
#        unset beresp.http.Set-Cookie;  
#    }  
     else  
     {  
         # Default, no cache  
         set beresp.ttl = 0s;  
     }  

     return (deliver);  
 }  

 sub vcl_deliver {  
     # Display hit/miss info  
     if (obj.hits > 0)  
     {  
         set resp.http.X-Cache = "HIT";  
         set resp.http.X-Cache-Hits = obj.hits;  
     }  
     else  
     {  
#        set resp.http.X-Cache = "MISS";  
         # No need to display "Age: 0"  
         unset resp.http.Age;  
     }  

     # Hide some informations  
     unset resp.http.Via;  
     unset resp.http.X-Varnish;  
     unset resp.http.X-Powered-By;  

     return (deliver);  
 }  

 sub vcl_error {  
     set obj.http.Content-Type = "text/html; charset=utf-8";  
     set obj.http.Retry-After = "5";  
     unset obj.http.Server;  
     unset obj.http.X-Cache;  

     synthetic {"  
 <?xml version="1.0" encoding="utf-8"?>  
 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"  
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">  
 <html>  
   <head>  
     <title>"} + obj.status + " " + obj.response + {"</title>  
   </head>  
   <body>  
     <h1>Error "} + obj.status + " " + obj.response + {"</h1>  
     <hr>  
   </body>  
 </html>  
 "};  
     return (deliver);  
 }  
#   
# sub vcl_init {  
#   return (ok);  
# }  
#   
# sub vcl_fini {  
#   return (ok);  
# }  

Il est relativement flexible dans l'élaboration, l'évolution et la maintenance de ses propres règles. Vous trouverez, ci-dessous, une méthode pour la prise en compte des modifications du script VCL actif, sans avoir à redémarrer le service :

#!/bin/sh  
# Reload a varnish config  

CONFIG_FILE="/usr/local/etc/varnish.vcl"  
VARNISH_ADM="localhost:6082"  
CONFIG1_NAME="vcl1"  
CONFIG2_NAME="vcl2"  

_S=`basename $0`  

CURRCONFIG_NAME=`varnishadm -t 1 -T $VARNISH_ADM vcl.list 2>/dev/null | grep active | awk '{ print $3 }'`  

if [ ! -z $CURRCONFIG_NAME ]; then  

    echo "$_S : Current config ($CURRCONFIG_NAME)."  

    if [ $CURRCONFIG_NAME = $CONFIG1_NAME ]; then  
    # Current configuration $CONFIG1_NAME in use.  
    # Load a new one  
        varnishadm -t 1 -T $VARNISH_ADM vcl.load $CONFIG2_NAME $CONFIG_FILE >/dev/null 2>&1  

        if [ $? -eq 0 ]; then  
            varnishadm -t 1 -T $VARNISH_ADM vcl.use $CONFIG2_NAME >/dev/null 2>&1  
            echo "$_S : Varnishd reloaded ($CONFIG2_NAME)."  
            varnishadm -t 1 -T $VARNISH_ADM vcl.discard $CONFIG1_NAME >/dev/null 2>&1  
        else  
            echo "$_S : Cannot load $CONFIG_FILE."  
        fi  

    elif [ $CURRCONFIG_NAME = $CONFIG2_NAME ] || [ $CURRCONFIG_NAME = "boot" ]; then  
    # Current configuration $CONFIG2_NAME (or "boot") in use.  
    # Load $CONFIG1_NAME configuration.  
        varnishadm -t 1 -T $VARNISH_ADM vcl.load $CONFIG1_NAME $CONFIG_FILE >/dev/null 2>&1  

        if [ $? -eq 0 ]; then  
            varnishadm -t 1 -T $VARNISH_ADM vcl.use $CONFIG1_NAME >/dev/null 2>&1  
            echo "$_S : Varnishd reloaded ($CONFIG1_NAME)."  
            if [ $CURRCONFIG_NAME = $CONFIG2_NAME ]; then  
                varnishadm -t 1 -T $VARNISH_ADM vcl.discard $CONFIG2_NAME >/dev/null 2>&1  
            fi  
        else  
            echo "$_S : Cannot load $CONFIG_FILE."  
        fi  
    fi  
fi  

Autre fonctionnalité particulièrement appréciable : Varnish Cache n'est pas vulnérable aux attaques de type "slowloris". Ce qui le rend intéressant pour "protéger" un serveur Apache HTTPD.

Plus d'informations sur le site officiel.

Ajouter un commentaire

  • Votre courriel ne sera jamais publié