La mise en conteneur d’une application ou d’un service avec LXD

Magazine
Marque
Linux Pratique
HS n°
Numéro
45
Mois de parution
juin 2019
Domaines


Résumé

LXD est une suite d’outils visant à faciliter le management de la couche LXC (LinuX Containers) du noyau Linux. Les containers sont des environnements confinés où les applications peuvent s’exécuter sans attenter à l’intégrité du système hôte ainsi que des autres containers.


Body

Plusieurs raisons sont susceptibles de motiver la mise en container d’une application ou d’un service. On peut souhaiter les isoler par rapport au système et/ou limiter la quantité de ressources leur étant accessibles et/ou les rendre autosuffisants en terme de librairies (un peu à la manière de chroot qui, je le rappelle, n’a jamais été créé pour faire de la sécurité). LXD permet à l’administrateur de créer facilement l’enveloppe de ces applications ou services en orchestrant les couches LXC, cgroups et namespaces.

1. Les cgroups

Comme mentionné ci-dessus, il est possible de mettre une application ou un service dans un container afin de limiter leurs ressources accessibles sur le système. Je fais volontairement une distinction entre une application et un service. Une application est un fichier binaire qu’on exécute, cette exécution entraîne la création d’une entité active sur le système qu’on appelle le processus. C’est cette entité qui va consommer de la mémoire et du CPU pour faire tourner le code machine du binaire exécutable. Le cgroup est un moyen de créer des groupes de processus.

Dans un système UNIX, un processus peut avoir des fils. Par exemple, lorsque vous lancez votre navigateur web, chaque onglet correspond en réalité à un processus fils de votre navigateur. Dans ce cas-là, pas de problèmes pour grouper les processus : on part du père et on récupère tous les fils. Pas besoin d’une structure de données supplémentaire comme les cgroups. Cela se complique lorsqu’on parle de service.

Un service est une vision de plus haut niveau que l’application. On pourrait dire qu’un service regroupe X applications. Par exemple, si on considère un serveur web de e-commerce, on sait qu’il s’appuie sur trois composants : un serveur HTTP (Apache), un interpréteur de script pour du web dynamique (PHP) et une base de données (MySQL). Dans ce cas, les cgroups nous proposent une structure de donnée plus agile que le modèle père/fils pour grouper les processus.

Chaque ressource limitable par les cgroup est représentée par un subsystem. Il y en a un pour le CPU, les entrées/sorties disques, la mémoire, etc. On superpose une ou plusieurs politiques à chaque subsystem, les processus suivent ensuite une politique. Petit exemple avec la consommation de CPU. Nous allons créer deux politiques, l’une avec l’utilisation par défaut du CPU et une autre limitant volontairement le processus.

Commençons par installer ce qu’il faut pour travailler avec les cgroups :

[root@lxd ~]# yum install libcgroup libcgroup-tools

Créons une politique par défaut qui n’applique pas de limites particulières :

[root@lxd ~]# cgcreate -g cpu:/cpudefault

Créons une autre politique qui dit que lorsqu’un processus est positionné dans ce cgroup, il est dans un ratio de consommation de 2:1 sur cette ressource. Le subsystem associé au CPU associe une valeur arbitraire de 1024 à la totalité du CPU. Si on veut représenter un ratio de 2:1, il faut positionner cette valeur à 512.

[root@lxd ~]# cgcreate -g cpu:/cpulimited

[root@lxd ~]# cgset -r cpu.shares=512 cpulimited

Si on lance quatre fois de suite cette commande :

[root@lxd ~]# cgexec -g cpu:cpudefault stress -c 1 --timeout 200s &

La commande cgexec permet de lancer une commande en plaçant les processus dans un cgroup (-g). Les processus vont hériter des limitations de ce groupe. La commande stress est un utilitaire qui fait des calculs pour mettre le CPU au taquet. Ici, on lui fait utiliser 100 % d’un seul CPU (-c). On obtient la sortie suivante pour top :

PID   USER  PR  NI  VIRT  RES  SHR  S  %CPU  %MEM    TIME+  COMMAND

3669  root  20   0  7308   96    0  R  25.3   0.0  0:01.37   stress

3667  root  20   0  7308  100    0  R  25.0   0.0  0:01.96   stress

3671  root  20   0  7308  100    0  R  25.0   0.0  0:00.90   stress

3665  root  20   0  7308  100    0  R  24.7   0.0  0:03.52   stress

Chaque processus est dans un rapport 1:1, donc tout le monde se partage équitablement le CPU disponible.

Lançons maintenant cette commande :

[root@lxd ~]# cgexec -g cpu:cpudefault stress -c 1 --timeout 200s &

Et trois fois la même commande, mais positionnée dans notre cgroup limité au niveau CPU :

[root@lxd ~]# cgexec -g cpu:cpulimited stress -c 1 --timeout 200s &

Le résultat de top est différent :

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND

3689 root 20 0 7308 100 0 R 66.8 0.0 0:35.62 stress

3693 root 20 0 7308 96 0 R 11.3 0.0 0:05.52 stress

3703 root 20 0 7308 100 0 R 11.3 0.0 0:01.70 stress

3691 root 20 0 7308 100 0 R 11.0 0.0 0:09.15 stress

On voit que le processus lancé avec la politique cpudefault prend 66 % du CPU tandis que les 3 autres plafonnent à 11 %. Or, 11 x 3 = 33 et 33, c’est bien la moitié de 66. Le ratio 2:1 est donc respecté.

2. Les namespaces

Les rôles des namespaces est de créer un nouveau contexte système isolé pour le processus ciblé. Cette isolation se fait sur trois axes :

  1. Un nouvel espace de PID est initialisé et votre processus prend le numéro 1, tous les autres processus s'en sont allés ;
  2. Une nouvelle pile réseau est allouée au processus, aucun conflit possible avec les services réseaux de l'hôte ;
  3. Les systèmes de fichiers sont indépendants, on peut monter et démonter des volumes sans que cela ait d'incidences sur l'hôte.

Nous allons illustrer les namespaces avec la commande unshare. Cette commande permet de lancer un programme en se détachant du namespace parent. Nous allons l'utiliser pour lancer bash dans un nouveau namespace.

[root@lxd ~]# unshare --fork --pid --mount-proc bash

[root@lxd ~]# ps -aux

USER  PID  %CPU  %MEM     VSZ  RSS     TTY  STAT  START  TIME  COMMAND

root    1   0.0   0.1  115440  2024  pts/0     S  21:32  0:00  bash

root   10   0.0   0.1  155360  1872  pts/0    R+  21:32  0:00  ps -aux

Nous avons lancé bash comme fils de unshare (--fork) avec son propre espace de PID (--pid) et sa propre instance de /proc (--mount-proc). Dans ce nouveau namespace, bash a le PID 1 et il est donc le père de tous les processus et il est seul (enfin avec la commande ps qu'on a lancé). Voyons ce que ça donne depuis le système hôte avec un pstree :

[root@lxd ~]# pstree

systemd─┬─NetworkManager─┬─2*[dhclient]

│ └─2*[{NetworkManager}]

...

├─login───bash───unshare───bash

...

On voit bien la ligne de parenté systemd → login → bash → unshare → bash. Donc du point de vue de l'hôte, le processus bash issu du unshare est un processus comme un autre. Il a un PID tout à fait standard. Vous avez maintenant les clés de compréhension pour la couche LXC/LXD.

3. Les containers LXD

Avant LXD, il y eu LXC (LinuX Containers). Pour lever tout de suite l’ambiguïté, il ne s'agit pas de choisir LXD ou LXC c'est plutôt LXC et LXD. En effet, LXC est un ensemble d'outils et de librairies en espace utilisateur faisant le lien entre les namespaces et les cgroups pour unifier ces deux technologies afin d'arriver à fournir des containers utilisables. LXD complète LXC notamment en ajoutant un accès par webservice qui rend les containers orchestrables par des systèmes type OpenStack et des outils plus haut niveau pour les gérer type gestion de profil de containers, etc.

Dernière précision sur les containers avant de passer à la pratique, on distingue deux types de containers : fat (gras) et thin (mince). Cette terminologie est empruntée aux jails BSD. Le thin jail revient à containeriser juste une application (comme on l’a fait ci-dessus avec bash). Un fat jail consiste à faire tourner un système entier dans un container. De façon schématique, on prend ce qu'on a fait au-dessus sauf qu'à la place de bash, on exécute systemd (processus de démarrage des Linux modernes). Par contre, contrairement à ce qu'on a vu précédemment dans le cas de la virtualisation complète, on ne fera tourner que du Linux dans un fat jail, car il n’y a qu'un seul noyau qui tourne sur la machine.

3.1 Installation de la couche LXD

Comme sur beaucoup de distributions (mis à part Ubuntu), LXD n'est pas disponible directement sous forme de package. Ici, on doit installer les dépôts epel et copr (dont les paquets sont de qualité assez variable) pour obtenir snapd. Les snap sont des paquets d'installation qui viennent compléter le système de gestion de paquets standard avec des paquets plus portables d'une distribution à l'autre dans la mesure où ils sont fournis avec leurs dépendances binaires (par conséquent, ils s'appuient beaucoup moins sur la distribution sous-jacente). Installons tout cela :

[root@lxd ~]# yum install yum-plugin-copr epel-release

[root@lxd ~]# yum copr enable ngompa/snapcore-el7

[root@lxd ~]# yum install snapd

[root@lxd ~]# systemctl enable --now snapd.socket

[root@lxd ~]# systemctl enable snapd

Nous avons maintenant snapd d'installé sur notre CentOS. Nous pouvons configurer le noyau pour expliciter le chargement des namespaces au démarrage du noyau (user_namespace.enable=1) et nous ménager la possibilité de lancer des containers en tant que non-root (namespace.unpriv_enable=1). Toutes ces modifications se font dans le fichier /etc/default/grub :

[root@lxd ~]# vi /etc/default/grub

...

GRUB_CMDLINE_LINUX="... user_namespace.enable=1 namespace.unpriv_enable=1 ..."

...

[root@lxd ~]# grub2-mkconfig >/boot/grub2/grub.cfg

Enfin, on passe un nombre de namespaces maximum instantiables par l'utilisateur :

[root@lxd ~]# echo "user.max_user_namespaces=3883" > /etc/sysctl.d/99-userns.conf

 

On redémarre la machine et on peut passer à l'installation de LXD :

[root@lxd ~]# snap install lxd

2019-03-07T22:43:22+01:00 INFO Waiting for restart...

lxd 3.10 from Canonical✓ installed

3.2 Configuration initiale de LXD

La configuration des containers passe par une commande d'initialisation qui va nous permettre de créer un profil par défaut. Sauf que nous allons tout faire nous même, donc nous allons répondre non partout.

[root@lxd ~]# lxd init

Would you like to use LXD clustering? (yes/no) [default=no]: no

Créer un emplacement où stocker les containers :

Do you want to configure a new storage pool? (yes/no) [default=yes]: no

Connexion à un serveur MAAS (produit Ubuntu pour déployer des machines automatiquement) :

Would you like to connect to a MAAS server? (yes/no) [default=no]: no

Création d'un bridge (on y reviendra) :

Would you like to create a new local network bridge? (yes/no) [default=yes]: no

Utilisation de ce bridge (on y reviendra) :

Would you like to configure LXD to use an existing bridge or host interface? (yes/no) [default=no]: no

Peut-on administrer les containers par le réseau :

Would you like LXD to be available over the network? (yes/no) [default=no]: no

Mise à jour automatique des images disponibles :

Would you like stale cached images to be updated automatically? (yes/no) [default=yes] yes

Afficher la configuration (en fait toutes les configurations de LXD se font au format YAML) :

Would you like a YAML "lxd init" preseed to be printed? (yes/no) [default=no]: no

Pour au moins tester notre installation de LXD, il va falloir créer un storage pool. En fait, c'est juste un répertoire qui va contenir nos containers. Pour créer ces choses-là à la main, on va repasser sur les commandes lxc :

[root@lxd ~]# lxc storage create store_lxd dir source=/store/lxd

Ici, on crée un pool de stockage nommé store_lxd qui est de type fichier (on peut aussi mettre des partitions en mode bloc, volume LVM, ZFS, etc.) et qui se trouve localisé physiquement dans /store/lxd. Nous allons pouvoir déployer notre première image dans ce pool fraîchement créé. Voyons si on peut descendre une image :

[root@lxd ~]# lxc launch -s store_lxd images:centos/7/amd64 apollo

Creating apollo

 

The container you are starting doesn't have any network attached to it.

To create a new network, use: lxc network create

To attach a network to a container, use: lxc network attach

 

Starting apollo

Cette commande va créer un container basé sur une image CentOS 7. Le système nous informe que ce container n'a pas de réseau, c'est normal nous allons le créer. Listons maintenant les containers en cours d’exécution :

[root@lxd ~]# lxc list

+--------+---------+------+------+------------+-----------+

|  NAME  |  STATE  | IPV4 | IPV6 |    TYPE    | SNAPSHOTS |

+--------+---------+------+------+------------+-----------+

| apollo | RUNNING |      |      | PERSISTENT |           |

+--------+---------+------+------+------------+-----------+

Nous retrouvons bien le container apollo. Mettons un petit mot de passe pour root. La sous-commande exec de la commande lxc lance une commande dans le container ciblé :

[root@lxd ~]# lxc exec apollo passwd root

Changing password for user root.

New password:

Retype new password:

passwd: all authentication tokens updated successfully.

Quant à lxc console, cela ouvre une console dans le container :

[root@lxd ~]# lxc console apollo

To detach from the console, press: <ctrl>+a q

 

CentOS Linux 7 (Core)

Kernel 3.10.0-957.5.1.el7.x86_64 on an x86_64

 

apollo login: root

Password:

[root@apollo ~]#

Sauf que parfois on n’arrive pas à en sortir… Dans ces cas-là, on peut exécuter un bash dans le container :

[root@lxd ~]# lxc exec apollo bash

[root@apollo ~]#

Le but maintenant c'est que ce container puisse aller sur le Web. Nous allons donc nous atteler au réseau.

4. Configuration réseau

État des lieux du réseau sur Apollo :

[root@apollo ~]# ip a

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000

...

Il n'y pas grand-chose, juste une interface de rebouclage (loopback). Notre premier travail va être de préparer l'hôte pour connecter apollo au monde extérieur. C'est ici que les fameux bridges entrent en jeu. Dans l’article page XX, nous avons dit que VirtualBox distribuait les adresses réseau en DHCP. L'idée est d'alimenter apollo avec un bail DHCP fourni par VirtualBox comme on le ferait dans un réseau domestique servi par une box ou dans un réseau d'entreprise embarquant du DHCP.

L'astuce va être de configurer un bridge sur l'hôte. Un bridge est une sorte de commutateur virtuel qu'on va monter sur un système d'exploitation. Pour mémoire, un commutateur est un concentrateur permettant aux machines qui y sont connectées de communiquer au niveau liaison (niveau 2 du modèle OSI). C'est-à-dire que les cartes réseau de ces machines peuvent s'envoyer et recevoir des trames, car elles font partie du même domaine de broadcast Ethernet (en conséquence, ils pourront recevoir les bails DHCP et contacter la passerelle par défaut ainsi que le cache DNS). Donc nous allons créer un bridge sur l'hôte et y « brancher » l'interface réseau réelle de l'hôte. Ensuite, nous allons créer une interface virtuelle pour apollo et la « brancher » sur ce bridge.

Sur l'hôte, il faut d'abord installer le paquet bridge-utils :

[root@apollo ~]# yum install bridge-utils

Ensuite, il faut configurer le bridge :

[root@lxd ~]# cat /etc/sysconfig/network-scripts/ifcfg-virbr0

DEVICE="virbr0"

BOOTPROTO="dhcp"

ONBOOT="yes"

TYPE="Bridge"

NM_CONTROLLED="no"

Les paramètres intéressants sont qu’on crée un bridge (TYPE='Bridge') correspondant au device /dev/virbr0 (DEVICE), configuré en DHCP (BOOTPROTO), monté au démarrage (ONBOOT='yes'). Ensuite, nous raccordons l'interface réseau au bridge :

[root@lxd ~]# cat /etc/sysconfig/network-scripts/ifcfg-enp0s3

TYPE=Ethernet

BOOTPROTO=none

NAME=enp0s3

UUID=dc83eab1-b18a-4c24-9ad9-818479905817

DEVICE=enp0s3

NM_CONTROLLED=no

BRIDGE=virbr0

ONBOOT=yes

Il s'agit d'une interface Ethernet (TYPE), dont le nom est enp0s3 (NAME) correspondant au device /dev/enp0s3 (DEVICE) et surtout membre du bridge virbr0 (BRIDGE). À ce moment-là, le bridge est arrosé par le réseau externe via l'interface enp0s3 de l'hôte.

La dernière étape est de créer une interface réseau sur apollo liée au bridge de l'hôte. Sur l'hôte :

[root@lxd ~]# lxc config device add apollo eth0 nic name=eth0 nictype=bridged parent=virbr0

Device eth0 added to apollo

Cette commande ajoute une carte réseau (nic) nommée eth0 (name) qui va s'attacher sur un bridge (nictype=bridged) nommé virbr0 (parent=virbr0). Retournons sur apollo :

[root@apollo ~]# ip a

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000

...

5: eth0@if6: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000

...

Tentons de récupérer un bail DHCP et de pinger Google :

[root@apollo ~]# dhclient

[root@apollo ~]# ping www.google.fr

PING www.google.fr (172.217.19.227) 56(84) bytes of data.

64 bytes from par21s11-in-f3.1e100.net (172.217.19.227): icmp_seq=1 ttl=52 time=2.48 ms

64 bytes from par21s11-in-f3.1e100.net (172.217.19.227): icmp_seq=2 ttl=52 time=2.16 ms

5. Et pour finir…

Quelques petites commandes bien utiles : comment stopper et arrêter les containers :

[root@apollo ~]# lxc stop apollo

[root@apollo ~]# lxc start apollo

Pour lancer automatiquement apollo :

[root@apollo ~]# lxc config set apollo boot.autostart=true

Afficher/modifier les valeurs par défaut lors de la création du container :

[root@lxd ~]# lxc profile edit default

config: {}

description: Default LXD profile

devices: {}

name: default

used_by:

- /1.0/containers/apollo

Et enfin, afficher la configuration du container :

[root@lxd ~]# lxc config edit apollo

 

architecture: x86_64

config:

   image.architecture: amd64

   image.description: Centos 7 amd64 (20190307_07:08)

   image.os: Centos

   image.release: "7"

   image.serial: "20190307_07:08"

   volatile.base_image: 6f065bff901509e641cb74436075d37bb5fcfde14539d74363b71911b33b9fe2

   volatile.eth0.hwaddr: 00:16:3e:0f:7b:eb

   volatile.idmap.base: "0"

   volatile.idmap.next: '[{"Isuid":true,"Isgid":true,"Hostid":1000000,"Nsid":0,"Maprange":1000000000}]'

   volatile.last_state.idmap: '[{"Isuid":true,"Isgid":true,"Hostid":1000000,"Nsid":0,"Maprange":1000000000}]'

   volatile.last_state.power: STOPPED

devices:

   eth0:

      name: eth0

      nictype: bridged

      parent: virbr0

      type: nic

   root:

      path: /

      pool: store_lxd

      type: disk

ephemeral: false

profiles:

- default

stateful: false

description: ""

On y retrouve bien tous nos éléments configurés à la main.

Conclusion

Cet article vous a présenté les bases pour appréhender les containers sous Linux. Il est très important de faire l'effort d'apprivoiser LXD dès maintenant, car tous les systèmes de cloud libres majeurs tels qu’OpenStack et OpenNebula proposent des drivers LXD pour déployer des containers sur leurs nœuds physiques. Autant partir dès maintenant sur de bonnes bases !

 



Articles qui pourraient vous intéresser...

Supervision des architectures à microservices avec Prometheus

Magazine
Marque
Linux Pratique
Numéro
121
Mois de parution
septembre 2020
Domaines
Résumé

Lorsqu’on supervise des services statiques, tel qu’un serveur apache ou un serveur de base de données, on se concentre sur le bon fonctionnement de ces derniers (réponse aux requêtes, état : démarré ou non...) et donc un outil de supervision est nécessaire pour évaluer le statut du service en question. Un outil comme Nagios est destiné à ce type de supervision. Mais si nos services sont susceptibles de disparaître à tout moment et sont remplacés par de nouvelles instances, comment pourra-t-on les superviser ? S’ils ne sont pas déployés sur leurs hôtes d’origine, comment peut-on les localiser dans ce cas ? Et si ces services sont sous forme de conteneurs, comment alors superviser les processus à l’intérieur de ces conteneurs ? Et enfin, si ces services sont déployés dans un orchestrateur à l’instar de Kubernetes, et donc sous forme de pod, comment superviser l’ensemble de ces pods repartis sur différents nœuds ? Dans cet article, nous répondons à toutes ces questions avec des cas pratiques. Mais voici déjà un indice concernant la réponse : Prometheus.

Premiers pas et prérequis

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

Dans ce premier chapitre, nous allons aborder de nombreux sujets, allant du respect des conditions d’utilisation de « Red Hat Enterprise Linux » (RHEL), de l’installation du système, de sa connexion au réseau « Red Hat » jusqu’à la description de notre cas d’étude. Une fois ces prérequis évoqués et ces étapes préliminaires étudiées, nous passerons au vif du sujet en installant Ansible, que nous utiliserons dans le prochain chapitre afin de commencer à mettre en place notre serveur.

Tendance actuelle : la conteneurisation d'applications

Magazine
Marque
GNU/Linux Magazine
HS n°
Numéro
110
Mois de parution
septembre 2020
Domaines
Résumé

Actuellement, il est fréquent de voir des applications proposées sous la forme d'un conteneur. Chaque mise en conteneur – ou conteneurisation – d'une application est particulière et j'ai justement dû récemment me plier à l'exercice : je partage avec vous mon expérience dans cet article.

Utilisez Terraform pour vos projets Docker

Magazine
Marque
GNU/Linux Magazine
Numéro
240
Mois de parution
septembre 2020
Domaines
Résumé

Terraform est un outil populaire pour déployer de l’infrastructure en particulier à destination des Clouds publics. Cependant, il possède de nombreux providers pour dialoguer avec différents hyperviseurs, bases de données ou solutions d’infrastructures en Software Defined. Voyons dans cet article son utilisation avec Docker.

Effectuer des sauvegardes avec rdiff-backup

Magazine
Marque
Linux Pratique
Numéro
121
Mois de parution
septembre 2020
Domaines
Résumé

Tous les jours, nous créons et manipulons des données. Certaines plus importantes que d’autres. Une chose que nous partageons tous c’est bien la peur de les perdre. Peut-être avez-vous déjà perdu des données suite à une panne de votre disque de stockage, l’attaque d’un virus ou le vol de votre ordinateur. Les personnes ayant déjà connu cette situation comprennent les tracas que cela peut causer. Vous allez découvrir dans ce tutoriel comment limiter le risque d’y faire face. La solution se trouve en un mot : « sauvegarde ». L’outil que nous allons vous présenter ici a été conçu pour vous aider à réaliser cette tâche de manière efficace et efficiente.