Os Immutable: Nixos
Nixos est une distribution Linux basée sur le gestionnaire de package fonctionnel Nix. Ce gestionnaire de packages peut être installé sur n’importe distribution Linux. Il permet d’utiliser plusieurs versions d’un même package en les installant dans des dossiers spécifiques, dont le nom est la somme de contrôle du paquet. Pour gérer les différents cas d’usage, il suffit de créer des fichiers de déclaration écrit dans le langage nix en y indiquant les dépendances nécessaires. Pour simplifier la gestion, Nix est livré avec toute une série d’utilitaires.
Pour le moment, je ne vais pas expliquer tout le fonctionnement de Nix, mais simplement montrer comment construire des images pour les instancier ensuite avec Terraform.
Installation du gestionnaire de package Nix
Pour pouvoir construire des images, il faut installer le gestionnaire de packages Nix sur votre poste de développement. Comme on ne va pas pour le moment l’utiliser sur ce poste, je l’installe en mode single-user.
sh <(curl -L https://nixos.org/nix/install) --no-daemon
L’outil d’installation se charge de créer les répertoires nécessaires à son fonctionnement et à ajouter la ligne de chargement du profil dans votre shell favori. Il faut juste ouvrir une nouvelle session pour la charger.
On va changer de version via les channels de nix. On vérifie quelle version on a pour le moment :
nix-channel --listnixpkgs https://nixos.org/channels/nixos-unstable
Unstable, pas cool, on change donc pour utiliser la version 21.11
:
nix-channel --add https://nixos.org/channels/nixos-21.11 nixpkgsnix-channel --update
Build d’une image Nixos pour kvm
Je ne vais pas utiliser nixos-generators
mais la commande nix-build
. Il faut
dans un premier temps créer la configuration nix de votre VM.
Déclaration de la configuration de la VM
On crée un premier fichier kvm-nixos.nix
avec ce contenu :
{ config, lib, pkgs, ... }:
with lib;
{ imports = [ <nixpkgs/nixos/modules/installer/cd-dvd/channel.nix> ./machine-config.nix ];
system.build.qcow2 = import <nixpkgs/nixos/lib/make-disk-image.nix> { inherit lib config; pkgs = import <nixpkgs> { inherit (pkgs) system; }; diskSize = 8192; format = "qcow2"; configFile = pkgs.writeText "configuration.nix" '' { imports = [ <./machine-config.nix> ]; } ''; };}
On importe le module permettant d’installer Nixos. On indique comment sera
construire l’image, taille du disque, son format et sa configuration via le
fichier nix machine-config.nix
dont le contenu est le suivant :
{ pkgs, lib, ... }:
with lib;
{ imports = [ <nixpkgs/nixos/modules/profiles/qemu-guest.nix> ];
config = { fileSystems."/" = { device = "/dev/disk/by-label/nixos"; fsType = "ext4"; };
boot.growPartition = true; boot.kernelParams = [ "console=ttyS0" ]; boot.loader.grub.device = "/dev/vda";
networking.hostName = "devbox"; time.timeZone = "Europe/Paris";
users.extraUsers.root.password = ""; services.openssh.enable = true; services.openssh.permitRootLogin = "yes"; virtualisation.docker.enable = true; nixpkgs.config.allowUnfree = true;
i18n = { defaultLocale = "fr_FR.UTF-8"; };
console.font = "Lat2-Terminus16"; console.keyMap = "fr";
nix.gc = { automatic = true; dates = "weekly"; options = "--delete-older-than 30d"; };
users.users.admuser = { isNormalUser = true; home = "/home/admuser"; description = "Adm User"; extraGroups = [ "wheel" "networkmanager" "docker" ]; openssh.authorizedKeys.keys = [ "ssh-ed25519 ... +hJf3H8hp30= vagrant@ubuntu2110.localdomain" ]; };
environment.systemPackages = [ pkgs.git pkgs.openssl pkgs.libxml2 pkgs.libzip pkgs.libffi pkgs.zlib pkgs.stdenv pkgs.htop pkgs.vim pkgs.unixtools.netstat pkgs.docker-compose pkgs.python3Full ];
system.autoUpgrade.enable = false;
};}
On importe le profil qemu-guest qui se charge de configurer l’OS pour qu’il
fonctionne sur notre hyperviseur. On crée la configuration de la machine
dans la variable config
. On peut tout décrire dedans depuis le partitionnement
des disques, jusqu’aux packages à intégrer, mais aussi les users et
groupes à créer, la configuration du clavier, etc. Nixos intégrant un
système de mise à jour automatique et que je veux rendre mon instance
immutable, je le désactive.
Pour rechercher les packages à installer vous pouvez utiliser la commande nix search <nom>
ou vous rendre sur le site nixos
search ↗.
Build de l’image
Maintenant tout est prêt. On peut lancer le build :
nix-build '<nixpkgs/nixos>' -A config.system.build.qcow2 --arg configuration "{ imports = [ ./kvm.nix ]; }"
C’est assez rapide et donc plutôt une belle surprise. Vérifions que notre
fichier qcow2 est bien présent dans le dossier result
:
ls -al resultlrwxrwxrwx 1 vagrant vagrant 60 avril 9 18:19 result -> /nix/store/k3ajyppzzqbffzp1dazy6bkg3zri7dyq-nixos-disk-image
L’image est bien présente, mais c’est un lien qui pointe vers un dossier qui intègre la somme de contrôle de l’image. Quelle taille fait le fichier
du -sh /nix/store/k3ajyppzzqbffzp1dazy6bkg3zri7dyq-nixos-disk-image2.4G /nix/store/k3ajyppzzqbffzp1dazy6bkg3zri7dyq-nixos-disk-image
Tout de même 2,4 Go. Allez on la teste !
Teste de l’image avec Terraform
Voici mon fichier de configuration permettant d’instancier une VM intégrant notre image générée :
terraform { required_version = ">= 0.13" required_providers { libvirt = { source = "dmacvicar/libvirt" // version = "0.6.3" } }}
provider "libvirt" { uri = "qemu:///system"}
resource "libvirt_pool" "volumetmp" { name = "nixos-pool" type = "dir" path = "/var/tmp/nixos-pool"}
resource "libvirt_volume" "base" { name = "nixos" source = "./result/nixos.qcow2" pool = libvirt_pool.volumetmp.name format = "qcow2"}
resource "libvirt_volume" "vm-disk" { name = "nixos.qcow2" base_volume_id = libvirt_volume.base.id pool = libvirt_pool.volumetmp.name format = "qcow2"}
resource "libvirt_domain" "machine" {
name = "vm1" vcpu = 2 memory = 2048
disk { volume_id = libvirt_volume.vm-disk.id }
graphics { listen_type = "address" }
# dynamic IP assignment on the bridge, NAT for Internet access network_interface { network_name = "default" wait_for_lease = true }}
output "ip-addresses" { value = libvirt_domain.machine.network_interface.0.addresses.*}
Allez on instancie :
terraform initterraform apply --auto-approve
...Outputs:
ip-addresses = tolist([ "192.168.122.12",])
Test de l’image nixos
Normalement le serveur ssh est actif avec la clé de mon compte m’autorisant
à me connecter avec le compte admuser
:
ssh admuser@192.168.122.12
Last login: Sun Apr 10 09:22:12 2022 from 192.168.122.1[admuser@vm1:~]$ cat /etc/os-releaseNAME=NixOSID=nixosVERSION="21.11 (Porcupine)"VERSION_CODENAME=porcupineVERSION_ID="21.11"BUILD_ID="21.11.336854.29abf698b38"PRETTY_NAME="NixOS 21.11 (Porcupine)"LOGO="nix-snowflake"HOME_URL="https://nixos.org/"DOCUMENTATION_URL="https://nixos.org/learn.html"SUPPORT_URL="https://nixos.org/community.html"BUG_REPORT_URL="https://github.com/NixOS/nixpkgs/issues"
[admuser@vm1:~]$ docker-compose --versiondocker-compose version 1.29.2, build unknown
[admuser@vm1:~]$ docker psCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
[admuser@vm1:~]$ docker run -it alpine:3.15 shUnable to find image 'alpine:3.15' locally3.15: Pulling from library/alpinedf9b9388f04a: Pull completeDigest: sha256:4edbd2beb5f78b1014028f4fbb99f3237d9561100b6881aabbf5acce2c4f9454Status: Downloaded newer image for alpine:3.15/ #
Cool, c’est bien la version 21.11 et docker fonctionne. Maintenant essayons d’installer ansible :
python --versionPython 3.9.6
python -m ensurepip
Defaulting to user installation because normal site-packages is not writeableLooking in links: /tmp/tmpgpnmyljyProcessing /tmp/tmpgpnmyljy/setuptools-56.0.0-py3-none-any.whlProcessing /tmp/tmpgpnmyljy/pip-21.1.3-py3-none-any.whlInstalling collected packages: setuptools, pip WARNING: The scripts pip3 and pip3.9 are installed in '/home/admuser/.local/bin' which is not on PATH. Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.Successfully installed pip-21.1.3 setuptools-56.0.0
C’est déjà bien pip s’installe. On doit modifier le PATH (faudra l’ajouter dans la config ):
export PATH=$HOME/.local/bin:$PATH
Allez on lance l’installation d’Ansible :
pip3 install ansible
Collecting ansible Downloading ansible-5.6.0.tar.gz (35.5 MB) |████████████████████████████████| 35.5 MB 2.0 MB/s...Installing collected packages: pycparser, pyparsing, MarkupSafe, cffi, resolvelib, PyYAML, packaging, jinja2, cryptography, ansible-core, ansible Running setup.py install for ansible-core ... done Running setup.py install for ansible ... done...
ansible --versionansible [core 2.12.4] config file = None configured module search path = ['/home/admuser/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules'] ansible python module location = /home/admuser/.local/lib/python3.9/site-packages/ansible ansible collection location = /home/admuser/.ansible/collections:/usr/share/ansible/collections executable location = /home/admuser/.local/bin/ansible python version = 3.9.6 (default, Jun 28 2021, 08:57:49) [GCC 10.3.0] jinja version = 3.1.1 libyaml = True
Ça fonctionne plutôt bien, vu que j’ai ajouté tous les packages nécessaires
pour
que cryptography
se compile.
Plus loin
J’apprécie vraiment la partie définition de l’image via un script de
configuration, même si cela impose d’apprendre le langage nix. Par contre,
je trouve l’apprentissage un peu hard
, surtout que je n’ai pas cherché à
astreindre d’autres versions que celles définies dans la version fournie.
J’adore aussi la possibilité de tester des outils sans les installer via
nix-shell.
Je vais continuer à vérifier peut réellement être utilisé contexte de production
et qu’il est réellement immutable
.
Je pense qu’il est possible de définir une architecture complexe en utilisant les inventaires et les templates Ansible pour générer toutes les images et en faisant appel à terraform pour tout instancier.
Je pense que nixos
peut également être une base pour construire des images
Docker à utiliser dans les pipelines de CI/CD. Je suis sûr que nix-build
permet de le faire. Un prochain billet.
Source : nix.dev ↗