Conteneurs avec les namespaces sous Linux
Ce TD est à réaliser dans une machine virtuelle Ubuntu.
A l'INSA-CVL, se connecter à la machine ubuntu-namespaces.
Objectifs
- Utiliser les interfaces du noyau Linux pour lancer un conteneur
Création « manuelle » d'un conteneur
Nous allons créer et lancer un conteneur sans utiliser Docker pour comprendre les mécanismes utilisés et les interactions avec le noyau Linux.
Création d'une image système
Nous allons utiliser debootstrap pour créer une racine de système de fichier (le contenu d'une image Docker) pour notre conteneur.
$ sudo apt-get -y -q install debootstrap
Nous utiliserons ici la version jammy (22.04 LTS) de la distribution Ubuntu, dans sa variante minimale :
$ mkdir rootfs
$ sudo debootstrap --variant=minbase jammy rootfs
Utilisation de unshare
Pour lancer un shell Bash dans notre image, nous pouvons utiliser chroot :
$ sudo chroot rootfs /bin/bash
Mais chroot « n'isole » que la vue du système de fichier pour une application :
# A l'extérieur du chroot
$ top
$ lsns
# A l'intérieur du chroot
$ mount -t proc proc /proc
$ top
$ ps aux | grep top
$ lsns
$ umount /proc
Pour isoler la vue des processus, il faut utiliser les namespaces avec unshare :
$ sudo unshare --pid --fork chroot rootfs /bin/bash
$ mount -t proc proc /proc
$ ps aux
$ lsns
Notez que nous utilisons des versions distinctes de la distribution Ubuntu à l'intérieur du conteneur de celle de l'hôte. Dans notre cas, c'est une version stable LTS sur un hôte récent non LTS. En général, on souhaite utiliser une distribution stable bien supportée comme hôte et potentiellement une distribution plus récente dans un conteneur pour bénéficier des dernières évolution d'un langage ou d'un compilateur.
Interface du noyau Linux : namespaces
Nous allons reproduire les commandes ci-dessus à l'aide d'un programme en C faisant directement appel à l'interface du noyau.
$ sudo apt-get -y -q install make
Voici un programme qui utilise l'appel système clone(2) pour créer un processus fils :
main.c (télécharger) :
// Modified from https://lwn.net/Articles/533492/
// Copyright 2013, Michael Kerrisk
// Licensed under GNU General Public License v2 or later
#define _GNU_SOURCE
#include <sched.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <signal.h>
#include <stdio.h>
#include <sys/mount.h>
// A simple error-handling function: print an error message based
// on the value in 'errno' and terminate the calling process
#define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \
} while (0)
// Function de départ pour le processus fils cloné
static int childFunc(void *arg)
{
char **argv = arg;
// TODO : Mise en place pour le processus fils
execvp(argv[0], &argv[0]);
errExit("execvp");
}
// Espace mémoire réservé pour la pile du processus fils
#define STACK_SIZE (1024 * 1024)
static char child_stack[STACK_SIZE];
int main(int argc, char *argv[])
{
int flags = 0;
pid_t child_pid;
// TODO : Mise en place du flag pour le PID namespace
child_pid = clone(childFunc, child_stack + STACK_SIZE, flags | SIGCHLD, &argv[1]);
if (child_pid == -1)
errExit("clone");
printf("%s: PID of child created by clone() is %ld\n", argv[0], (long) child_pid);
// Le processus parent continue ici
// Attent que le processus fils se termine
if (waitpid(child_pid, NULL, 0) == -1)
errExit("waitpid");
printf("%s: terminating\n", argv[0]);
exit(EXIT_SUCCESS);
}
et un Makefile (télécharger) :
.PHONY: all clean
all:
gcc -o main -Wall -Wextra main.c
clean:
rm -f main
Compilez et essayez l'application avec :
$ make
$ sudo ./main ls
$ sudo ./main ps aux
Namespace PID
Nous allons ajouter la mise en place du namespace PID :
- Ajoutez le flag pour créer un processus fils dans un PID namespace distinct
- Faites appel à
chroot(2)etchdir(2)pour utiliser le shell du rootfs obtenu avec debootstrap - Montez
/procdans le rootfs pour obtenir les bonnes informations sur les processus - Validez le résultat avec les commandes :
ps auxls -al /proc/self/nslsns
Namespace réseau
Nous allons ensuite ajouter le support du namespace réseau.
Installer le paquet
iproute2pour obtenir la commandipdans le chroot.Indices
Pour cela vous pouvez utiliser
chroot rootfs /bin/bashpour obtenir un shell puis installer le paquet avecapt updatepuisapt install iproute2.Une fois le processus fils lancé dans le nouveau namespace, créer une paire d'interfaces réseau virtuelles
vethà l'aide de la commandipsuivante dans le processus père:ip link add name veth0 type veth peer name veth1 netns [child pid]Vous pouvez par exemple appeler cette commande à l'aide de la fonction
system().Ajouter un appel à
sleep()dans le processus fils pour qu'il attende que l'interface réseau soit créée par le père dans son namespace avant d'essayer de la configurer.Configurer l'interface réseau à l'aide de
ipdans le namespace du père et du fils.Exécuter la command ping depuis le fils pour communiquer avec le père.
Solution
Une solution pour cette section est proposée dans l'article : Linux – Writing a Simple Container App with Namespaces, Liran B. H.