feat(system) — masquer gvfs-udisks2 sur les 3 Pi : un démon du bureau fuyait 2,5 Gi sur pi2

Sur pi2, gvfs-udisks2-volume-monitor (session de bureau de `pi`) occupait
2,54 Gi de PSS après 154 jours, contre 5 à 9 Mi sur pi1 et pi3. pi2
saturé, le moteur Longhorn a manqué son délai de 8 s et ext4 a abandonné
son journal sur quatre volumes (tools#49). Son redémarrage a rendu 2,6 Gi.

Le playbook masque l'unité utilisateur en portée globale (le bus de
session tourne en --systemd-activation, donc le bus ne peut plus la
réveiller), arrête l'instance en cours, puis relit le masque et l'absence
de processus. Masquer plutôt que retirer `gvfs-daemons`, dont dépend tout
gvfs et le bureau de Raspberry Pi OS.

Refs arcodange-org/tools#52

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-15 12:25:06 +02:00
co-authored by Claude Opus 5
parent 93e7abfc57
commit 9acd91a7cd
2 changed files with 92 additions and 0 deletions
@@ -0,0 +1,88 @@
---
# Couper le moniteur de disques du bureau (gvfs-udisks2-volume-monitor).
#
# Incident arcodange-org/tools#52 (2026-09-15) : sur pi2, ce démon de la
# session de bureau de `pi` occupait 2,54 Gi de PSS après 154 jours de marche,
# contre 5 à 9 Mi sur pi1 et pi3 — un tiers de la RAM du nœud, invisible du
# scheduler k8s. pi2 saturé, le moteur Longhorn a manqué son délai de 8 s, et
# ext4 a abandonné son journal sur quatre volumes (tools#49). Son redémarrage
# a rendu 2,6 Gi d'un coup (`free -m` : available 1 728 → 4 340 Mi).
#
# Il surveille les périphériques bloc pour le gestionnaire de fichiers :
# chaque attachement iSCSI de Longhorn en est un. Les Pi sont des serveurs, on
# n'y monte pas de clé USB depuis le bureau : on le coupe au lieu de le
# redémarrer périodiquement.
#
# Pourquoi MASQUER l'unité et pas retirer le paquet : le binaire vit dans
# `gvfs-daemons`, dont dépend tout gvfs (et, par lui, le bureau de Raspberry Pi
# OS) — le retirer arracherait des paquets sans rapport. Masquer est ciblé,
# réversible (`systemctl --global unmask`), et suffit : le bus de session tourne
# en `--systemd-activation`, et le fichier D-Bus du service porte
# `SystemdService=gvfs-udisks2-volume-monitor.service` — une unité masquée ne
# peut donc plus être réveillée par le bus.
#
# `--global` (et pas `--user`) : le masque vit dans /etc/systemd/user/ et vaut
# pour tout utilisateur, sans dépendre du $HOME de `pi`.
- name: Couper gvfs-udisks2-volume-monitor (moniteur de disques du bureau)
hosts: raspberries:&local
gather_facts: no
vars:
gvfs_udisks2_unit: gvfs-udisks2-volume-monitor.service
gvfs_udisks2_desktop_user: pi
tasks:
- name: Masquer l'unité utilisateur pour tous les utilisateurs
become: yes
ansible.builtin.systemd:
name: "{{ gvfs_udisks2_unit }}"
scope: global
masked: yes
- name: Relever l'uid de l'utilisateur du bureau
ansible.builtin.command: id -u {{ gvfs_udisks2_desktop_user }}
register: gvfs_udisks2_uid
changed_when: false
- name: Savoir si une session systemd utilisateur tourne
ansible.builtin.stat:
path: "/run/user/{{ gvfs_udisks2_uid.stdout }}/systemd/private"
register: gvfs_udisks2_user_manager
# On se connecte déjà en `pi` : pas de become, le gestionnaire utilisateur
# se joint par son XDG_RUNTIME_DIR.
- name: Arrêter l'instance en cours dans la session de bureau
when: gvfs_udisks2_user_manager.stat.exists
environment:
XDG_RUNTIME_DIR: "/run/user/{{ gvfs_udisks2_uid.stdout }}"
ansible.builtin.systemd:
name: "{{ gvfs_udisks2_unit }}"
scope: user
state: stopped
# On relit le système vivant, pas la tâche : le masque doit être posé ET
# aucun processus ne doit survivre (ni avoir été réveillé par le bus).
- name: Relire le masque global
ansible.builtin.command: systemctl --global is-enabled {{ gvfs_udisks2_unit }}
register: gvfs_udisks2_mask
changed_when: false
failed_when: false
- name: Chercher un processus survivant
ansible.builtin.command: pgrep -x gvfs-udisks2-vo
register: gvfs_udisks2_pgrep
changed_when: false
failed_when: false
- name: Vérifier que le moniteur est masqué et arrêté
ansible.builtin.assert:
that:
- gvfs_udisks2_mask.stdout == "masked"
- gvfs_udisks2_pgrep.rc == 1
fail_msg: >-
gvfs-udisks2 n'est pas coupé sur {{ inventory_hostname }} :
masque={{ gvfs_udisks2_mask.stdout }}, pgrep rc={{ gvfs_udisks2_pgrep.rc }}
({{ gvfs_udisks2_pgrep.stdout }}).
success_msg: "gvfs-udisks2 masqué et arrêté sur {{ inventory_hostname }}"
@@ -8,6 +8,10 @@
- name: Filet mémoire earlyoom
ansible.builtin.import_playbook: earlyoom.yml
# Un démon du bureau qui fuyait 2,5 Gi sur pi2 (tools#52).
- name: Couper le moniteur de disques du bureau
ansible.builtin.import_playbook: gvfs_udisks2.yml
- name: dns
ansible.builtin.import_playbook: ../dns/dns.yml