Aller au contenu principal

Os Immutable: Nixos

· 7 minutes de lecture
Stéphane ROBERT

logo ansible

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 --list
nixpkgs 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 nixpkgs
nix-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 result
lrwxrwxrwx 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-image
2.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 init
terraform 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-release
NAME=NixOS
ID=nixos
VERSION="21.11 (Porcupine)"
VERSION_CODENAME=porcupine
VERSION_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 --version
docker-compose version 1.29.2, build unknown

[admuser@vm1:~]$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

[admuser@vm1:~]$ docker run -it alpine:3.15 sh
Unable to find image 'alpine:3.15' locally
3.15: Pulling from library/alpine
df9b9388f04a: Pull complete
Digest: sha256:4edbd2beb5f78b1014028f4fbb99f3237d9561100b6881aabbf5acce2c4f9454
Status: Downloaded newer image for alpine:3.15
/ #

Cool, c'est bien la version 21.11 et docker fonctionne. Maintenant essayons d'installer ansible :

python --version
Python 3.9.6

python -m ensurepip

Defaulting to user installation because normal site-packages is not writeable
Looking in links: /tmp/tmpgpnmyljy
Processing /tmp/tmpgpnmyljy/setuptools-56.0.0-py3-none-any.whl
Processing /tmp/tmpgpnmyljy/pip-21.1.3-py3-none-any.whl
Installing 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 --version
ansible [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