virli/tutorial/docker-internals/linuxkit-content.md

9.8 KiB

Prérequis

Si vous n'avez pas déjà le binaire linuxkit, vous pouvez le télécharger ici :
https://github.com/linuxkit/linuxkit/releases/latest.

Notez qu'étant donné qu'il est écrit en Go, aucune dépendance n'est nécessaire en plus du binaire1. Un simple chmod +x vous permettra de l'exécuter depuis n'importe quel dossier

Vous aurez également besoin de QEMU pour tester vos créations.

Structure d'un fichier linuxkit.yml

Le fichier utilisé pour construire notre image se décompose en plusieurs parties :

  • kernel : il est attendu ici une image OCI contenant le nécessaire pour pouvoir utiliser un noyau : l'image du noyau, ses modules et un initramfs ;
  • init : l'ensemble des images OCI de cette liste seront fusionnés pour donner naissance au rootfs de la machine. On y place normalement qu'un gestionnaire de conteneurs, qui sera chargé de lancer chaque conteneur au bon moment ;
  • onboot, onshutdown et services : il s'agit de conteneurs qui seront lancés par le gestionnaire disponible dans l'init, au moment désigné.\ Les conteneurs indiqués dans onboot seront lancés séquentiellement au démarrage de la machine, ceux dans onshutdown seront lancés lors de l'arrêt de la machine. Les conteneurs dans services seront lancés simultanément une fois que le dernier conteneur de onboot aura rendu la main ;
  • files : des fichiers supplémentaires à placer dans le système de fichier à l'emplacement déterminé.

Le format est documenté ici : https://github.com/linuxkit/linuxkit/blob/master/docs/yaml.md

Hello?

L'image la plus simple que l'on puisse réaliser pourrait être :

```yaml kernel: image: linuxkit/kernel:5.10.104 cmdline: "console=tty0 console=ttyS0" init: - linuxkit/init:8f1e6a0747acbbb4d7e24dc98f97faa8d1c6cec7 - linuxkit/runc:f01b88c7033180d50ae43562d72707c6881904e4 - linuxkit/containerd:de1b18eed76a266baa3092e5c154c84f595e56da onboot: - name: dhcpcd image: linuxkit/dhcpcd:52d2c4df0311b182e99241cdc382ff726755c450 command: ["/sbin/dhcpcd", "--nobackground", "-f", "/dhcpcd.conf", "-1"] services: - name: getty image: linuxkit/getty:76951a596aa5e0867a38e28f0b94d620e948e3e8 env: - INSECURE=true ```

On retrouve nos différentes sections : kernel indique qu'il faut récupérer l'image linuxkit/kernel depuis le registre Docker : il ne s'agit pas d'une image qui sera lancé, elle est plutôt utilisée comme une archive de stockage pour le noyau et ses modules. LinuxKit au moment de la construction de l'image se chargera de placer les fichiers aux bons endroits.

Ensuite, nous avons une section init qui déclare 3 images Docker :

  • linuxkit/init contient les fichiers de base et un binaire /sbin/init qui servira de système d'initialisation ;
  • linuxkit/runc nous donnera les outils pour lancer des conteneurs ;
  • linuxkit/containerd apporte un daemon pour gérer les conteneurs pendant leur durée de vie.

Ces trois images ne sont pas non plus des images Docker conventionnelles, dans le sens où on ne peut pas les utiliser pour faire un docker container run. Elles contiennent chacune une partie de l'arborescence du système de fichiers, uniquement les fichiers nécessaire, en plus, au fonctionnement du programme qu'elles ajoutent. Les images déclarées dans la section init seront fusionnées ensemble et formeront le système de fichiers de base de notre image LinuxKit.

Enfin les sections onboot et services sont plus conventionnelles : il s'agit bien d'images Docker, les conteneurs seront lancés comme tel, à partir d'une configuration runc qui sera générée au moment de la construction de l'image LinuxKit.

L'image getty est notamment très pratique pour déboguer, car elle permet d'avoir un shell sur la machine !

::::: {.more}

On notera cependant que, positionné dans services, le shell que nous obtiendrons sera lui-même exécuté dans un conteneur, nous n'aurons donc pas un accès entièrement privilégier. Pour déboguer, il faut placer cette image dans la partie init, elle sera alors lancée comme un équivalent de init=/bin/sh.2

:::::

namespaces

Chaque nouveau conteneur est donc lancé dans un espace distinct où il ne pourra pas interagir avec les autres, ou déborder s'il s'avérait qu'il expose une faille exploitable.

Néanmoins, contrairement à Docker qui, de base, va nous dissocier du maximum de namespaces, linuxkit ne le fait pas pour les namespaces net, ipc et uts. C'est-à-dire que, par défaut, la pile réseau est partagée entre tous les conteneurs, tout comme les IPC et le nom de la machine.

Il reste possible de se dissocier également de ces namespaces, en précisant :

```yaml - name: getty image: linuxkit/getty:ed32c71531f5998aa510847bb07bd847492d4101 net: new ```

Ou inversement, pour persister dans le namespace initial :

```yaml - name: getty image: linuxkit/getty:ed32c71531f5998aa510847bb07bd847492d4101 pid: host ```

Partage de namespace

Dans le cas où l'on souhaite que deux conteneurs partagent le même namespace, il faut passer le chemin vers la structure du noyau correspondante.

On commence donc d'abord par créer le nouveau namespace, en prenant soin de bind mount la structure du noyau à un emplacement connu :

```yaml - name: getty image: linuxkit/getty:ed32c71531f5998aa510847bb07bd847492d4101 net: new runtime: bindNS: /run/netns/mynewnetns ```

À la création du namespace net, le lien vers la structure du noyau correspondante sera bind mount sur /run/netns/synchro. On pourra alors réutiliser plus tard ce chemin, en remplacement du mot clef new :

```yaml - name: xxxx image: linuxkit/xxxx:v0.8 net: /run/netns/mynewnetns ```

Construction et démarrage

Toute la puissance de linuxkit repose dans son système de construction et surtout de lancement. En effet, il peut construire des images pour un grand nombre de plate-forme, mais il est également possible d'utiliser les API de ces plates-formes pour aller y lancer des instances de cette image !

Pour construire l'image faite précédemment :

```bash linuxkit build hello.yml ```

Cela va générer plusieurs fichiers dont un noyau (extrait de l'image de la partie kernel) ainsi qu'une image. Exactement ce qu'attend QEMU ! Pour tester, n'attendons pas davantage pour lancer :

```bash linuxkit run qemu -gui hello ```

Ajouter un service

Maintenant que notre machine fonctionne, que nous pouvons interagir avec elle, tentons de se passer de l'interface de QEMU (option -gui) en ajoutant un serveur SSH aux services :

```yaml - name: sshd image: linuxkit/sshd:4696ba61c3ec091328e1c14857d77e675802342f binds.add: - /root/.ssh:/root/.ssh ```

::::: {.question}

Que fait la ligne binds.add ? {-}

\

Avec l'instruction binds.add, LinuxKit va créer un bind mount selon le même principe que les volumes des conteneurs Docker. Ici nous allons partager le dossier /root/.ssh de notre image LinuxKit avec celui du conteneur sshd. \

Un certain nombre de bind mounts sont effectués par défaut. Ceux-ci sont déclarés dans les métadonnées des images. Pour avoir la liste, il convient de regarder le fichier build.yml de chaque image :
https://github.com/linuxkit/linuxkit/blob/master/pkg/sshd/build.yml#L5

:::::

Comme nous n'avons défini aucun mot de passe, il va falloir utiliser une clef SSH pour se connecter. Voilà un bon début d'utilisation de la section files :

```yaml - path: root/.ssh/authorized_keys source: ~/.ssh/id_rsa.pub mode: "0600" ```

Ceci va aller chercher votre clef RSA publique sur votre machine, pour la placer directement comme contenu du fichier authorized_keys. À adapter en fonction de votre situation.

Interface réseau virtuelle

Lorsque l'on souhaite se dissocier d'un namespace net afin de s'isoler, mais que l'on veut tout de même pouvoir communiquer, il est nécessaire de créer une interface virtual ethernet :

```yaml - name: db image: mariadb:latest net: new runtime: bindNS: net: /run/netns/db interfaces: - name: vethin-db add: veth peer: veth-db ```

::::: {.exercice}

Réalisez une recette vault.yml démarrant une instance du gestionnaire de secrets Hashicorp Vault, utilisant une base de données au choix (Consul, Etcd, MySQL, Cassandra, ...).

Au démarrage, Vault devra déjà être configuré pour parler à sa base de données, qui devra se trouver dans un conteneur isolé et non accessible d'internet. Il faudra donc établir un lien virtual ethernet entre les deux conteneurs ; et ne pas oublier de le configurer (automatiquement au runtime, grâce à un poststart hook ou bien à un conteneur issu du package ip).

Les permissions étant généralement très strictes, vous aurez sans doute besoin de les assouplir un peu en ajoutant des capabilities autorisées à vos conteneurs, sans quoi vos conteneurs risquent d'être tués prématurément.

En bonus, vous pouvez gérer la persistance des données stockées dans Vault.

:::::


  1. à condition tout de même que vous utilisiez une libc habituelle. ↩︎

  2. Plus d'infos à https://github.com/linuxkit/linuxkit/blob/master/pkg/getty/README.md#linuxkit-debug ↩︎