## 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 binaire[^lollibc]. Un simple `chmod +x` vous permettra de l'exécuter
depuis n'importe quel dossier

[^lollibc]: à condition tout de même que vous utilisiez une libc habituelle.

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 :

<div lang="en-US">
```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
```
</div>

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ée, 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écessaires, 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égié. Pour déboguer, il faut placer cette image dans
la partie `init`, elle sera alors lancée comme un équivalent de
`init=/bin/sh`.[^infogetty]

[^infogetty]: Plus d'infos à
    <https://github.com/linuxkit/linuxkit/blob/master/pkg/getty/README.md#linuxkit-debug>

:::::


## `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 :

<div lang="en-US">
```yaml
- name: getty
  image: linuxkit/getty:ed32c71531f5998aa510847bb07bd847492d4101
  net: new
```
</div>

Ou inversement, pour persister dans le namespace initial :

<div lang="en-US">
```yaml
- name: getty
  image: linuxkit/getty:ed32c71531f5998aa510847bb07bd847492d4101
  pid: host
```
</div>


### 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 :

<div lang="en-US">
```yaml
- name: getty
  image: linuxkit/getty:ed32c71531f5998aa510847bb07bd847492d4101
  net: new
  runtime:
    bindNS: /run/netns/mynewnetns
```
</div>

À 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` :

<div lang="en-US">
```yaml
- name: xxxx
  image: linuxkit/xxxx:v1.0
  net: /run/netns/mynewnetns
```
</div>


## 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-formes, 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 :

<div lang="en-US">
```bash
linuxkit build hello.yml
```
</div>

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 :

<div lang="en-US">
```bash
linuxkit run qemu -gui hello
```
</div>


## 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` :

<div lang="en-US">
```yaml
- name: sshd
  image: linuxkit/sshd:4696ba61c3ec091328e1c14857d77e675802342f
  binds.add:
    - /root/.ssh:/root/.ssh
```
</div>

::::: {.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` :

<div lang="en-US">
```yaml
- path: root/.ssh/authorized_keys
  source: ~/.ssh/id_rsa.pub
  mode: "0600"
```
</div>

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` :

<div lang="en-US">
```yaml
- name: db
  image: mariadb:latest
  net: new
  runtime:
    bindNS:
      net: /run/netns/db
    interfaces:
      - name: vethin-db
        add: veth
        peer: veth-db
```
</div>
