
🌐 Aussi en: English · Deutsch · Español
Deep Dive de la série autour de l’Ansible Tour. Niveau : administration Linux avancée. Le tour explique les rôles ; les deep dives expliquent la technique en dessous – comment elle fonctionne, pourquoi elle est là, ce que cette installation en fait exactement, comment l’exploiter au quotidien et ce qui aurait existé d’autre.
Ce blog est un WordPress qui tourne dans un cluster K3s à un seul nœud (la partie 3 du tour présente les rôles). WordPress suppose un serveur web classique : il écrit dans son propre répertoire, installe et met à jour ses extensions depuis sa page d’administration, et planifie ses tâches en se greffant sur les visites. Kubernetes suppose l’inverse – des conteneurs remplaçables, une configuration venue de l’extérieur, aucun état dans l’image. Cet article explique comment les deux se rencontrent ici.
🧭 Comment fonctionne WordPress
Le chemin d’une requête
Une requête atteint l’ingress controller (TLS, limites de débit, en-têtes de sécurité), puis le sidecar nginx du pod WordPress. nginx cherche d’abord un fichier HTML prérendu du cache de pages ; s’il existe, PHP n’intervient pas du tout. Sinon il transmet la requête à PHP-FPM dans le même pod, où WordPress charge son cœur, les extensions et le thème, demande à Redis les objets en cache et à MariaDB le reste, puis rend la page.
Où WordPress garde son état
- Fichiers :
wp-content/– extensions, thèmes, médias, cache de pages, fichiers de langue. WordPress y écrit lui-même, par exemple lorsqu’il met à jour une extension. - Base de données : articles, pages, utilisateurs, et la table
wp_options, où WordPress et chaque extension rangent leurs réglages – ainsi que les transients, des valeurs en cache de courte durée. wp-config.php: connexion à la base et clés. Dans l’image officielle, le point d’entrée le génère à partir de variables d’environnement.
Modèle d’extension
Tout dans WordPress est un hook : des actions (« maintenant X se produit ») et des filtres (« cette valeur passe ici, modifie-la si tu veux »). Extensions et thèmes accrochent des fonctions aux hooks. Les must-use plugins de wp-content/mu-plugins/ se chargent automatiquement, avant les extensions normales, et ne peuvent pas être désactivés depuis l’administration – le bon endroit pour des règles qui doivent toujours s’appliquer.
WP-Cron
WordPress n’a pas de démon. Par défaut, il vérifie à chaque affichage de page si des tâches planifiées sont dues et, si oui, déclenche une requête vers wp-cron.php. Sur un site calme, les tâches s’exécutent en retard ; sur un site chargé, la vérification coûte du temps sur de nombreuses requêtes.
🛡️ Pourquoi il est là
- Un vrai éditeur et un écosystème d’extensions. SEO, compression d’images, connexion à deux facteurs, table des matières – chacun est à une extension près.
- Quatre langues. Polylang gère les versions anglaise, allemande, française et espagnole de chaque article, avec des traductions liées.
- Open source et portable. Un dump de la base plus
wp-content, c’est tout le site ; il peut déménager sur n’importe quel hôte.
🔧 Ce que fait ce dépôt
Pods et volumes
| Pod | Conteneurs | État |
|---|---|---|
| wordpress | nginx, PHP-FPM, php-fpm_exporter | /var/www/html depuis un hostPath (/srv/wordpress-www) |
| mariadb | MariaDB, mysqld_exporter | hostPath /srv/wordpress-db |
| redis | Redis | aucun (mémoire seulement) |
| CronJobs | wordpress-cron toutes les 5 min, wordpress-autoupdate à 03:30 | le même volume, brièvement |
Les images sont sans état ; l’état vit dans deux répertoires de l’hôte avec leurs propres étiquettes SELinux, si bien que le conteneur peut y écrire et nulle part ailleurs. Le conteneur PHP-FPM a un système de fichiers racine en lecture seule et seulement cinq capabilities – celles dont le point d’entrée de l’image a réellement besoin pour décompresser WordPress dans le volume au premier démarrage et passer de root à www-data ; chacune a été testée en la retirant. WP-CLI est téléchargé par un conteneur d’initialisation dans un volume hors de la racine web : nginx refuse seulement de servir les fichiers .php, et un .phar à la racine aurait été un téléchargement de 7 Mo pour n’importe qui.
WP-CLI comme poste de pilotage
Ansible ne clique pas dans l’administration. Chaque changement passe par WP-CLI dans le conteneur en marche :
k3s kubectl exec deployment/wordpress -n wordpress -c wordpress-fpm -- \
su -s /bin/sh www-data -c \
"php /usr/local/lib/wp-cli/wp-cli.phar --path=/var/www/html plugin list --format=json"Le rôle blog_wordpress l’utilise pour l’installation, la liste des extensions, les réglages, le menu et les cinq must-use plugins. Chaque tâche lit d’abord et ne change que ce qui diffère – la tâche des extensions, par exemple, installe seulement celles qui manquent et active seulement celles qui sont inactives ; une version antérieure réinstallait chaque extension à chaque passage et les signalait toutes comme « changed ». Une leçon de l’exploitation : WP-CLI appartient à www-data. Un vidage de cache lancé en root avec --allow-root a laissé un jour un répertoire de cache appartenant à root, dans lequel WordPress ne pouvait plus écrire. L’installation des extensions tourne encore en root pour des raisons historiques, suivie d’un chown -R dans un bloc always:, afin que la propriété soit rétablie même si une installation échoue.
Cinq must-use plugins
| mu-plugin | Fait |
|---|---|
| security-hardening | Masque les numéros de version, désactive l’éditeur de fichiers de l’administration (une session admin serait sinon une exécution de code), ferme l’énumération des utilisateurs via l’API REST. |
| upload-images | Convertit chaque image envoyée en WebP, au plus 1 536 px et sous 280 Ko – la limite au-delà de laquelle les messageries abandonnent l’image d’aperçu. |
| performance-hints | Précharge la police, charge immédiatement la plus grande image d’une page. |
| language-links | « Aussi disponible en … » sous chaque article, à partir des liens de traduction de Polylang. |
| ai-disclosure | Une mention discrète en pied de page et la page sur l’usage de l’IA. |
Un cache à trois étages
- Cache de pages (WP Super Cache) : des fichiers HTML finis sur disque ; nginx les sert directement, avec un contrôle des cookies pour que les utilisateurs connectés et les commentateurs reçoivent toujours des pages fraîches.
- Cache d’objets (Redis) : les résultats des requêtes en base, côté PHP.
- OPcache : le code PHP compilé, avec vérification des dates toutes les 60 s – parce que les extensions se mettent à jour sur place la nuit.
La sécurité en bordure
xmlrpc.php,install.php, les restes de sauvegardes et d’éditeurs (.bak,.sql,~,.phar) et l’énumération par?author=répondent 403.- Limites de débit dans l’ingress, par IP cliente : connexion 5 requêtes par seconde avec une rafale de 5, recherche 2 par seconde avec une rafale de 10 ; au-delà, 429.
- Connexion et
/wp-admin/seulement depuis des réseaux allemands, avec la même liste d’IP que celle qui protège SSH (le filtre géographique) ;admin-ajax.phpreste ouvert, car le site public en a besoin. En plus, Limit Login Attempts et la connexion à deux facteurs pour les administrateurs. - Une Content Security Policy en mode report-only – la partie 5 explique pourquoi elle n’est pas encore appliquée.
Cron et mises à jour sans visiteurs
DISABLE_WP_CRON désactive le cron déclenché par les visiteurs ; à la place, un CronJob Kubernetes exécute php wp-cron.php toutes les cinq minutes, dans son propre conteneur avec le même volume (concurrencyPolicy: Forbid, pour qu’un passage lent ne soit jamais doublé par le suivant). Un second CronJob à 03:30 met à jour le cœur (versions mineures seulement), les extensions, les thèmes et les paquets de langue, lance les migrations de base, vide le cache et vérifie que la page d’accueil répond encore. Les mises à jour majeures et les changements d’image passent délibérément par blog-update.yml.
📊 Mesuré
| Valeur | |
|---|---|
| WordPress | 7.1, PHP 8.4, thème MoreNews |
| Articles publiés (les quatre langues) | 488 |
| Pages | 16 |
| Base de données | 49 Mo |
| Médias | 83 Mo en 1 253 fichiers (après la migration vers WebP) |
| Extensions | 10 normales, 5 must-use, 2 drop-ins (cache de pages, cache d’objets) |
| Événements cron planifiés | 33 |
| Temps serveur jusqu’au premier octet, page en cache | ~46 ms |
| Temps serveur jusqu’au premier octet, rendue par PHP | ~140 ms |
| Requêtes PHP par jour | ~21 700 |
Les temps sont mesurés depuis l’extérieur, poignée de main TLS déduite ; ils comprennent donc encore un aller-retour réseau. Le cache de pages économise environ 90 ms par requête – et, surtout, des workers PHP : une page en cache coûte à nginx une lecture de fichier, rien d’autre.
🔍 Ce qu’une reconstruction à partir d’Ansible seul manquerait
L’intérêt d’Ansible est de pouvoir reconstruire le serveur à partir du dépôt. Pour WordPress, c’est vrai pour la plateforme et seulement en partie pour le site :
- Contenus et réglages – articles, configuration des langues de Polylang, réglages des extensions, configuration du thème – sont dans la base. Ils viennent de la sauvegarde nocturne, pas du dépôt. C’est voulu.
- Une extension n’est pas dans Ansible. Easy Table of Contents a été installée depuis l’administration et est documentée comme telle ; une installation neuve par le playbook ne l’aurait pas, une restauration depuis la sauvegarde si. Dans les deux cas, cela doit être une décision consciente, c’est pourquoi le guide d’exploitation la mentionne.
- All-in-One WP Migration a été supprimée en octobre 2026 : ses archives
.wpressà la racine web n’étaient protégées que par un fichier.htaccess, que nginx ne lit pas. nginx les bloquait de toute façon ; l’extension est partie parce que la sauvegarde nocturne etblog.ymlfont déjà son travail.
🧰 Exploitation au quotidien
# a WP-CLI shell function on the host
wp() { k3s kubectl -n wordpress exec deploy/wordpress -c wordpress-fpm -- \
su -s /bin/sh www-data -c "php /usr/local/lib/wp-cli/wp-cli.phar --path=/var/www/html $*"; }
wp core version; wp plugin list
wp cron event list --fields=hook,next_run_relative
wp eval 'wp_cache_clear_cache();' # WP Super Cache (wp cache flush = object cache only)
wp db export - | gzip > before-change.sql.gz
# nightly update log
k3s kubectl -n wordpress logs job/$(k3s kubectl -n wordpress get jobs \
-o name | grep autoupdate | tail -1 | cut -d/ -f2)- Deux caches, deux commandes.
wp cache flushvide Redis ; le cache de pages demandewp_cache_clear_cache(). Après avoir modifié un article hors de l’éditeur : les deux. - Sauvegardes. Une machine de sauvegarde récupère chaque nuit un dump de la base et le répertoire
wp-content; restaurer, c’est le dump dans la base et les fichiers dans/srv/wordpress-www. - Annuler une mise à jour. Les images sont épinglées dans
versions.yml: revenir à l’ancien épinglage et lancer le playbook. Une extension :wp plugin install <slug> --version=<ancienne> --force. - Modifier du contenu par script. N’écrire le contenu des articles qu’au moyen de fonctions WordPress qui échappent correctement – une mise à jour directe a un jour supprimé les barres obliques inverses des exemples de code d’une douzaine d’articles.
🆘 Antisèche : symptôme → commande
La version courte de cet article, pour le moment où quelque chose ne va pas. À lancer sur l’hôte en root ; <…> sont des espaces réservés. occ et wp sont les fonctions shell des deep dives Nextcloud et WordPress.
| Symptôme | Commande |
|---|---|
| La page affiche un ancien contenu | wp eval 'wp_cache_clear_cache();' # page cache |
| Page blanche ou erreur 500 | k3s kubectl -n wordpress logs deploy/wordpress -c wordpress-fpm --tail=50 |
| Une extension a cassé le site | wp plugin deactivate <slug> |
| Quelles versions sont installées, qu’est-ce qui attend ? | wp core version |
| Les tâches planifiées ne s’exécutent pas | wp cron event list --fields=hook,next_run_relative |
| La mise à jour de la nuit a-t-elle fonctionné ? | k3s kubectl -n wordpress get jobs | grep autoupdate |
| Avant toute modification du contenu ou des extensions | wp db export - | gzip > before-change.sql.gz |
| Fichiers soudain possédés par root | # wp-cli only as www-data (the function above), never --allow-root |
🔭 La suite
- Appliquer la CSP dès que la phase report-only ne réserve plus de surprises.
- Un cache de pages complet dans l’ingress économiserait l’aller-retour vers le pod ; à ~46 ms, ce n’est pas urgent.
- Les médias dans un stockage objet rendraient le pod indépendant du répertoire de l’hôte – pertinent seulement avec plus d’un nœud.
⚖️ Alternatives – et pourquoi WordPress
| Option | Point fort | Pourquoi pas ici |
|---|---|---|
| Hugo / site statique | Le plus rapide et le plus sûr : ni PHP ni base | Pas d’éditeur web, pas de commentaires, traductions gérées à la main. |
| Ghost | Moderne, rapide, bon éditeur | Node.js, beaucoup moins d’extensions, multilingue seulement par contournements. |
| WordPress headless | WordPress comme éditeur, un front statique ou JS | Deux systèmes au lieu d’un pour un blog à auteur unique. |
| WordPress | Éditeur, extensions, Polylang, et l’auteur le connaît | Choisi. |
Faire tourner WordPress sous Kubernetes tient moins de Kubernetes que de l’honnêteté sur l’endroit où se trouve l’état. Ici, il est dans deux répertoires et une base, tous trois sauvegardés, tout le reste est remplaçable – et le poste de pilotage n’est pas l’administration mais WP-CLI, appelé par Ansible, avec le bon utilisateur.


