Cache¶
Un cache devant les origines ne décide rien : il ne bloque pas, il ne note pas, il ne défie personne. Il garde une réponse déjà produite et la rend plus vite, pendant que le reste de la chaîne continue de travailler. C'est pour cela qu'il est posé derrière le WAF et derrière le décideur, jamais devant.
Pourquoi un opérateur, et pas un Deployment dans un manifeste¶
Un moteur de cache n'est pas un pot de yaourt : sa politique est un fichier, ce fichier
change, et ce changement doit être appliqué sans perdre la mémoire chaude. Écrit à la
main, un Deployment Varnish impose de redémarrer le pod à chaque retouche de durée de
vie, donc de jeter tout ce qui était en cache au moment précis où l'on voulait
l'améliorer.
L'opérateur prend la politique comme une ressource, rend le VCL, le publie, et laisse le moteur recharger à chaud. Il publie aussi ce que le moteur a réellement servi, parce qu'une console qui affiche un taux de succès inventé ne sert à rien.
Deux ressources, deux rôles¶
VarnishCache est le cache : une image, une mémoire, une politique. Il vit sa vie et
peut servir plusieurs sites.
EdgeSite est le site. Il dit seulement s'il passe par un cache, et lequel :
Tant que le cache n'est pas prêt, la route du site reste directe. C'est volontaire : un site ne doit pas tomber parce qu'un cache démarre. La bascule se fait quand le cache dit qu'il sert, et le site repasse en direct si on lui retire son cache.
La vérité est croisée : le cache nomme ses sites, le site nomme son cache. Si les deux ne se répondent pas, rien ne se monte. Une faute de frappe dans une liste ne peut donc pas envoyer un domaine vers les origines d'un autre.
La politique¶
apiVersion: edge.exoledge.io/v1alpha1
kind: VarnishCache
metadata:
name: edge-cache
namespace: edge
spec:
image: docker.io/library/varnish:7.6
memoryMiB: 192
ttlSeconds: 60
graceSeconds: 30
sites:
- name: demo
rules:
- prefix: /app
ttlSeconds: 0
- prefix: /assets
ttlSeconds: 3600
stripCookies: true
purgeSecretRef:
name: cache-purge
key: token
Les routes viennent des EdgeSite nommés, avec leurs poids : le cache choisit
l'origine exactement comme le bord l'aurait fait, et une origine à poids zéro ne reçoit
rien, pas même du cache.
Les règles disent ce qui a le droit d'être gardé. Une règle à durée nulle n'est pas un oubli : c'est un refus de stocker, et la requête part directement. Le préfixe le plus long gagne.
stripCookies retire le Set-Cookie avant de stocker. Sans lui, une réponse qui pose
un cookie n'est jamais partageable, ce qui est le comportement par défaut et le bon : une
page qui dépend d'une session n'a rien à faire dans un cache partagé.
Sans stripCookies, toute requête portant un cookie traverse sans être stockée. Une
requête authentifiée aussi, toujours.
Qui en profite vraiment¶
Une requete qui porte un cookie n'est pas stockee, sauf si la regle du prefixe
demande stripCookies. Comme le navigateur qui a franchi le defi porte sa
clearance, le cache sert surtout les clients sans cookie : moteurs de recherche
verifies, appels d'API, ressources statiques declarees partageables.
C'est un choix, pas un oubli : un cache qui stocke les reponses d'une session finit par servir la page d'un visiteur a un autre. La consequence est a connaitre avant de promettre un taux de cache eleve : il porte sur la part du trafic qui n'a pas de session, et cette part doit etre mesuree, pas supposee.
Preuve de fonctionnement, depuis le cluster, sans passer par le defi :
1. requete : X-Cache: MISS
2. requete : X-Cache: HIT
3. purge / : varnishadm ban.list montre req.url == "/"
4. requete : X-Cache: MISS
Ce que le moteur ne stocke jamais¶
- une réponse d'erreur d'origine (5xx), ou partielle (206) ;
- une réponse que l'origine interdit de stocker (
no-store,private) : l'origine a le dernier mot ; - une méthode autre que
GETetHEAD.
Purge¶
La purge est la seule écriture acceptée par le cache, et elle exige un jeton :
Sans jeton configuré, la purge répond 405 : fermée. Avec un jeton et le mauvais en-tête,
403. Un cache qu'un inconnu peut vider à distance est un moyen de faire tomber le site
par un simple en-tête.
Le jeton est généré par OpenTofu, jamais écrit dans un manifeste :
Le CRS n'autorisait que GET, HEAD, POST et OPTIONS : PURGE était refusé en 403
par le WAF avant d'atteindre le cache. La méthode est désormais dans
tx.allowed_methods, et c'est le cache qui décide, pas le pare-feu.
Rechargement à chaud¶
varnishd ne relit pas son VCL de lui-même. Le conteneur démarre donc le moteur, puis
surveille l'empreinte du fichier : quand elle change, il charge la nouvelle politique et
l'active, sans redémarrer. Vérifiable dans le conteneur :
Une politique modifiée prend effet en une minute environ : le temps que le ConfigMap propage le fichier, puis cinq secondes de surveillance. Le pod ne change pas de nom, la mémoire chaude est conservée.
L'empreinte de la politique est publiée dans status.vclHash, et l'opérateur ne la pose
pas sur le modèle de pod : une annotation sur le pod ferait redémarrer le cache à chaque
changement de politique, ce qui est exactement ce qu'on veut éviter.
Ce que le cache raconte¶
Chaque réponse porte son verdict, sans outil :
et le compteur est publié dans status : hits, misses, objects, hitRatio. La
console les lit dans l'écran Répartition de charge, avec la mémoire, la durée de vie
et l'empreinte de la politique.
Ces chiffres viennent du moteur lui-même, lus par varnishstat dans le pod. Le cache
n'expose pas de port de statistiques : ce serait une porte de plus au bord pour un
chiffre.
Origin tombee : ce que voit le visiteur¶
Quand plus aucune origine ne repond, Varnish fabrique lui-meme la reponse. Par defaut il sert sa page d'erreur : « Backend fetch failed », « Guru Meditation », un XID, et le nom du logiciel en clair. Trois problemes d'un coup : le visiteur lit un message technique, la marque du client disparait derriere celle du cache, et la pile est annoncee a qui sait la lire.
Le VCL rendu definit donc vcl_synth et vcl_backend_error : un 503 avec
Retry-After: 60, Cache-Control: no-store et une page autonome, sans police
distante ni script. Elle s'affiche pendant une panne reseau, pas seulement
pendant une panne de site.
Le 502 reste volontairement intouche : il signifie que l'origine a repondu quelque chose d'invalide, et le masquer derriere une page de maintenance ferait perdre le seul signal utile a l'exploitant.
La page est une chaine longue VCL (synthetic({"..."})). Elle ne doit contenir
aucun guillemet double : il fermerait la chaine au milieu du document et la
compilation echouerait en silence, le cache gardant l'ancien VCL. Un test fige
cette regle.
Limites assumées¶
- Un seul réplica, et c'est un choix. Deux caches devant les mêmes origines divisent le taux de succès, chacun ayant sa propre mémoire froide. Le cache n'est pas la haute disponibilité, il est l'accélération ; la passerelle reste le point unique.
- Mémoire seule. Le stockage est dans la RAM allouée, pas sur disque : un redémarrage vide le cache. C'est acceptable pour un cache, pas pour un magasin.
- Pas de purge par étiquette. On purge une URL, pas un ensemble.
- Pas de cache pour les réponses qui varient. Pas de
Varynégocié, pas d'ESI : ce sont des fonctionnalités à ajouter consciemment, pas des réglages à exposer. - Le cache ne filtre pas. Il ne remplace ni le WAF ni le décideur, et il ne doit jamais être placé devant eux : il servirait alors une réponse bloquée, ou l'inverse.