Eris Linux offre un socle logiciel maintenu et maîtrisé sur lequel les applications métiers peuvent être exécutées en toute sécurité dans des containers. La mise à jour régulière du socle système est réalisée par nos soins, au gré des correctifs intégrés dans les recettes de Yocto Project et des nécessités liées à la découverte de vulnérabilités comme nous l’avons évoqué dans les précédents articles.
Nous allons mettre à l’épreuve le système de mise de jour d’Eris Linux, en vérifiant sa robustesse dans des circonstances difficiles volontairement provoquées.
Installation initiale
Je commence par installer Eris Linux sur un Raspberry Pi 4.
Pour cela, je me rends sur www.eris-linux.net, je me connecte, et dans l’onglet My devices je clique sur le bouton New device.
Dans la liste qui apparaît, je choisis une image Graphical pour Raspberry Pi 4. Je précise que la carte pourra accéder à Internet par son port Ethernet avec une adresse fournie par DHCP. Au bout de quelques minutes, je reçois un mail m’indiquant que l’image est prête.
Je récupère le fichier au format .img.bz2 , je le décompresse, et je l’installe sur la carte SD branchée sur mon PC. Personnellement j’utilise cp pour cela mais on peut très bien préférer dd ou un utilitaire comme Etcher par exemple.
$ bunzip2 eris-linux_Raspberry-Pi-4_1.1.0-dev-*.img.bz2
$ lsblk
[...]
sdc 8:32 1 14.4G 0 disk
$ sudo umount /dev/sdc?
$ sudo cp eris-linux_Raspberry-Pi-4_1.1.0-dev-*.img /dev/sdc
(Remplacez sdc par le nom de périphérique correspondant à votre carte SD tel qu’indiqué par lsblk)
Je branche mon Raspberry Pi, et au bout de quelques minutes (le temps de formater et chiffrer la partition de données) je le vois apparaître sur le Device Manager.
Je renomme la carte Update-Test. Dans cette nouvelle version, elle contacte le serveur toutes les minutes, les mises à jour du socle système sont automatiques et le redémarrage après mise à jour a également lieu automatiquement. Bien entendu ces paramètres sont configurables en fonction de vos besoins fonctionnels.
J’ai installé un container de démonstration dans le premier emplacement, ce qui me permet de vérifier sur l’écran HDMI connecté à la carte si le démarrage des containers est correct. Comme on le voit sur la figure 4, la version du socle logiciel installé est 1.1.0-dev-0 (les versions suffixées -dev-x sont produites par une branche de développement utilisée en interne uniquement).
Premières mises à jour
Je prépare sur le serveur Eris-Linux une mise à jour 1.1.0-dev-1 identique à la version précédente. Au bout de quelques instants le Raspberry Pi se met à jour. Sur le tableau de bord du Device Manager, le champ « System status » passe par « Update in progress » , « Update Ok » puis « Restarting » et enfin « System Ok » avec le nouveau numéro de version.
Nous utilisons un système de mise à jour de type A/B amélioré (s’appuyant sur deux partitions en alternance et un initramfs de secours). Une nouvelle version n’écrase donc jamais directement le système actuellement validé. Elle est installée sur la partition inactive puis essayée au redémarrage. Elle ne devient la version confirmée que lorsque le démarrage a été validé.
Je réitère donc l’opération deux fois pour être sûr de passer par toutes les transitions de partition en fonctionnement nominal, en m’affranchissant de l’image initialement installée.
Nous voyons que le système de mise à jour fonctionne bien lorsqu’aucun grain de sable ne vient gripper ses rouages. Nous allons pourtant lui jeter quelques poignées de sable dans les opérations à venir !
Échec de mise à jour et rollback
Pour l’étape suivante je veux forcer l’échec de démarrage d’une image suite à une mise à jour. Ce cas peut malheureusement arriver en situation réelle. Imaginons par exemple que l’on a envoyé par erreur une mise à jour considérant que l’équipement doit obligatoirement avoir un contrôleur d’écran HDMI en oubliant que certaines cartes plus anciennes n’ont qu’un contrôleur graphique LVDS.
Je vais simuler cette erreur en forçant l’échec du programme qui est appelé en fin de démarrage normal pour vérifier que tout le socle logiciel s’est initialisé correctement (indépendamment des containers qui peuvent échouer pour des raisons applicatives).
J’ai ajouté temporairement une boucle infinie vide dans le programme de vérification du démarrage correct. La version 1.1.0-dev-4 ne pourra donc pas démarrer complètement.
Je vois passer l’installation de la nouvelle version :
Puis le redémarrage de la carte :
Sur l’écran HDMI du Raspberry Pi, je vois bien que le boot démarre sur la version 1.1.0-dev-4, mais au bout de quelques minutes le système, incapable de terminer son initialisation correctement, redémarre. La photo de l’écran est de piètre qualité.
Au bout de trois tentatives, le système de mise à jour abandonne et revient sur la version précédente. C’est ce qu’on appelle un rollback.
On notera le message « Update rejected » dans le champ « System Status » . En effet, le device n’acceptera plus la version 1.1.0-dev-4 même si le Device Manager lui propose à nouveau. C’est uniquement cette version qui est bloquée, les suivantes seront acceptées et installées.
J’envoie à présent une nouvelle version 1.1.0-dev-5 correcte. La mise à jour et le redémarrage se font normalement.
Partition de données dégradée et restaurée
Le système ayant besoin de stocker des données applicatives de des paramètres de configuration, une partition est accessible en écriture. Dans le cas où une coupure d’alimentation électrique se produit au mauvais moment, on peut imaginer que cette partition souffre d’incohérences au re-démarrage.
Le choix qui est fait dans Eris Linux est de privilégier le fonctionnement applicatif, et de pouvoir redémarrer le système quitte à perdre des données précédemment enregistrées. Si le besoin s’en fait sentir, n’hésitez pas à nous contacter pour obtenir le fonctionnement inverse : privilégier la sauvegarde des données (système d’enregistrement à la manière des boîtes noires par exemple) même si la fonctionnalité applicative est interrompue.
Je ne vais pas essayer de reproduire une corruption aléatoire provoquée par une coupure électrique inopinée, mais je vais être nettement plus brutal en détruisant volontairement les 256 premiers Mo de la partition de données. Je vais prendre la carte SD du Raspberry Pi, l’insérer sur un PC et écraser le début de cette partition avec des zéros.
$ lsblk
[...]
sdc 8:32 1 14.4G 0 disk
├─sdc1 8:33 1 166.4M 0 part /run/media/cpb/boot
├─sdc2 8:34 1 738.9M 0 part
├─sdc3 8:35 1 739.4M 0 part
└─sdc4 8:36 1 12.8G 0 part
[...]
$
La partition de données est la quatrième, elle a été étendue pour remplir tout l’espace disponible sur la mémoire flash.
Attention : les commandes dd qui suivent détruisent volontairement des données. Ne les reproduisez que sur un support de test dont vous avez vérifié le nom de périphérique. Une erreur sur /dev/sdX peut détruire immédiatement les données d’un autre disque.
$ sudo dd if=/dev/zero of=/dev/sdc4 bs=1M count=256
256+0 records in
256+0 records out
268435456 bytes (268 MB, 256 MiB) copied, 0.368766 s, 729 MB/s
$
Nous venons d’écraser les 256 premiers Mo de la partition de données.
Je redémarre le système. Au bout de quelques minutes, le Device Manager m’indique qu’il a bien retrouvé notre équipement. Le temps de redémarrage est comparable au premier boot du système, il faut le temps de formater et chiffrer la partition de données.
Soyons clair, cette récupération a nécessité de réinitialiser entièrement la partition en perdant les données précédemment écrites tout en restaurant les données de fonctionnement permettant l’identification de l’équipement par le Device Manager. Ceci évite d’avoir un device « briqué » (incapable de redémarrer du point de vue de l’utilisateur) nécessitant un retour en réparation.
Comme je l’ai dit plus tôt, nous pouvons vous proposer sur demande des versions alternatives, où les données restantes sont conservées et l’équipement se met en défaut, impliquant un retour en maintenance pour le restaurer en récupérant, au moins partiellement, les données restantes.
Environnement de U-boot
Nous utilisons le bootloader U-boot pour charger le noyau Linux. La configuration de U-boot s’appuie sur des variables d’environnement dans lesquelles nous venons enregistrer des données (numéro de partition en cours d’utilisation, partition candidate pour l’installation d’une nouvelle image, version bloquée par une mise à jour échouée, etc.)
Nous devons donc pouvoir ré-écrire l’environnement de U-boot. Il s’agit de deux zones redondantes se trouvant la mémoire flash de la carte, hors des partitions utilisées par le système. Lorsqu’on extrait la carte SD pour l’analyser sur un PC, on peut voir les deux copies de l’environnement aux offsets 0x100000 et 0x200000.
$ sudo hexdump -C -s 0x100000 -n 0x4000 /dev/sdc
00100000 5e fd f7 7d 03 5f 62 6f 6f 74 5f 6f 6e 3d 30 00 |^..}._boot_on=0.|
00100010 5f 69 6e 69 74 72 61 6d 66 73 3d 30 00 5f 69 6e |_initramfs=0._in|
00100020 69 74 72 61 6d 66 73 5f 73 69 7a 65 3d 30 00 5f |itramfs_size=0._|
00100030 76 61 6c 75 65 3d 30 00 61 72 63 68 3d 61 72 6d |value=0.arch=arm|
00100040 00 62 61 75 64 72 61 74 65 3d 31 31 35 32 30 30 |.baudrate=115200|
00100050 00 62 6f 61 72 64 3d 72 70 69 00 62 6f 61 72 64 |.board=rpi.board|
00100060 5f 6e 61 6d 65 3d 34 20 4d 6f 64 65 6c 20 42 00 |_name=4 Model B.|
[...]
$ sudo hexdump -C -s 0x200000 -n 0x4000 /dev/sdc
00200000 0b 24 78 3d 04 5f 62 6f 6f 74 5f 6f 6e 3d 30 00 |.$x=._boot_on=0.|
00200010 5f 69 6e 69 74 72 61 6d 66 73 3d 30 00 5f 69 6e |_initramfs=0._in|
00200020 69 74 72 61 6d 66 73 5f 73 69 7a 65 3d 30 00 5f |itramfs_size=0._|
00200030 76 61 6c 75 65 3d 30 00 61 72 63 68 3d 61 72 6d |value=0.arch=arm|
00200040 00 62 61 75 64 72 61 74 65 3d 31 31 35 32 30 30 |.baudrate=115200|
00200050 00 62 6f 61 72 64 3d 72 70 69 00 62 6f 61 72 64 |.board=rpi.board|
00200060 5f 6e 61 6d 65 3d 34 20 4d 6f 64 65 6c 20 42 00 |_name=4 Model B.|
[...]
Avec une pointe de cruauté, j’écrase les 4 premiers kilo-octets du premier environnement (ce qui suffit à détruire son en-tête et sa somme de contrôle), le rendant inutilisable. Je vérifie que la zone soit bien remplie de zéros.
$ sudo dd if=/dev/zero of=/dev/sdc bs=4k seek=$((0x100)) count=1
$ sudo hexdump -C -s 0x100000 -n 0x4000 /dev/sdc
00100000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00104000
Après réinsertion de la carte SD, je peux vérifier que U-boot a bien détecté que cet environnement est invalide et a choisi d’utiliser la copie au second offset.
Nous pouvons prolonger l’expérience en refaisant la même opération, sur le second emplacement contenant l’environnement. Ceci permettra de s’assurer que la première copie de l’environnement a été restaurée lors du dernier boot.
Bien entendu, je n’écrase pas les deux copies simultanément, sinon je n’ai aucune chance de voir le système
se réparer.
$ sudo dd if=/dev/zero of=/dev/sdc bs=4k seek=$((0x200)) count=1
1+0 records in
1+0 records out
4096 bytes (4.1 kB, 4.0 KiB) copied, 0.000504819 s, 4.1 MB/s
$ sudo hexdump -C -s 0x200000 -n 0x4000 /dev/sdc
00200000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00204000
Et le système redémarre bien après récupération de l’environnement U-boot.
Conclusion
Nous avons volontairement soumis le mécanisme de mise à jour d’Eris Linux à plusieurs situations anormales :
- une nouvelle version incapable de terminer son démarrage,
- la destruction de la partition de données,
- la corruption successive des deux copies de l’environnement U-Boot.
Dans chacun de ces cas, le système a pu retrouver automatiquement un état permettant son redémarrage, sans intervention sur l’équipement déployé.
C’est précisément l’objectif du mécanisme de mise à jour d’Eris Linux : une mise à jour distante ne doit pas transformer un équipement fonctionnel en système inutilisable sans intervention physique.















