Eseguire mount Rclone in Docker in modo affidabile
Affinché un mount FUSE da un container funzioni anche sull'host e in altri container, devono collaborare la propagazione dei mount, AppArmor e il ripristino dopo i guasti.
Un mount Rclone viene eseguito in un container Docker, ma deve essere disponibile anche sull’host e in altri container. Per questo, gli eventi di mount devono attraversare più namespace. Una sola opzione Compose non è sufficiente.
In un test pratico con Ubuntu 25.10, kernel 6.17 e Docker 29.6 si sono verificati tre errori indipendenti: Docker ha degradato rshared senza segnalarlo, AppArmor ha bloccato fusermount3, e un container consumer è rimasto collegato al vecchio mount dopo un riavvio. Il caso d’uso concreto era un archivio cloud per Paperless-ngx; gli stessi meccanismi valgono anche per altri strumenti FUSE come sshfs.
1. La sorgente host deve essere essa stessa shared
Affinché un mount dal container raggiunga l’host, il bind necessita della propagazione rshared:
volumes:
- type: bind
source: /srv/storage/media
target: /data
bind:
propagation: rshared
rshared funziona solo se la sorgente del bind sull’host è essa stessa un mountpoint con propagazione Shared. Una normale directory non soddisfa questo requisito. Docker non segnala comunque alcun errore, ma usa silenziosamente una propagazione più debole. Lo si riconosce in /proc/self/mountinfo all’interno del container:
1938 2077 8:2 /srv/storage/media /data rw,relatime master:1 - ext4 /dev/sda2 rw
master:1 significa propagazione slave: i mount entrano dall’host, ma non escono mai. Corretto sarebbe shared:N. La soluzione è un bind della sorgente su sé stessa, contrassegnato come shared:
mount --bind /srv/storage/media /srv/storage/media
mount --make-shared /srv/storage/media
Perché sopravviva a un riavvio, va inserito in una unit systemd con Before=docker.service. Verifica: findmnt -no PROPAGATION /srv/storage/media deve restituire shared.
2. AppArmor verifica fusermount3 anche nel container
Con la propagazione corretta è comparso il problema successivo. Il mount sul percorso condiviso continuava a fallire:
NOTICE: mount helper error: fusermount3: mount failed: Permission denied
CRITICAL: Fatal error: failed to mount FUSE fs: fusermount: exit status 1
I consueti permessi aggiuntivi del container non cambiavano nulla: né CAP_SYS_ADMIN e /dev/fuse né unconfined o persino --privileged. Un mount tmpfs funzionava sulla stessa destinazione, e FUSE funzionava su altri percorsi. Solo il log di audit del kernel ha mostrato la vera causa:
audit: type=1400 apparmor="DENIED" operation="mount" class="mount"
info="failed mntpnt match" error=-13 profile="fusermount3"
name="/data/documents/originals/" fstype="fuse.rclone"
Ubuntu include un profilo AppArmor per il binario fusermount3 che consente mount FUSE solo su una lista positiva di pattern di mountpoint. Questo profilo si applica anche a fusermount3 nel container. Determinante è il percorso come lo vede il container:
mount fstype=@{fuse_types} ... -> @{HOME}/**/,
mount fstype=@{fuse_types} ... -> /mnt/{,**/},
mount fstype=@{fuse_types} ... -> /media/**/,
mount fstype=@{fuse_types} ... -> /tmp/**/,
/data non è nell’elenco, così come /srv. Il fatto che il container venga eseguito senza confinamento non aiuta: il profilo è associato al file eseguibile, non al container.
La soluzione sfrutta il fatto che solo fusermount3 è soggetto al profilo, mentre un normale mount --bind non lo è: montare FUSE su un percorso consentito e pubblicarlo da lì sul percorso condiviso tramite bind.
rclone mount remote:pfad /mnt/inner/dokumente --allow-other --vfs-cache-mode full &
# attendere che il mount risponda, quindi:
mount --bind /mnt/inner/dokumente /data/dokumente
Il bind è una normale chiamata mount(2) e, come ogni altra, si propaga all’host attraverso il percorso Shared. Questo è stato verificato fino a un secondo container, che poteva leggere i file come uid 1000. --allow-other è obbligatorio non appena un utente diverso da quello che ha effettuato il mount accede ai file; nel container Rclone, user_allow_other deve essere presente in /etc/fuse.conf (già così nell’immagine ufficiale).
3. I consumer necessitano di rslave
Il terzo problema riguarda l’altra parte. Se il processo Rclone si interrompe e il mount viene ricostruito, l’host lo vede immediatamente. Un container che ha incluso normalmente il percorso tramite bind, invece, non lo vede:
ls: cannot access '/usr/src/app/media': Transport endpoint is not connected
Per impostazione predefinita Docker usa rprivate per i bind mount: un mount creato sull’host dopo l’avvio del container non raggiunge mai il suo namespace di mount. Il container rimane collegato al mount FUSE non più connesso finché non viene ricreato. La soluzione richiede una sola riga:
volumes:
- type: bind
source: /srv/storage/media
target: /usr/src/app/media
bind:
propagation: rslave
Con rslave, l’host inoltra al container i nuovi eventi di mount. Nel test, dopo un mount terminato forzatamente e ricostruito, il consumer ha rivisto tutti i file senza un proprio riavvio. Il contatore dei riavvii è rimasto a zero.
Ripristino senza intervento manuale
Dai tre elementi risulta un modello complessivo robusto che non richiede un demone watchdog:
- Il container di mount controlla i propri mount in un ciclo. Se uno non risponde più, termina con un codice di errore.
restart: unless-stoppedfa riavviare il container da Docker.- All’avvio, il container rimuove innanzitutto i mount orfani dell’esecuzione precedente: un bind orfano sul percorso di destinazione altrimenti bloccherebbe una nuova pubblicazione e un utente non privilegiato non può rimuoverlo dall’host. Nel container è possibile, e l’umount si propaga verso l’esterno:
while grep -q " /data/dokumente " /proc/self/mountinfo; do
umount -l /data/dokumente 2>/dev/null || break
done
- Poi monta e pubblica normalmente; i consumer con
rslaveadottano automaticamente il mount aggiornato.
Nel test, l’intera catena ha richiesto 160 secondi: il processo Rclone è stato terminato, l’errore rilevato, il container riavviato, il mount orfano rimosso e il nuovo mount nuovamente pubblicato. Il container consumer è rimasto in esecuzione nel frattempo e ha notato solo una breve interruzione.
Chi esegue Rclone direttamente sull’host tramite systemd evita i primi due problemi e necessita solo di rslave nei container consumer. Il container aggiuntivo conviene soprattutto se l’host deve rimanere privo di installazioni Rclone o se si vogliono gestire più mount in modo uniforme. In tal caso, tutti e tre i livelli devono essere configurati consapevolmente.
Commenti
I commenti vengono caricati da GitHub / Giscus.