Orchestration de conteneurs façon Kubernetes sous FreeBSD avec le couple pot et Nomad

Spécialité(s)


Résumé

Forts de soutiens solides, les outils tels que Docker pour la conteneurisation et Kubernetes pour l'orchestration se sont imposés en tant que leaders dans leur catégorie respective... mais cela ne veut pas dire que des alternatives ne peuvent pas être considérées, au contraire !!!


Body

Il y a quelques semaines [0], je vous présentais une option à considérer face à cette plateforme très populaire qu’est Kubernetes.

Ancré dans une philosophie DevOps, le couple HashiCorp Nomad et le framework dédié à la gestion des conteneurs pot peut être ainsi mis en œuvre afin de proposer non seulement une alternative intéressante aux architectures monolithiques, mais aussi une plateforme adaptée à l’accueil de vos microservices.

Après la théorie, passons à la pratique au travers d’un premier cas d’utilisation avec un environnement relativement « standard » sous GNU/Linux… puis un exemple de mise en œuvre dans un contexte d’exploitation sous FreeBSD !

1. Démarrer avec un premier manifeste Nomad

Pour déployer notre première application via Nomad, nous allons retenir une des trois options qui se présentent à nous. Ainsi, pour l’exemple, notre administrateur système rédigera son manifeste depuis son poste de travail personnel et il le soumettra à l’outil d’orchestration à l’aide d’un terminal, en ligne de commandes. Il est tout à fait possible de déporter cela dans une chaîne d’intégration et de déploiement continu. Le schéma en Figure 1 illustre le workflow qui sera mis en œuvre dans ce contexte.

nomad pot figure 05-s

Fig. 1 : Un schéma de synthèse de la logique des opérations.

Le job est donc soumis par une personne qui souhaite obtenir un résultat précis. Il comprend un ensemble de tâches, décrites dans des groupes, avec des contraintes et des ressources exprimées. Elles sont planifiées en vue de leur exécution par l’orchestrateur sur le cluster. L’association entre un job et un client passe par une étape appelée l’allocation. Elle est nécessaire pour déclarer un ensemble de déploiements qui doivent s’exécuter sur un nœud en particulier.

Ce processus d’allocation est réalisé par le composant en charge de l’ordonnancement, avant la phase d’évaluation à proprement dite. Cette opération d’évaluation se produit lorsque le job est confié de manière à mettre en phase le serveur compte tenu de ce qui est demandé par la personne : c’est une manière de réconcilier les états.

Une fois que la tâche est soumise à un serveur Nomad, chaque groupe de tâches aura donc un certain nombre d'allocations à créer en fonction du nombre d'instances requises. S'il doit y avoir 3 instances d'un groupe de tâches spécifié dans le job, l’ordonnanceur créera 3 allocations et les placera en conséquence. Dans ce cas, nous affecterons la valeur 3 à la variable count dans le group qui sera défini.

Par convention, nos déploiements seront décrits dans des fichiers qui porteront une extension .nomad. Vous pouvez utiliser n’importe quel éditeur de code pour écrire ce fichier. J’ai retenu personnellement Visual Studio Code, développé par Microsoft, car il est extensible : parmi les greffons présents sur mon poste, j’ai installé celui proposé par HashiCorp qui ajoute la coloration syntaxique et la reconnaissance de leur langage HCL.

Pour démarrer, je vais appeler le binaire nomad et spécifier la commande init de la manière suivante dans une fenêtre de terminal :

$ nomad init -short redis.nomad

Nous avons ajouté le commutateur optionnel -short afin d’obtenir un fichier de synthèse qui n’inclut pas, en outre, les commentaires et les nombreuses possibilités de configuration offertes par l’outil. Dans notre cas, le fichier généré correspond à un déploiement, à l’aide d’un conteneur Docker, du système de stockage de données clé-valeur en mémoire Redis en mode service. Ci-après son contenu :

job "example" {
  datacenters = ["dc1"]
 
  group "cache" {
    network {
      port "db" {
        to = 6379
      }
    }
 
    task "redis" {
      driver = "docker"
 
      config {
        image          = "redis:7"
        ports          = ["db"]
        auth_soft_fail = true
      }
 
      resources {
        cpu    = 500
        memory = 256
      }
    }
  }
}

L’intérêt de cette commande est de disposer d’un squelette et d’une base réutilisable pour mon manifeste. Nous allons ensuite modifier ce fichier pour l’adapter à nos besoins. Pour l’exemple, nous modifierons la valeur du champ datacenters afin d’indiquer ce qui correspond à notre environnement. Nous utiliserons toujours le binaire nomad associé à la commande job afin de réaliser les actions sur les travaux que nous décrivons.

Avec ce manifeste, nous voyons apparaître une petite nouveauté. Au sein de notre tâche (task), nous pourrons définir un bloc resources optionnel. Il permet de contraindre l’utilisation des ressources matérielles en termes de consommation mémoire (memory = 256 Mo ici) et processeur (cpu = 500 Mhz). Cela rend possible une gestion fine de ces variables d’ajustement et l’utilisation, en conséquence, du matériel virtuel dans de bonnes conditions.

Avant de lancer ce job en production, vous avez la possibilité d’effectuer un test à blanc (ou dry-run) à l’aide de l’instruction plan. Ce processus de test consiste à anticiper sur les effets d’une éventuelle défaillance en simulant une exécution sur notre cluster. Nous pouvons demander l’exécution de cette opération toujours depuis notre poste client à l’aide du terminal de la manière suivante :

$ nomad job plan redis.nomad
+ Job: "example"
+ Task Group: "cache" (1 create)
  + Task: "redis" (forces create)
 
Scheduler dry-run:
- All tasks successfully allocated.
 
Job Modify Index: 0
To submit the job with version verification run:
 
nomad job run -check-index 0 redis.nomad

Si tout est conforme à l’issue de ce processus, nous pouvons ensuite soumettre le job nommé « example » à notre plateforme d’orchestration et d’ordonnancement. L’exécution précédente nous propose même le détail de la commande run à soumettre pour notre tâche sur notre poste client.

$ nomad job run -check-index 0 redis.nomad
==> 2023-03-09T10:11:08+01:00: Monitoring evaluation "6517665f"
    2023-03-09T10:11:08+01:00: Evaluation triggered by job "example"
    2023-03-09T10:11:09+01:00: Evaluation within deployment: "3ed192a0"
    2023-03-09T10:11:09+01:00: Allocation "a64d2169" created: node "4b56ab75", group "cache"
    2023-03-09T10:11:09+01:00: Evaluation status changed: "pending" -> "complete"
==> 2023-03-09T10:11:09+01:00: Evaluation "6517665f" finished with status "complete"
==> 2023-03-09T10:11:09+01:00: Monitoring deployment "3ed192a0"
  ✓ Deployment "3ed192a0" successful
 
    2023-03-09T10:11:24+01:00
    ID          = 3ed192a0
    Job ID      = example
    Job Version = 0
    Status      = successful
    Description = Deployment completed successfully
 
    Deployed
    Task Group Desired Placed Healthy Unhealthy Progress Deadline
    cache      1       1      1       0         2023-03-09T10:21:23+01:00

Des identifiants courts et alphanumériques uniques sont attribués aux éléments constitutifs d’un déploiement, que ce soit pour la tâche de déploiement à proprement dit ou l’identification du job lui-même.

Vous remarquerez la présence de l’option -check-index suivie d’un numéro de version. Cela oblige Nomad à réaliser la tâche ou à la mettre à jour si et seulement s’il existe une correspondance, côté serveur, pour le numéro de version indiqué. Le comportement de l’orchestrateur est le suivant dans ce cas :

  • si la valeur de ce paramètre est égale à 0, le job est enregistré s’il n’existe pas ;
  • si la valeur indiquée est différente de 0, le job est mis à jour depuis son dernier état connu sur le cluster.

L’interface graphique permet d’obtenir une vue complémentaire sur le nœud qui va prendre en charge l’exécution de la tâche confiée par la grappe d’orchestration (voir la figure 6). Grâce aux informations d’identifications remontées dans la trace de l’exécution, nous pouvons facilement identifier le worker node en charge de l’exécution du job.

nomad pot figure 06-s

Fig. 2 : Le résumé des allocations réalisées sur un nœud client (worker).

Nous pouvons obtenir des détails complémentaires sur celui-ci en cliquant sur l’identifiant unique qui correspond à l’allocation réalisée par le serveur cible pour ce job (Figure 3).

nomad pot figure 07-s

Fig. 3 : Les informations détaillées sur l’allocation qui concerne notre job d’exemple. Nous avons des éléments visuels qui permettent d’observer des métriques et d’obtenir des détails complémentaires sur le déploiement (comme la configuration réseau appliquée par exemple).

Je peux aussi obtenir ces informations en ligne de commandes, depuis mon poste client, grâce à la commande status. Pour avoir ces éléments, je dois indiquer le nom du job concerné, comme le montre l’exemple ci-après :

$ nomad job status example
ID            = example
Name          = example
Submit Date   = 2023-03-09T10:11:08+01:00
Type          = service
Priority      = 50
Datacenters   = example
Namespace     = default
Status        = running
Periodic      = false
Parameterized = false
 
Summary
Task Group Queued Starting Running Failed Complete Lost Unknown
cache      0      0        1       0      0        0    0
 
Latest Deployment
ID          = 3ed192a0
Status      = successful
Description = Deployment completed successfully
 
Deployed
Task Group Desired Placed Healthy Unhealthy Progress Deadline
cache      1       1      1       0         2023-03-09T10:21:23+01:00
 
Allocations
ID        Node ID   Task Group Version Desired Status   Created     Modified
a64d2169  4b56ab75  cache      0       run     running  43m44s ago  43m30s ago

Dans une chaîne d’intégration et de déploiement continus, nous trouvons souvent la définition d’un stage comprenant un job qui vise, par exemple, à analyser la syntaxe d’un code : c’est ce qui est appelé le linting. Cela permet d’améliorer la qualité du code, assure sa reprise et sa maintenabilité. Vous pouvez mettre en œuvre cette démarche avec Nomad.

J’ai réalisé deux modifications dans le fichier redis.nomad fourni en exemple. J’ai déplacé le bloc « network » pour le mettre au même niveau que le bloc « group ». J’ai également fait une faute volontaire dans la définition du bloc nommé « task » en ajoutant un s supplémentaire … ce qui en fait une instruction non connue par Nomad. Voyons ci-après l’exécution de l’instruction validate pour ce déploiement :

$ nomad job validate redis.nomad
Error getting job struct: Error parsing job file from redis.nomad:
redis.nomad:4,3-10: Unsupported block type; Blocks of type "network" are not expected here.
redis.nomad:17,5-10: Unsupported block type; Blocks of type "tasks" are not expected here. Did you mean "task"?

Le composant dédié au linting fonctionne et remonte correctement les erreurs que nous avons créées volontairement. Cela montre ainsi les possibilités d’interaction de cette plateforme dans le contexte d’une chaîne d’intégration et de déploiement continu.

2. De Docker à pot ...

Docker et Kubernetes ont contribué à faire évoluer les choses positivement en matière de gestion des microservices. Le petit problème est que ces outils s’adressent essentiellement aux environnements de type GNU/Linux. Mais qu’en est-il des autres systèmes d’exploitation ? Sont-ils en reste sur ce plan ?

Figurez-vous que non ! En effet, grâce à l’ajout de pilotes supplémentaires (tasks drivers), Nomad peut être facilement enrichi et autoriser l’utilisation de beaucoup de technologies différentes pour venir en support au déploiement de services.

Cela nous autorise ainsi à mettre en œuvre des conteneurs, des machines virtuelles plus traditionnelles avec QEMU, ou encore des jails [1] … et c’est plutôt une aubaine, car la mise en œuvre de Docker ou Kubernetes n’est pas si simple que cela dans un contexte d’exploitation tel qu’une ferme de serveurs exécutant FreeBSD !

Si vous êtes familier avec ce système d’exploitation, vous savez probablement que la réponse proposée dans cet environnement passe par la mise en œuvre de jails … et là où cela devient intéressant, c’est qu’il existe un gestionnaire de tâches pour Nomad qui permet leur déploiement et leur orchestration dans ce contexte. Celui-ci s’appuie sur pot, développé pour offrir une alternative aux conteneurs tels que nous les connaissons notamment avec Podman ou Docker.

Un challenge intéressant se pose alors à nous : est-il possible de proposer le couple pot et Nomad comme alternative aux outils traditionnels d’orchestration et de conteneurisation ?

2.1 Présentation de pot

Le projet pot (pour Prison Operating Technology) a vu le jour en 2017. C’est un ensemble d’outils et de composants logiciels open source (ou framework) construit pour s’appuyer pleinement sur les possibilités offertes par les systèmes FreeBSD. Il a été créé pour faciliter la gestion et l’exploitation des environnements de type jail.

Pour rappel, une jail peut être assimilée à un système d'exploitation virtuel indépendant qui fonctionne comme une instance isolée du système d'exploitation principal. Cette solution technique se présente comme une alternative aux solutions de sa catégorie (ezjail, iocage ou encore BastilleBSD [2]).

De manière générale, pot peut donc s’avérer particulièrement utile pour les développeurs ou les administrateurs de systèmes qui souhaitent tester ou déployer des applications dans un environnement de production (ou de développement) de manière sécurisée et contrôlée.

pot est écrit majoritairement en shell et est distribué sous les termes de la licence BSD-3. Le code source du projet est hébergé sur GitHub. Une équipe d’une douzaine de personnes suit ce projet dont le développeur principal est Luca Pizzamiglio. L’application nécessite, au minimum, la version 12 ou supérieure de FreeBSD. Sa dernière version a été publiée mi-décembre 2022. Elle est numérotée 0.15.4.

2.2 Et si nous l’installions ?

pot est disponible en tant que paquet pour la distribution (via pkg) et peut être également installé facilement grâce aux ports. Les développeurs préconisent toutefois l’utilisation de cette première méthode pour le déployer sur un hôte. Notez qu’il existe également un dépôt GitHub qui autorise son téléchargement : https://github.com/bsdpot/pot/releases.

Pour l’exemple, nous nous reposerons sur des machines virtuelles exécutant la version 13.1 de FreeBSD. Le binaire Nomad (utilisé en tant que client) est également installé et sa configuration se trouve à l’emplacement /usr/local/etc/nomad/client.hcl dans le système de fichiers. Il convient aussi de renseigner la directive plugin_dir dans le fichier client.hcl. Nous lui avons affecté la valeur /usr/local/libexec/nomad/plugins.

C’est à cet emplacement que nous installerons le task driver qui assurera les échanges entre Nomad et pot dans le déploiement pour les opérations d’orchestration et d’ordonnancement : nomad-pot-driver. Il est écrit en Go et publié sous les termes de la licence Apache 2.0. Son principal contributeur est Esteban Barrios.

Il est livré sous la forme d’un binaire qui peut être téléchargé sur GitHub à l’adresse URL ci-après : https://github.com/bsdpot/nomad-pot-driver/releases. La dernière version disponible lors de la rédaction de cet article a été publiée mi-septembre 2022 et est numérotée v0.9.0.

Après nous être assuré que nous disposions bien du binaire nomad sur notre machine, nous réaliserons enfin l’installation des paquets pot et de nomad-pot-driver dans un terminal en tant qu’utilisateur root de la manière suivante :

$ pkg install -y pot nomad-pot-driver

Une fois l’opération terminée, il est nécessaire de procéder à quelques ajustements avant de pouvoir démarrer et créer nos premières jails.

2.3 Configuration de base

Dans un premier temps, nous allons configurer l’environnement d’exécution et l’espace d’accueil des jails dans le système invité virtuel. L’ensemble des fichiers de configuration nécessaires à l’application se trouvent dans le répertoire /usr/local/etc/pot sur notre machine.

Nous éditerons le fichier pot.conf fourni par défaut et apporterons cinq changements afin que cela puisse fonctionner dans notre contexte d’exploitation. Nous modifierons donc les variables suivantes :

  • POT_ZFS_ROOT : permet d’indiquer l’emplacement du pool qui va recevoir les données de pot ;
  • POT_FS_ROOT : spécifie l’emplacement, sur l’hôte, du montage pour le pool défini dans POT_ZFS_ROOT ;
  • POT_EXTIF : permet de spécifier l’interface réseau à utiliser sur l’hôte pour la communication des services exécutés dans les jails ;
  • POT_NETWORK : autorise la création d’un sous-réseau interne ;
  • POT_NETMASK : correspond au masque du sous-réseau interne déclaré au-dessus ;
  • POT_GATEWAY : permet de spécifier l’adresse IPv4 utilisée en tant que passerelle pour nos conteneurs.

Ci-après un exemple de valeurs utilisées dans notre fichier de configuration de référence :

POT_ZFS_ROOT=zroot/pot
POT_FS_ROOT=/opt/pot
POT_EXTIF=em0
POT_NETWORK=192.168.0.0/24POT_NETMASK=255.255.255.0
POT_GATEWAY=192.168.0.1

Vous remarquerez que nous avons fait le choix de renseigner l’emplacement d’un pool ZFS pour le stockage des données des conteneurs dans le fichier pot.conf. Celui-ci sera nommé pot et sera placé dans le pool zroot. Nous utiliserons l’utilitaire système zfs pour le créer. Ci-après la trace de l’exécution :

$ zfs create -o mountpoint=/opt/pot zroot/pot
$ zfs set compression=off zroot/pot

C’est purement facultatif… mais je recommande néanmoins sa mise en place pour séparer le volume disque utilisé par les conteneurs de celui du système d’exploitation hôte. pot supporte IPv4 ainsi que IPv6. Il propose quatre modèles différents pour la configuration du réseau :

  • inherit : hérite tout simplement de la pile IP de l’hôte ;
  • alias : permet de spécifier une adresse IP différente sur la même carte réseau ;
  • public-brigde : utilisation d’un bridge, partagé entre les jails, pour faire le lien avec un réseau virtuel (VNET) ;
  • private-bridge : utilisation d’un bridge, dédié à certaines jails, pour faire le lien avec un réseau virtuel (VNET).

Vous pouvez demander au système de contrôler que la configuration réseau que vous avez appliquée est cohérente grâce à l’utilitaire potnet et au paramètre associé config-check :

$ potnet config-check -v

Dans la mesure où nous utiliserons pot dans le contexte d’une machine virtuelle, il est important d’appliquer la modification ci-après pour bénéficier d’une bonne expérience utilisateur :

$ echo hw.vtnet.lro_disable=1 >> /boot/loader.conf

Ce paramétrage supplémentaire est indispensable, car il permet de se prémunir d’un effet de bord qui entraîne des latences ou des soucis de performance réseau dans les environnements virtuels. Il n’est d’ailleurs toujours pas corrigé à la date de la rédaction de cet article.

Tout comme iocage ou d’autres systèmes de gestion des jails, pot s’appuie sur l’outil pf fourni nativement dans FreeBSD pour l’administration des réseaux virtuels (VNET) et des flux qui transitent sur ces réseaux (NAT, redirection, etc.). Il est toutefois préférable de réaliser une copie de sauvegarde du fichier pf.conf afin d’éviter l’écrasement ou la suppression accidentelle des règles que vous avez pu définir jusqu’à présent.

Avec Nomad, nous avons vu qu’il était possible de positionner des contraintes sur les ressources machines utilisées (bloc resources au sein d’une task). Dans ce cas, l’outil s’appuie sur l’utilitaire cpuset afin de limiter l’utilisation du processeur.

Des contraintes d’allocation mémoire peuvent aussi être appliquées dans les environnements FreeBSD grâce à un framework disponible depuis la version 9 du système pour la mémoire : rtcl. Par défaut, ce comportement n’est pas activé lors de l’installation sur nos serveurs. Nous modifierons donc la configuration du système afin d’apporter ce changement :

$ echo kern.racct.enable=1 >> /boot/loader.conf

Une fois cette dernière modification appliquée, je vous recommande l’utilisation de la commande reboot pour obtenir l’assurance que tous ces ajustements soient effectivement pris en compte.

3. Nos premiers pas avec les conteneurs pot

3.1 Démarrons avec pot

Si vous êtes assez familier de l’utilisation d’outils comme Docker ou Podman, utiliser pot au quotidien ne devrait donc pas vous poser de problème particulier. L’outil s’utilise en ligne de commandes, en tant qu’utilisateur disposant de privilèges suffisants pour construire et déployer une jail.

Nous commencerons par initialiser l’environnement pot à l’aide de l’instruction init puis ensuite le réseau virtuel interne grâce à l’instruction vnet-start comme suit :

$ pot init; pot vnet-start;

Ces commandes auront pour effet de lire le contenu du fichier de configuration défini précédemment et de mettre en place tous les éléments utiles. Je vous propose maintenant de créer notre première jail, basée sur la version 13.1 de FreeBSD, à l’aide de l’instruction create pour pot :

$ zfs create -o mountpoint=/opt/pot zroot/pot
===> Creating a new pot
===> pot name     : majail
===> type         : single
===> base         : 13.1
===> pot_base     :
===> level        : 0
===> network-type : inherit
===> network-stack: ipv4
===> ip           :
===> bridge       :
===> dns          : inherit
===> flavours     :
/var/cache/pot/13.1-RELEASE_base.txz      186 MB    9 MBps    19s===> Extract the tarball

Pour les opérations de création, le paramètre -p est obligatoire : il permet de spécifier le nom de notre conteneur. Vous remarquerez que j’ai ajouté le commutateur -t. Ce dernier prend deux valeurs : single pour indiquer que le conteneur s’appuie sur un seul dataset ZFS ou multi dans le cas où vous utilisez une collection de 3 datasets ZFS. Enfin, le dernier couple permet de spécifier quelle image de base sert à provisionner notre jail grâce au commutateur -b.

Vous l’aurez vite compris, nous retrouvons ici une première similitude avec Docker qui autorise lui aussi la création depuis une image de base (plus ou moins complexe)… De manière concrète, c’est l’instruction FROM du Dockerfile qui permet de réaliser, par exemple, cette action. Mais les analogies ne s’arrêtent pas là : vous constaterez vite certaines similitudes dans la documentation en ligne.

3.2 Quelques opérations élémentaires avec nos conteneurs

Tout comme ses cousins, pot permet de lancer (run), arrêter (stop) et lister des conteneurs en cours d’exécution (ps). Nous utiliserons ainsi l’instruction run pour démarrer une jail :

$ pot run majail
===> Starting the pot majail
ELF ldconfig path: /lib /usr/lib /usr/lib/compat
32-bit compatibility ldconfig path: /usr/lib32
Updating motd:.
[…]
Mon Mar 8 21:03:18 UTC 2023
root@majail:~ # exit

En réalité, c’est une combinaison de deux instructions, start et term, qui aura pour effet de vous placer dans un shell interactif au sein de votre conteneur : il faudra donc utiliser la commande exit pour quitter l’environnement qui continuera son exécution tant que l’instruction stop (suivi du nom de la jail à interrompre) n’aura pas été envoyée par pot.

$ pot ps
===> Internal network up
majail
$ pot stop majail

Nous allons pouvoir ainsi mettre en place, dans notre conteneur, l’environnement utile à l’exécution de notre service ou de notre application. Une fois ces étapes réalisées, nous pourrons mettre en œuvre une fonction extrêmement utile avec Docker ou Podman : l’export de notre image.

C’est une combinaison de deux actions qu’il faudra opérer. La première étape consiste à prendre un instantané de notre conteneur. Après avoir vérifié que notre jail est correctement arrêtée (car les sauvegardes à chaud ne sont pas encore supportées), nous appellerons le binaire pot avec l’instruction snapshot afin de provoquer une sauvegarde de celle-ci :

$ pot snapshot -p majail

Sachez qu’il existe aussi une instruction qui permet de revenir en arrière en cas d’effacement accidentel d’un fichier ou encore lorsque votre conteneur dysfonctionne : il s’agit de rollback, qui nécessite les mêmes conditions d’exploitation que snapshot pour être invoquée et appliquée.

Notre cliché peut alors être exporté (grâce à l’instruction export) et autoriser ainsi sa distribution facilement, tout comme les images générées pour être utilisées avec Docker ou Podman. Pour simplifier la gestion des versions, il est possible d’indiquer lors de l’export un tag pour la numérotation comme le montre l’exemple ci-après :

$ pot export -p majail -t 0.1

Cette seconde étape dans le processus de distribution peut prendre un peu de temps. Une bonne pratique en matière de construction de conteneurs consiste à essayer de réduire la taille de l’image créée afin de faciliter sa distribution et son déploiement rapidement par le réseau.

Vous retrouvez cette philosophie avec pot : une opération de compression de la jail est systématiquement réalisée lors de l’export… ce qui explique la latence que vous pouvez observer pendant cette partie du processus.

Lorsque cette phase se termine, vous obtenez trois fichiers : il est indispensable d’en publier au moins deux d’entre eux sur le dépôt central d’images de votre organisation si vous tenez à ce que votre service soit utilisé par un tiers. Le premier est notre image compressée (extension en .xz) et le second contient une somme de contrôle (extension en .xz.skein) qui est exploitable par le binaire pot lors de l’utilisation de l’instruction import afin de vérifier l’intégrité de l’image que vous avez pu télécharger précédemment.

Les processus de distribution et d’exploitation des images peuvent ainsi être rendus assez fiables de cette manière ! Mais justement, comment assurer ces opérations sereinement dans notre environnement ?

4. Un registre d’image avec potluck

4.1 Présentation générale

Les conteneurs revêtent un caractère extrêmement pratique pour les développeurs… surtout lorsqu’il s’agit de distribuer une application. Ils embarquent deux choses : un runtime dont la responsabilité est de créer et de faire fonctionner le conteneur, des API, des couches d’abstraction supplémentaires… mais aussi l’image proprement dite.

Jusqu’à présent, nous avons construit et utilisé nos propres jails avec pot. Il manque toutefois une fonctionnalité essentielle sur laquelle Docker ou Podman s’appuient pour diffuser les images réalisées par leurs auteurs : un registre. Le registre d’images est un peu comme un magasin de jeux vidéo en ligne dans lequel l’utilisateur vient faire son petit marché, récupérer son jeu favori et le déployer sur sa machine.

pot n’est pas en reste sur ce plan : il peut même se vanter de faire jeu égal avec ses cousins grâce à potluck. Ce projet, dont le principal contributeur est Stephan Lichtenauer, existe maintenant depuis plus de 3 ans. Il offre un moyen de mettre à disposition des utilisateurs des images au format binaire créées avec le gestionnaire de jails.

potluck est publié sous les termes de la licence BSD-3. Pour fonctionner, il s’appuie sur un simple serveur web qui donne non seulement des actualités sur le projet, mais aussi permet également de télécharger des images construites et maintenues par la communauté. L’adresse URL du registre est la suivante : https://potluck.honeyguide.net/.

Il est vrai que le catalogue des images disponible n’est pas aussi riche que celui du Docker Hub. Cependant, les contenus proposés constituent un bon point de départ si vous souhaitez, vous aussi, démarrer avec FreeBSD ou distribuer vos propres applications empaquetées avec pot. C’est une source d’inspiration à ne pas négliger ! D’ailleurs, toutes les opérations de constructions d’images sont très bien documentées et les sources utilisées par la communauté disponibles sur le dépôt GitHub qui se trouve à l’adresse URL ci-après : https://github.com/bsdpot/potluck.

Dès lors, à l’aide d’un simple serveur web capable de fournir des pages statiques comme nginx, il devient très facile de mettre en place un registre d’images privées (ou publiques). L’opération reviendra alors, pour l’utilisateur final, à déclencher le téléchargement de l’image avec pot ou via un manifeste écrit pour Nomad. Cela revient, dans ce cas, à réaliser une opération de type pull avec un outil comme Docker, par exemple.

4.2 Un exemple d’utilisation

Dans le registre, nous retrouvons un certain nombre d’applications plutôt utilisées dans nos organisations telles que NextCloud. Il est extrêmement facile de déployer un conteneur qui propose ce service sur notre serveur virtuel FreeBSD. Les prérequis sont déjà satisfaits puisqu’ils consistent à disposer de pot et de Nomad.

Son déploiement repose ainsi sur l’écriture préalable d’un manifeste pour notre orchestrateur. Dans ce fichier, la section qui décrit la task contiendra un bloc config qui demande le chargement de l’image pot pour NextCloud … et non pas une image traditionnelle Docker !

La fiche de présentation du conteneur sur le registre détaille les instructions à appliquer pour procéder à cette opération : les éventuels pools ZFS à créer au préalable (qui seront montés comme nous pouvons le faire pour un volume Docker), la configuration réseau ou encore les autres services à mettre en œuvre afin de résoudre les problèmes de dépendances. Les contributeurs suggèrent également un modèle de manifeste que vous retrouverez à l’adresse URL ci-après : https://github.com/bsdpot/potluck/tree/master/nextcloud-nginx-nomad#nomad-job-file.

Lorsque vous utilisez pot, vous pouvez donc créer des environnements d'exécution isolés pour des applications ou des services spécifiques, sans avoir à vous soucier des dépendances ou des conflits avec le reste du système d'exploitation. Cela ne vous rappelle rien ? Ce fonctionnement imite en réalité, dans les grandes lignes, celui adopté par Docker (et le recours aux Dockerfile associés).

En combinant pot, potluck et Nomad, les administrateurs système et les développeurs obtiennent donc un moyen simple de déployer leurs applications et d’orchestrer leur déploiement. Nous pouvons tout à fait imaginer utiliser une application comme Traefik pour jouer le rôle de contrôleur Ingress afin de lier une URL à un service et la piloter via Nomad, car il existe un provider pour cet usage.

L’outil est bien pensé et permet de se reposer sur une chaîne d’intégration et de déploiement continue. De cette façon, nous pouvons imaginer reconstruire périodiquement nos images lors d’un nouveau commit ou d’une requête de fusion par exemple… et obtenir un processus fiable d’exploitation en le couplant à GitLab-CI. Nous ne sommes pas limités à un seul outil : les auteurs et contributeurs de potluck utilisent d’ailleurs Jenkins pour ces mêmes actions.

Nous retrouvons ici, d’une certaine manière, l’expérience que propose Kubernetes à ses utilisateurs… mais dans un contexte d’exploitation sous FreeBSD !

5. Help ! Mais je ne suis pas administrateur système, moi !

Il peut s’avérer vite complexe, pour un développeur, de maîtriser toute la chaîne d’outils à mettre en œuvre afin d’obtenir l’équivalent d’un cluster Kubernetes pour tester le déploiement de nos applications conteneurisées sous FreeBSD avec pot.

C’est là qu’entre en jeu Minipot. Le but du projet est de proposer un outil qui facilite l’installation, sur un nœud, de l’ensemble des outils nécessaires pour concevoir, orchestrer et ordonnancer l’exécution de nos images sur nos serveurs (physiques ou virtuels). Cette philosophie n’est pas sans rappeler, dans l’écosystème Kubernetes, un outil comme kind par exemple (qui facilite le provisionnement d’un cluster K8s en s’appuyant sur Docker).

Il est construit autour d’une collection de fichiers et de scripts, publiés sous les termes de la licence BSD-3. Ces derniers peuvent être téléchargés sur un dépôt public GitHub : https://github.com/bsdpot/minipot. Derrière ce projet, nous retrouvons l’un des auteurs de pot, Luca Pizzamiglio.

Minipot prend ainsi en charge l’installation et la configuration de Nomad ainsi que de pot… mais cela ne s’arrête pas là ! En effet, il n’est pas rare d’avoir à utiliser des outils tiers pour déployer quelques services proposés sur le registre public potluck : vous remarquerez que certains nécessitent un service de découverte comme Consul ou encore la mise en place d’un proxy inverse tel que Traefik pour exposer notre application.

Là encore, tout est prévu pour faciliter l’adoption de ces outils, car ces scripts procèdent aussi à l’installation de ces outils. Les journaux de la pile technique se voient aussi configurés pour être stockés dans un emplacement unique (/var/log/) au sein du système de fichiers.

La dernière version disponible lors de la rédaction de cet article est numérotée 0.2.3 et a été publiée début janvier 2021. Si l’expérience vous tente, sachez toutefois qu’il existe un paquet disponible pour FreeBSD. L’installation se résume alors à cette opération simple avec l’utilitaire pkg :

$ pkg install -y minipot

Les dépendances sont automatiquement installées si elles ne sont pas présentes sur le système hôte. Nous avons presque terminé. Il nous restera à définir la configuration de base pour pot (une étape qui reste encore indispensable, car dépendante du contexte) avant de nous lancer dans l’utilisation de notre cluster fraîchement déployé ! Tout comme pour le gestionnaire de conteneurs, l’étape d’initialisation est indispensable. Elle peut être réalisée comme suit :

$ minipot-init

Comment tester maintenant que tout est fonctionnel ? Facile, un exemple est fourni avec le paquet. Il se trouve à l’emplacement /usr/local/share/examples/minipot. Un manifeste pour Nomad qui prévoit le déploiement du serveur web nginx est fourni afin de réaliser la recette de notre déploiement. Il nous faudra le modifier, car certaines choses sont désormais dépréciées (comme la manière de gérer la pile réseau) depuis sa publication :

01: job "nginx-minipot" {
02:   datacenters = ["minipot"]
03:   type        = "service"
04:
05:   group "group1" {
06:     count = 1
07:
08:     network {
09:       port "http" {}
10:     }
11:
12:     task "www1" {
13:       driver = "pot"
14:
15:       service {
16:         tags = ["nginx", "www"]
17:         name = "hello-web"
18:         port = "http"
19:
20:          check {
21:             type     = "tcp"
22:             name     = "tcp"
23:             interval = "5s"
24:             timeout = "2s"
25:           }
26:       }
27:
28:       config {
29:         image = "https://potluck.honeyguide.net/nginx"
30:         pot = "nginx-amd64-13_1"
31:         tag = "1.0.9"
32:
33:         port_map = {
34:           http = "80"
35:         }
36:       }
37:       resources {
38:         cpu = 200
39:         memory = 64
40:       }
41:     }
42:   }
43: }

La première de nos modifications porte sur le déplacement de la section network (lignes 08 à 10) qui quitte le bloc resource pour directement intégrer le bloc group. La seconde consiste à faire évoluer le bloc config défini pour notre task de manière à utiliser une version récente de l’image pot pour nginx (lignes 29 à 31).

Dès lors, nous pouvons demander l’exécution du job Nomad correspondant. Ci-après la trace de son exécution et le résultat d’un test à l’aide de la commande curl (suivi du nom de l’hôte virtuel tel que défini à la ligne 17 du manifeste) qui vise à valider cette installation :

$ nomad run nginx.job
==> 2023-03-08T22:37:28Z: Monitoring evaluation "4db9ecd5"
    2023-03-08T22:37:28Z: Evaluation triggered by job "nginx-minipot"[...]    Deployed
    Task Group Desired Placed Healthy Unhealthy Progress Deadline
    group1     1       1      1       0         2023-03-08T22:38:40Z$ curl -H 'Host: hello-web.minipot' 127.0.0.1:8080
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
 
<p><em>Thank you for using nginx.</em></p>
</body>
</html>

Parfois, la tâche peut paraître bloquée ou être marquée comme non fonctionnelle. Ce comportement s’explique par la durée de téléchargement de l’application retenue qui peut être assez longue parfois. En effet, le task driver pour pot ne propose pas une option similaire à celui de Docker afin d’autoriser la tâche à ne pas échouer après 5 minutes (absence de directive image_pull_timeout dans le bloc config).

N’hésitez donc pas à contrôler le contenu du répertoire local /var/cache/pot/ qui va accueillir l’image source : celui-ci devrait comporter au moins un fichier avec une extension .xz et dont la taille évolue au fil des secondes.

Ce cas d’utilisation montre que des bases solides sont posées : toute la complexité technique se voit réduite au strict minimum. Il devient alors aussi facile d’exploiter un cluster FreeBSD qui opère le déploiement de conteneurs qu’un cluster sous GNU/Linux exécutant Kubernetes !

Conclusion

Comme nous l’avons vu précédemment, les nombreux pilotes de tâches supportés par Nomad font de lui un véritable couteau suisse : son support de FreeBSD permet d’apporter une réponse aux administrateurs système désireux de se lancer dans la construction d’un centre de données virtuel basé sur des conteneurs pot. Son intégration est, en ce sens, plutôt réussie.

En marge, l’outil d’aide à la construction de conteneurs qu’est pot n’a pas à rougir des principaux Docker ou Podman. Même s’il nécessite d’être un peu familier avec les environnements BSD, il présente l’atout d’offrir des performances supérieures à ses cousins. En effet, les jails sont une forme de technologie de virtualisation extrêmement légère dont le niveau de sécurité est plus que satisfaisant… et elles ne souffrent pas de la complexité amenée par l’hôte dans le cas de la para-virtualisation par exemple.

Les solutions techniques construites autour de ce gestionnaire de jails telles que potluck et Minipot sont prometteuses : la feuille de route de ces projets ne laisse aucun doute quant au fait qu’elles gagneront encore en maturité au fil du temps.

Même si le registre d’images pour pot n’est pas aussi riche que le Docker Hub, le mécanisme de distribution des applications mis en œuvre est bien plus simple que son « concurrent ». Sa philosophie rend le processus de duplication des images construites et leur hébergement sur des centres de données géographiquement distants maîtrisable facilement : un simple serveur web suffit, inutile de sortir l’artillerie lourde avec l’outil Nexus de Sonatype pour remplir ce besoin !

Dans un contexte d'utilisation de FreeBSD, pot et Nomad peuvent être des alternatives à considérer face à Docker et Kubernetes, car ils sont spécialement prévus pour fonctionner avec ce système d'exploitation. Combinés à potluck et Minipot, je ne peux donc que vous recommander de « tenter l’expérience » si vous êtes à la recherche d’une pile d’outils capable de créer, gérer et déployer des environnements de conteneurs légers et isolés sur une machine FreeBSD.

Références

[0] M. Masquelin, « Legacy apps et conteneurs : à la découverte de Nomad pour tous les gouverner ! », Linux Pratique n°138, juillet-août 2023 :
https://connect.ed-diamond.com/linux-pratique/lp-138/legacy-apps-et-conteneurs-a-la-decouverte-de-nomad-pour-tous-les-gouverner

[1] E. Heitor, « La grande migration, Épisode I », Linux Pratique n°137, mai-juin 2023 :
https://connect.ed-diamond.com/linux-pratique/lp-137/la-grande-migration-episode-i

[2] M. Masquelin, « BastilleBSD : la gestion d’applications en conteneur avec FreeBSD », Linux Pratique n°133, septembre-octobre 2022 :
https://connect.ed-diamond.com/linux-pratique/lp-133/bastillebsd-la-gestion-d-applications-en-conteneur-avec-freebsd



Article rédigé par

Abonnez-vous maintenant

et profitez de tous les contenus en illimité

Je découvre les offres

Déjà abonné ? Connectez-vous