NixOS, quand la programmation fonctionnelle rencontre Linux

Magazine
Marque
Linux Pratique
Numéro
117
Mois de parution
janvier 2020
Domaines


Résumé

NixOS est une distribution basée sur le gestionnaire de paquets Nix et dont toute la configuration se fait à partir d’un fichier. Nous verrons que NiXOS permet de gérer son système de manière reproductible, avec des mises à jour atomiques et des rollbacks, des espaces utilisateurs, l’installation simultanée de plusieurs versions d’un paquet, la configuration fine de chaque composant ainsi qu’un cache binaire, testé et contenant plus de 47 000 paquets. Nous testerons ensuite NixOS dans une machine virtuelle.


Body

Un programme est souvent une succession d’assignations de variables, de réassignations, de boucles for, de boucles while… À la place de cette approche impérative, déclarer le résultat que l’on souhaite atteindre peut suffire si un compilateur trouve les instructions à votre place. Cette approche déclarative, la programmation fonctionnelle l’applique à tous types d’applications : avec NixOS, il s’agit de la construction d’un système d’exploitation.

1. NixOS

1.1 Une configuration centralisée

v-nixos figure02 v2

Fig. 1 : Construction purement fonctionnelle du système d'exploitation. La commande nixos-install déduit du fichier configuration.nix comment installer une première version du système – OS1 – ainsi qu'un bootloader (e.g. GRUB). Par la suite, l'utilisateur édite le fichier de configuration puis exécute la commande nixos-rebuild ; ce qui construit et active un nouveau système : OS2. Ainsi de suite jusqu'à la version courante, OSn. La gestion est purement fonctionnelle, sans effets de bord, la nouvelle version du système découle directement du fichier de configuration et sa mise en mémoire n'écrase pas la précédente version – similairement aux langages de programmation purement fonctionnels. Concrètement, les versions du système sont précautionneusement stockées, ce qui permet notamment de faire des mises à jour atomiques, et des rollbacks en cas de pépin.

À l’origine de toute installation de NixOS, il y a un fichier source, configuration.nix, écrit dans le langage de programmation fonctionnelle Nix. Ce fichier contient la totalité de la configuration de votre système : les composants installés, le noyau linux, les options du bootloader, les services, les noms et les droits des utilisateurs, etc.

Dans NixOS, tout est fait pour que cette configuration soit reproductible, de manière à ce qu’à un état de la configuration corresponde un unique état du système.

En plus de cette centralisation de la configuration, le gros avantage de NixOS est probablement son gestionnaire de paquets et la manière dont il résout un problème récurrent.

1.2 L’enfer des dépendances

La majorité des distributions Linux choisissent d’organiser leurs dossiers en suivant, grosso modo, la norme FHS (File Hierarchy System) [1]. Cela signifie, entre autres, que les exécutables et les bibliothèques, installés par les utilisateurs, sont installés dans des dossiers bien spécifiques : notons-les <bin> et <lib>. Selon les distributions, <bin> sera /bin ou /usr/bin ou parfois même /usr/local/bin. <lib> sera plutôt /lib ou /usr/lib. Le hic avec cette norme c’est, qu’en la suivant, les paquets ont tendance à installer leur exécutable ou leur librairie à un unique chemin – quelle que soit sa version.

Si un paquet foo-1.0 installe la version 1.0 d’une librairie au chemin <lib>/foo ; vous ne pourrez généralement pas installer, en plus de cette version 1.0, la version 1.1. En effet, la plupart du temps, le paquet foo-1.1 voudra, lui aussi, installer sa version de la bibliothèque au chemin <lib>/foo. À cause de ce type de conflits, des gestionnaires de paquets comme apt, rpm ou pacman auront bien du mal à, par exemple, gérer deux versions de glibc côte à côte.

D’où le fameux « enfer des dépendances » dans le cas où une application app1 dépend de foo-1.0 alors qu’ app2 dépend de foo-1.1. Par exemple, avec les gestionnaires de paquets précédents, installer une application qui dépend d’une nouvelle version de glibc requiert d’abandonner tous les paquets qui dépendent de l’ancienne.

1.3 Les solutions existantes

Certains paquets de Debian incluent dans leur nom la version – e.g. Python2.7 et Python3.7 – mais cette solution est difficilement généralisable. Installer nativement, côte à côte, de multiples versions du même composant informatique semble permis sur une poignée de distributions : Gentoo a un système de slotting pour ça et GoboLinux le permet aussi grâce à une structure de dossiers émancipée de l’omniprésent FHS.

De nombreux gestionnaires de composants, multi-distributions, tendent à supplanter les gestionnaires natifs, car ils proposent une solution à cet enfer des dépendances : empaqueter de nombreuses dépendances dans le paquet lui-même.

- Une image Docker contient toutes les dépendances entre le noyau Linux et l’application – d’où la notion de conteneur – et un système de layers apporte un peu de composition.

- AppImage, empaquette lui aussi les dépendances, mais s’appuie sur un environnement préexistant [2], censé être présent dans toute distribution.

- Flatpak insère une couche intermédiaire entre les paquets et le noyau : les runtimes. Mais « il n’est clairement pas adapté pour les serveurs » [3].

- Le populaire Snap force malheureusement l’usage d’un dépôt contrôlé par la société Canonical [4].

Spack a une autre approche pour résoudre les conflits de versions. Son API puissante et user-friendly finira peut-être par le faire connaître. Ce dernier gestionnaire reprend les principes du gestionnaire de paquets multiplateforme Nix, utilisé dans NixOS.

Le gestionnaire de paquets Nix évalue des fichiers de packaging écrits dans le langage Nix. Attention à bien distinguer les deux selon le contexte ! Dans l’univers de Nix on ne parle pas exactement de paquets, mais de « dérivations ». Nous ferons abstraction de ce qui les distingue dans cet article.

1.4 Nix et son store

Nix stocke chaque paquet, séparément, dans un dossier au chemin bien compliqué de prime abord : /nix/store/<hash-nom-version>. Par exemple, pour une version 1.0.5 de bzip2, le chemin sera /nix/store/6iyhp953ay3c0f9mmvw2xwvrxzr0kap5-bzip2-1.0.5. Le calcul du hash du paquet inclut l’ensemble de ses dépendances de build ; ce qui fait que, en plus du dossier précédent, si bzip2 est compilé avec une autre version de gcc, le nouveau build sera stocké dans le store, avec un nouveau hash. Les deux compilations de bzip2 seront côte à côte.

Dans NixOS, le dossier qui ressemble le plus à la racine du FHS est : /run/current-system/sw. Le début du chemin, /run/current-system, est un lien symbolique vers un dossier du store, par exemple /nix/store/2wqdx…-nixos-system-<hostname>-19.03 (le hash est écrit partiellement ici). Nous constatons là que, comme bzip2, le système de NixOS est un paquet ! dont le nom est nixos-system-<hostname>, et qu’il est lui aussi stocké dans le store. Son dossier contient une arborescence de liens symboliques qui pointent vers le contenu d’autres paquets du store : les dépendances du système telles que le noyau, les services, les scripts d’initialisation, les exécutables et les bibliothèques des logiciels installés, etc. Par exemple, le lien /run/current-system/sw/bin/bzip2 pointe vers /nix/store/6iyhp…-bzip2-1.0.5-bin/bin/bzip2.

1.5 Mise à jour atomique et rollback

Grâce à Nix, NixOS garde en mémoire les chemins vers les différentes versions du paquet système. Le dossier /nix/var/nix/profiles/ contient des liens, par exemple system-139-link et system-140-link, appelés des générations du système. La première génération pointe vers le dossier /nix/store/2wqdx…-nixos-system-<hostname>-19.03 et la deuxième vers /nix/store/7oajh…-nixos-system-<hostname>-19.03. Dans le même dossier que les générations, un lien spécial, /nix/var/nix/profiles/system, appelé profil du système, pointe vers la génération actuelle : disons la 140ème.

v-nixos figure03 v2

Fig. 2 : Mise à jour du système correspondant à l'ajout du paquet bzip2 : passage de la 140ème génération du système à la 141ème. L'origine d'une flèche bleue correspond à un lien symbolique et la tête de la flèche correspond à la cible du lien. La flèche bleue, la plus à droite, montre un lien, situé dans le nouveau paquet système, qui pointe vers un élément du paquet bzip2. Plus généralement, ce type de liens symboliques, intra-store, permet de composer un paquet à partir d'éléments d'autres paquets. La flèche rouge montre que, au cours de la mise à jour, le lien /nix/var/nix/profiles/system est édité pour pointer vers la 141ème génération.

Lors d’une mise à jour du système :

- le nouveau système est stocké dans le store à l’adresse /nix/store/qzaqjh…-nixos-system-<hostname>-19.03 ;

- la génération /nix/var/nif2x/profiles/system-141-link correspondante est créée ;

- le profil est mis à jour et /nix/var/nix/profiles/system pointe après coup sur la génération 141 ;

- pour finir, le lien /run/current-system est édité. Alors qu’il pointait sur /nix/store/7oajh…-nixos-system-<hostname>-19.03, il cible /nix/store/qzaqjh…-nixos-system-<hostname>-19.03 a posteriori.

Nix assure une mise à jour atomique du profil. À tout moment, les générations précédentes peuvent être utilisées pour faire un rollback du système.

1.6 Environnement utilisateur

En plus des paquets du système, accessibles à tous les utilisateurs, le gestionnaire de paquets Nix permet à chaque utilisateur de gérer ses propres paquets. Comme pour le système, un utilisateur a un profil :

- l’utilisateur root a le profil /nix/var/nix/profiles/default ;

- et un utilisateur <user> a le profil /nix/var/nix/profiles/per-user/<user>/profile.

La variable environnement PATH est choisie de manière à ce que les exécutables du profil d’un utilisateur <user> soient prioritaires sur les exécutables de root et sur ceux du système. Concrètement :

PATH=…:/home/<user>/.nix-profile/bin:/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin:…

avec /home/<user>/.nix-profile un lien vers /nix/var/nix/profiles/per-user/<user>/profile.

1.7 Les expressions Nix

Les paquets de Nix sont basés sur les sources des logiciels. Pour chaque paquet, un fichier écrit en Nix – l’expression du paquet – décrit les URL des sources, les options du paquet, ses dépendances, la manière de faire le build, etc. Les options (et même les dépendances) peuvent facilement être éditées au niveau du fichier configuration.nix avant de (re)construire le système. Il est ainsi possible d’activer une dépendance optionnelle : par exemple gtkSpell pour pan, l’agrégateur de nouvelles. Les expressions officielles sont disponibles sur le dépôt GitHub NIXOS/nixpkgs. Chacun peut cloner ce dépôt puis modifier ou ajouter des expressions en local selon ses souhaits.

Lorsque Nix tente de construire un paquet – on dit qu’il évalue l’expression – les fichiers sources du paquet sont téléchargés avant d’être gérés de différentes manières. Chacun de ces téléchargements est systématiquement vérifié en comparant sa somme de contrôle à celle renseignée par le packageur. Ne vous arrachez plus la tête à chercher la cause d’une non-reproductibilité due à la modification d’un fichier source distant ! Une fois validé, le fichier est emmagasiné dans le store au chemin /nix/store/<checksum>-<name>.

1.8 Le cache binaire

Il serait long de recompiler chaque paquet, à chaque installation et à chaque mise à jour ; à la manière de Gentoo. Raison pour laquelle, un cache binaire distant, contenant de nombreux paquets précompilés, est à votre disposition. Si les paramètres du paquet à installer sont standards, vous devriez avoir accès à une version archivée et économiser de précieuses minutes.

1.9 Les channels

En réalité, le gestionnaire local, Nix, s’abonne à un channel du cache binaire. Un channel stable (e.g. « nixos-19.03 ») vous proposera des corrections et des mises à jour mineures : par exemple, le passage du noyau de la version 4.19.34 à la version 4.19.38. Le channel instable, lui, vous apportera les dernières mises à jour, mais au risque de radicalement changer votre système.

Les archives des channels sont mises à jour automatiquement en se nourrissant de l’évolution du dépôt nixpkgs. Le processus de mise à jour récupère un ensemble d’expressions Nix, les évalue (build) puis leur fait passer une batterie de tests propre au channel. Ce dernier n’actualise les archives de ses paquets qu’à partir du moment où tous les tests passent, ce qui filtre d’éventuelles erreurs commises dans nixpkgs.

1.10 Tests de non-régression des paquets

L’intégration continue des paquets effectue des tests poussés. Par exemple, le test du paquet de Firefox est exécuté dans une machine virtuelle, dans laquelle il est demandé à l’exécutable d’ouvrir une page HTML avant de vérifier qu’elle a le bon titre. Le test simule aussi la pression de la touche [F12] et s’assure qu’elle déclenche l’ouverture des outils de développement. Un article [5] explique comment cette prouesse et de nombreuses autres sont possibles.

1.11 Nombre de paquets et vivacité de la communauté

D’après Repology [6], nixpkgs contient environ 47 000 paquets, ce qui le place deuxième, juste derrière le AUR : le dépôt des utilisateurs d’Arch Linux. Le manuel de NixOS et son wiki sont bien fournis en documentation. Ils manquent peut-être encore un peu de maturité, mais les informations sont claires et concises. Des publications, notamment la thèse du créateur de l’écosystème Nix, Eelco Dolstra, permettent d’avoir des précisions. Pour des informations complémentaires, la communauté est joignable sur son forum Discourse (1000 posts durant le mois d’août) ainsi que sur des channels IRC.

Les anglophones sont vivement incités à lire la présentation officielle de NixOS [7] en supplément de ce qui précède.

v-nixos photo01

Fig. 3 : Communauté de NixOS à l'occasion de la NixCon de 2019 à Brno.

2. Testez NixOS dans une machine virtuelle

Assez de théorie, place à la pratique ! Nous vous proposons maintenant d’essayer NixOS dans une machine virtuelle, communément appelée une VM.

2.1 Démarrez la VM

- Installez VirtualBox. Pensez à installer l’Extension Pack et à vérifier que la virtualisation est bien activée dans le BIOS.

- Téléchargez le fichier OVA à l’adresse https://nixos.org/nixos/download.html.

- Importez-le dans VirtualBox : File > Import Appliance et lancez la VM.

- Loguez-vous avec le login « demo » et le mot de passe « demo ».

- Ouvrez la Konsole de KDE et obtenez les droits sudo avec sudo -i.

2.2 Choisissez un channel

Listez les channels auxquels vous êtes abonné :

nix-channel --list

Parmi la liste des channels disponibles [8], choisissez le channel qui vous convient le mieux :

nix-channel --add https://nixos.org/channels/<channel> nixos

Mettez à jour les expressions Nix locales du channel de nixos :

nix-channel --update nixos

2.3 Gérez le système

Éditez le fichier de configuration :

nano /etc/nixos/configuration.nix

Comme suggéré, décommentez les lignes qui permettent de changer le Desktop Manager de SDDM à GDM et le Desktop Environment de KDE Plasma5 à GNOME 3.

Si besoin, ajoutez pkgs. devant mkForce dans les lignes à décommenter.

Réalisez ces modifications en reconstruisant et en basculant vers la nouvelle génération du système :

nixos-rebuild switch

Rebootez la VM. Sélectionnez All configurations dans le menu de GRUB. Vous devriez maintenant avoir le choix de la génération à lancer (Figure 4).

v-nixos figure04

Fig. 4

À partir de la liste des paquets disponibles dans les channels [9] et de la liste des options du système [10], éditez /etc/nixos/configuration.nix à votre guise. Inspirez-vous des nombreux exemples de configurations.nix [11]. Réalisez chaque changement avec :

nixos-rebuild switch

À tout moment, vous pouvez faire un rollback du système vers la génération précédente :

nixos-rebuild switch --rollback

v-nixos screenshot

Fig. 5 : Screenshot d'un bureau installé à partir de NixOS. Le gestionnaire de fenêtres par pavage, i3, est utilisé ici avec l’environnement de bureau Xfce. La fenêtre de droite montre un fichier configuration.nix.

2.4 Pistes pour gérer l’espace utilisateur

Chaque utilisateur peut gérer les paquets de son profil avec la commande nix-env. Cette commande s’utilise à la manière d’un gestionnaire classique comme apt, mais sans avoir besoin des droits super utilisateur.

Nous conseillons toutefois de centraliser la configuration de l’espace utilisateur avec home-manager [12] et son fichier ~/user/.config/nixpkgs/home.nix. Ce fichier déclare notamment :

- les paquets utilisateurs : home.packages ;

- le contenu de vos dotfiles : home.file."<path>".source, avec "<path>", le chemin relatif à partir de votre HOME jusqu’au dotfile ;

- vos services utilisateurs.

Une fois tout écrit, l’utilisateur n’a plus qu’à exécuter la commande home-manager switch pour construire son espace.

2.5 Ramassez les miettes

Après vous être amusé un moment, vous pouvez facilement faire le ménage avec :

nix-collect-garbage --delete-old

Cette commande efface du dossier /nix/var/nix/profiles, les anciennes générations de tous les profils ; puis « ramasse les miettes » en supprimant tous les éléments du store qui ne sont pas dans une arborescence de dossiers de /nix/var/nix/gcroots (gcroots signifie « garbage collector roots »). Plus concrètement, une de ces arborescences de dossiers a pour racine /nix/var/nix/gcroots/profiles : un lien qui correspond au dossier /nix/var/nix/profiles. Initialement, toutes les générations de ce dossier connectent donc la racine /nix/var/nix/gcroots/profiles à de nombreux éléments du store. Lorsque les générations anciennes sont supprimées, des éléments du store se retrouvent orphelins, ils seront donc effacés durant l’étape.

Pour garder quelques générations, utilisez l’option --delete-older-than <x>d. Vous ne supprimerez ainsi que les générations qui ont plus de <x> jours.

Conclusion et ouverture

L’organisation rigoureuse de NixOS a pour contrepartie une courbe d’apprentissage bien plus raide que celle d’une distribution classique :

- Le langage Nix, est un nouveau langage à apprendre. Sa syntaxe ad hoc facilitera toutefois la configuration sur le moyen-long terme.

- L’interface utilisateur du gestionnaire de paquets est en évolution. Depuis la version 2.0, les développeurs ont entamé le remplacement des multiples commandes nix-* par une unique : nix, plus intuitive.

- Enfin, ne pas suivre la norme FHS impose des adaptations durant le packaging et il est souvent nécessaire d’éditer le RPATH et le dynamic loader des exécutables ELF. Heureusement, l’outil patchELF a été développé pour ça.

À côté de ces difficultés, les choix structurants de NixOS et du gestionnaire de paquets Nix permettent d’y greffer de nombreuses fonctionnalités bien utiles pour les développeurs et les DevOps. À ce sujet, nous vous invitons à vous renseigner sur :

- la génération d’environnements virtuels avec nix-shell, à la manière des virtualenvs de Python, mais cette fois-ci adossée aux paquets polyglottes de Nix ;

- les dockerTools pour utiliser ou construire des images Docker ;

- la gestion d’un parc de machines NixOS avec NixOps : au lieu de modifier pas à pas l’état des machines, comme Ansible, utilisez un fichier pour décrire l’état souhaité de chacune ;

- le déploiement de services distribués avec Disnix ;

- l’intégration continue de paquets Nix avec Hydra.

De nombreuses utilisations concrètes de Nix et de son écosystème sont détaillées dans des tutoriels français en ligne [13].

En guise d’ouverture, sachez qu’un gestionnaire de paquets, GNU Guix, et la distribution qui l’utilise, Guix System, reprennent le fonctionnement de Nix et de NixOS. Au lieu d’être écrits en Nix, les paquets sont en Guile, un dérivé du LISP. GNU Guix assure actuellement un accès plus durable aux sources des paquets grâce à un partenariat avec Software Heritage. Cette émulation féconde semble un bon présage pour les distributions qui s’inspirent de la programmation fonctionnelle.

Remerciements

Je remercie Julien Dehos pour ses nombreux conseils ainsi que la communauté de NixOS.

Références

[1] Page Wikipédia sur le File Hierarchy System : https://en.wikipedia.org/wiki/Filesystem_Hierarchy_Standard#FHS_compliance

[2] Liste des dépendances que les développeurs d’AppImage considèrent présentes dans toutes les distributions Linux : https://github.com/AppImage/pkg2appimage/blob/master/excludelist

[3] FAQ officielle de Flatpak : https://flatpak.org/faq/#Can_Flatpak_be_used_on_servers_too_

[4] Un fil de discussion du forum de Snap : https://forum.snapcraft.io/t/external-repositories/1760

[5] S. van der Burg et E. Dolstra « Automating System Tests Using Declarative Virtual Machines », IEEE 21st International, 2011 : https://nixos.org/~eelco/pubs/decvms-issre2010-final.pdf

[6] Statistiques sur de très nombreux dépôts de paquets : https://repology.org/repositories/statistics/newest

[7] Présentation officielle de NixOS en anglais : https://nixos.org/nixos/about.html

[8] Les différents channels : https://nixos.wiki/wiki/Nix_channels

[9] Moteur de recherche de paquets du channel nixos-unstable : https://nixos.org/nixos/packages.html?channel=nixos-unstable

[10] Moteur de recherche des options de configuration de NixOS : https://nixos.org/nixos/options.html#

[11] Collection de fichiers configuration.nix, notamment des exemples de configurations permettant l’installation de NixOS sur différentes machines : https://nixos.wiki/wiki/Configuration_Collection

[12] Un gestionnaire d’environnement utilisateur basé sur Nix : https://github.com/rycee/home-manager

[13] Des tutoriels en français avec des usages avancés de Nix et de NixOS : https://nokomprendo.gitlab.io/



Articles qui pourraient vous intéresser...

Surveiller son système avec Monit

Magazine
Marque
Linux Pratique
HS n°
Numéro
49
Mois de parution
novembre 2020
Domaines
Résumé

La supervision d’un système en production demeure un enjeu aussi complexe qu’essentiel. Il existe de nombreuses solutions, très complètes, de supervision, mais la plupart adoptent une approche centralisée, qui demande l’utilisation de ressources dédiées. Aujourd’hui, nous étudierons une approche alternative, une solution de supervision décentralisée, nommée Monit.

Fabric, le couteau suisse de l’automatisation

Magazine
Marque
Linux Pratique
Numéro
122
Mois de parution
novembre 2020
Domaines
Résumé

Fabric est une bibliothèque Python et une interface en ligne de commandes facilitant l’utilisation de SSH, que ce soit pour des applications ou dans le but d’automatiser certaines tâches répétitives d’administration système. La grande force de Fabric est d’être particulièrement simple à utiliser.

Comprendre les bases de données relationnelles

Magazine
Marque
Linux Pratique
Numéro
122
Mois de parution
novembre 2020
Domaines
Résumé

Indispensables pour le stockage et le traitement massif de données, les bases de données relationnelles sont partout. Si elles sont utilisées principalement pour l’informatique de gestion, on les rencontre également dans des domaines aussi divers que les sites web, les systèmes d’exploitation ou même les jeux vidéo. Dans cet article, nous allons vous faire découvrir les principaux concepts qui sous-tendent leur fonctionnement.

Automatiser intégralement la mise en place de Wildfly avec Ansible

Magazine
Marque
Linux Pratique
HS n°
Numéro
49
Mois de parution
novembre 2020
Domaines
Résumé

Si les outils comme Ansible permettent d’aller très loin dans l’automatisation d’un déploiement logiciel, ils sont souvent limités dans leurs capacités de réglage fin d’un outil aussi complexe et avancé qu’un serveur Java EE tel que Wildfly (ou son pendant commercial, JBoss EAP). Afin de résoudre cette problématique, l’outil JCliff a été développé pour permettre à Puppet (un concurrent d’Ansible) de s’intégrer sans difficulté avec ce serveur applicatif. Cet outil est maintenant aussi intégré avec Ansible sous la forme d’une collection et cet article propose un tour exhaustif des capacités d’automatisation du déploiement et de la configuration des sous-systèmes de Wildfly à l’aide de cette nouvelle extension.