
🌐 Aussi en: English · Deutsch · Español
Partie 4 de la visite Ansible. La partie 1 a expliqué le cadre, la partie 2 le serveur et la plateforme, la partie 3 le blog. Cette partie est la seconde application : Nextcloud. Elle décrit la version v3.3.0. Si vous avez lu la partie 3, beaucoup de choses vous sembleront familières – je signale les différences et j’explique ce qui est nouveau, plutôt que de répéter les motifs communs.
Nextcloud est un cloud de fichiers : fichiers, agendas, contacts, dossiers partagés et – avec Collabora – des documents édités à plusieurs dans le navigateur. Deux des trois serveurs ici le font tourner pour des groupes de tailles très différentes, avec le même code et d’autres valeurs d’inventaire. Le travail se répartit sur trois rôles plus le playbook de mise à jour :
| Rôle / playbook | Ce qu’il fait |
|---|---|
next_mariadb, next_redis | Base de données et cache, directement sur l’hôte |
next_k3s_deploy | Nextcloud, Collabora, le service push et tout ce qui les entoure sous forme d’objets Kubernetes |
next_config | Les réglages propres de Nextcloud, appliqués avec occ – lire, comparer, n’écrire que ce qui diffère |
nextcloud_verify | Tests de fumée à la fin de l’exécution |
nextcloud-update.yml | La mise à jour de routine, avec des contrôles de santé avant et après et un chemin défini en cas d’échec |
🏗️ Ce qui tourne, et pourquoi
Le chemin d’une requête est le même que pour le blog : contrôleur Ingress (TLS) → sidecar nginx → PHP-FPM, dans un seul pod. Ce qui diffère, c’est tout ce qui l’entoure.
| Composant | Pourquoi il est là |
|---|---|
| Nextcloud (PHP-FPM + nginx) | L’application elle-même, depuis l’image officielle nextcloud:fpm, avec nginx en sidecar pour les fichiers statiques – le même raisonnement que pour WordPress. Mettre Nextcloud à jour, c’est changer d’image ; pas de PHP sur l’hôte. |
| Deux répertoires de l’hôte | Les fichiers de l’application (/srv/nextcloud-www) et les données des utilisateurs (/data) sont volontairement séparés. Une nouvelle image remplace les fichiers de l’application ; les fichiers des utilisateurs restent où ils sont et peuvent être sauvegardés indépendamment, et une installation neuve n’a besoin que d’un répertoire d’application vide. |
| MariaDB sur l’hôte | Contrairement au blog, la base de données tourne comme un service normal sur l’hôte. Ses fichiers de données se trouvent directement dans le système de fichiers de l’hôte, sans couche de volume de conteneur entre les deux – pour une base de cette taille et de cette importance, c’est une chose de moins entre les données et le disque. Les pods la joignent par un Service Kubernetes qui pointe vers l’hôte (expliqué plus bas). |
| Redis sur l’hôte | Nextcloud utilise Redis pour deux choses : un cache partagé par tous les processus PHP, et le verrouillage de fichiers – pour que deux clients qui enregistrent le même fichier au même moment ne le corrompent pas. Le verrouillage est indispensable pour un cloud de fichiers avec des clients de synchronisation. Un cache par processus (APCu) gère les petites recherches locales ; Redis tout ce qui doit rester cohérent entre processus. |
| Collabora Online (CODE) | Une suite bureautique qui tourne sur le serveur et s’affiche dans le navigateur. Nextcloud et Collabora communiquent via WOPI, un protocole dans lequel l’éditeur demande au serveur de fichiers « donne-moi ce fichier » et « enregistre cette version », signé avec une clé pour que le serveur de fichiers sache que les requêtes sont authentiques. Collabora tourne dans son propre pod, derrière son propre nom d’hôte. |
| notify_push | Sans lui, chaque client de bureau et mobile demande au serveur toutes les trente secondes si quelque chose a changé. notify_push garde une WebSocket ouverte et prévient les clients quand un fichier change – moins de charge, synchronisation plus rapide. C’est un petit programme livré dans une app Nextcloud, exécuté dans son propre pod. |
| Un CronJob pour les tâches de fond | Nextcloud a une file de travail en arrière-plan : aperçus, nettoyages, notifications, expiration des versions. Un CronJob exécute cron.php toutes les cinq minutes – le mode que Nextcloud recommande lui-même plutôt que « au chargement de la page ». |
| Aperçus | Des vignettes pour images, vidéos et documents, générées en arrière-plan par l’app Preview Generator plutôt qu’au premier clic. |
Le contexte de cette installation est réparti sur des articles plus anciens : comment les instances tournaient avant K3s, et la migration vers K3s, étape par étape.
🗄️ next_mariadb : une base de données sur l’hôte
Le rôle installe MariaDB depuis le dépôt que next_packages a configuré (partie 2), le sécurise et crée la base de données de Nextcloud. Son en-tête donne l’ordre, et l’ordre explique le rôle.
Bac à sable avant le premier démarrage
MariaDB reçoit un drop-in systemd avec des réglages de bac à sable (depuis le files/ du rôle) : dans quels répertoires il peut écrire, à quelles fonctions du noyau il ne peut pas toucher. Le commentaire d’en-tête fait une remarque qui mérite d’être répétée : le même genre de restriction n’a volontairement pas été appliqué à SSH dans sa forme la plus stricte, parce que sshd lance des sessions utilisateur et que tout ce que fait un utilisateur ou Ansible en hériterait. MariaDB ne lance rien, donc une restriction n’atteint que la base de données. Depuis le 16/10/2026, son /tmp est un tmpfs privé de 4 Go : tables temporaires et fichiers de tri restent en RAM. À ne pas combiner avec PrivateTmp=yes : testé avec systemd-run, ce réglage l’emporte en silence et garde /tmp sur le disque.
L’ordre de trois tâches compte : écrire le drop-in → daemon_reload directement (pas en handler) → démarrer le service. Un handler s’exécuterait à la fin du play – sur un serveur neuf, MariaDB démarrerait d’abord sans le bac à sable. Ensuite, wait_for attend que le fichier socket apparaisse, signe que MariaDB accepte des connexions.
Configuration, et un répertoire de journaux que personne n’a créé
Un fichier de configuration dans /etc/my.cnf.d/ définit l’essentiel : écouter sur toutes les interfaces (les pods se connectent via l’adresse de l’hôte ; le pare-feu limite le port 3306 au réseau des pods), le buffer pool InnoDB (le cache des pages de la base, dimensionné par hôte dans l’inventaire – environ un quart de la RAM), le comportement d’écriture, les limites de connexions, le journal des requêtes lentes et skip_name_resolve. Ce dernier empêche MariaDB de chercher un nom d’hôte pour chaque connexion de pod – une recherche qui ne peut jamais aboutir pour une adresse de pod et ne coûte que du temps.
Deux histoires se cachent dans les tâches autour. Le journal des requêtes lentes a besoin d’un répertoire que MariaDB ne crée pas. Sans lui, MariaDB journalise une erreur au démarrage puis tourne avec ce journal désactivé en silence – un diagnostic qui paraît activé et ne collecte rien. Le rôle le crée donc, avec le bon type SELinux. Et la rotation des journaux : le fichier logrotate du paquet et un fichier supplémentaire pour le journal lent couvraient les mêmes fichiers, et logrotate échouait chaque nuit avec « duplicate log entry ». Désormais, le rôle gère lui-même le fichier du paquet (il est marqué comme fichier de configuration, donc les mises à jour du paquet déposent un .rpmnew à côté au lieu de l’écraser) et utilise copytruncate, qui n’a besoin d’aucune connexion à la base.
Un avertissement du rôle mérite d’être cité pour quiconque le modifie : les lignes de commentaire à l’intérieur du content: du fichier de configuration font partie du fichier sur le serveur. Changer un commentaire à cet endroit change le fichier, et le handler redémarre MariaDB.
Seulement à la première exécution – et à chaque exécution
- name: MariaDB | Check if the Nextcloud database exists
ansible.builtin.shell: |
set -o pipefail
mysql --protocol=socket --socket="{{ mariadb_socket }}" -u root -p"..." \
-e "SHOW DATABASES LIKE '{{ nextcloud_db }}';" | grep -Fx "{{ nextcloud_db }}"
register: db_check
failed_when: false
changed_when: false
no_log: true
- name: MariaDB | Create Nextcloud database user (host restricted to K3s pod CIDR)
community.mysql.mysql_user:
name: "{{ nextcloud_user }}"
host: "{{ k3s_pod_cidr.split('.')[:2] | join('.') }}.%"
password: "{{ nextcloud_pass }}"
priv: "{{ nextcloud_db }}.*:ALL"
when: db_check.rc != 0
no_log: trueLa première tâche décide s’il s’agit d’un nouveau serveur : la base de données de Nextcloud existe-t-elle ? grep -Fx compare la ligne entière littéralement, donc « nextcloud » ne correspond pas à « nextcloud_old ». Si elle n’existe pas, quatre tâches avec when: db_check.rc != 0 définissent le mot de passe root, écrivent un /root/.my.cnf pour root, créent la base de données et l’utilisateur. L’utilisateur n’est autorisé que depuis le réseau des pods : k3s_pod_cidr.split('.')[:2] | join('.') transforme 10.42.0.0/16 en 10.42, et 10.42.% est le joker de MariaDB pour « toute adresse qui commence ainsi ». Même avec une erreur de pare-feu, la base refuserait cette connexion venant de n’importe où ailleurs.
Ces tâches utilisent les modules community.mysql, qui parlent à MariaDB via Python et sont idempotents : « present » crée ou corrige, « absent » supprime. Ils se connectent par le fichier socket local (login_unix_socket) – et un petit set_fact avec un if/else en ligne choisit celui des deux chemins de socket habituels qui existe sur cet hôte.
D’autres tâches s’exécutent à chaque play : supprimer les comptes anonymes et la base de test que MariaDB fournit (ce que ferait mysql_secure_installation ; certaines mises à niveau les recréent), et maintenir l’utilisateur en lecture seule de l’exporteur. Cet utilisateur partageait autrefois le mot de passe root, ce qui voulait dire que le fichier de configuration de l’exporteur contenait de fait les identifiants de root. Désormais, il a le sien. Sa liste de privilèges porte une petite leçon d’idempotence : MariaDB signale le privilège REPLICATION CLIENT sous son nouveau nom BINLOG MONITOR, et tant que le rôle demandait l’ancien nom, le module voyait une différence et le réattribuait à chaque exécution.
⚡ next_redis : trois lignes et une garde
Redis est installé par le rôle des paquets ; ce rôle ne fait que modifier /etc/redis/redis.conf avec trois tâches lineinfile – écoute sur localhost et sur l’adresse publique de l’hôte (pour que les pods puissent le joindre), protected mode activé, et un mot de passe. backup: true garde une copie horodatée du fichier dès qu’une ligne change.
La ligne du mot de passe a le meilleur commentaire. Redis écoute sur l’adresse publique ; une seule règle de pare-feu le séparait d’Internet. « Qu’une règle de pare-feu échoue en silence n’a rien d’hypothétique ici » – le commentaire fait référence à fail2ban resté mort deux jours sur le blog sans que personne ne s’en aperçoive. Un Redis sans authentification qui détient les verrous de fichiers et les sessions de Nextcloud, on ne le laisse pas derrière une seule couche. Le même mot de passe va à Nextcloud par le Secret Kubernetes, et un assert au début du rôle refuse de tourner si le vault ne l’a pas.
- name: Redis | Flush handlers to apply config changes now
ansible.builtin.meta: flush_handlers
- name: Redis | Wait for Redis to be ready on host IP
ansible.builtin.wait_for:
host: "{{ nextcloud_ipv4 }}"
port: 6379
timeout: 30Encore flush_handlers au lieu d’attendre la fin du play : le rôle de configuration, plus loin dans le même play, parle à Redis à travers les pods. Si Redis ne redémarrait qu’à la fin, il n’écouterait encore que sur localhost au moment où ces commandes s’exécutent. wait_for vérifie ensuite que le port accepte vraiment des connexions sur l’adresse qu’utilisent les pods.
🚀 next_k3s_deploy : Nextcloud, Collabora et push sous forme d’objets
Même structure que le rôle de déploiement du blog : un fichier de tâches par composant dans l’ordre des dépendances, chacun appliqué avec le motif d’application K8s. Une différence de comportement : ici, les attentes ne s’exécutent que when: <apply>.changed – une exécution sans changement n’attend jamais un déploiement.
Services externes : un nom pour quelque chose hors du cluster
Comment un pod joint-il une base de données qui n’est pas dans le cluster ? Avec un Service sans sélecteur de pods et un objet Endpoints rempli à la main :
apiVersion: v1
kind: Service
metadata:
name: mariadb
namespace: nextcloud
spec:
ports:
- port: 3306
---
apiVersion: v1
kind: Endpoints
metadata:
name: mariadb # same name = belongs to the Service above
namespace: nextcloud
subsets:
- addresses:
- ip: <the host's address>
ports:
- port: 3306Normalement, Kubernetes remplit lui-même les Endpoints avec les adresses des pods correspondants. Sans sélecteur, il nous laisse faire, et nous y mettons l’adresse de l’hôte. Dans le cluster, mariadb et redis sont désormais des noms DNS ordinaires ; qu’ils mènent hors du cluster vers l’hôte, Nextcloud ne le voit pas. La même astuce donne à Grafana sur l’hôte un nom vers lequel l’Ingress peut router /grafana/.
Le pod Nextcloud
Le template de déploiement suit le motif du blog – conteneur d’initialisation pour les propriétaires des répertoires, trois conteneurs, capabilities retirées avec un commentaire par capability, somme de contrôle de la configuration dans une annotation pour qu’une ConfigMap modifiée redéploie le pod, sondes startup/readiness/liveness. Les différences en disent long sur Nextcloud :
- Installation depuis des variables d’environnement. Au premier démarrage face à une base vide, l’image installe Nextcloud à partir des variables
MYSQL_*,NEXTCLOUD_ADMIN_*etREDIS_*. Les mots de passe arrivent là encore sous forme de variables*_FILEqui pointent vers des fichiers de Secret montés – l’image le prend en charge. Bon à savoir pour l’exploitation : les valeurs de base de données et d’administration ne sont lues que lors de cette toute première installation. Ensuite, les identifiants vivent dansconfig.php, et changer le Secret seul ne change rien. - Une fenêtre de démarrage de dix minutes. Lors d’un saut de version majeure, l’entrypoint exécute la mise à niveau avant le démarrage de PHP-FPM, et cela peut prendre des minutes. La sonde de démarrage autorise jusqu’à soixante tentatives espacées de dix secondes ; sans elle, la sonde liveness tuerait le conteneur au milieu de sa propre mise à niveau.
- Trois nombres qui doivent concorder. Des uploads jusqu’à 2 Go demandent
PHP_UPLOAD_LIMIT, leclient_max_body_sizede nginx dans le sidecar et la même limite sur l’Ingress – et la limite mémoire de PHP doit dépasser la limite d’upload. Le template le dit là où les valeurs sont définies, parce que changer un seul des trois produit des erreurs qui pointent ailleurs. - La configuration en fichier. Nextcloud charge chaque fichier
*.phpde son répertoire de configuration. Le rôle montecustom.config.phpdepuis une ConfigMap à côté duconfig.phpde Nextcloud : langue et région, durée de session, cache et verrouillage, réglages d’aperçu, niveau de journalisation, le proxy de confiance (le réseau des pods, pour que Nextcloud voie la vraie adresse du visiteur), conservation de 180 jours dans la corbeille. Une valeur définie dans ce fichier l’emporte sur la même clé dansconfig.php, elle ne peut donc pas dériver via l’interface web. - Des caches dimensionnés pour Nextcloud. OPcache reçoit 256 Mo pour jusqu’à 20 000 fichiers – une installation avec ses apps compte 15 500 à 17 800 fichiers PHP, et l’ancienne limite de 10 000 faisait ressortir des fichiers du cache. APCu, le cache par pod que partagent tous les workers, reçoit 128 Mo au lieu des 32 Mo de PHP. Le nombre de workers PHP-FPM est défini par hôte : 30 sur la grande instance et 20 sur la petite (pics mesurés : 17 sur 20 et 10 sur 12), chacun avec une limite de conteneur adaptée.
Collabora et une clé qui doit exister une fois
Collabora signe chaque requête WOPI avec une proof key ; Nextcloud vérifie la signature. Les images plus anciennes généraient cette clé dans un script de démarrage. L’image actuelle démarre directement son serveur et journalisait « Could not open proof RSA key » – cela fonctionnait, mais Nextcloud ne pouvait plus vérifier que les requêtes WOPI venaient vraiment de ce Collabora. Le rôle génère désormais la clé une fois et la stocke comme Secret :
- name: Collabora | Does the WOPI proof key secret exist?
kubernetes.core.k8s_info:
kind: Secret
namespace: nextcloud
name: collabora-proof-key
register: _collabora_proof_key
no_log: true
- name: Collabora | Generate the WOPI proof key (first run only)
ansible.builtin.shell: |
set -o pipefail
openssl genrsa -traditional 2048 2>/dev/null \
| k3s kubectl -n nextcloud create secret generic collabora-proof-key --from-file=proof_key=/dev/stdin
when: _collabora_proof_key.resources | length == 0
no_log: trueTout l’intérêt est dans le pipe : openssl écrit la clé sur sa sortie, kubectl la lit depuis /dev/stdin dans le Secret. La clé privée ne touche jamais le disque du serveur et n’apparaît jamais dans un journal. Le pod la monte, et fsGroup la rend lisible pour l’utilisateur de Collabora sans la rendre lisible pour tous. Après un changement dans Collabora, next_config demande à Nextcloud de relire le document de découverte de Collabora, pour que la nouvelle clé soit utilisée tout de suite.
Deux remarques honnêtes du template Collabora sont bonnes à connaître pour l’exploitant. D’abord, c’est le seul conteneur qui ne retire pas toutes les capabilities : Collabora construit son propre bac à sable par document avec chroot et namespaces et a besoin de capabilities dont les autres n’ont pas besoin. Deviner cet ensemble à partir de la documentation pourrait casser l’édition de documents d’une manière qui ne se voit que lorsqu’un utilisateur ouvre un fichier – c’est donc laissé en l’état jusqu’à pouvoir le tester correctement. Ensuite, les sondes : readiness et liveness interrogent / (deux octets, « OK »), pas le document de découverte de 43 Ko. Le kubelet ne lit que les 10 premiers Ko d’une réponse de sonde et ferme la connexion – et Collabora journalisait chacune de ces fermetures comme deux erreurs, environ 1 300 lignes tous les deux jours par serveur. Seule la sonde de démarrage utilise encore la découverte, une fois, parce qu’elle prouve que tout le service est en marche.
NetworkPolicy : Collabora est le maillon risqué
La règle du blog protège la base de données de tout sauf de WordPress. Celle de Nextcloud a une autre cible : Collabora analyse des documents arbitraires que les utilisateurs envoient – un point d’entrée plus plausible pour un attaquant que la plupart du code PHP –, et il n’a aucune raison de parler un jour à MariaDB ou à Redis. La règle autorise donc à Collabora exactement ce dont on a vérifié qu’il avait besoin : les requêtes de l’Ingress et du pod Nextcloud, le DNS, et les rappels WOPI vers Nextcloud – le port 443 sur l’adresse de l’hôte, et explicitement pas 3306 ni 6379 sur cette même adresse. Si Collabora était compromis, la base de données resterait hors d’atteinte.
Ingress : un hôte, quatre minions
La même structure master/minion que sur le blog, avec plus de minions : les routes de Nextcloud (avec les en-têtes de sécurité), Grafana et – si push est activé – /push/, avec des en-têtes WebSocket et un long délai. Le minion push réécrit /push/x en /x, parce que notify_push sert ses points d’accès à sa racine, et son nom compte : le contrôleur ordonne les locations regex des minions selon le nom de l’Ingress, et le mauvais nom laissait la route fourre-tout de Nextcloud répondre en premier (« 404 pour /push/test/cookie » au premier déploiement). Collabora a son propre master pour son propre nom d’hôte.
Le CronJob d’arrière-plan
Toutes les cinq minutes, un pod exécute cron.php en www-data, avec les mêmes volumes que Nextcloud. Deux détails : il garde les capabilities SETUID/SETGID parce qu’il utilise runuser pour passer de root à www-data, ce qui échoue sans elles ; et il reçoit le mot de passe Redis, parce que les tâches de fond prennent les mêmes verrous de fichiers que les workers web.
apps.yml : seulement les écarts
Quelles apps sont activées ou non doit être reproductible – mais pas sous forme de liste complète. Les apps fournies appartiennent à l’image et changent entre versions majeures. L’inventaire ne liste donc que les écarts délibérés : nextcloud_apps_disabled (désactivées de force et maintenues désactivées au fil des mises à niveau) et nextcloud_apps_enabled (activées – si possible).
- name: Apps | Ensure required apps are enabled (only if compatible with this major)
ansible.builtin.command: >-
k3s kubectl exec -n nextcloud {{ nc_pod.stdout }} -c nextcloud-fpm --
php occ app:enable {{ item }}
loop: "{{ nextcloud_apps_enabled }}"
register: nc_app_enable
changed_when: "'already enabled' not in (nc_app_enable.stdout | lower)"
failed_when: false
- name: Apps | Report apps left disabled (no version compatible with this major)
ansible.builtin.debug:
msg: "App '{{ item.item }}' could not be enabled and stays disabled ..."
loop: "{{ nc_app_enable.results | default([]) }}"
when:
- item.rc is defined
- item.rc != 0Après une mise à niveau majeure, une app n’a parfois pas encore de version compatible. Le programme de mise à niveau de Nextcloud la laisse simplement désactivée, et ce rôle fait de même : failed_when: false à l’activation, puis une seconde tâche boucle sur les résultats et indique exactement quelles apps sont restées désactivées. Une exécution qui échouerait sinon sur une app va maintenant jusqu’au bout et vous dit où regarder. (occ s’exécute ici en root : depuis Nextcloud 34, le script occ passe de lui-même au propriétaire de config.php. Le rôle de configuration utilise tout de même explicitement runuser -u www-data.)
notify_push.yml : binaire et app au même pas
Le service push s’active par hôte (nextcloud_notify_push_enabled). Le binaire est livré dans l’app notify_push, l’ordre est donc : installer et activer l’app, lire sa version, déployer le pod pour exactement cette version, lancer l’autotest. Deux techniques rendent cela robuste :
- La version en annotation. Après une mise à jour de l’app, le processus en cours a encore l’ancien binaire. La version installée de l’app est placée en annotation dans le template du pod – une nouvelle version change le template, et Kubernetes redéploie le pod. Binaire et app ne peuvent jamais diverger.
- Une boucle qui tient compte de
when. Ansible évalue laloopd’une tâche avant sonwhen. Le template a besoin de la version de l’app, qui n’existe pas quand push est désactivé – son rendu échouerait donc alors même que la tâche va être sautée. La boucle renvoie par conséquent une liste vide quand push est désactivé :{{ [] if not (nextcloud_notify_push_enabled | bool) else ... }}. Un détail qu’on n’apprend qu’en se cognant dedans.
La dernière étape avait son propre bug d’idempotence. occ notify_push:setup enregistre le point d’accès et affiche « configuration saved » à chaque fois, il signalait donc un changement à chaque exécution. Désormais, le rôle lit d’abord le point d’accès enregistré : s’il est déjà juste, il n’exécute que notify_push:self-test – les mêmes vérifications, rien d’écrit. Un setup raté est d’ailleurs sans danger : Nextcloud ne bascule les clients sur push qu’une fois l’autotest réussi ; d’ici là, ils continuent d’interroger le serveur.
| Élément | Où | Ce qu’il faut retenir |
|---|---|---|
daemon_reload en tâche, pas en handler | next_mariadb | Quand la tâche suivante en a besoin |
grep -Fx + when: db_check.rc != 0 | next_mariadb | Une mise en place unique sans bloc |
split / join sur un CIDR | next_mariadb | Déduire un motif d’hôte MariaDB de l’inventaire |
modules community.mysql | next_mariadb | Des utilisateurs et des bases idempotents |
lineinfile backup: true | next_redis | Garder la version précédente d’un fichier de configuration |
flush_handlers + wait_for | next_redis | Redémarrer maintenant et prouver qu’il écoute |
| Service sans sélecteur + Endpoints | services externes | Donner un nom dans le cluster à un service de l’hôte |
openssl ... | kubectl create secret --from-file=/dev/stdin | collabora.yml | Créer un secret qui ne touche jamais le disque |
failed_when: false + un rapport sur results | apps.yml | Tolérer, mais le dire |
| annotation de version | notify-push | Redéployer un pod quand quelque chose hors de son image change |
boucle évaluée avant when | notify_push.yml | Rendre la boucle elle-même sûre quand la tâche est sautée |
⚙️ next_config : lire, comparer, définir
Nextcloud tourne ; maintenant ses réglages. L’outil est occ, la ligne de commande de Nextcloud, exécutée dans le pod en tant que propriétaire de config.php :
k3s kubectl exec deployment/nextcloud -n nextcloud -c nextcloud-fpm \
-- runuser -u www-data -- php /var/www/html/occ <command>Le rôle stocke la première partie une fois avec set_fact (occ_exec) et l’utilise dans chaque tâche. Puis il applique à chaque valeur un motif – celui que son en-tête appelle « get, compare, set » : lire la valeur actuelle avec occ config:...:get et l’enregistrer ; n’écrire que when elle diffère. Une exécution qui ne change rien ne signale rien, et une valeur que quelqu’un a modifiée dans l’interface d’administration est remise en place et apparaît comme un changement. C’est cette seconde partie qui rend le contrôle de dérive pertinent pour les réglages de l’application, pas seulement pour les fichiers.
nc_config.yml : domaines, URL et mail
Les domaines de confiance et l’URL pour la ligne de commande sont de simples paires lire/comparer/définir. Les réglages de messagerie sont plus intéressants, car il y en a neuf et l’un d’eux est un mot de passe :
- name: Config | Read current mail settings
ansible.builtin.command: "{{ occ_exec }} ... config:system:get {{ item.key }}"
loop: "{{ nc_mail_settings }}"
register: nc_mail_current
changed_when: false
failed_when: false # rc 1 = not set yet
- name: Config | Set mail settings that differ
ansible.builtin.command:
argv: "{{ ... + ['config:system:set', item.item.key, '--value', item.item.value] }}"
loop: "{{ nc_mail_current.results }}"
when: item.rc != 0 or (item.stdout | trim) != (item.item.value | string)
no_log: true
- name: Config | Report mail settings a real run would change (--check only)
ansible.builtin.debug: ...
when: ansible_check_mode and (...same condition...)
changed_when: trueTrois idées au même endroit. La tâche d’écriture boucle sur les résultats de la tâche de lecture, si bien que chaque élément porte à la fois la valeur voulue (item.item.value) et la valeur actuelle (item.stdout). argv transmet les valeurs telles quelles – un guillemet ou une espace dans le mot de passe ne peut pas casser la ligne de commande. Et comme les tâches command sont sautées en simulation, le changement en attente y serait invisible ; la troisième tâche n’existe que pour le mode check et indique ce qu’une vraie exécution changerait. L’ancienne version réécrivait les neuf valeurs à chaque exécution et signalait « ok » dans tous les cas, si bien qu’un mot de passe SMTP modifié n’apparaissait jamais.
db_maintenance.yml, apps.yml, wopi.yml
- Maintenance de la base :
occ db:add-missing-indicesetocc maintenance:repair --include-expensive– les deux choses que la vue d’ensemble d’administration de Nextcloud réclame sinon après une mise à niveau. Les deux vérifient ce qui manque et ne font rien quand tout est en place. - Bureautique et aperçus : Nextcloud Office (l’app
richdocuments) est téléchargé et décompressé avec les propres modules d’Ansible au lieu deocc app:install, parce que ce dernier tronquait de façon reproductible un fichier de cette app, toujours au même octet. Contournerocc, c’est aussi contourner sa vérification de signature – le rôle la rétablit donc avecocc integrity:check-appet le certificat livré avec l’app, et avertit en cas d’écart. L’app Preview Generator est installée et activée normalement. - WOPI : la liste des adresses autorisées à appeler le point d’accès WOPI de Nextcloud (elle doit contenir le réseau des pods, car Collabora rappelle depuis l’adresse de son pod) et l’URL de Collabora – lire, comparer, définir. Une tâche utilise une variable enregistrée par
next_k3s_deploy, un rôle antérieur du même play : les variables enregistrées restent disponibles pour tous les rôles suivants sur cet hôte.collabora_apply is definedprotège les exécutions où ce rôle a été sauté par tags.
password_policy.yml : un verrouillage et une leçon
Le dernier fichier active l’app de politique de mots de passe et définit ses règles : au moins 12 caractères, pas de mots de passe courants, et une vérification contre les mots de passe divulgués connus. Cette vérification utilise le service Have I Been Pwned avec le k-anonymat : seuls les cinq premiers caractères du hachage du mot de passe quittent le serveur, et le service renvoie tous les hachages divulgués ayant ce préfixe pour une comparaison locale – le mot de passe lui-même n’est jamais envoyé. Les règles de classes de caractères (majuscules, chiffres, caractères spéciaux) sont explicitement désactivées : la longueur et la vérification de fuite comptent davantage, et les règles de classes produisent surtout des « Password1! ».
Ce fichier contient aussi l’incident le plus instructif de tout le dépôt. Quand l’app de politique a été activée sur un serveur, chaque utilisateur dont le mot de passe avait plus de 300 jours a été verrouillé à la connexion suivante – l’administrateur compris. La cause était un réglage resté de l’ancien serveur, expiration = 300, qui dormait inaperçu dans la base jusqu’à ce que l’app qui le lit soit activée. Depuis, le rôle définit explicitement chaque clé de la politique, y compris celles qui doivent être désactivées (expiration = 0), si bien qu’aucun reliquat ne peut revenir. La leçon générale pour les exploitants : avant d’activer une app, lisez la configuration qu’elle a déjà.
Un second détail est technique : Nextcloud 34 stocke les réglages des apps avec un type, et les anciennes valeurs des versions précédentes n’en ont pas. Définir une telle clé avec --type=boolean s’arrête sur une invite interactive « type yes to confirm » – ce qui interrompt une exécution Ansible. Une ancienne valeur différente est donc d’abord supprimée, puis réécrite avec son type.
✅ nextcloud_verify
La même structure que sur le blog (partie 3) : des vérifications qui ne font que collecter, un rapport, un mail et un assert à la fin ; les bases hôte, Kubernetes et HTTP viennent de common_verify. Les vérifications propres à Nextcloud : MariaDB et Redis tournent sur l’hôte, tous les pods et Collabora tournent, status.php en bonne santé, la page de connexion et la découverte de Collabora joignables, config.php et le répertoire de données non joignables depuis l’extérieur, tâches de fond en mode cron, Redis et MariaDB joignables depuis le pod Nextcloud, et l’URL bureautique correctement configurée.
🔄 nextcloud-update.yml
La partie 1 a présenté ses garde-fous ; voici la séquence complète, car mettre à jour un cloud de fichiers dont des gens dépendent mérite du soin :
- Refuser sans
--limit– une instance à la fois. - Refuser une instance en mauvaise santé : installée, pas en mode maintenance, pas de mise à niveau de base en attente. Pourquoi ce garde-fou existe.
- Images reconstruites :
common_image_refreshtrouve les Deployments dont le tag d’image a été reconstruit en amont ; seuls ceux-là sont redémarrés, dans un ordre fixe. occ upgrade– normalement fait par l’entrypoint au démarrage, rappelé ici comme filet de sécurité. Seul « Update successful » dans la sortie compte comme un changement.occ app:update --all– les mises à jour des apps.- Un second redémarrage, et celui-ci mérite une explication. Nextcloud tourne avec
opcache.validate_timestamps=0: PHP ne vérifie jamais si un fichier a changé, le code compilé reste en mémoire pendant toute la vie du pod. C’est rapide – et cela signifie que les fichiers écrasés parocc upgradeet les mises à jour d’apps continueraient de tourner comme l’ancien code jusqu’au prochain redémarrage sans rapport, correctifs de sécurité compris. Le pod est donc redémarré une fois de plus après les mises à jour. - Contrôle final : les mêmes conditions de santé qu’avant. L’exécution échoue si Nextcloud n’est pas en bonne santé ensuite.
Les étapes 3 à 7 sont dans un block. Le rescue affiche le statut, les pods et les events, et les dernières lignes de nextcloud.log. Puis il prend une décision prudente : il ne quitte le mode maintenance que si la mise à niveau elle-même est terminée (aucune mise à niveau de base en attente). Si une mise à niveau est en attente, le mode maintenance reste actif – sinon, les utilisateurs arriveraient sur une instance à moitié mise à niveau. Ensuite, il s’arrête et indique quoi faire.
🛠️ En exploitation : Nextcloud au quotidien
Regarder
# Pods: nextcloud, collabora, notify-push; cron pods come and go
k3s kubectl get pods -n nextcloud
# Nextcloud's own view: version, maintenance mode, pending upgrade
k3s kubectl exec -n nextcloud deployment/nextcloud -c nextcloud-fpm -- \
runuser -u www-data -- php occ status
# Nextcloud's log (warnings and errors), on the host
tail -f /data/nextcloud.log
# Background jobs: when did cron last run? (Admin settings → Basic settings shows it too)
k3s kubectl get jobs -n nextcloud
# Disk: user data, trash, versions
df -h /dataLa routine
| Quand | Quoi | Comment |
|---|---|---|
| Quand des mises à jour sont disponibles | Images reconstruites, mises à jour d’apps, versions correctives | ansible-playbook nextcloud-update.yml --limit <host>, d’abord --check |
| Pour une nouvelle version majeure | Changer le tag d’image dans versions.yml – une version majeure à la fois | lire les notes de version, prendre un snapshot, puis ansible-playbook nextcloud-k3s.yml --limit <host> (l’entrypoint exécute la mise à niveau au démarrage de la nouvelle image) |
| Après chaque exécution | Lire le mail de vérification | tout vert, ou les noms des vérifications en échec |
| De temps en temps | Regarder la vue d’ensemble d’administration | Paramètres d’administration → Vue d’ensemble liste les avertissements que Nextcloud a trouvés lui-même |
Quand quelque chose tourne mal
- « Échec du chargement de la bureautique », et un nouvel essai fonctionne : un raté passager entre navigateur, Collabora et Nextcloud – généralement sans gravité. Si cela arrive souvent, Collabora a besoin de plus de processus prédémarrés ou de plus de mémoire (
collabora_num_preallocet la limite mémoire, par hôte dans l’inventaire). La grande instance garde 10 processus préchauffés dans 8 Gio, la petite 6 dans 4 Gio. - Nextcloud bloqué en mode maintenance après une mise à jour ratée : regardez
occ status. Si une mise à niveau est en attente, lancezocc upgradeet lisez sa sortie ; seulement ensuiteocc maintenance:mode --off. Ne le désactivez jamais tant qu’une mise à niveau est en attente. - Utilisateurs verrouillés par la politique de mots de passe : vérifiez
occ config:app:get password_policy expiration– cela doit valoir 0. Le rôle l’épingle, mais c’est la première chose à regarder. - Le disque se remplit : l’alerte arrive à 80 pour cent (partie 6). La corbeille garde les fichiers supprimés 180 jours et se nettoie toute seule ; la corbeille d’un seul utilisateur peut tout de même être grosse. Les aperçus peuvent être régénérés et ne sont pas une vraie réserve.
occ trashbin:cleanup --all-usersvide toutes les corbeilles – avec l’accord des utilisateurs, pas par surprise. - Tout le serveur a disparu : en installer un nouveau avec le playbook complet, puis restaurer le dump de la base, le répertoire de l’application (avec
config.phpet les apps installées) et les données des utilisateurs depuis la sauvegarde externe avec le script de restauration, et relancer le playbook une fois. Le manuel d’exploitation du dépôt liste chaque étape.
🧭 La suite
Avec la partie 4, les deux applications sont couvertes. La partie 5 rassemble chaque rôle de sécurité au même endroit – SSH, le pare-feu, le durcissement CIS, SELinux, l’audit, AIDE, rkhunter, ClamAV et fail2ban – et explique contre quoi protège chaque couche. La partie 6 traite de la supervision : exporteurs, Prometheus, Grafana et le chien de garde Monit, et ce que signifie chaque alerte.

