Dans le noyau 3.18 un nouveau système de fichiers est apparu : overlayfs. Je l’avais déjà utilisé à maintes reprises sur des systèmes embarqués, mais cela nécessitait jusqu’alors l’ajout de patches supplémentaires. J’ai eu envie de vérifier si cette fonctionnalité à présent disponible dans le nouveau noyau mainline fonctionnait comme je la connaissais auparavant.
Système Overlayfs
Le système overlayfs n’est pas à proprement parler un véritable système de fichiers, mais un mécanisme de montage permettant de superposer dans un répertoire le contenu de plusieurs autres répertoires. La configuration la plus simple est celle de deux répertoires, appelons-les upper et lower que l’on peut imaginer comme des calques contenant des fichiers. Lorsque l’on veut lire le contenu d’un fichier, on regarde tout d’abord sur le calque supérieur – upper – puis, si aucun fichier ne correspond au nom recherché on ira examiner ensuite le contenu du calque inférieur.
Ce qui est particulièrement intéressant dans un contexte embarqué, est le fait que le répertoire lower est considéré comme accessible en lecture seulement. Les modifications que l’on apporte aux fichiers issus de la superposition des deux calques affecteront uniquement le calque supérieur, et jamais celui sous-jacent (qui peut lui même être un montage overlay d’autres répertoires…)
Cela implique une gestion interne assez compliquée :
- Lorsqu’on veut modifier un fichier du répertoire lower, cela crée tout d’abord une copie dans le répertoire upper qui sera effectivement modifiée. Le fichier de lower ne sera jamais touché.
- La suppression d’un fichier existant uniquement dans le répertoire upper est assez simple. En revanche, effacer un fichier appartenant à lower met en œuvre une mécanique complexe, car il ne faut pas modifier réellement le répertoire lower (considéré comme accessible en lecture seulement). Le système overlayfs utilise un troisième répertoire de travail, dans lequel il mémorise l’état des éléments de lower qu’il doit faire disparaître de l’empilement des calques
Dans le cas d’un système embarqué, la robustesse face aux erreurs liées aux systèmes de fichiers est une préoccupation constante. Dans ce but, on préfère généralement monter les partitions contenant les fichiers système (/bin
, /lib
, /etc
, /usr
…) en lecture seule. Toutefois, il est parfois nécessaire de modifier des éléments de configuration (par exemple le nom d’hôte stocké dans /etc/hostname
). La possibilité de disposer d’une partition indépendante que l’on viendra placer en calque supérieur dans un répertoire dont le contenu reste inamovible est très intéressante.
Pour tester le nouveau système de fichiers, j’ai choisi de construire un système minimal avec Buildroot et le nouveau noyau Linux 3.18 pour une cible que je trouve plutôt sympathique : la carte OLinuXino iMX233 d’Olimex (https://www.olimex.com/Products/OLinuXino/iMX233) plus précisément dans sa version « micro ».
Noyau Linux 3.18 sur OLinuXino iMX233
Pour préparer mon image, j’ai fait appel à Buildroot en utilisant la dernière version, celle de novembre 2014.
[~]$ wget http://buildroot.uclibc.org/downloads/buildroot-2014.11.tar.bz2 [~]$ tar xjf buildroot-2014.11.tar.bz2 [~]$ cd buildroot-2014.11/ [buildroot-2014.11]$ ls configs/ [...] atstk100x_defconfig nitrogen6x_defconfig qmx6_defconfig beaglebone_defconfig olimex_imx233_olinuxino_defconfig raspberrypi_defconfig calao_qil_a9260_defconfig openblocks_a6_defconfig s6lx9_microboard_defconfig [...]
Nous voyons qu’il existe bien une cible olimex_imx233_olinuxino_defconfig
prédéfinie pour Buildroot, vérifions sa configuration…
[buildroot-2014.11]$ make olimex_imx233_olinuxino_defconfig [...] [buildroot-2014.11]$ make menuconfig
Dans le menu de configuration « Kernel« , nous voyons que la version actuellement proposée est le noyau 3.17. Il nous suffit de remplacer cette version par 3.18. Nous devons également activer le support du système overlayfs dans la configuration du noyau :
[buildroot-2014.11]$ make linux-menuconfig
Ceci s’obtient dans le menu « File Systems » en activant l’option « Overlay filesystem support« . Puis nous lançons la compilation avec un simple
[buildroot-2014.11]$ make
Au bout de quelques minutes, nous obtenons :
[buildroot-2014.11]$ ls output/images/ imx23_olinuxino_dev_linux.sb imx23-olinuxino.dtb rootfs.ext2 zImage
Nous devons préparer une carte micro-SD pour notre système cible. Il faut nécessairement créer au moins deux partitions, pour accueillir le système de boot et l’arborescence des fichiers. J’ai choisi de rajouter une troisième partition pour le calque supérieur.
- Partition 1 : 16 Mo. On notera – c’est une particularité de l’OLinuXino iMX233 – qu’il faut obligatoirement que cette partition de démarrage soit dotée du type 53 (hexadécimal).
- Partition 2 : 32 Mo ext4 pour le système en lecture-seule.
- Partition 3 : 32 Mo ext4 pour les modifications apportées à la configuration système.
J’insère une carte SD vierge sur mon PC de développement, elle apparaît sous le nom /dev/sdb/
(à vérifier sur votre système !)
[buildroot-2014.11]$ sudo umount /dev/sdb? [buildroot-2014.11]$ sudo fdisk /dev/sdb Commande (m pour l'aide) : p Disque /dev/sdb : 3924 Mo, 3924819968 octets 28 têtes, 40 secteurs/piste, 6844 cylindres, total 7665664 secteurs Unités = secteurs de 1 * 512 = 512 octets Taille de secteur (logique / physique) : 512 octets / 512 octets taille d'E/S (minimale / optimale) : 512 octets / 512 octets Identifiant de disque : 0x361d7260 Périphérique Amorçage Début Fin Blocs Id. Système /dev/sdb1 2048 7665663 3831808 c W95 FAT32 (LBA) Commande (m pour l'aide) : d Partition sélectionnée 1 Commande (m pour l'aide) : n Partition type: p primary (0 primary, 0 extended, 4 free) e extended Select (default p): p Numéro de partition (1-4, 1 par défaut) : 1 Premier secteur (2048-7665663, 2048 par défaut) : (entrée) Utilisation de la valeur 2048 par défaut Dernier secteur, +secteurs ou +taille{K,M,G} (2048-7665663, 7665663 par défaut) : +16M Commande (m pour l'aide) : t Partition sélectionnée 1 Code Hexa (taper L pour lister les codes): 53 Type système de partition modifié de 1 à 53 (OnTrack DM6 Aux3) Commande (m pour l'aide) : n Partition type: p primary (1 primary, 0 extended, 3 free) e extended Select (default p): p Numéro de partition (1-4, 2 par défaut) : 2 Premier secteur (34816-7665663, 34816 par défaut) : (Entrée) Utilisation de la valeur 34816 par défaut Dernier secteur, +secteurs ou +taille{K,M,G} (34816-7665663, 7665663 par défaut) : +32M Commande (m pour l'aide) : n Partition type: p primary (2 primary, 0 extended, 2 free) e extended Select (default p): p Numéro de partition (1-4, 3 par défaut) : 3 Premier secteur (100352-7665663, 100352 par défaut) : (Entrée) Utilisation de la valeur 100352 par défaut Dernier secteur, +secteurs ou +taille{K,M,G} (100352-7665663, 7665663 par défaut) : +32M Commande (m pour l'aide) : p Disque /dev/sdb : 3924 Mo, 3924819968 octets 28 têtes, 40 secteurs/piste, 6844 cylindres, total 7665664 secteurs Unités = secteurs de 1 * 512 = 512 octets Taille de secteur (logique / physique) : 512 octets / 512 octets taille d'E/S (minimale / optimale) : 512 octets / 512 octets Identifiant de disque : 0x361d7260 Périphérique Amorçage Début Fin Blocs Id. Système /dev/sdb1 2048 34815 16384 53 OnTrack DM6 Aux3 /dev/sdb2 34816 100351 32768 83 Linux /dev/sdb3 100352 165887 32768 83 Linux Commande (m pour l'aide) : w La table de partitions a été altérée. Appel d'ioctl() pour relire la table de partitions. Synchronisation des disques. [buildroot-2014.11]$
Copions les deux images produites par Buildroot sur leurs partitions respectives, et formatons la troisième partition.
[buildroot-2014.11]$ sudo dd if=output/images/imx23_olinuxino_dev_linux.sb of=/dev/sdb1 bs=512 seek=4 [...] [buildroot-2014.11]$ sudo dd if=output/images/rootfs.ext2 of=/dev/sdb2 bs=1M [...] [buildroot-2014.11]$ sudo mkfs.ext4 /dev/sdb3 mke2fs 1.42.9 (4-Feb-2014) Étiquette de système de fichiers= Type de système d'exploitation : Linux Taille de bloc=1024 (log=0) [...]
On peut alors démarrer le système OLinuXino iMX233 en observant les traces sur la console série.
PowerPrep start initialize power... Battery Voltage = 0.92V No battery or bad battery detected!!!.Disabling battery EMI_CTRL 0x1C084040 FRAC 0x92926192 init_ddr_mt46v32m16_133Mhz power 0x00820710 Frac 0x92926192 start change cpu freq hbus 0x00000003 cpu 0x00010001 LLLLLLLFCLJ[ 0.000000] Booting Linux on physical CPU 0x0 [ 0.000000] Linux version 3.18.0 (cpb@TR-B-01) (gcc version 4.8.3 (Buildroot 2014.11) ) #1 Sat Dec 13 16:37:23 CET 2014 [ 0.000000] CPU: ARM926EJ-S [41069265] revision 5 (ARMv5TEJ), cr=0005317f [ 0.000000] CPU: VIVT data cache, VIVT instruction cache [ 0.000000] Machine model: i.MX23 Olinuxino Low Cost Board [ 0.000000] Memory policy: Data cache writeback [ 0.000000] Built 1 zonelists in Zone order, mobility grouping on. Total pages: 16256 [ 0.000000] Kernel command line: console=ttyAMA0,115200 root=/dev/mmcblk0p2 rw rootwait [ 0.000000] PID hash table entries: 256 (order: -2, 1024 bytes) [...] [ 1.730000] EXT4-fs (mmcblk0p2): re-mounted. Opts: errors=remount-ro Starting logging: OK Starting mdev... Initializing random number generator... [ 2.510000] random: dd urandom read with 5 bits of entropy available done. Starting network... Welcome to Buildroot buildroot login: root
Mise en place de l’overlay
Je commence par créer un point de montage spécifique pour le calque supérieur de mon montage overlay.
# mkdir /data
Je préfère remonter tout de suite ma partition système en lecture-seule.
# mount / -o ro,remount [ 129.290000] EXT4-fs (mmcblk0p2): re-mounted. Opts: errors=remount-ro
Vérifions que le contenu du répertoire /etc
est accessible en lecture seulement.
# cat /etc/hostname buildroot # echo HELLO > /etc/hostname -sh: can't create /etc/hostname: Read-only file system
Je vais monter la partition numéro 3 sur /data
et y créer les répertoires indispensables. Sur l’OLinuXino, la carte SD est vue comme un périphérique bloc /dev/mmcblk0
contrairement au PC qui la voyait en /dev/sdb
(par l’intermédiaire d’un lecteur USB).
# mount /dev/mmcblk0p3 /data/ -t ext4 [ 182.390000] EXT4-fs (mmcblk0p3): mounted filesystem with ordered data mode. Opts: (null) # mkdir /data/etc # mkdir /data/work
Enfin nous pouvons réaliser le montage overlay de /data/etc
en superposition sur /etc
, tout en utilisant /data/work
comme répertoire de travail interne.
# mount none -t overlayfs -o lowerdir=/etc,upperdir=/data/etc,workdir=/data/work /etc
Essayons de consulter puis modifier un fichier /etc/
.
# cat /etc/hostname buildroot # echo OLinuXino > /etc/hostname # cat /etc/hostname OLinuXino
La modification semble normale. Essayons d’y ajouter un nouveau fichier.
# ls /etc/ fstab init.d ld.so.conf network protocols services group inittab ld.so.conf.d os-release random-seed shadow hostname inputrc mdev.conf passwd resolv.conf hosts issue mtab profile securetty # echo HELLO > /etc/new-file # ls /etc/ fstab init.d ld.so.conf network profile securetty group inittab ld.so.conf.d new-file protocols services hostname inputrc mdev.conf os-release random-seed shadow hosts issue mtab passwd resolv.conf
Le fichier est bien visible. Mais qu’en est-il du répertoire /etc
sous-jacent ? Pour le savoir, démontons l’architecture overlay et vérifions.
# umount /etc # ls /etc/ fstab init.d ld.so.conf network protocols services group inittab ld.so.conf.d os-release random-seed shadow hostname inputrc mdev.conf passwd resolv.conf hosts issue mtab profile securetty # cat /etc/hostname buildroot
Le répertoire inférieur est donc intact. Où sont stockées les modifications que nous avons réalisées ? Naturellement, dans /data/etc
.
# ls /data/etc/ hostname new-file # cat /data/etc/hostname OLinuXino # cat /data/etc/new-file HELLO
Montage overlay dès le boot
Je souhaite que la partition racine soit montée en lecture seule, et que le répertoire /etc
soit accessible en overlay dès le boot. Pour cela je dois modifier deux fichiers système qui se trouvent dans /etc
. Le premier est inittab
.
/etc/inittab: [...] # Startup the system null::sysinit:/bin/mount -t proc proc /proc #null::sysinit:/bin/mount -o remount,rw / null::sysinit:/bin/mkdir -p /dev/pts null::sysinit:/bin/mkdir -p /dev/shm null::sysinit:/bin/mount -a null::sysinit:/bin/mount none -t overlay -o lowerdir=/etc,upperdir=/data/etc,workdir=/data/work /etc null::sysinit:/bin/hostname -F /etc/hostname [...]
J’ai ajouté un #
pour mettre en commentaire la ligne qui remonte /
en lecture-écriture, et une ligne de montage overlay avant celle qui invoque hostname
. En outre je modifie la table des systèmes de fichiers à monter automatiquement.
/etc/fstab: # /dev/root / ext2 ro,noauto 0 1 proc /proc proc defaults 0 0 devpts /dev/pts devpts defaults,gid=5,mode=620 0 0 tmpfs /dev/shm tmpfs mode=0777 0 0 tmpfs /tmp tmpfs mode=1777 0 0 sysfs /sys sysfs defaults 0 0 /dev/mmcblk0p3 /data ext4 defaults 0 0
J’ai remplacé une option ‘rw
‘ par ‘ro
‘ dans le montage de /dev/root
et une ligne pour monter la troisième partition sur /data
.
Je reboote le système :
[...] [ 1.160000] EXT4-fs (mmcblk0p3): mounted filesystem with ordered data mode. Opts: (null) Starting logging: OK Starting mdev... Initializing random number generator... [ 1.940000] random: dd urandom read with 6 bits of entropy available done. Starting network... Welcome to Buildroot OLinuXino login:
Visiblement, le nom d’hôte (juste avant « login
« ) a été fixé en lisant bien le fichier hostname
du calque supérieur. Toutefois, un souci persiste.
OLinuXino login: root # echo HELLO > /abcd # cat /abcd HELLO # rm /abcd
Notre partition racine n’est pas montée en lecture seulement, malgré notre configuration. Ceci est dû aux options que le noyau reçoit au démarrage.
# cat /proc/cmdline console=ttyAMA0,115200 root=/dev/mmcblk0p2 rw rootwait
L’option « rw » lui indique de monter la racine en lecture-écriture. Nous devons modifier cette option.
Partition système en lecture-seule
La première chose à faire lorsqu’on désire conserver une partition système en lecture seulement, est de s’assurer que le noyau ne la montera pas automatiquement en lecture-écriture. Pour cela, retournons dans la configuration du kernel.
[buildroot-2014.11]$ make linux-menuconfig
Dans le menu « Boot Options
« , je modifie l’option « Default kernel command string
» pour
(console=ttyAMA0,115200 root=/dev/mmcblk0p2 ro rootwait) Default kernel command string
(on notera le ro
à la place de rw
). En outre, par précaution, je modifie l’option « Kernel command line type
» en
(X) Always use the default kernel command string
Après avoir quitté ce menu de configuration, je m’assure que Buildroot va recréer l’image de la partition de boot, et je relance la compilation.
[buildroot-2014.11]$ rm output/build/mxs-bootlets-10.12.01/.stamp_built [buildroot-2014.11]$ make
Après installation de la nouvelle version, le boot est conforme à nos attentes.
[ 0.970000] VFS: Mounted root (ext2 filesystem) readonly on device 179:2. [ 0.990000] devtmpfs: mounted [ 0.990000] Freeing unused kernel memory: 148K (c0527000 - c054c000) [ 1.160000] EXT4-fs (mmcblk0p3): mounted filesystem with ordered data mode. Opts: (null) Starting logging: OK Starting mdev... Initializing random number generator... [ 1.940000] random: dd urandom read with 6 bits of entropy available done. Starting network... Welcome to Buildroot OLinuXino login: root # echo HELLO > abcd -sh: can't create abcd: Read-only file system #
Conclusion
Nous disposons ainsi facilement d’un système dont la configuration « usine » reste inamovible dans le répertoire /etc
inférieur tandis que les modifications de l’utilisateur restent sur le répertoire supérieur. Le retour aux paramètres « factory defaults » se fait très facilement en effaçant (reformatant éventuellement) la partition 3.
J’ai utilisé ici un montage automatique de cette dernière via le fichier /etc/fstab
. Sur un véritable système embarqué, je préférerais un montage par script qui vérifie d’abord l’intégrité de la partition et la reformate si elle est défectueuse (ou bascule sur une version de secours).
Belle expérience! Merci pour vos article, j’utilise en ce moment votre livre Développement système sous Linux qui me rend énormément service, sans oublier les manpages!
Merci pour cet article, encore une fois très intéressant.
Par contre, je n’arrive pas à comprendre la différence entre UnionFS et OverlayFS.
Pourriez-vous éclairer ma lanterne?
Cordialement
Fabrice
Bonsoir Fabrice,
Il y a essentiellement trois systèmes d’union de répertoires : UnionFS (surtout v.2), OverlayFS et AUFS (Another Union File System). Je ne crois pas qu’il y ait véritablement de supériorité marquée d’un système sur les autres.
Le choix de OverlayFS comme premier système d’union de répertoire intégré dans le noyau mainline est semble-t-il motivé par sa simplicité et éventuellement par ses résultats lors de tests en montée de charge comme ceux décrits dans cet article sur Docker
https://developerblog.redhat.com/2014/09/30/overview-storage-scalability-docker/
Quoiqu’il en soit pour l’utilisateur final ou le concepteur d’un système embarqué il n’y a pas beaucoup de différences d’usage.
Merci pour le retour rapide, ça confirme ce que je pensais…
Comme c’est un peu la saison, j’en profite pour vous souhaiter de bonnes fêtes de fin d’année 🙂
Il y a MHDDFS aussi, en userspace 😉
OverlayFS est quand même limité à 2 couches contrairement à AUFS.
Merci pour les explications.
J’ai une petite question : si l’on supprime un fichier uniquement disponible dans le lowerdir (par exemple /etc/os-release), qu’est-ce que l’on verra dans /data/work ? Est-ce que cette gestion des états est faite par un fichier binaire, ou est-ce que c’est à peu près lisible pour un être humain ?
Le répertoire
/data/work
contient lui-même un répertoirequi apparaît vide. Son rôle est plutôt je pense réservé à des opérations de
synchronisation et de verrouillage temporaire pour assurer la cohérence de l’ensemble.
Je viens de faire un essai en supprimant un fichier (
/etc/issue
) qui existe dans le répertoire inférieur.Bien sûr il disparaît du montage mais est toujours présent dans ce lower directory (vérifié en démontant temporairement).
Dans le répertoire upper
/data/etc
apparaît un fichierspécial de type caractère avec le nom du fichier sous-jacent qui sera ainsi masqué
Le même mécanisme de masquage est employé si on efface un sous-répertoire existant dans le
lower directory
.Merci pour ces informations.
Et en effet le workdir semble être un répertoire temporaire pour les opérations : http://lwn.net/Articles/600106/
Merci pour ce lien intéressant.
On y apprend également que le workdir doit se trouver sur le même système de fichiers que le répertoire upper.
Dans votre article vous dites que vous avez utilisé OverlayFS sur des noyaux antérieurs à la version 3.18.
Plusieurs questions me viennent à l’esprit:
1. Sur quels noyaux?
2. Où trouver les bons patchs pour le bon noyau (par exemple pour le 3.2)?
3. Pourquoi faire un « backport » de OverlayFS et ne pas utiliser UnionFS?
Bonjour
j’ai accidentellement mis mon répertoire /etc en overlay et je ne sais pas comment le remettre en état normal sans devoir tout réinstaller. Pouvez me donner la manip?
Merci