Le système Overlayfs de Linux 3.18

Publié par cpb
Déc 14 2014

OverlayfsDans 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.

 Overlayfs

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).

10 Réponses

  1. Michael dit :

    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!

  2. Fabrice dit :

    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

    • cpb dit :

      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.

  3. D0ll dit :

    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 ?

    • cpb dit :

      Le répertoire /data/work contient lui-même un répertoire

      d---------  2  root  root     1024  Jan  1  00:00 work

      qui 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 fichier
      spécial de type caractère avec le nom du fichier sous-jacent qui sera ainsi masqué

      c---------  1  root  root    0,  0  Jan  1  00:00 issue

      Le même mécanisme de masquage est employé si on efface un sous-répertoire existant dans le lower directory.

  4. Fabrice dit :

    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?

URL de trackback pour cette page