## Prérequis Si vous n'avez pas déjà le binaire `linuxkit`, vous pouvez télécharger ici :\ . Notez qu'étant donné qu'il est écrit en Go, aucune dépendance n'est nécessaire en plus du binaire[^lollibc] ;-) [^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 n'y place normalement qu'un gestionnaire de conteneur, 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 système disponible dans l'`init`, au bon moment. 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 rootfs. 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:4.14.80 cmdline: "console=tty0 console=ttyS0" init: - linuxkit/init:c563953a2277eb73a89d89f70e4b6dcdcfebc2d1 - linuxkit/runc:83d0edb4552b1a5df1f0976f05f442829eac38fe - linuxkit/containerd:326b096cd5fbab0f864e52721d036cade67599d6 onboot: - name: dhcpcd image: linuxkit/dhcpcd:v0.6 command: ["/sbin/dhcpcd", "--nobackground", "-f", "/dhcpcd.conf", "-1"] services: - name: getty image: linuxkit/getty:2eb742cd7a68e14cf50577c02f30147bc406e478 env: - INSECURE=true trust: org: - linuxkit ```
L'image `getty` est très pratique pour déboguer, car elle permet d'avoir un shell sur la machine ! 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é comme un équivalent de `init=/bin/sh`.[^infogetty] [^infogetty]: Plus d'infos à ## `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 va de base 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:2eb742cd7a68e14cf50577c02f30147bc406e478 net: new ```
Ou inversement, pour persister dans le namespace initial :
```yaml - name: getty image: linuxkit/getty:2eb742cd7a68e14cf50577c02f30147bc406e478 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:2eb742cd7a68e14cf50577c02f30147bc406e478 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.6 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:c4bc89cf0d66733c923ab9cb46198b599eb99320 ```
Comme nous n'avons définis 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 sur votre machine, pour la placer dans Notons qu'il est inutile d'ajouter un *bind mount* du dossier `.ssh` ainsi recopié, car le *package* `linuxkit/sshd` défini déjà cela : [pkg/sshd/build.yml#L5](https://github.com/linuxkit/linuxkit/blob/master/pkg/sshd/build.yml#L5). ## 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](https://www.vaultproject.io/), utilisant une [base de données au choix](https://www.vaultproject.io/docs/configuration/storage/index.html) (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*](https://github.com/opencontainers/runtime-spec/blob/master/config.md#posix-platform-hooks) ou bien à un conteneur issu du *package* [`ip`](https://github.com/linuxkit/linuxkit/tree/master/pkg/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](https://github.com/linuxkit/linuxkit/blob/master/examples/swap.yml) stockées dans Vault.