Gérer une infrastructure avec Terraform, CDKTF et NixOS

Vincent Bernat

Il y a quelques années, j’ai réduit mon infrastructure personnelle au strict minimum. Jusqu’en 2018, elle représentait une douzaine de conteneurs tournant sur un seul serveur Hetzner1. J’ai migré mon courriel vers Fastmail et mes zones DNS vers Gandi. Il ne me restait plus que mon blog à héberger. À ce jour, ma petite infrastructure est composée de 4 machines virtuelles exécutant NixOS sur Hetzner Cloud et Vultr, d’une poignée de zones DNS sur Gandi et Route 53, et de quelques distributions Cloudfront. Elle est gérée par CDK pour Terraform (CDKTF), tandis que les déploiements de NixOS sont gérés par NixOps.

Dans cet article, je présente brièvement Terraform, CDKTF et l’écosystème Nix. J’explique également comment utiliser Nix pour accéder à ces outils dans votre shell afin de les utiliser rapidement.

Mise à jour (11.2023)

Récemment, HashiCorp a opté pour la Business Source License pour tous ces logiciels. C’est une déception, notamment pour Terraform qui est un composant clé pour automatiser les infrastructures et qui a bénéficié d’une large communauté. Il existe désormais un fork communautaire, OpenTofu, mais celui-ci ne couvre pas CDKTF.

CDKTF : infrastructure en tant que code#

Terraform est un outil d’« infrastructure en tant que code ». Vous pouvez définir votre infrastructure en déclarant des ressources avec le langage HCL. Ce dernier possède quelques fonctionnalités supplémentaires, comme des boucles permettant de déclarer plusieurs ressources à partir d’une liste, des fonctions intégrées que vous pouvez appeler dans les expressions et l’expansion de variables dans les chaînes de caractères. Terraform s’appuie sur un large ensemble de fournisseurs pour gérer les ressources.

Gérer des serveurs#

Voici un court exemple utilisant le fournisseur pour Hetzner Cloud pour créer une machine virtuelle :

variable "hcloud_token" {
  sensitive = true
}
provider "hcloud" {
  token = var.hcloud_token
}

resource "hcloud_server" "web03" {
  name = "web03"
  server_type = "cpx11"
  image = "debian-11"
  datacenter = "nbg1-dc3"
}

resource "hcloud_rdns" "rdns4-web03" {
  server_id = hcloud_server.web03.id
  ip_address = hcloud_server.web03.ipv4_address
  dns_ptr = "web03.luffy.cx"
}

resource "hcloud_rdns" "rdns6-web03" {
  server_id = hcloud_server.web03.id
  ip_address = hcloud_server.web03.ipv6_address
  dns_ptr = "web03.luffy.cx"
}

L’expressivité de HCL est assez limitée et je trouve qu’un langage généraliste est plus pratique pour décrire les ressources. C’est là qu’intervient CDK pour Terraform : vous pouvez gérer votre infrastructure à l’aide de votre langage de programmation préféré, notamment TypeScript, Go et Python. Voici l’exemple précédent utilisant CDKTF et TypeScript :

import { App, TerraformStack, Fn } from "cdktf";
import { HcloudProvider } from "./.gen/providers/hcloud/provider";
import * as hcloud from "./.gen/providers/hcloud";

class MyStack extends TerraformStack {
  constructor(scope: Construct, name: string) {
    super(scope, name);

    const hcloudToken = new TerraformVariable(this, "hcloudToken", {
      type: "string",
      sensitive: true,
    });
    const hcloudProvider = new HcloudProvider(this, "hcloud", {
      token: hcloudToken.value,
    });

    const web03 = new hcloud.server.Server(this, "web03", {
      name: "web03",
      serverType: "cpx11",
      image: "debian-11",
      datacenter: "nbg1-dc3",
      provider: hcloudProvider,
    });
    new hcloud.rdns.Rdns(this, "rdns4-web03", {
      serverId: Fn.tonumber(web03.id),
      ipAddress: web03.ipv4Address,
      dnsPtr: "web03.luffy.cx",
      provider: hcloudProvider,
    });
    new hcloud.rdns.Rdns(this, "rdns6-web03", {
      serverId: Fn.tonumber(web03.id),
      ipAddress: web03.ipv6Address,
      dnsPtr: "web03.luffy.cx",
      provider: hcloudProvider,
    });
  }
}

const app = new App();
new MyStack(app, "cdktf-take1");
app.synth();

La commande de cdktf synth génère un fichier de configuration pour Terraform, terraform plan prévisualise les changements et terraform apply les applique. Maintenant que vous disposez d’un langage généraliste, vous pouvez utiliser des fonctions.

Gérer des enregistrements DNS#

Si l’utilisation de CDKTF pour 4 serveurs web peut sembler un peu exagérée, il en va tout autrement lorsqu’il s’agit de gérer quelques zones DNS. Avec DNSControl, qui utilise JavaScript comme langage, j’ai pu définir la zone bernat.ch avec ce bout de code :

D("bernat.ch", REG_NONE, DnsProvider(DNS_BIND, 0), DnsProvider(DNS_GANDI),
  DefaultTTL('2h'),
  FastMailMX('bernat.ch', {subdomains: ['vincent']}),
  WebServers('@'),
  WebServers('vincent');

Cela produit 38 enregistrements. Avec CDKTF, j’écris :

new Route53Zone(this, "bernat.ch", providers.aws)
  .sign(dnsCMK)
  .registrar(providers.gandiVB)
  .www("@", servers)
  .www("vincent", servers)
  .www("media", servers)
  .fastmailMX(["vincent"]);

Toute la magie est située dans les fonctions appelées. Vous pouvez regarder le fichier dns.ts dans le dépôt cdktf-take1 pour comprendre comment cela fonctionne. Rapidement :

  • Route53Zone() crée une zone sur Route 53,
  • sign() signe la zone avec une clé maître,
  • registrar() inscrit la zone auprès du registre de domaines et configure DNSSEC,
  • www() crée les enregistrements A et AAAA pour les serveurs web,
  • fastmailMX() crée les enregistrements MX et d’autres enregistrements liés pour configurer Fastmail en tant que fournisseur de courriel.

Voici le contenu de la fonction fastmailMX(). Elle génère quelques enregistrements et retourne la zone en cours pour faciliter le chaînage :

fastmailMX(subdomains?: string[]) {
  (subdomains ?? [])
    .concat(["@", "*"])
    .forEach((subdomain) =>
      this.MX(subdomain, [
        "10 in1-smtp.messagingengine.com.",
        "20 in2-smtp.messagingengine.com.",
      ])
    );
  this.TXT("@", "v=spf1 include:spf.messagingengine.com ~all");
  ["mesmtp", "fm1", "fm2", "fm3"].forEach((dk) =>
    this.CNAME(`${dk}._domainkey`, `${dk}.${this.name}.dkim.fmhosted.com.`)
  );
  this.TXT("_dmarc", "v=DMARC1; p=none; sp=none");
  return this;
}

Je vous encourage à parcourir le dépôt pour plus de détails !

À propos de Pulumi#

Ma première tentative autour de Terraform a été d’utiliser Pulumi. Vous pouvez trouver cette tentative sur GitHub. C’est assez similaire à ce que je fais actuellement avec CDKTF. La principale différence est que j’utilise Python au lieu de TypeScript2 car ce dernier ne m’était pas familier à l’époque.

Pulumi est antérieur à CDKTF et il utilise une approche légèrement différente. CDKTF génère une configuration pour Terraform (au format JSON au lieu de HCL), laissant la planification, la gestion des états et le déploiement à ce dernier. Il est donc lié aux limites de ce qui peut être exprimé par Terraform, notamment lorsque vous devez transformer des données obtenues d’une ressource à une autre3. Pulumi a besoin de fournisseurs spécifiques pour chaque ressource. De nombreux fournisseurs encapsulent des fournisseurs Terraform.

Bien que Pulumi offre une bonne expérience utilisateur, je suis passé à CDKTF car écrire des fournisseurs pour Pulumi est une corvée. CDKTF ne nécessite pas de passer par cette étape. En dehors des grands acteurs (AWS, Azure et Google Cloud), l’existence, la qualité et la fraîcheur des fournisseurs Pulumi sont inégales. La plupart des fournisseurs s’appuient sur un fournisseur Terraform et il se peut qu’ils soient en retard de quelques versions, qu’il leur manque quelques ressources ou qu’ils présentent des bogues qui leur sont propres.

Lorsqu’un fournisseur n’existe pas, vous pouvez en écrire un à l’aide de la bibliothèque pulumi-terraform-bridge. Le projet Pulumi fournit un modèle à cet effet. J’ai eu une mauvaise expérience avec celui-ci lors de l’écriture de fournisseurs pour Gandi et Vultr : le Makefile installe automatiquement Pulumi en utilisant curl | sh et ne fonctionne pas avec /bin/sh. Il y a un manque d’intérêt pour les contributions communautaires4 ou même pour les fournisseurs pour les acteurs tiers.

NixOS & NixOps#

Nix est un langage de programmation purement fonctionnel. Nix est aussi le nom du gestionnaire de paquets qui est construit au-dessus du langage Nix. Il permet aux utilisateurs d’installer des paquets de manière déclarative. nixpkgs est un dépôt de paquets. Vous pouvez installer Nix au-dessus d’une distribution Linux ordinaire. Si vous voulez plus de détails, une bonne ressource est le site officiel, notamment la section « Apprendre ». La courbe d’apprentissage est rude, mais la récompense est grande.

NixOS : distribution Linux déclarative#

NixOS est une distribution Linux construite au-dessus du gestionnaire de paquets Nix. Voici un bout de configuration pour ajouter quelques paquets :

environment.systemPackages = with pkgs;
  [
    bat
    htop
    liboping
    mg
    mtr
    ncdu
    tmux
  ];

Il est possible de modifier une dérivation5 existante pour utiliser une version différente, activer une fonctionnalité spécifique ou appliquer un correctif. Voici comment j’active et configure Nginx pour désactiver le module stream, ajouter le module de compression Brotli et ajouter le module d’anonymisation des adresses IP. De plus, au lieu d’utiliser OpenSSL 3, je continue à utiliser OpenSSL 1.16.

services.nginx = {
  enable = true;

  package = (pkgs.nginxStable.override {
    withStream = false;
    modules = with pkgs.nginxModules; [
      brotli
      ipscrub
    ];
    openssl = pkgs.openssl_1_1;
  });

Si vous avez besoin d’ajouter certaines modifications, c’est également possible. À titre d’exemple, voici comment j’ai corrigé en avance les failles de sécurité découvertes en 2019 dans Nginx en attendant que cela soit corrigé dans NixOS7 :

services.nginx.package = pkgs.nginxStable.overrideAttrs (old: {
  patches = old.patches ++ [
    # HTTP/2: reject zero length headers with PROTOCOL_ERROR.
    (pkgs.fetchpatch {
      url = https://github.com/nginx/nginx/commit/dbdd[].patch;
      sha256 = "a48190[…]";
    })
    # HTTP/2: limited number of DATA frames.
    (pkgs.fetchpatch {
      url = https://github.com/nginx/nginx/commit/94c5[].patch;
      sha256 = "af591a[…]";
    })
    #  HTTP/2: limited number of PRIORITY frames.
    (pkgs.fetchpatch {
      url = https://github.com/nginx/nginx/commit/39bb[].patch;
      sha256 = "1ad8fe[…]";
    })
  ];
});

Si cela vous intéresse, jetez un coup d’œil à ma configuration relativement réduite : common.nix contient la configuration à appliquer sur tous les serveurs (SSH, utilisateurs, paquets communs), web.nix contient la configuration pour les serveurs web et isso.nix exécute Isso dans un conteneur systemd.

NixOps : outil de déploiement pour NixOS#

Sur un seul nœud, la configuration de NixOS se trouve dans le fichier /etc/nixos/configuration.nix. Après l’avoir modifiée, vous devez exécuter la commande nixos-rebuild switch. Nix va chercher toutes les dépendances possibles dans le cache binaire et construit le reste. Il crée une nouvelle entrée dans le menu du chargeur de démarrage et active la nouvelle configuration.

Pour gérer plusieurs nœuds, il existe plusieurs options, dont NixOps, deploy-rs, Colmena et morph. Je ne les connais pas toutes, mais de mon point de vue, les différences ne sont pas si importantes. Il est également possible de construire un tel outil soi-même car Nix fournit les blocs de construction les plus importants : nix build et nix copy. NixOps est l’un des premiers outils publiés mais je vous encourage à explorer les alternatives.

La configuration de NixOps est écrite avec la langage Nix. Voici une configuration simplifiée pour déployer znc01.luffy.cx, web01.luffy.cx et web02.luffy.cx, à l’aide des fonctions server et web :

let
  server = hardware: name: imports: {
    deployment.targetHost = "${name}.luffy.cx";
    networking.hostName = name;
    networking.domain = "luffy.cx";
    imports = [ (./hardware/. + "/${hardware}.nix") ] ++ imports;
  };
  web = hardware: idx: imports:
    server hardware "web${lib.fixedWidthNumber 2 idx}" ([ ./web.nix ] ++ imports);
in {
  network.description = "Luffy infrastructure";
  network.enableRollback = true;
  defaults = import ./common.nix;
  znc01 = server "exoscale" [ ./znc.nix ];
  web01 = web "hetzner" 1 [ ./isso.nix ];
  web02 = web "hetzner" 2 [];
}

Connecter le tout avec Nix#

L’écosystème Nix est une solution unifiée aux différents problèmes liés à la gestion des logiciels et des configurations. Les environnements de développement déclaratifs et reproductibles en constituent une caractéristique très intéressante. Cela ressemble aux environnements virtuels de Python, mais ils ne sont pas spécifiques à un langage.

Courte introduction aux « flakes » Nix#

J’utilise les « flakes », une nouvelle fonctionnalité de Nix qui améliore la reproductibilité en fixant toutes les dépendances et en isolant le processus de construction. Bien que cette fonctionnalité soit marquée comme expérimentale8, elle est de plus en plus populaire et vous pouvez trouver flake.nix et flake.lock à la racine de certains dépôts.

A titre d’exemple, voici le contenu du flake.nix livré avec Snimpy, un outil SNMP interactif pour Python reposant sur libsmi, une bibliothèque C :

{
  inputs = {
    nixpkgs.url = "nixpkgs";
    flake-utils.url = "github:numtide/flake-utils";
  };
  outputs = { self, ... }@inputs:
    inputs.flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = inputs.nixpkgs.legacyPackages."${system}";
      in
      {
        # nix build
        packages.default = pkgs.python3Packages.buildPythonPackage {
          name = "snimpy";
          src = self;
          preConfigure = ''echo "1.0.0-0-000000000000" > version.txt'';
          checkPhase = "pytest";
          checkInputs = with pkgs.python3Packages; [ pytest mock coverage ];
          propagatedBuildInputs = with pkgs.python3Packages; [ cffi pysnmp ipython ];
          buildInputs = [ pkgs.libsmi ];
        };
        # nix run + nix shell
        apps.default = { 
          type = "app";
          program = "${self.packages."${system}".default}/bin/snimpy";
        };
        # nix develop
        devShells.default = pkgs.mkShell {
          name = "snimpy-dev";
          buildInputs = [
            self.packages."${system}".default.inputDerivation
            pkgs.python3Packages.ipython
          ];
        };
      });
}

Si Nix est installé sur votre système :

  • nix run github:vincentbernat/snimpy exécute Snimpy,
  • nix shell github:vincentbernat/snimpy fournit un shell avec Snimpy prêt à être utilisé,
  • nix build github:vincentbernat/snimpy construit le paquet Python,
  • nix develop . fournit un shell pour développer autour de Snimpy depuis un clône du dépôt9.

Pour plus d’informations sur les flakes, regardez le tutoriel de Tweag.

Nix et CDKTF#

A la racine du dépôt que j’utilise pour CDKTF, il y a un fichier flake.nix pour configurer un shell avec Terraform et CDKTF installés et avec les variables d’environnement nécessaires pour automatiser mon infrastructure.

Terraform est déjà présent dans nixpkgs, mais je dois appliquer une rustine sur le fournisseur Gandi. Ce n’est pas un problème avec Nix !

terraform = pkgs.terraform.withPlugins (p: [
  p.aws
  p.hcloud
  p.vultr
  (p.gandi.overrideAttrs
    (old: {
      src = pkgs.fetchFromGitHub {
        owner = "vincentbernat";
        repo = "terraform-provider-gandi";
        rev = "feature/livedns-key";
        hash = "sha256-V16BIjo5/rloQ1xTQrdd0snoq1OPuDh3fQNW7kiv/kQ=";
      };
    }))
]);

CDKTF est écrit en TypeScript. J’ai un fichier package.json avec toutes les dépendances nécessaires, y compris celles pour utiliser TypeScript comme langage cible :

{
  "name": "cdktf-take1",
  "version": "1.0.0",
  "main": "main.js",
  "types": "main.ts",
  "private": true,
  "dependencies": {
    "@types/node": "^14.18.30",
    "cdktf": "^0.13.3",
    "cdktf-cli": "^0.13.3",
    "constructs": "^10.1.151",
    "eslint": "^8.27.0",
    "prettier": "^2.7.1",
    "ts-node": "^10.9.1",
    "typescript": "^3.9.10",
    "typescript-language-server": "^2.1.0"
  }
}

J’utilise Yarn pour obtenir un fichier yarn.lock qui peut ensuite être utilisé directement pour construire la dérivation contenant toutes les dépendances :

nodeEnv = pkgs.mkYarnModules {
  pname = "cdktf-take1-js-modules";
  version = "1.0.0";
  packageJSON = ./package.json;
  yarnLock = ./yarn.lock;
};

L’étape suivant est de générer les fournisseurs CDKTF à partir des fournisseurs Terraform et de les inclure dans une dérivation :

cdktfProviders = pkgs.stdenvNoCC.mkDerivation {
  name = "cdktf-providers";
  nativeBuildInputs = [
    pkgs.nodejs
    terraform
  ];
  src = nix-filter {
    root = ./.;
    include = [ ./cdktf.json ./tsconfig.json ];
  };
  buildPhase = ''
    export HOME=$(mktemp -d)
    export CHECKPOINT_DISABLE=1
    export DISABLE_VERSION_CHECK=1
    export PATH=${nodeEnv}/node_modules/.bin:$PATH
    ln -nsf ${nodeEnv}/node_modules node_modules

    # Build all providers we have in terraform
    for provider in $(cd ${terraform}/libexec/terraform-providers; echo */*/*/*); do
      version=''${provider##*/}
      provider=''${provider%/*}
      echo "Build $provider@$version"
      cdktf provider add --force-local $provider@$version | cat
    done
    echo "Compile TS → JS"
    tsc
  '';
  installPhase = ''
    mv .gen $out
    ln -nsf ${nodeEnv}/node_modules $out/node_modules
  '';
};

Enfin, nous définissons l’environnement de développement :

devShells.default = pkgs.mkShell {
  name = "cdktf-take1";
  buildInputs = [
    pkgs.nodejs
    pkgs.yarn
    terraform
  ];
  shellHook = ''
    # No telemetry
    export CHECKPOINT_DISABLE=1
    # No autoinstall of plugins
    export CDKTF_DISABLE_PLUGIN_CACHE_ENV=1
    # Do not check version
    export DISABLE_VERSION_CHECK=1
    # Access to node modules
    export PATH=$PWD/node_modules/.bin:$PATH
    ln -nsf ${nodeEnv}/node_modules node_modules
    ln -nsf ${cdktfProviders} .gen

    # Credentials
    for p in \
      njf.nznmba.pbz/Nqzvavfgengbe \
      urgmare.pbz/ivaprag@oreang.pu \
      ihyge.pbz/ihyge@ivaprag.oreang.pu; do
        eval $(pass show $(echo $p | tr 'A-Za-z' 'N-ZA-Mn-za-m') | grep '^export')
    done
    eval $(pass show personal/cdktf/secrets | grep '^export')
    export TF_VAR_hcloudToken="$HCLOUD_TOKEN"
    export TF_VAR_vultrApiKey="$VULTR_API_KEY"
    unset VULTR_API_KEY HCLOUD_TOKEN
  '';
};

Les dérivations listées dans buildInputs sont disponibles dans le shell fourni. Le contenu de shellHook est exécuté lors du démarrage du shell. Il établit des liens symboliques pour rendre disponible l’environnement JavaScript construit à une étape précédente, ainsi que les fournisseurs CDKTF générés. Il exporte également toutes les informations d’identification10.

J’utilise également direnv avec un fichier .envrc pour passer automatiquement dans l’environnement de développement. Cela permet également à ce dernier d’être disponible depuis Emacs, notamment lors de l’utilisation de lsp-mode pour obtenir les complétions. nix develop . permet aussi d’activer manuellement l’environnement.

J’utilise les commandes suivantes pour déployer11 :

$ cdktf synth
$ cd cdktf.out/stacks/cdktf-take1
$ terraform plan --out plan
$ terraform apply plan
$ terraform output -json > ~-automation/nixops-take1/cdktf.json

La dernière commande produit un fichier JSON contenant les données nécessaires pour finir le déploiement avec NixOps.

NixOps#

Le fichier JSON exporté par Terraform contient la liste des serveurs avec quelques attributs :

{
  "hardware": "hetzner",
  "ipv4Address": "5.161.44.145",
  "ipv6Address": "2a01:4ff:f0:b91::1",
  "name": "web05.luffy.cx",
  "tags": [
    "web",
    "continent:NA",
    "continent:SA"
  ]
}

Dans le fichier network.nix, cette liste est importée et transformée en un ensemble d’attributs décrivant les serveurs. Une version simplifiée ressemble à cela :

let
  lib = inputs.nixpkgs.lib;
  shortName = name: builtins.elemAt (lib.splitString "." name) 0;
  domainName = name: lib.concatStringsSep "." (builtins.tail (lib.splitString "." name));
  server = hardware: name: imports: {
    networking = {
      hostName = shortName name;
      domain = domainName name;
    };
    deployment.targetHost = name;
    imports = [ (./hardware/. + "/${hardware}.nix") ] ++ imports;
  };
  cdktf-servers-json = (lib.importJSON ./cdktf.json).servers.value;
  cdktf-servers = map
    (s:
      let
        tags-maybe-import = map (t: ./. + "/${t}.nix") s.tags;
        tags-import = builtins.filter (t: builtins.pathExists t) tags-maybe-import;
      in
      {
        name = shortName s.name;
        value = server s.hardware s.name tags-import;
      })
    cdktf-servers-json;
in
{
  // []
} // builtins.listToAttrs cdktf-servers

Pour web05, on obtient ceci :

web05 = {
  networking = {
    hostName = "web05";
    domainName = "luffy.cx";
  };
  deployment.targetHost = "web05.luffy.cx";
  imports = [ ./hardware/hetzner.nix ./web.nix ];
};

Comme pour CDKTF, à la racine du dépôt que j’utilise pour NixOps, il y a un fichier flake.nix pour fournir un shell avec NixOps configuré. Comme NixOps ne supporte pas les déploiements progessifs, j’utilise généralement ces commandes pour déployer sur un unique serveur12 :

$ nix flake update
$ nixops deploy --include=web04
$ ./tests web04.luffy.cx

Si les tests se déroulent sans soucis, je déploie les autres nœuds un par un avec la commande suivante :

$ (set -e; for h in web{03..06}; do nixops deploy --include=$h; done)

La commande nixops deploy déploie tous les serveurs en parallèle et peut donc provoquer une panne si tous les serveurs Nginx sont indisponibles au même moment.


Cet article est en chantier depuis trois ans. Le contenu a été mis à jour et affiné au fur et à mesure de mes expérimentations. Il y a encore beaucoup à explorer13, mais j’estime que le contenu est désormais suffisant pour être publié ! 🎄


  1. C’était un AMD Athlon 64 X2 5600+ avec 2 Go de RAM et 2 disques de 400 Go en RAID logiciel. Je payais quelque chose autour de 59 € par mois pour cela. Si c’était une bonne affaire en 2008, en 2018, ce n’était plus rentable. Il fonctionnait sous Debian Wheezy avec Linux-VServer pour l’isolation, tous deux dépassés en 2018. ↩︎

  2. Je n’ai pas non plus utilisé Python car le support de Poetry dans Nix était cassé quand j’ai commencé à essayer d’utiliser CDKTF↩︎

  3. Pulumi peut appliquer des fonctions arbitraires à l’aide de apply(). Cela permet de transformer les données qui ne sont pas connues lors de l’étape de planification. Terraform a des fonctions pour un usage similaire mais elles sont plus limitées. ↩︎

  4. Les deux modifications mentionnées ne sont pas encore fusionnées. La seconde est remplacée par la PR #61, soumise deux mois plus tard, qui impose l’utilisation de /bin/bash. J’ai également soumis la PR #56, qui a été fusionnée 4 mois plus tard et rapidement annulée sans explication. ↩︎

  5. Grosso modo, une dérivation est un synonyme pour paquet dans l’écosystème Nix↩︎

  6. OpenSSL 3 a de nombreux problèmes de performance↩︎

  7. NixOS peut être un peu lent à intégrer les correctifs car il est nécessaire de reconstruire une partie du cache binaire. Dans ce cas précis, cela a été rapide : la vulnérabilité et les correctifs ont été publiés le 13 août 2019 et disponibles dans NixOS le 15 août. À titre de comparaison, Debian n’a publié la version corrigée que le 22 août, ce qui est inhabituellement tardif. ↩︎

  8. Comme les flakes sont expérimentaux, de nombreuses documentations ne les utilisent pas et c’est un aspect supplémentaire à apprendre. ↩︎

  9. Comme dans les autres exemples, il est possible de remplacer . par github:vincentbernat/snimpy. Cependant, obtenir les dépendances de Snimpy sans son code source a peu d’intérêt. ↩︎

  10. J’utilise le gestionnaire de mots de passe pass. Le nom des mots de passe sont brouillés uniquement pour éviter les courriers indésirables. ↩︎

  11. La commande cdktf sait appeler les commandes terraform, mais je préfère les utiliser directement car elles sont plus flexibles. ↩︎

  12. Si le changement est risqué, je désactive le serveur avec CDKTF. Cela le retire des enregistrements DNS. ↩︎

  13. Je voudrais remplacer NixOps avec une alternative gérant les déploiements progressifs et les tests. Je voudrais aussi passer à Nomad ou Kubernetes pour déployer les applications. ↩︎