Configuraciones específicas / ejemplos
Contenido de la caché
Eliminar cookies/encoding/auth y cachear ficheros estáticos
Algunos de los contenidos más susceptibles de ser cacheados son los ficheros estáticos, para lo cual tenemos que asegurarnos de que el cliente no manda cookies, autenticación ni Encoding:
if (req.url ~ "\.(gif|jpg|swf|css|js|png|jpg|jpeg|gif|png|tiff|tif| pdf|zip|gz|rar|arj|svg|swf|ico|doc|ppt|pps|xls| odc|odb|odf|odg|odi|odp|ods|odt|sxc|sxd|sxi|sxw)$") { unset req.http.Cookie; unset req.http.Authorization; unset req.http.Accept-Encoding; set req.backend = nginx; return(lookup); }
Establecer un período de gracia a los datos de caché
En caso de que el backend no responda cuando tratamos de renovar en caché un objeto, podemos indicarle a Varnish que sirva objetos expirados durante un tiempo determinado (grace).
Esta circunstancia ocurre en 2 casos:
- El backend/director está caído y no responde.
- Un hilo diferente de Varnish ha realizado una petición igual al backend y todavía no ha terminado.
sub vcl_recv { set req.grace = 5m; # (...) } sub vcl_fetch { set obj.grace = 5m; # (...) }
Si queremos contemplar ambas posibilidades por separado, podemos hacerlo con la siguiente lógica VCL:
sub vcl_recv { if (req.backend.healthy) { set req.grace = 30s; } else { set req.grace = 1h; } } sub vcl_fetch { set beresp.grace = 1h; }
Evitar que los robots cacheen contenidos antiguos
sub vcl_miss { # Si el user-agent indica que es un "robot" tipo Google if (req.http.user-agent ~ ”spider”) { # No continuar con el proceso de cacheo: devolver error error 503 ”Not presently in cache”; } }
Cachear contenido específico de usuarios/sesiones
Manipulando la función que genera el hash (por defecto, la URL) de forma que se añada un determinado elemento propio y personalizado de cada usuario autenticado de la web podemos forzar a que se cachee contenido propio para ese usuario.
Por ejemplo, para cachear la página de perfil de cada usuario aprovechando las cookies, podemos:
sub vcl_hash { if( req.url ~ "^/profile$" && req.http.Cookie ) { set req.hast += req.http.cookie; } }
Podemos incluso hacerlo no sólo para /profile/, sino para todo la web:
sub vcl_hash { # Cacheamos la web para cada usuario: ojo, mucho uso de caché if (req.http.Cookie) { set req.hash += req.http.Cookie; } }
Cachear versión específica para dispositivos móviles
Podemos también manipular vcl_hash() para almacenar objetos de caché diferentes en el caso de usuarios de dispositivos móviles, por lo que tendremos cacheada tanto la versión "de escritorio" como la versión "para móviles":
sub vcl_hash { # Cacheamos la web también para Android, iPod, iPad e iPhone por separado. if (req.http.host == "www.domain.org" && req.http.User-Agent ~ ("iPhone|iPad|iPod|Android)") { set req.hash += "mobile"; } }
No cachear páginas o dominios específicas
sub recv { # Excluir paginas concretas O un dominio concreto: if (req.url ~ "(cron|upload|mailing)\.php$" || req.http.host ~ "domain2.org") { return(pass); } # Excluir paginas concretas DE un dominio concreto: if (req.http.host ~ "domain3.org") && req.url ~ "blah\.php$") { return(pass); } }
No cachear ni inspeccionar ficheros grandes
Conviene que hagamos un pipe de los ficheros especialmente grandes (vídeo, audio, etc) de forma que se transfieran "directamente" desde el backend HTTP al cliente sin que Varnish tenga que "inspeccionarlos" ni cachearlos:
# Ficheros grandes, directamente a nginx: if (req.url ~ "\.(mp3|mp4|m4a|ogg|mov|avi|wmv)$" && req.url !~ "audio/download") { set req.backend = nginx; return(pipe); } # Ficheros de audio no estáticos, "escupidos" por un .php (drupal): if (req.url ~ "audio/play" || req.url ~ "audio/download") { set req.backend = apache; return(pipe); }
Soporte para Pragma: nocache
Un servidor HTTP 1.0 podría enviar a Varnish la cabecera "Pragma: nocache", que es actualmente ignorada por Varnish. Podemos implementar soporte para esta cabecera con lógica VCL:
sub vcl_fetch { #(...) if (beresp.http.Pragma ~ "nocache") { pass; } }
Cabecera Cache-Control
Las cabeceras Cache-Control informan a las cachés cómo deben manejar el contenido al que referencian. Varnish hace uso del parámetro max-age de esta cabecera y lo utiliza para calcular el TTL de un objeto.
Al igual que en el caso de "Pragma: nocache", la cabecera "Cache-Control: nocache" es ignorada por Varnish pero es fácil implementar soporte para ella.
if( obj.http.Pragma ~ "no-cache" || obj.http.Cache-Control ~ "no-cache" || obj.http.Cache-Control ~ "private") { return(pass); }
Es importante asegurarse de que nuestro servidor envía una cabecera Cache-Control con "max-age".
Establecer un valor correcto para beresp.ttl
Antes de que Varnish ejecute vcl_fetch(), la variable beresp.ttl tiene ya un valor inicial. Varnish utilizará el primer valor que encuentre de los siguientes:
- Cabecera Cache-Control → campo "s-maxage".
- Cabecera Cache-Control → campo "max-age".
- Cabecera Expires de la respuesta.
- Valor de "default_ttl" (por defecto 120s a menos que lo modifiquemos en la configuración de arranque de Varnish).
Como el valor se establece antes de entrar en vcl_fetch(), podemos alterar Cache-Control sin afectar a beresp.ttl, y viceversa.
Hay que tener en cuenta que sólo los códigos 200, 203, 300, 301, 302, 307, 410 y 404 son cacheados por defecto. Se pueden cachear otros códigos de error poniendo beresp.ttl a un valor > 0 en vcl_fetch().
En el caso de múltiples niveles de caché, hay que tener cuidado con la cabecera "Age", ya que puede provocar que si el servidor Web manda una cabecera Cache-Control y no eliminamos "Age" en la respuesta, Varnish podría enviar una cabecera Age que exceda el max-age, haciendo que los navegadores no cacheen el contenido.
Reglas para software de terceros
Wordpress MU
Soluciona un problema de los temas de Wordpress MU con el cacheo de estáticos:
sub vcl_recv { // ... # Ubicar antes de las reglas de lookup de contenidos estáticos. if (req.http.host == "domain.org" && req.url ~ "/blogs/(.*)/wp-content/themes") { return (pass); } }
(Gracias a Evan Donovan).
Moodle
sub vcl_recv { // ... // Cachear los ficheros de temas de Moodle: if (req.url ~ "/pix/.*\.gif$") { return (lookup); } // Pero no cachear a moodle en sí mismo: if (req.http.Cookie ~ "(MoodleSession|MoodleSessionTest)") { return (pass); } if (req.http.host == "www.domain.edu" && req.url ~ "^/courses") { return (pass); } if (req.url ~ "file.php") { return (pass); } }
(Gracias a Evan Donovan y gchaix ).
Control de acceso
Bloquear acceso a /server-status/ excepto para sysadmins
sub vcl_recv { if( req.url ~ "^/server-status" && !(client.ip ~ acl_ips_sysadmins)) { error 404 "Not Found"; } }
Reescrituras y redirecciones
Reescribir subdominio como URL del dominio principal
A continuación se muestra un ejemplo que reescribe la URL blog.dominio.com como dominio.com/blog, guardando una copia de las cabeceras originales.
Nótese que el código no sólo reescribe blog.dominio.com como dominio.com/blog sino que los contenidos también se ven afectados por la reescritura (blog.dominio.com/pagina.php se reescribe como dominio.com/pagina.php):
sub vcl_recv { # Hacemos una copia de las cabeceras originales set req.http.x-host = req.http.host; set req.http.x-url = req.url; # Normalizamos www.dominio a dominio: set req.http.host = regsub(req.http.host, "^www\.", ""); # Reescribimos subdominio como /subdominio: # Opcion 1, por asignacion y sustitucion: if (req.http.host == "blog.dominio.com") { set req.http.host = "dominio.com"; set req.url = regsub(req.url, "^", "/blog"); } # Opcion 2: con sustituciones de regexps (matchea mas subdominios): #if (req.http.host ~ "^blog\.") #{ # set req.http.host = regsub(req.http.host,"^blog\.", ""); # set req.url = regsub(req.url, "^", "/blog"); #} }
Realizar redirecciones en vcl_error
Es posible redirigir a los usuarios (realizar reescritura de direcciones permanentes) usando vcl_error(), devolviendo al usuario final un error 301 (Moved permanently):
sub vcl_recv { if (req.http.host == "www.example.com") { set req.http.Location = "http://example.com" + req.url; error 750 "Permanently moved"; } } sub vcl_error { if (obj.status == 750) { set obj.http.location = req.http.Location; set obj.status = 301; return (deliver); } }
Realizar un redirect sin que lo realice el cliente HTTP
Podemos utilizar return(restart) para realizar un redirect sin tener que esperar a que la petición la realice de nuevo el cliente:
sub vcl_fetch { # Con req.restarts == 0 evitamos bucle infinito: if (req.restarts == 0 && req.request == "GET" && beresp.status == 301) { set beresp.http.location = regsub(beresp.http.location,"^http://",""); set req.http.host = regsub(beresp.http.location,"/.*$",""); set req.url = regsub(beresp.http.location,"[^/]*",""); return (restart); } }
Logs, estadísticas, errores y troubleshooting
Utilizar curl para estudiar las cabeceras devueltas
Podemos utilizar curl con el flag -I para estudiar las cabeceras que devuelve una petición HTTP. La sintaxis básica es:
# curl -I -H "Host: host-header" URL
Por ejemplo:
$ curl -I -H "Host: www.sromero.org" http://www.sromero.org/wiki/ HTTP/1.1 200 OK Date: Wed, 02 May 2012 12:37:25 GMT Server: Apache/2.2.21 (Unix) mod_ssl/2.2.21 OpenSSL/0.9.8e-fips-rhel5 mod_auth_passthrough/2.1 mod_bwlimited/1.4 FrontPage/5.0.2.2635 Expires: Thu, 19 Nov 1981 08:52:00 GMT Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0 Pragma: no-cache Set-Cookie: DokuWiki=95138d04c6fc70674f21704a0d63f00d; path=/wiki/; HttpOnly Set-Cookie: DWcb100c0f811c1b6d691e3e7c406b56ba=deleted; expires=Tue, 03-May-2011 12:37:25 GMT; path=/wiki/; httponly Vary: Accept-Encoding,User-Agent Content-Type: text/html; charset=utf-8
Es importante destacar que también podemos forzar un User-Agent concreto con el flag -A (por ejemplo si queremos acceder a versiones "mobile" de las webs). Por ejemplo, para aparentar que accedemos desde un iPad:
# Partido para facilitar la lectura, pegar en una sola linea: $ curl -I -H "Host: www.sromero.org" http://www.sromero.org -A "Mozilla/5.0(iPad; U; iPhone OS 3_2 like Mac OS X; en-us)<espacio> AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4<espacio> Mobile/7B314 Safari/531.21.10"
Atacar a un servidor Varnish con curl tiene la ventaja de que podremos ver las cabeceras que establece Varnish (X-Cache-*):
$ curl -I -H "Host: www.wikipedia.org" http://www.wikipedia.org HTTP/1.0 200 OK Date: Wed, 02 May 2012 12:41:33 GMT Server: Apache X-Content-Type-Options: nosniff Cache-Control: s-maxage=3600, must-revalidate, max-age=0 Last-Modified: Sat, 14 Apr 2012 00:41:17 GMT Vary: Accept-Encoding Content-Length: 48305 Content-Type: text/html; charset=utf-8 X-Cache: MISS from sq63.wikimedia.org X-Cache-Lookup: HIT from sq63.wikimedia.org:3128 X-Cache: MISS from amssq38.esams.wikimedia.org X-Cache-Lookup: HIT from amssq38.esams.wikimedia.org:3128 Age: 4 X-Cache: HIT from amssq38.esams.wikimedia.org X-Cache-Lookup: HIT from amssq38.esams.wikimedia.org:80 Connection: close
En la documentación oficial de Varnish utilizan, en lugar de curl, la herramienta GET de libwww-perl:
# yum/apt-get install libwww-perl # GET -H 'Host: www.dominio.com' -Used http://www.dominio.com/
Información sobre la vida de los objetos en Varnish
Varnish añade a las respuestas HTTP una cabecera llamada Age que indica cuánto tiempo ha mantenido el objeto. Podemos estudiar estas cabeceras con Varnishlog mediante:
varnishlog -i TxHeader -I ^Age
Definir el tiempo de cacheo para hit_for_pass
Los objetos de tipo "hit_for_pass" se cachearán durante el TTL por defecto (120s) de Varnish. Si queremos alterar esta lógica, podemos hacerlo en vcl_fetch():
sub vcl_fetch { if (!obj.cacheable) { # Limitar el tiempo de vida de los hit_for_pass a 30 segundos obj.ttl = 30s; return(hit_for_pass); } }
Personalizar la salida de error de Varnish
En caso de error, Varnish utiliza para presentar la página de error al cliente HTTP su función vcl_error() por defecto, que es la siguiente:
sub vcl_error { set obj.http.Content-Type = "text/html; charset=utf-8"; 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> <p>"} obj.response {"</p> <h3>Guru Meditation:</h3> <p>XID: "} req.xid {"</p> <address><a href="http://www.varnish-cache.org/">Varnish</a></address> </body> </html> "}; return(deliver); }
Podemos personalizar la salida de error en función del código con "markups" personalizados para 404, 500, etc:
sub vcl_error { set obj.http.Content-Type = "text/html; charset=utf-8"; if (obj.status == 404) { synthetic {" <!-- Markup for the 404 page goes here --> "}; } else if (obj.status == 500) { synthetic {" <!-- Markup for the 500 page goes here --> "}; } else { synthetic {" <!-- Markup for a generic error page goes here --> "}; } }
Añadir cabeceras de HIT y MISS
Podemos modificar la función vcl_deliver() para añadir múltiples cabeceras informativas, como por ejemplo indicar si se ha producido un HIT o un MISS en la caché:
sub vcl_deliver { # Eliminamos cabecera Age pero guardamos una copia: set resp.http.X-Age = resp.http.Age; unset resp.http.Age; if (obj.hits > 0) { set resp.http.X-Cache = "HIT"; } else { set resp.http.X-Cache = "MISS"; } }