Mise en place de Varnish avec Drupal 8

Introduction

Lorsque j’ai commencé à étudier Varnish avec Drupal 8 dans le cadre d’un projet à fort trafic, je me suis aperçu qu’il était difficile de trouver une documentation complète.

Malgré l’abondance de documentations disponibles, beaucoup se focalisent sur Varnish, concernent Drupal 7 ou sont carrément obsolètes.

Il me semblait donc intéressant de faire un point sur la configuration de ces deux outils pour un projet concret.

Le contexte

Le projet réalisé est un site événementiel générant un fort trafic ponctuel sur des périodes de quelques jours.

Pendant le plus important des événements, plus de 50 000 visiteurs uniques vont venir sur le site Internet chaque jour. Cela représente, dans notre cas, des pics entre 700 et 800 connexions simultanées au plus haut de la fréquentation.

Une grande partie des données du projet provient d’un outil tiers. Ces données sont importées quotidiennement pour la plupart mais certaines doivent être mises à jour le plus fréquemment possible.

Le site permet ensuite aux administrateurs de rajouter des données spécifiques au projet Web et associées aux données importées.

Le principe

Environnement

Notre environnement est très classique, il s’agit d’un LAMP standard auquel on ajoute Varnish 4.

Le premier point important à noter avec Varnish est qu’il gère uniquement le protocole HTTP (si vous voulez savoir pourquoi : https://varnish-cache.org/docs/trunk/phk/ssl_again.html).

Pour utiliser une connexion sécurisée et le protocole HTTPS il va falloir mettre en place un intermédiaire en amont (ou reverse proxy).

Dans ce cas de figure, les plus utilisés sont HAProxy et Nginx. Nous avons fait le choix de partir sur Nginx, que nous utilisons déjà sur d’autres projets.

Lorsqu’un requête HTTPS est envoyée au serveur :

  • Nginx réceptionne cette requête et s’occupe de toute la partie SSL / handshaking,
  • il la transmet ensuite à Varnish,
  • Varnish regarde s’il possède déjà une réponse en cache à envoyer, si oui il la renvoie directement,
  • sinon il va interroger Apache pour obtenir la réponse, qui sera transférée à l’internaute.

Varnish et Apache ne seront jamais appelés directement depuis l’extérieur dans cette configuration.

Le cache Drupal

Côté Drupal, il y a principalement deux systèmes de cache bien distincts.

  • Internal Page Cache, pour les internautes anonymes,
  • Internal Dynamic Page Cache, pour les utilisateurs authentifiés.

Le deuxième, plus complexe, utilise des contextes et des mots-clés qui peuvent être associés aux données, ce qui lui permet d’invalider le cache uniquement lorsque c’est pertinent.

Si vous voulez en savoir plus sur la gestion du cache avec Drupal 8, je vous invite à regarder la documentation officielle.

La principale problématique du projet vis-à-vis du cache est la nécessité de mettre à jour très fréquemment certaines données qui proviennent de l’extérieur durant les événements.

Pour cela, nous pouvons :

  • exclure du cache de Varnish les pages contenant ces données,
  • utiliser du micro-cache sur ces pages,
  • utiliser une invalidation du cache qui soit pertinente par rapport aux données.

La première solution rendrait pratiquement inutile la mise en place de Varnish, ces pages étant parmi les plus consultées du projet.

La deuxième est simple à mettre en place, mais nécessite beaucoup d’expérimentations pour optimiser correctement la durée de vie du cache.

Nous avons donc opté pour la troisième solution, via la notion de tags présente dans Drupal et Varnish.

Configuration Drupal

Installation des modules

Pour Drupal 8, il faut installer les modules suivants :

  • Modules du coeur
    • page_cache
    • dynamic_page_cache
    • big_pipe
  • Modules contributeurs
# Modules contributeurs à télécharger en premier.

composer require drupal/big_pipe_sessionless drupal/purge drupal/varnish_purge

# Installation des modules via Drush.

drush en -y page_cache dynamic_page_cache big_pipe big_pipe_sessionless purge \
purge_processor_cron purge_queuer_coretags purge_tokens purge_ui purge \ 
varnish_image_purge varnish_purge_tags varnish_purger

Les modules du coeur vont gérer le cache côté Drupal. Si vous n’êtes pas familier avec BigPipe, je vous invite à lire la présentation officielle.

Le module big_pipe_sessionless va permettre de bénéficier de la technologie BigPipe pour les utilisateurs qui n’ont pas de session (c’est-à-dire les internautes anonymes ou non authentifiés).

La suite de modules purge permet de gérer l’invalidation de cache externe de manière générique. purge_ui fournit l’interface permettant de configurer le module et de gérer des “purgers”.

  • purge_tokens est requis pour utiliser des tokens dans la configuration des “purgers”.
  • purge_queuer_coretags va permettre de gérer des files d’attentes de “tags” invalidés par Drupal.
  • purge_processor_cron va permettre de traiter les files d’attentes de “tags” grâce au cron.

La suite de modules varnish_purge fournit des “purgers” utilisable avec le module purge et compatibles avec Varnish.

  • varnish_purger fournit un “purger” dédié à Varnish
  • varnish_image_purge s’assure d’invalider les styles d’images associés à une entité
  • varnish_purge_tags est nécessaire pour utiliser l’invalidation par “tags”

Fonctionnement

Le principe est le suivant : lorsqu’une donnée est modifiée dans le back office, les tags de cache qui lui sont associés vont être invalidés par Drupal. Chaque tag ainsi invalidé va être ajouté à une file d’attente qui sera traité à la prochaine exécution du cron Drupal.

Lors de l’exécution du cron, chaque élément de la file d’attente va entraîner l’envoi d’une requête HTTP BAN à Varnish, qui va supprimer toutes les données en cache associées à ce tag. Il est également possible de traiter plusieurs tags à la fois en les combinant au sein d’une même requête.

Une fois les modules installés, nous allons pouvoir mettre en place la configuration nécessaire au bon fonctionnement de ce système de cache.

Configuration des modules

Dans un premier temps, il est recommandé d’utiliser le “max-age” le plus élevé possible :

Max age

Administration > Configuration > Développement > Performance

Ensuite nous pouvons créer notre premier “purger” et sa configuration associée via l’onglet “Purger”.

Vous pouvez ici choisir le “bundled” purger ou bien le purger classique, la seule différence en terme de paramétrage est le token utilisé pour les tags. Nous utiliserons le purger classique pour la suite de cet article.

Une fois créé, vous pourrez modifier sa configuration via le dropdown.

Purger configuration dropdown

Saisissez un libellé, et sélectionnez le type “Etiquette” (tags).

Les paramètres “Requête” vont dépendre de la configuration de Varnish, mais sur une configuration par défaut cela donne :

Purger configuration

Dans la partie “Entêtes”, il vous faut ajouter les valeurs suivantes :

Purger headers configuration

Chaque requête BAN contiendra un entête “Cache-Tags” avec la valeur du tag à invalider.

Dans la partie “Performance”, vous pouvez augmenter le nombre de requêtes maximum effectuées en une exécution mais cela dépend de la fréquence de mise à jour de vos données et surtout de la fréquence d’exécution du cron.

Dans notre cas beaucoup de données doivent être mises à jour fréquemment, nous avons donc opté pour une exécution du cron côté serveur toutes les 2 minutes et 500 requêtes BAN maximum par exécution.

Purger performance configuration

Vous pouvez maintenant enregistrer la configuration.

Dans la partie “File”, vous pouvez supprimer les deux “Purge block(s)” (sauf si vous voulez laisser la possibilité à des administrateurs de déclencher la purge directement via l’interface) et ajouter :

  • un “queuer” (pour alimenter la file d’attente) qui sera le “Core tags queuer” avec la configuration par défaut,
  • un “file” (pour le stockage de la file d’attente) qui peut être la base de données Drupal, en mémoire ou sur le système de fichier. Nous avons opté pour un stockage via la base de données.
  • un “processor” (pour traiter la file d’attente) qui sera le “Cron processor”.
Purger file

La partie droite de cette page vous indique quelques informations sur l’état actuel du système de purge avec notamment le nombre d’éléments dans la file d’attente.

Purger state

Vous pouvez également ajuster les paramètres de journalisation en bas à droite, à votre convenance.

L’onglet “Varnish image purge” vous permet de sélectionner les bundles pour lesquels les styles d’image des fichiers associés seront purgés après une modification de l’entité.

Dernier paramétrage

Il reste une dernière petite chose à faire pour finaliser la configuration Drupal, c’est d’ajouter les lignes suivantes dans votre settings.php :

$settings['reverse_proxy'] = TRUE;

// Modifier l'IP si Varnish n'est pas sur le même serveur.
$settings['reverse_proxy_addresses'] = array('127.0.0.1'); 

Il ne reste plus qu’à s’occuper de Varnish !

Configuration de Varnish

La majeure partie de notre default.vcl est basée sur https://gitlab.wklive.net/snippets/32

Cet exemple est fourni par l’auteur du module “Varnish purger”.

Il y a deux ajustements à faire, le premier est d’ajouter la gestion de la méthode URIBAN pour les images dans la routine vcl_recv (vous pouvez le mettre juste après le bloc BAN) :

if (req.method == "URIBAN") {
  ban("req.http.host == " + req.http.host + " && req.url == " + req.url);
  # Throw a synthetic page so the request won't go to the backend.
  return (synth(200, "Ban added."));
}

Le deuxième est la gestion du streaming avec BigPipe, à mettre dans la routine vcl_backend_response () :

# Disable buffering only for BigPipe responses
if (beresp.http.Surrogate-Control ~ "BigPipe/1.0") {
  set beresp.do_stream = true;
  set beresp.ttl = 0s;
}

Je ne détaillerai pas plus cette partie configuration de Varnish, d’autres articles très détaillés existent déjà, notamment : Varnish et Drupal, gérer un cache anonyme étendu (attention c’est pour Drupal 7 mais il explique tellement bien le fonctionnement de Varnish que ça vaut le coup d’oeil).

Les limites

Une fois tout ceci fonctionnel et mis en production, nous avons été confrontés à plusieurs problématiques.

La première est qu’il n’y a pas de procédure établie pour invalider le cache de fichiers qui ne sont pas liés à du contenu, par exemple les ressources statiques du thème (CSS, JS, images, etc.).

Lors des déploiements il fallait déclencher une reconstruction complète du cache côté Drupal pour que ces modifications soient prises en compte. Une solution simple mais fastidieuse serait de mettre en place un formulaire permettant de déclencher manuellement des URIBAN, mais nous étudierons cette problématique plus en détails ultérieurement.

La deuxième est liée au nombre de tags traité à chaque exécution. Nous avions initialement laissé la configuration par défaut, mais parfois la quantité de tags ajoutés en file d’attente était plus importante que le nombre de tags traités à chaque exécution. Nous avons d’une part augmenté ce nombre à 500, mais nous avons également dû revoir notre système d’import de données pour limiter au maximum le nombre d’entités modifiées.

Enfin, sur des pages comportant beaucoup d’entités nous avons été confronté à des erreurs 500 qu’il a été difficile de retracer. Il s’avère que ce problème est lié à une limite d’Apache vis à vis de la taille des en-têtes de la réponse. Le problème est très bien expliqué et détaillé ici : https://maxchadwick.xyz/blog/http-response-header-size-limit-with-mod-proxy-fcgi.

Malgré l’ajout d’un paramètre responsefieldsize dans Apache 2.4.34, le problème ne semble pas avoir été résolu.

Il n’y a donc pas de réelle solution pour Apache, mais ce problème ne se produit pas avec Nginx ce qui augmente fortement l’intérêt de simplifier notre architecture en se passant d’Apache.

Conclusion

Nous avons vu comment mettre en place Varnish avec Drupal 8 et gérer l’invalidation du cache par le biais des tags. Quelques difficultés peuvent être rencontrées, mais les gains apportés par cette gestion de cache sont indéniables. Elle a permis à notre serveur de tenir la charge en plein pic de trafic avec des données mises à jour en quasi temps réel (< 2mn), sauf cas particuliers – notamment lors des déploiements de modifications liées au thème (CSS / JS / images) qui ont nécessité un vidage de cache complet.

Certaines limites liées à Apache et la couche de complexité apportée par celui-ci suggèrent qu’une architecture plus simple avec seulement Nginx + Varnish est possible.

Cela nous permettra d’aborder le prochain projet de cette ampleur avec des acquis et une certaine tranquillité d’esprit quant à la mise en place d’une gestion de cache de ce type.

Voir l’étude de cas
Lire l’article
Voir le témoignage
Fermer