8. Conteneurs système Incus

Maintenant que la configuration réseau de la topologie étudiée est complète, on peut passer à la gestion des conteneurs de service du site distant.

8.1. Installation du gestionnaire de conteneurs Incus

Sur le routeur Spoke, la gestion des conteneurs est confiée à Incus. Dans le contexte de ces manipulations, nous utilisons le mode bridge pour le raccordement réseau des conteneurs. Que l'on lance 3 conteneurs ou 300, ceux-ci seront raccordés de façon transparente au commutateur virtuel asw-host et bénéficieront d'un adressage automatique.

Q52.

Comment installer le gestionnaire de conteneurs Incus ?

Lancer une recherche dans la liste des paquets Debian.

Le paquet s'appelle tout simplement incus.

apt search ^incus
sudo apt -y install incus

Q53.

Comment faire pour que l'utilisateur normal etu devienne administrateur et gestionnaire des conteneurs ?

Rechercher le nom du groupe système correspondant à l'utilisation des outils Incus.

Il faut que l'utilisateur normal appartienne au groupes systèmes incus et incus-admin pour qu'il ait tous les droits sur la gestion des conteneurs.

grep incus /etc/group
incus:x:990:
incus-admin:x:989:
sudo adduser etu incus
sudo adduser etu incus-admin
[Avertissement] Avertissement

Attention ! Il faut se déconnecter/reconnecter pour bénéficier de la nouvelle attribution de groupe. On peut utiliser les commandes groups ou id pour vérifier le résultat.

groups
etu adm sudo users incus-admin incus

8.2. Configuration et lancement des conteneurs

Q54.

Quelle est l'instruction de configuration initiale du gestionnaire Incus ?

Utiliser l'aide de la commande incus.

C'est l'instruction incus admin init qui nous intéresse.

Voici une copie d'écran de son exécution.

incus admin init
Would you like to use clustering? (yes/no) [default=no]:
Do you want to configure a new storage pool? (yes/no) [default=yes]:
Name of the new storage pool [default=default]:
Where should this storage pool store its data? [default=/var/lib/incus/storage-pools/default]:
Would you like to create a new local network bridge? (yes/no) [default=yes]: no
Would you like to use an existing bridge or host interface? (yes/no) [default=no]: yes
Name of the existing bridge or host interface: asw-host
Would you like the server to be available over the network? (yes/no) [default=no]:
Would you like stale cached images to be updated automatically? (yes/no) [default=yes]:
Would you like a YAML "init" preseed to be printed? (yes/no) [default=no]:

Q55.

Quelle est l'instruction qui permet d'afficher le profil par défaut des conteneurs ?

Rechercher dans les options de la commande incus profile.

Voici un exemple d'exécution.

incus profile show default
config: {}
description: Default Incus profile
devices:
  eth0:
    name: eth0
    nictype: macvlan
    parent: asw-host
    type: nic
  root:
    path: /
    pool: default
    type: disk
name: default
used_by: []
project: default

L'affichage de ce profil par défaut permet d'identifier les clés à modifier ou à ajouter.

Mode de raccordement, nictype

La valeur attribuée par défaut est macvlan. Elle ne correspond pas à notre besoin puisque les services hébergés sur le site distant doivent pouvoir communiquer entre eux. On remplace cette valeur par bridged.

incus profile device set default eth0 nictype bridged
incus profile device get default eth0 nictype
bridged
Attribution de VLAN, vlan

La configuration du profil par défaut n'attribue pas l'appartenance à un VLAN particulier. Dans le contexte de cette maquette, le réseau des conteneurs correspond au VLAN 40. On ajoute la clé vlan au profil par défaut.

incus profile device set default eth0 vlan 40
incus profile device get default eth0 vlan
40

Q56.

Quelle est l'instruction de création et de lancement de nouveaux conteneurs ?

Rechercher dans les options de la commande incus.

Tester son exécution avec un conteneur de type debian/trixie.

Voici un exemple d'exécution pour 3 nouveaux conteneurs.

for i in {0..2}; do incus launch images:debian/trixie c$i; done
Launching c0
Launching c1
Launching c2
incus ls
+------+---------+----------------------+------------------------------------------+-----------+-----------+
| NAME |  STATE  |         IPV4         |                   IPV6                   |   TYPE    | SNAPSHOTS |
+------+---------+----------------------+------------------------------------------+-----------+-----------+
| c0   | RUNNING | 203.0.113.144 (eth0) | fda0:7a62:28:0:216:3eff:fe67:b848 (eth0) | CONTAINER | 0         |
+------+---------+----------------------+------------------------------------------+-----------+-----------+
| c1   | RUNNING | 203.0.113.76 (eth0)  | fda0:7a62:28:0:216:3eff:fe0a:c3ba (eth0) | CONTAINER | 0         |
+------+---------+----------------------+------------------------------------------+-----------+-----------+
| c2   | RUNNING | 203.0.113.184 (eth0) | fda0:7a62:28:0:216:3eff:febb:bfb9 (eth0) | CONTAINER | 0         |
+------+---------+----------------------+------------------------------------------+-----------+-----------+

La copie d'écran ci-dessus montre que l'adressage automatique des conteneurs a fonctionné.

Q57.

Comment tester les communications réseau depuis chaque conteneur ?

Rechercher dans les options de la commande incus celle qui permet de lancer un traitement dans le conteneur.

C'est la commande incus exec qui correspond à notre besoin. Voici une exemple de boucle qui permet de lancer les tests ICMP IPv4 et IPv6 dans les 3 conteneurs actifs.

for i in {0..2}
do
    echo ">>>>>>>>>>>>>>>>> c$i"
    incus exec c$i -- ping -qc2 9.9.9.9
    incus exec c$i -- ping -qc2 2620:fe::fe
done
>>>>>>>>>>>>>>>>> c0
PING 9.9.9.9 (9.9.9.9) 56(84) bytes of data.

--- 9.9.9.9 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 29.420/29.666/29.913/0.246 ms
PING 2620:fe::fe (2620:fe::fe) 56 data bytes

--- 2620:fe::fe ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 41.829/47.305/52.782/5.476 ms
>>>>>>>>>>>>>>>>> c1
PING 9.9.9.9 (9.9.9.9) 56(84) bytes of data.

--- 9.9.9.9 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1002ms
rtt min/avg/max/mdev = 29.477/29.490/29.504/0.013 ms
PING 2620:fe::fe (2620:fe::fe) 56 data bytes

--- 2620:fe::fe ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1002ms
rtt min/avg/max/mdev = 41.401/41.546/41.692/0.145 ms
>>>>>>>>>>>>>>>>> c2
PING 9.9.9.9 (9.9.9.9) 56(84) bytes of data.

--- 9.9.9.9 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 28.820/29.427/30.034/0.607 ms
PING 2620:fe::fe (2620:fe::fe) 56 data bytes

--- 2620:fe::fe ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1002ms
rtt min/avg/max/mdev = 40.708/41.037/41.367/0.329 ms

Q58.

Comment exécuter des jeux d'instructions dans les conteneurs depuis le serveur d'hébergement ?

On entre ici dans le domaine de l'automatisation à l'aide de scripts Bash. Même si l'ambition reste très modeste, on peut développer un script qui utilise la liste des conteneurs actifs pour lancer une suite de traitements dans ces mêmes conteneurs.

Comme les conteneurs Incus appartiennent à la famille des conteneurs système, ils disposent d'une arborescence complète et d'une gestion de paquets. Allons y pour une mise à jour des paquets de chaque conteneur actif.

Voici un exemple de code qui stocke les commandes à lancer dans un tableau constitué des arguments de la ligne de commande. Ces commandes sont exécutées sur chacun des conteneurs actifs.

#!/bin/bash

cmds=("$@")

clist=$(incus list status=running -c n -f compact | grep -v NAME | tr '\n' ' ' | tr -s ' ')

for c in $clist; do
  echo ">>>>>>>>>>>>>>>>> $c"
  for cmd in "${cmds[@]}"; do
    eval "incus exec $c -- $cmd"
  done
done

Voici un exemple d'exécution du script run-commands-in-containers.sh qui contient les instructions ci-dessus.

bash run-commands-in-containers.sh "apt update" "apt -y full-upgrade" "apt clean" "apt -y autopurge"
>>>>>>>>>>>>>>>>> c0
Get:1 http://deb.debian.org/debian trixie InRelease [169 kB]
Get:2 http://deb.debian.org/debian trixie-updates InRelease [49.6 kB]
Get:3 http://deb.debian.org/debian-security trixie-security InRelease [43.5 kB]
Get:4 http://deb.debian.org/debian trixie/main amd64 Packages.diff/Index [27.9 kB]
Get:5 http://deb.debian.org/debian trixie/main amd64 Packages 2024-09-22-0804.28.pdiff [5327 B]
Get:5 http://deb.debian.org/debian trixie/main amd64 Packages 2024-09-22-0804.28.pdiff [5327 B]
Fetched 296 kB in 1s (216 kB/s)
All packages are up to date.
Summary:
  Upgrading: 0, Installing: 0, Removing: 0, Not Upgrading: 0
Summary:
  Upgrading: 0, Installing: 0, Removing: 0, Not Upgrading: 0
>>>>>>>>>>>>>>>>> c1
Get:1 http://deb.debian.org/debian trixie InRelease [169 kB]
Get:2 http://deb.debian.org/debian trixie-updates InRelease [49.6 kB]
Get:3 http://deb.debian.org/debian-security trixie-security InRelease [43.5 kB]
Get:4 http://deb.debian.org/debian trixie/main amd64 Packages.diff/Index [27.9 kB]
Get:5 http://deb.debian.org/debian trixie/main amd64 Packages 2024-09-22-0804.28.pdiff [5327 B]
Get:5 http://deb.debian.org/debian trixie/main amd64 Packages 2024-09-22-0804.28.pdiff [5327 B]
Fetched 296 kB in 1s (279 kB/s)
All packages are up to date.
Summary:
  Upgrading: 0, Installing: 0, Removing: 0, Not Upgrading: 0
Summary:
  Upgrading: 0, Installing: 0, Removing: 0, Not Upgrading: 0
>>>>>>>>>>>>>>>>> c2
Get:1 http://deb.debian.org/debian trixie InRelease [169 kB]
Get:2 http://deb.debian.org/debian trixie-updates InRelease [49.6 kB]
Get:3 http://deb.debian.org/debian-security trixie-security InRelease [43.5 kB]
Get:4 http://deb.debian.org/debian trixie/main amd64 Packages.diff/Index [27.9 kB]
Get:5 http://deb.debian.org/debian trixie/main amd64 Packages 2024-09-22-0804.28.pdiff [5327 B]
Get:5 http://deb.debian.org/debian trixie/main amd64 Packages 2024-09-22-0804.28.pdiff [5327 B]
Fetched 296 kB in 1s (277 kB/s)
All packages are up to date.
Summary:
  Upgrading: 0, Installing: 0, Removing: 0, Not Upgrading: 0
Summary:
  Upgrading: 0, Installing: 0, Removing: 0, Not Upgrading: 0

8.3. Adressage statique des conteneurs

Avec l'adressage automatique des conteneurs système, on sait les administrer depuis le routeur Spoke. Dans un scénario plus réaliste, il faut être capable d'administrer ces mêmes conteneurs depuis le routeur Hub ou mieux encore depuis l'infrastructure de déploiement CI/CD de l'entreprise. Ce type d'usage suppose que le conteneurs du site distant utilisent des adresses IPv4 et IPv6 statiques.

Q59.

Comment passer d'un adressage automatique à un adressage statique pour chaque conteneur ?

Comme la gestion de la configuration des interfaces est assurée par systemd-networkd, il faut s'intéresser à la syntaxe du fichier /etc/systemd/network/eth0.network de chaque conteneur.

Cette question est un prétexte pour utiliser le transfert de fichier depuis le serveur d'hébergement vers les conteneurs.

Voici une liste des actions à réaliser sur tous les conteneurs actifs.

  1. Installer le paquet netplan.io

  2. Générer le fichier de déclaration YAML des paramètres de configuration réseau des interfaces eth0

  3. Transférer le fichier de déclaration YAML dans le dossier /etc/netplan/

  4. Effacer le fichier /etc/systemd/network/eth0.network

  5. Appliquer la nouvelle configuration réseau

Pour connaître les paramètres de configuration réseau d'une interface de conteneur, on peut extraire le fichier /etc/systemd/network/eth0.network et consulter son contenu.

incus file pull c0/etc/systemd/network/eth0.network .
cat eth0.network
[Match]
Name=eth0

[Network]
DHCP=true

[DHCPv4]
UseDomains=true

[DHCP]
ClientIdentifier=mac

On vérifie ainsi que la configuration réseau issue de la source de tirage des conteneurs implique un adressage automatique au moins en IPv4. On propose donc de remplacer cet adressage automatique par un adressage statique.

Voici une proposition de script qui traite chacun des points définis dans la question.

#!/bin/bash

# Préparation -> générer la liste des conteneurs actifs
clist=$(incus list status=running -c n -f compact | grep -v NAME | tr '\n' ' ' | tr -s ' ')

# Étape 1 -> installer le paquet netplan.io
. run-commands-in-containers.sh "apt -y install netplan.io"

addr_idx=0
for c in $clist; do
  echo ">>>>>>>>>>>>>>>>> $c"

# Étape 2 -> générer le fichier de configuration réseau YAML
$(cat << EOF > eth0.yaml
network:
  version: 2
  renderer: networkd
  ethernets:
    eth0:
      dhcp4: false
      dhcp6: false
      accept-ra: true
      addresses:
        - 203.0.113.$((addr_idx + 10))/24
        - fda0:7a62:28::$(printf "%x" $((addr_idx + 10)))/64
      routes:
        - to: default
          via: 203.0.113.1
        - to: "::/0"
          via: fe80:28::1
          on-link: true
      nameservers:
        addresses:
          - 172.16.0.2
          - 2001:678:3fc:3::2
EOF
)

# Étape 3 -> transférer le fichier de déclaration YAML
  incus file push eth0.yaml $c/etc/netplan/eth0.yaml

# Étape 4 -> effacer le fichier /etc/systemd/network/eth0.network
  incus exec $c -- rm /etc/systemd/network/eth0.network

# Étape 5 -> appliquer la nouvelle configuration
  incus exec $c -- netplan apply

  ((addr_idx++))
done

exit 0

Si le code du script ci-dessus est placé dans un fichier appelé set-static-addressing.sh, on peut l'exécuter directement et relever les résultats.

bash set-static-addressing.sh
incus ls
+------+---------+---------------------+------------------------------------------+-----------+-----------+
| NAME |  STATE  |        IPV4         |                   IPV6                   |   TYPE    | SNAPSHOTS |
+------+---------+---------------------+------------------------------------------+-----------+-----------+
| c0   | RUNNING | 203.0.113.10 (eth0) | fda0:7a62:28::a (eth0)                   | CONTAINER | 0         |
|      |         |                     | fda0:7a62:28:0:216:3eff:fe67:b848 (eth0) |           |           |
+------+---------+---------------------+------------------------------------------+-----------+-----------+
| c1   | RUNNING | 203.0.113.11 (eth0) | fda0:7a62:28::b (eth0)                   | CONTAINER | 0         |
|      |         |                     | fda0:7a62:28:0:216:3eff:fe0a:c3ba (eth0) |           |           |
+------+---------+---------------------+------------------------------------------+-----------+-----------+
| c2   | RUNNING | 203.0.113.12 (eth0) | fda0:7a62:28::c (eth0)                   | CONTAINER | 0         |
|      |         |                     | fda0:7a62:28:0:216:3eff:febb:bfb9 (eth0) |           |           |
+------+---------+---------------------+------------------------------------------+-----------+-----------+

Q60.

Comment vérifier la connectivité réseau depuis les conteneurs ?

La question précédente montre que la configuration réseau des conteneurs est complète. On doit donc lancer des tests IPv4 et IPv6.

Voici deux exemples de tests ICMP.

for i in {0..2}
do
        echo ">>>>>>>>>>>>>>>>> c$i"
        incus exec c$i -- ping -c2 2620:fe::fe
done
for i in {0..2}
do
        echo ">>>>>>>>>>>>>>>>> c$i"
        incus exec c$i -- ping -c2 9.9.9.9
done

Q61.

Comment ouvrir un accès SSH avec un compte utilisateur normal dans les conteneurs ?

Reprendre le script d'exécution des commandes à l'intérieur des conteneurs pour installer les paquets nécessaires, créer le compte utilisateur et autoriser la connexion SSH par mot de passe.

Voici un exemple de script qui assure les traitements demandés.

#!/bin/bash

# Function to check if a variable is a non-empty string
is_non_empty_string() {
    if [[ -n "$1" && "$1" == *[!\ ]* ]]; then
        return 0 # True
    else
        return 1 # False
    fi
}

# Check if both arguments are provided
if [ $# -ne 2 ]; then
    echo "Error: This script requires exactly two arguments."
    echo "Usage: $0 <username> <password>"
    exit 1
fi

# Check if $1 is a non-empty string
if ! is_non_empty_string "$1"; then
    echo "Error: First argument is not a valid non-empty string."
    exit 1
fi

# Check if $2 is a non-empty string
if ! is_non_empty_string "$2"; then
    echo "Error: Second argument is not a valid non-empty string."
    exit 1
fi

USER=$1
shift
PASSWD=$1

. run-commands-in-containers.sh \
        "apt -y install ssh sudo" \
        "adduser --gecos \"\" --disabled-password ${USER}" \
        "adduser ${USER} sudo" \
        "adduser ${USER} adm" \
        "sh -c \"echo '${USER}:${PASSWD}' | chpasswd\"" \
        "sed -i 's/#Port 22/Port 22\nPort 2222/' /etc/ssh/sshd_config" \
        "sed -i 's/PasswordAuthentication no/PasswordAuthentication yes/' /etc/ssh/sshd_config" \
        "dpkg-reconfigure openssh-server"

exit 0

Voici un exemple de lancement du script avec un nom d'utilisateur et son mot de passe comme arguments de la ligne de commande.

bash set-user-and-ssh.sh "etu" "xxxxxxx"

Le résultat est évalué par une connexion SSH depuis le routeur Hub.

etu@hub:~$ ssh etu@fda0:7a62:28::b
The authenticity of host 'fda0:7a62:28::b (fda0:7a62:28::b)' can't be established.
ED25519 key fingerprint is SHA256:6qkAjHutYP6hbpa4iNh94y1Bk4wRqRD62ahOC+3WC9g.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'fda0:7a62:28::b' (ED25519) to the list of known hosts.
etu@fda0:7a62:28::b's password:
Linux c1 6.10.9-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.10.9-1 (2024-09-08) x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Sun Sep 22 14:24:07 2024 from 203.0.113.1
etu@c1:~$