Configuración básica de Varnish
Varnish es un proxy cache que acelera drásticamente la entrega de contenido a nuestros visitantes entre otras funciones. Además permite balancear carga entre varios backends y comprobar la salud de estos de manera que sólo se envíe peticiones aquellos que están en condiciones de procesar las peticiones. En esencia consiste en un almacenamiento clave-valor. Cada vez que recibe una petición comprueba calcula la clave asociada a esa url, comprueba si esa clave esta en memoria y es válida y entrega el contenido. En caso contrario lo pide al backend.
La información necesaria sobre el proceso de instalación y configuración inicial está disponible en la web del proyecto.
La versión que voy a usar para este post es la 4.1.
Que hace Varnish
Varnish trabaja sobre el protocolo http. Entiende host (carloscompains.com, api.carloscompains.com…), metodos (GET, PUT, POST, DELETE….), rutas (/v1/test, v1/test/123) y cabeceras (Vary: Cookie,Accept-Encoding, Expires: Sun, 25 Dec 2017 00:00:00 GMT). Es decir trabaja sobre la capa 7 del modelo OSI.
Varnish por defecto respetará las cabeceras que nuestra aplicación le entregue a no ser que especifiquemos una regla que las altere. Esto quiere decir que si programamos nuestra aplicación teniendo en cuenta los aspecto referentes a la cache, gran parte del trabajo ya estará hecho y necesitaremos poca o ninguna configuración en Varnish.
Cada vez que Varnish recive una petición, la analiza, ve si tiene la respuesta correspondiente en memoria, comprueba que esa respuesta no está caducada y la entrega si es así. Esto tienen un impacto directo en los tiempos de carga, ya que ahorra al backend tener que calcular esa respuesta reduciendo los tiempos de carga significactivamente.
Podemos cachear respuestas Html, Json, Xml… cualquiera de los formatos habituales de respuesta de una aplicación web. Además podemos cachear diferentes respuestas para una misma url usando la cabecera Vary.
Un ejemplo de uso de Varnish.
Entrega de contenidos según dispositivo
Este blog entrega diferentes versiones de la misma imagen dependiendo del disposivo que los solicita.
La ruta de una imagen puede ser: http://carloscompains.com/cache/xl/4.jpg
Cuando varnish reciba la respuesta analizará el User-agent determinando si es un móvil, tablet o equipo de escritorio el que se está conectando. Una vez conocido el tipo de disposivo fijará una cabecera (X-UA-Device) indicándolo.
El backend analizará esa cabecera y determinará en base a la misma y a la url (en concreto al segmento “xl” de la url, en este caso) el tamaño de imagen a entregar fijando una cabecera Vary que le indica a Varnish que ha de cachear distintas versiones del recurso segun el valor que tenga esa cabecera. De esta manera Varnish tendrá en memoria tres versiones del mismo recurso para una url dada y las entregará en función del tipo de dispositivo.
El fichero vcl. Configurar el comportamiento de Varnish
Varnish utiliza los ficheros vcl (Varnish configuration language) para definir el comportamiento que ha de tener. Este fichero esta dividido en varias funciones, cada una de las cuales se dispara en diferentes momentos.
Además hay un primer bloque en el cual se configura los diferentes backends a los que Varnish ha de conectarse y se defininen variables que luego podemos usar en el resto del fichero como por ejemplo Acl’s (Access control list) para decidir quien tiene permiso para determindas acciones.
En el siguiente diagrama se puede ver como se relacionan los distintos bloques del fichero, así como algunas de las variables disponibles en cada uno de ellos.
Configurar un backend
Es necesario configurar cada uno de los servidores a los que Varnish se va a conectar especificando ciertos valores como la dirección IP o el puerto entre otros. Opcionalmente además se establece una url a la que Varnish enviará peticiones para ver el estado de salud de ese backend para decidir si sigue enviandole tráfico o no.
backend server1 { # Define un backend
.host = "blog_app"; # IP or Hostname del backend
.port = "80"; # Puerto al que conectarse
.max_connections = 300; # Máximo de conexiones simultaneas .probe = { #Prueba de salud del backend
# Petición http. Si usamos un fichero estático en vez de una url de nuestra aplicación evitaremos que las pruebas de sobrecargen la misam.
.request =
"GET /probe.html HTTP/1.1"
"Host: blog_app"
"Connection: close"
"User-Agent: Varnish Health Probe"; .interval = 5s; # Prueba la salud de cada backend cada 5 segundos
.timeout = 5s; # Si en 5 segundos no ha contestado lo considera como un timeout
.window = 5; # Si 1 de las últimas 5 peticiones es correcta se considera que el backend esta saludable. Si no se marca como enfermo y no se le envia tráfico
.threshold = 1;
} .first_byte_timeout = 300s; # Cuanto tiempo hay que esperar como máximo para recibir el primer byte desde el backend
.connect_timeout = 5s; # Cuanto tiempo hay que esperar para establecer la conexión con un backend.
.between_bytes_timeout = 2s; # Cuanto tiempo hay que esperar entre bytes recibidos desde el backend.
}
Estos valores son un poco excesivos, pero sirven para asegurarnos de que Varnish no va a considerar el backend enfermo si el servidor tarda en responder lo cual es útil en entornos de desarrollo o si determinadas peticiones a nuestra aplicación son lentas.
Conectando con los backends
Varnish nos permite distribuir el tráfico entre diferentes backends, lo cual nos da una mejor toleracia a fallos y permite balancear carga entre varios backends. Disponer de varios backends ademas nos permite mejorar los procesos de despliegue al poder ir conectando y desconectando backends y actualizandolos de manera secuencial, eliminando tiempos de caida.
Para agrupar los backends Varnish utiliza la figura de los directors. Un director es un grupo de backends (ese grupo puede contener un solo backend) sobre los que se define una política de distribución de trafico. En la versión 4 de Varnish hay dos opciones round_robin y random. La primera ira alternando las peticiones de manera secuencial entre los diferentes backends y la segunda lo hará de manera aleatoria.
Ademas el director tendrá en cuenta los resultados de los health checks realizados contra las urls o con las peticiones definidas en la variable .probe de cada backend, de manera que si un backend está marcado como enfermo, no le enviará peticiones hasta que vuelva a estar saludable.
No es obligatorio tener mas de un backend para definir un director.
sub vcl_init {
new vdir = directors.round_robin();
vdir.add_backend(server1);
# vdir.add_backend(server...);
}
La inicialización de los directors se hace en la función vcl_init.
Otras variables a definir
Es util definir Acl’s (Access control list) para decidir quien puede por ejemplo invalidar y forzar a Varnish a pedir ese contenido al backend de nuevo. En este caso creamos una Acl con diferentes direcciones IP de manera que cuando Varnish reciba una petición del tipo PURGE o BAN comprobara si la ip desde el que se pide está en esta lista y en caso afirmativo ejectutará la invalidación.
acl purge {
"127.0.0.1";
"::1";
}
Configurando el comportamiento de Varnish
Petición recibida vcl_recv()
Cuando Varnish reciba una petición en primer lugar ejecutará el método vcl_recv(). En esta función tendremos disponibles el objeto req, el cual representa la petición recibida por Varnish. Se puede leer y escribir (Más información).
En esta función se suelen hacer varias cosas que se consideran estandar:
- Decidir a que backend se envía el tráfico. Si tenemos varios dominios, podemos discriminar en función del valor de req.http.host y fijar uno u otro director mediante set req.backend_hint = vdir.backend();
- Sanear la petición. Eliminar cookies, devolver un error en caso de metodos no estandar (GET, HEAD, PUT, POST, TRACE, OPTIONS, PATCH, DELETE).
- Establecer reglas generales de cacheado, como por ejemplo que solo se cacheen los metodos GET y HEAD (han de ser idempotentes). No se debe cachear el resto de metodos ya que si se respeta el protocolo http el resto de métodos cambiarán el estado de la aplicación. Si se cacheasen ese cambio no se llegaría a realizar ya que Varnish devolvería la respuesta cacheada sin llegar a impactar nuestra petición en el backend y por lo tanto sin generar los cambios deseados.
- Anunciar al backend del soporte para fragmentos ESI
- Gestionar acciones de PURGE y BAN
Esta es la función vcl_recv que tal y como se muestra en la documentación de Varnish con algunas personalizaciones para este blog.
sub vcl_recv { call devicedetect; # script que fija una cabecera segun el tipo de dispositivo que hace la petición # Urls que no se han de cachear
if(
req.url ~ "panel" # Zona de administración
|| req.url ~ "login" # Formulario de login
|| req.url ~ "login_check" # Comprobación de credenciales
|| req.url ~ "logout" # Fin de la sesion
|| req.url ~ "admin" #Zona de administración
|| req.url ~ "_profiler" # Profiler de Symfony cuando está disponible en entornos de desarrollo
){
return (pass);
}else{
unset req.http.cookie;
} # Como solo hay un backend se le envía todo el tráfico
set req.backend_hint = vdir.backend(); # Saneado de la cabecera. Se elimina el puerto en caso de que estemos testeando con diferentes puertos TCP
set req.http.Host = regsub(req.http.Host, ":+", ""); # Se elimina la cabecera Proxy (see https://httpoxy.org/#mitigate-varnish)
unset req.http.proxy; # Eliminamos las cabeceras que envía el navegador para que sea Varnish quien decida cuando pedir contenido fresco al backend
unset req.http.cachecontrol;
unset req.http.pragma; # Se normalizan los valores del querystring
set req.url = std.querysort(req.url); # Permitir purgar urls
if (req.method == "PURGE") {
if (!client.ip ~ purge) { # Se comprueba la ip en la lista que hemos definido al principio
# Si no está permitido se envía un error
return (synth(405, "This IP is not allowed to send PURGE requests."));
}
# Ejecutar el purgado
return (purge);
}
#Permitir la ejecución de BAN
if(req.method == "BAN"){
if (!client.ip ~ purge) { # Se comprueba la ip en la lista que hemos definido al principio
# Si no está permitido se envía un error
return (synth(405, "This IP is not allowed to send BAN requests."));
}
# Invalidación por tag
if (req.http.X-Cache-Tags) {
# Se invalidan todos los objetos en memoria que tengaun una cabecera X-Cache-Tags que contengan el varlor recibido en la petición.
# Por ejemplo si se actualiza un post del blog con id 12 se invalida todos los objetos en memoria que contengan en su cabecera X-Cache-Tags el valor "Content12"
ban("obj.http.X-Cache-Tags ~ " + req.http.X-Cache-Tags); return (synth(200, "Banned by tag " + req.http.X-Cache-Tags));
}
} # Solo se manejan los verbos estandar de http. Los no estandar como BAN y PURGE ya se han gestionado antes
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") {
return(synth(404, "Non-valid HTTP method!"));
} # Soporte para websockets (https://www.varnish-cache.org/docs/4.0/users-guide/vcl-example-websockets.html)
if (req.http.Upgrade ~ "(?i)websocket") {
return (pipe);
} # Solo se cachean peticiones GET y HEAD el resto impactarán nuestra aplicación
if (req.method != "GET" && req.method != "HEAD") {
return (pass);
} # Saneado de la petición
# Se eliminan parametros propios de Google Analytics que no son útiles para el backedn
if (req.url ~ "(\?|&)(utm_source|utm_medium|utm_campaign|utm_content|gclid|cx|ie|cof|siteurl)=") {
set req.url = regsuball(req.url, "&(utm_source|utm_medium|utm_campaign|utm_content|gclid|cx|ie|cof|siteurl)=(+)", "");
set req.url = regsuball(req.url, "\?(utm_source|utm_medium|utm_campaign|utm_content|gclid|cx|ie|cof|siteurl)=(+)", "?");
set req.url = regsub(req.url, "\?&", "?");
set req.url = regsub(req.url, "\?$", "");
} # Se elimina el segmento en caso de que lo haya.
if (req.url ~ "\#") {
set req.url = regsub(req.url, "\#.*$", "");
} # Se elimina el ? al final de la url
if (req.url ~ "\?$") {
set req.url = regsub(req.url, "\?$", "");
} # Limpieza de cookies
# Se elimina la cookie "has_js"
set req.http.Cookie = regsuball(req.http.Cookie, "has_js=+(; )?", ""); # Se quitan las cookies de Google
set req.http.Cookie = regsuball(req.http.Cookie, "__utm.=+(; )?", "");
set req.http.Cookie = regsuball(req.http.Cookie, "_ga=+(; )?", "");
set req.http.Cookie = regsuball(req.http.Cookie, "_gat=+(; )?", "");
set req.http.Cookie = regsuball(req.http.Cookie, "utmctr=+(; )?", "");
set req.http.Cookie = regsuball(req.http.Cookie, "utmcmd.=+(; )?", "");
set req.http.Cookie = regsuball(req.http.Cookie, "utmccn.=+(; )?", ""); # Se quitan las cookies de DoubleClick
set req.http.Cookie = regsuball(req.http.Cookie, "__gads=+(; )?", ""); # Se eliminan las cookies de Quant Capital (las añaden algunos plugins, todas las __qca)
set req.http.Cookie = regsuball(req.http.Cookie, "__qc.=+(; )?", ""); # Se eliminan las cookies de AddThis
set req.http.Cookie = regsuball(req.http.Cookie, "__atuv.=+(; )?", ""); # Se elimina el prefijo ";" de la cookie si existe
set req.http.Cookie = regsuball(req.http.Cookie, "^;\s*", ""); # Quedan cookies vacias o que solo contienen espacios?
if (req.http.cookie ~ "^\s*$") {
unset req.http.cookie;
} # Los ficheros estáticos grandes se entregan directamente al visitante sin esperar a que Varnish lea el fichero primero
# Varnish 4 soporta Streaming así que activaremos "do_stream" en la función vcl_backend_response()
if (req.url ~ "^*\.(7z|avi|bz2|flac|flv|gz|mka|mkv|mov|mp3|mp4|mpeg|mpg|ogg|ogm|opus|rar|tar|tgz|tbz|txz|wav|webm|xz|zip)(\?.*)?$") {
unset req.http.Cookie;
return (hash);
} # Se eliminan las cookies de los ficheros estáticos
# Solo es necesario cachear los ficheros estáticos si tienes memoria de sobra. Los ficherso estáticos no generan carga en la aplicaión.
# Hay muchas probabilidades de que el própio sistema operativo tenga estos ficheros en memoria.
# Antes de habilitar esto es recomendable que leas esto: https://ma.ttias.be/stop-caching-static-files/
if (req.url ~ "^*\.(7z|avi|bmp|bz2|css|csv|doc|docx|eot|flac|flv|gif|gz|ico|jpeg|jpg|js|less|mka|mkv|mov|mp3|mp4|mpeg|mpg|odt|otf|ogg|ogm|opus|pdf|png|ppt|pptx|rar|rtf|svg|svgz|swf|tar|tbz|tgz|ttf|txt|txz|wav|webm|webp|woff|woff2|xls|xlsx|xml|xz|zip)(\?.*)?$") {
unset req.http.Cookie;
return (hash);
} # Enviar la cabecera Surrogate-Capability para anunciar al backend el soporte para ESI
set req.http.Surrogate-Capability = "key=ESI/1.0"; # Si hay una cabecera Authorization (un usuario se está autentificando) no se cachea.
if (req.http.Authorization) {
return (pass);
} return (hash);
}
Donde guardar el objeto cuando se cree vcl_hash()
La función vcl_hash() se encarga de crear una clave que permitirá recuperar el contenido una vez guardado en memoria. Además esa clave será la que Varnish utilize para comprobar si ese objeto (el objeto contiene la respuesta que se va a entregar al cliente, puede venir desde el backend o desde la memoria de Varnish) ya está cacheado.
Por defecto Varnish usará la url de la petición, el host (lo extrae de la petición) o la dirección ip del servidor (lo extrae del objeto server). Tal y como dice la documentación Varnish no pasa a minúsculas ni el nombre del host ni la url, es decir que carloscompains.com/Varnish y carloscompains.com/varnish serian urls distintas para Varnish y se cachearian por separado.
Es preferible evitar alterar esta función. Cuando necesitamo diferentes versiones del mismo recurso es preferible usar la cabecera Vary.
El recurso está en la cache vcl_hit()
Despues de obtener la clave asociada a la petición, Varnish comprobará si el objeto está en memoria y si lo encuentra llamará a esta función. En esta función tendremos disponible el objeto req que ya hemos visto antes, en modo lectura y escritura y el objeto obj que representa el recurso en memoria que aquí es de solo lectura.
Esta es la función que aparece en la documentación de Varnish.
sub vcl_hit {
if (obj.ttl >= 0s) {
# Es un hit valido. Se entrega
return (deliver);
} # https://www.varnish-cache.org/docs/trunk/users-guide/vcl-grace.html
# Cuando varios clientes estan pidendo la misma página, Varnish enviará una petición al backend an pondra el resto en pausa mientras obtiene una respuesta desde el backend. Esto se llama a veces coalescencia de solicitudes y Varnish lo hace de manera autmática.
# Si estás sirviendo miles de hits por segundo la cola de espera se puede hacer muy grande. Hay dos problemas potenciales. Por un lado cuando el backend conteste, si de repente se generan miles de hilos para entregar la respuesta a todas las peticiones que estan en la cola de espera, la carga se puede disparar. El segundo problema es que a nadie le gusta esperar.
# Para paliar esto se le puede decir a Varnish que mantenga los objetos en cache mas allá de su tiempo de vida y servir contenido no actualizado, pero por lo menos dar una respuesta
# if (!std.healthy(req.backend_hint) && (obj.ttl + obj.grace > 0s)) {
# return (deliver);
# } else {
# return (fetch);
# } # No hay datos frescos, buscamos en la cache a ver si hay algo que poder enviar
if (std.healthy(req.backend_hint)) {
# Backend is healthy. Limit age to 10s.
if (obj.ttl + 10s > 0s) {
#set req.http.grace = "normal(limited)";
return (deliver);
} else {
# No hay ningun candidato para el periodo de gracia. Pedimos datos al backend
return(fetch);
}
} else {
# El backend está caido, usamos el periodo de gracia para servir datos no actualizados
if (obj.ttl + obj.grace > 0s) {
#set req.http.grace = "full";
return (deliver);
} else {
# No hay objetso en periodo de gracia. Preguntamos al back.
return (fetch);
}
} # Recuperar y enviar.
return (fetch); # Codigo muerto. La ejecución no debería llegar aqui. Se mantiene por segurida.
}
El recurso no está en la cache vcl_miss()
Si el recurso no está disponible en la cache de Varnish se invoca al método vcl_miss() En esta función tendremos disponibles los objetos req y bereq en modo lectura/escritura. El objeto bereq representa la petición que Varnish va a hacer al backed. Aqui se puede personalizar si es necesario.
En general esta función no se suele alterar.
sub vcl_miss {
# Se llama despues de una búsqueda en la cache si no se encontro el documento. En esta función se
# decide si pedir o no el recurso al backen y a cual hacerlo.
return (fetch);
}
Invalidar un objeto en la cache vcl_purge()
Es bastante probable que en un momento dado queramos eliminar un objeto de la cache de Varnish para forzarlo a pedir una versión actualizada al backend. Esto se puede hacer con BAN o con PURGE.
Con BAN podemos enviar un patron e invalidar varios objetos a la vez (por ejemplo tal y como se ha hecho antes, todos aquellos que contengan un string determinado en una cabecera determinada).
Con PURGE, hay que enviar una url, y Varnish purgará el contenido asociado.
Cabe recordar que ya hemos comprobado en la función vcl_recv() si el host que está solicitando el PURGE esta en la acl definida y por lo tanto puede o no puede hacer un PURGE.
Esta función se invoca cuando en alguna otra ejecutamos el código return (purge). Varnish realiará el purgado y después llamara a vcl_purge
sub vcl_purge {
# Se cambia el método de la petición y se reinicia desde vcl_recv() con el método de la petición cambiado.
# De esta manera inmediatamente despues de eliminar un objeto de memoria lo tendremos disponible.
# Si abusamos de e
set req.method = "GET";
return (restart);
}
Uno de los parámetros que se le pasa a Varnish al iniciarlo es max_restarts, este parámetro está para evitar bucles infinitos cuando reiniciamos una petición. Si por algun motivo, lanzamos una invalidación que ejecuta un restart y el backend al recibir la petición, vuelve a generar un PURGE, Varnish como máximo reiniciará la petición max_restarts veces y despues lanzará un Guru Meditation error para evitar un bucle infinito.
Cuando Varnish da un paso atras vcp_pipe()
Cuando Varnish entra en modo pipe, se limita a recibir datos del cliente, enviarlos al backend y enviar la respuesta del mismo al cliente, sin hacer nada con ellos. Pasa a ser un proxy TCP sin mas funciones. En general se usa cuando la petición es rara o no responde al estandar HTTP o se detecta alguna cabecera o metodo extraño, o la petición es para un websocket.
sub vcl_pipe {
# Este método se llamna al entrar en modo pipe.
# Solo la primera petición al backend tendrá la cabecera X-Forwarded-For fijada.
# Si necesitas esa cabecera en todas las peticiones asegurate de tener la línea
# set bereq.http.Connection = "Close";
# descomentada.
# No está activada por defecto puesto que puede romper algunas aplicaciones web como IIS con autentificacion NTLM # set bereq.http.Connection = "Close"; # Soporte para websocket (https://www.varnish-cache.org/docs/4.0/users-guide/vcl-example-websockets.html)
if (req.http.upgrade) {
set bereq.http.upgrade = req.http.upgrade;
} return (pipe);
}
Algunas urls no se han de cachear vcl_pass()
Cuando desde alguna de las subrutinas ejecutamos return (pass) le estamos diciendo a Varnish que simplemente le pregunte al backend por los datos, se los de al cliente y no guarde nada.
Normalmente no haremos nada en este método.
Preparando la petición para el backend vcl_backend_fetch()
Cuando Varnish ha determinado que necesita pedirle datos al backend, preparará la petición de la siguiente manera:
- Fijará el método a GET a no ser que la petición venga de un pass en cuyo caso mantendrá el método de la petición original
- Fijara la cabecera Accept_Encoding a gzip si http_gzip_support(parámetro de configuración que se pasa a Varnish al iniciarlo) está habilitada a no ser que la petición venga de un pass
- Si hay un objeto en cache que necesita ser revalidado, se fijará bereq.http.If-Modified-Since desde su cabecera Last-Modified y/o se fija bereq.http.If-None-Match desde la cabecera Etag del objeto en memoria.
- Se fija bereq.http.X-Varnish con el id de la transacción. Este id puede ser útil para tareas de logueado, ya que se puede usar para identificar la transacción en los diferentes backends o microservicios que pueda tener nuestra aplicación.
El backend responde vlc_backend_response()
Una vez que el backend responde a la petición, Varnish llamará a esta función y pondrá a nuestra disposición los objetos bereq y beresp que contienen respectivamente la petición y la respuesta del backend.
sub vcl_backend_response {
# Si la ruta corresponde con alguno de estos patrones entregamos el contenido. Son rutas que no se han de cachear y no deben tener cookies.
if(
bereq.url ~ "panel"
|| bereq.url ~ "login"
|| bereq.url ~ "login_check"
|| bereq.url ~ "logout"
|| bereq.url ~ "admin"
|| bereq.url ~ "_profiler"
){
return (deliver);
}else{
unset beresp.http.set-cookie;
} # Si el backend nos avisa de que hay fragmentos ESI los procesamos.
if (beresp.http.Surrogate-Control ~ "ESI/1.0") {
unset beresp.http.Surrogate-Control;
set beresp.do_esi = true;
} # Cacheado de ficheros estáticos
if (bereq.url ~ "^*\.(7z|avi|bmp|bz2|css|csv|doc|docx|eot|flac|flv|gif|gz|ico|jpeg|jpg|js|less|mka|mkv|mov|mp3|mp4|mpeg|mpg|odt|otf|ogg|ogm|opus|pdf|png|ppt|pptx|rar|rtf|svg|svgz|swf|tar|tbz|tgz|ttf|txt|txz|wav|webm|webp|woff|woff2|xls|xlsx|xml|xz|zip)(\?.*)?$") {
set beresp.http.x-obj-ttl = "2600000s";
unset beresp.http.set-cookie;
} # Varnish soporta estreaming, así que lo habilitamos para evitar bloqueos cuando sirvamos ficheros grandes.
if (bereq.url ~ "^*\.(7z|avi|bz2|flac|flv|gz|mka|mkv|mov|mp3|mp4|mpeg|mpg|ogg|ogm|opus|rar|tar|tgz|tbz|txz|wav|webm|xz|zip)(\?.*)?$") {
unset beresp.http.set-cookie;
set beresp.do_stream = true; # Check memory usage it'll grow in fetch_chunksize blocks (128k by default) if the backend doesn't send a Content-Length header, so only enable it for big objects
} # To prevent accidental replace, we only filter the 301/302 redirects for now.
# A veces cuando Apache responde con una redirección con el modulo mod_rewrite y el puerto se esta enviando hacia las peticiones internas se puede generar cierta confusión. Por ejemplo si Varnish escucha en el puerto 80 y Apache en el 8080, la redirección generada podría enviar al cliente al puerto 8080 erroneamente. Esto puede necesitar cierta configuración ajustada a cada aplicación.
if (beresp.status == 301 || beresp.status == 302) {
set beresp.http.Location = regsub(beresp.http.Location, ":+", "");
} # Se fija una vida de dos minutos para los ficheros estáticos
if (beresp.ttl <= 0s || beresp.http.Set-Cookie || beresp.http.Vary == "*") {
set beresp.ttl = 120s; # Lo correcto sería fijar estas cabeceras en el backend para tener mas control.
set beresp.uncacheable = true;
return (deliver);
} # No cachear respuestas 500
if (beresp.status == 500 || beresp.status == 502 || beresp.status == 503 || beresp.status == 504) {
return (abandon);
} # Permitir contenido desactualizado en caso de que el backend se caiga
# Fijamos un periodo de gracia de 6h más respecto del periodo de vida establecido.
set beresp.grace = 6h; return (deliver);
}
Conclusiones
En este artículo he repasado el fichero de configuración de Varnish con la configuración más básica y alguna personalización. En general es interesante dejar que sea la aplicación la que maneje la cache mediante el uso de las cabeceras adecuadas.
Por ejemplo en este blog, las respuestas se encapsulan en dos clases PublicResponse y PrivateResponse. Ambas clases extienden de la clase Response de Symfony. En el caso de la petición sea una respuesta cacheable (parte pública del blog) se establece la cabecera “max-age” que varnish respetará fijando el ttl correspondiente y guardando la respuesta para futuras peticiones.
En ocasiones por simplicidad o para no tocar codigo de terceros, podemos definir el comportamiento por patrones de la url. En el caso de este blog, al utilizar un bundle para gestionar el proceso de login, el código de las urls que gestionan el login no se debe editar ya que no forma parte del proyecto, por lo que no se puede fijar cabeceras direactamente en el controlador. Probablemente se podría fijar igualmente usando event listeners, pero me resulta mas sencillo fijar esas excepciones en el bloque vcl_recv usando patrones de url.