MobyDocker

Cet article est une introduction à LinuxKit, un composant du Projet Moby qui permet de construire un système sécurisé, portable et fiable, basé sur des conteneurs.

Public visé : développeurs et architectes cloud, ainsi que tout technique souhaitant manipuler conteneurs, cloud, OS et CI/CD.

Par Maxime Cottret, Consultant Cloud.

LinuxKit: un Dockerfile pour constuire ses VMs ?

Introduction

La plupart des utilisateurs de cloud auront un jour à créer des images de VM pour une application spécifique (i.e. une appliance).

Quelles sont les options pour y parvenir ?

  • A la main: démarrer une VM, la configurer, faire une sauvegarde de son image. Ce n’est pas la méthode la plus efficace (même si vous pouvez l’améliorer en utilisant Ansible ou Puppet pour le provisionning) et vous êtes obligé d’utiliser un format spécifique de VM.
  • La méthode Packer: parmi les très bons outils d’Hashicorp, Packer permet de construire des images identiques de VMs pour de nombreuses plateformes, à partir d’une seule et même source de configuration. C’est ensuite simple de l’intégrer dans la chaîne CI/CD. Cependant, Packer ne fournit pas d’abstraction complète et vous aurez à expliciter la configuration de chaque plateforme (kickstart pour Redhat/Centos, preseed pour Ubuntu/Debian, etc).

Pour faire face à la difficulté d’adapter la plateforme Docker à différents systèmes (comme MacOS, Windows, AWS, GCE, etc.), Docker a décidé de créer son propre outil de génération d’images virtuelles en exploitant les technlogies de conteneurs et d’unikernels. Voici les principes de LinuxKit :

  • Construire des images de zéro avec le strict nécessaire, et ainsi obtenir des images de taille minimale.
  • Tout dans l’image tourne sur du conteneurs, de l’init aux services
  • Disposer d’images sans état et non modifiables (immutable) auxquelles on peut attacher du stockage persistant
  • Permettre une configuration simple par fichier Yaml.

Respectant sa devise « build, ship, run », Docker apporte la simplicité d’utilisation d’un Dockerfile et de docker image build  au monde des VMs.

Afin d’illustrer cet article, nous allons choisir une application pour installer et configurer notre image pour en faire une appliance. Et il me semble que LDAP est un bon candidat !

Installation

Les versions de Linuxkit sont disponibles dans un Github, mais peuvent aussi être installées directement comme des packages Go ou en utilisant Homebrew sur MacOS X.

Allez sur LinuxKit Github Repo et choisissez votre méthode.

Vous aurez préalablement installé Docker et un hyperviseur local (qemu, vbox, etc.) pour les tests.

Construire l’appliance LDAP

Tout d’abord, voyons comment une image Linuxkit est conçue.

Les images Linuxkit sont construites sur un kernel et un système init minimum dont le seul objectif est de faire tourner le démon containerD. Tous les autres services et processus tournent dans des conteneurs.

LinuxKit définit 3 types de conteneurs:

  • on-boot et shutdown: ces conteneurs sont à usage unique et visent à configurer l’instance de l’image à son démarrage, et son nettoyage lors de son arrêt.
  • service: ce sont des processus au long cours.

L’objectif de LinuxKit est de construire des systèmes linux dédiés, non des OS génériques comme Ubuntu ou Arch.

Le cas d’usage nominal de Docker était la création d’un OS linux minimum pour faire tourner des conteneurs sur des VM packagées dans Docker4Max. Pour cela, il suffit d’un kernel et d’un sous-système minimum dédié à l’exécution du Docker engine.

Spécification de l’image

Pour spécifier l’image LinuxKit, vous avez seulement besoin d’un ficher yaml qui décrit le contenu de l’image virtuelle en utilisant des modules LinuxKit ou des images OCI (i.e. les images Docker classiques).

Les modules LinuxKit sont des images OCI spécifiques, construites par la communauté LinuxKit et fournissant des fonctions linux de base comme sysctl configuration management, dhcpd, volume mount, etc.

Pour LDAP, nous utiliserons une image OpenLDAP basée sur alpine, disponible sur notre dockerhub : objectiflibre/slapd. Vous pouvez consulter la documentation disponible dans le dockerhub pour plus d’information sur son utilisation.

Le fichier yaml contient les sections suivantes :

  • kernel: contient les informations sur le kernel à utiliser
  • init: contient la liste des conteneurs utilisés à l’initialisation de l’image
  • onboot: contient une liste de conteneurs qui s’exécutent au démarrage, dans l’ordre décrit dans le fichier yaml
  • services: liste les conteneurs qui tournent en arrière-plan
  • onshutdown: liste les conteneurs qui s’exécutent à l’arrêt, dans l’ordre décrit dans le fichier yaml
  • files: liste les fichiers à importer depuis la machine locale de construction pendant la construction de l’image
  • trust: liste les organisations ou les images pour lesquelles la sécurité Docker Content doit être vérifiée

Une fois que vous avez en tête le design de l’image, la structure du fichier parait simple et logique. Les paramètres de configuration des conteneurs sont proches de ceux disponible sur la plateforme Docker, comme : command, binds, network, environment variables, etc.

Le fichier yaml pour l’appliance LDAP ressemble à ceci:

kernel:
  image: linuxkit/kernel:4.9.75
  cmdline: "console=ttyS0"
init:
  - linuxkit/init:5a577d070817b4f17821657823082651baafd4ed
  - linuxkit/runc:abc3f292653e64a2fd488e9675ace19a55ec7023
  - linuxkit/containerd:e58a382c33bb509ba3e0e8170dfaa5a100504c5b
  - linuxkit/ca-certificates:de21b84d9b055ad9dcecc57965b654a7a24ef8e0
onboot:
  - name: sysctl
    image: linuxkit/sysctl:4c1ef93bb5eb1a877318db4b2daa6768ed002e21
  - name: dhcpcd
    image: linuxkit/dhcpcd:0d59a6cc03412289ef4313f2491ec666c1715cc9
    command: ["/sbin/dhcpcd", "--nobackground", "-f", "/dhcpcd.conf", "-1"]
  - name: format
    image: linuxkit/format:e945016ec780a788a71dcddc81497d54d3b14bc7
  - name: mount
    image: linuxkit/mount:b346ec277b7074e5c9986128a879c10a1d18742b
    command: ["/usr/bin/mountie", "/var/lib/openldap"]
  - name: metadata
    image: linuxkit/metadata:2af15c9f4b0e73515c219b7cc14e6e65e1d4fd6d
    command: ["/usr/bin/metadata", "openstack"]
services:
  - name: rngd
    image: linuxkit/rngd:94e01a4b16fadb053455cdc2269c4eb0b39199cd
  - name: sshd
    image: linuxkit/sshd:ac5e8364e2e9aa8717a3295c51eb60b8c57373d5
    binds:
     - /etc/resolv.conf:/etc/resolv.conf
     - /run:/run
     - /tmp:/tmp
     - /etc:/hostroot/etc
     - /usr/bin/ctr:/usr/bin/ctr
     - /usr/bin/runc:/usr/bin/runc
     - /containers:/containers
     - /var/log:/var/log
     - /var/config:/var/config
     - /dev:/dev
     - /sys:/sys
     - /var/config/ssh/authorized_keys:/root/.ssh/authorized_keys
  - name: slapd
    image: objectiflibre/demo-linuxkit-slapd
    env:
     - SUFFIX_FILE=/var/config/ldap_suffix
     - ORGANISATION_NAME_FILE=/var/config/ldap_organisation
     - ROOT_PW_FILE=/var/config/ldap_rootpass
    binds:
     - /var/config:/var/config
     - /var/lib/openldap:/var/lib/openldap/openldap-data
     - /etc/resolv.conf:/etc/resolv.conf
    capabilities:
     - CAP_NET_BIND_SERVICE
     - CAP_CHOWN
     - CAP_SETUID
     - CAP_SETGID
     - CAP_DAC_OVERRIDE
trust:
  org:
    - linuxkit
    - library

Pour plus de détails sur le format yaml, cf. la documentation officielle sur Github.

La difficulté principale sera de comprendre comment les modules core comme systctl, dhcpd ou metadata fonctionnent: ils ne sont pas documentés (ou presque) et il faut fouiller dans les sources, voire dans le code source des applications. Vous pouvez utilisez le linuxkit.yml fourni par défaut, ou les exemples de fichiers, comme point de départ pour votre propre image.

Notez à quel point la définition de l’image est portable: le yaml ne contient aucune référence au format final de l’image ni au cloud de destination.

NB : vous trouverez tout ce qui est utilisé dans ce article dans le GitHub : GitHub d’Objectiflibre.

Build, local run, ship, run une image LinuxKit

Nous allons retrouver l’esprit « build, ship, run » de Docker.

L’outil supporte plusieurs formats d’images (qcow2, raw, iso, etc.) et plusieurs plateformes cibles (Azure, OpenStack, etc.). Voyez les aides des lignes de commande pour l’adaptation à votre propre plateforme.

Build

Pour construire l’image, vous devez juste spécifier un nom, un format d’image et un fichier yaml. Par exemple:

linuxkit build -format qcow2-bios -name linuxkit-ldap ldap.yml

Vous avez maintenant un système linux minimal au format qcow2 avec un LDAP installé de seulement 80 Mo.

Local run

Vous pouvez tester vos images sur un hypervieur local compatible, par exemple qemu:

linuxkit run qemu --mem 1024 --publish 8389:389 linuxkit-ldap.qcow2

Ici, le port LDAP  389 de la VM est attaché au port 8389 de la machine locale.

Vous pouvez ensuite tester votre instance LDAP avec ldap-client et les paramètres ci-dessous:

  • url: localhost:8389
  • admin dn: cn=admin,dc=example,dc=org
  • admin passwd: password

Ship

Pour utiliser votre image sur un cloud, vous pouvez charger directement l’image sur le dépôt d’images du cloud, ou utiliser la commande LinuxKit.

J’utilise le cloud OpenStack interne d’Objectif Libre dans cet exemple, mais vous pouvez facilement adapter pour utiliser un autre cloud.

$ linuxkit push openstack -img-name=linuxkit-ldap -authurl=$OS_AUTH_URL \
  -domain=$OS_USER_DOMAIN -username=$OS_USERNAME -project=$OS_PROJECT_NAME \
  -password=$OS_PASSWORD linuxkit-ldap.qcow2

Run

Une fois l’image chargée sur votre cloud, vous pouvez l’utiliser comme n’importe quelle image avec les outils propres au cloud. Vous pouvez aussi utiliser la commande LinuxKit pour créer une instance, en donnant les informations requises :gabarit, réseau, régles de sécurité, clés ssh, etc.

$ linuxkit run openstack -authurl=$OS_AUTH_URL -domain=$OS_USER_DOMAIN \
  -username=$OS_USERNAME -project=$OS_PROJECT_NAME -password=$OS_PASSWORD \
  -flavor=m1.small -instancename=demo-ldap -keyname=mcottret -sec-groups="openbar" \
  -network=d5c237e1-0376-4d68-87b6-e208f6dc210d linuxkit-ldap

Vous avez maintenant un serveur LDAP fonctionnel qui tourne sur votre cloud.

Pour vous connecter à l’instance ldap, récupérez son IP publique (ajoutez une IP flottante si nécessaire) et utilisez les paramètres par défaut avec ldap-client:

  • url: LDAP_IP:389
  • admin dn: cn=admin,dc=example,dc=org
  • admin passwd: password

Dans la mesure où l’image est construite avec un service sshd, vous pouvez vérifier si l’instance fonctionne bien:

$ ssh root@LDAP_IP
$ ctr c ls # list containers
$ ctr t ls # list tasks

Si ce n’est pas le cas, les logs sont disponibles dans /var/log .

Et si on veut personnaliser la configuration initiale du ldap ?

Désormais, nous avons une image utilisable ; comment pouvons-nous la configurer ? LinuxKit construit une image immuable, donc il n’est pas possible de simplement faire un ssh et d’appliquer les modification ( sshd fonctionnant DANS un conteneur).

Heureusement, notre image OCI de slapd peut être configurée en utilisant des variables d’environnement. On précise également, dans le fichier yaml, à l’intérieur de la section boot le paquet metadata, qui est capable de récupérer les métadonnées user-init fournies par Openstack (ou autre cloud compatible) à la création de l’instance.

Ainsi, vous avez plusieurs manières de configurer votre instance LDAP:

  • en modifiant le fichier ldap.yml pour inclure vos variables d’environnement, reconstruire et pousser la nouvelle image
  • en modifiant le fichier ldap.yml pour charger des fichiers locaux dans votre image (à l’aide de la clé files), reconstruire et pousser la nouvelle image
  • en fournissant un fichier json de métadonnées à l’exécution avec le schéma suivant:
    {
      "ldap_suffix": {
        "content": "dc=ol,dc=lab",
        "perm": "0644"
      },
      "ldap_rootpass": {
        "content": "totolitoto",
        "perm": "0644"
      },
      "ldap_organisation": {
        "content": "OL Lab",
        "perm": "0644"
      }
    }
    

Qu’en est-il des modules LinuxKit ?

LinuxKit peut utiliser des modules pour assembler une image. Comme dit précédemment, ces modules sont des images OCI avec des metadonnées additionnelles, nativement multi-architectures et signées.

La plupart des modules sont construits et maintenus par la communauté LinuxKit, mais tout le monde peut en créer un.

Voyons la manière de construire un module ldap à la place de l’image slapd.

Métadonnées du module

Tout d’abord, il est nécessaire d’avoir un build.yml qui contienne les métadonnées suivantes:

image: ldap
org: oldemo
arches:
  - amd64
gitrepo: https://github.com/ObjectifLibre/linuxkit-ldap-demo.git
network: yes
disable-content-trust: yes
config:
  capabilities:
   - CAP_NET_BIND_SERVICE
   - CAP_CHOWN
   - CAP_SETUID
   - CAP_SETGID
   - CAP_DAC_OVERRIDE

La seule clé obligatoire est image qui contiendra le nom de l’image.

Le org indique l’espace de nom pour le registre (l’image va être poussée vers docker.io/ORG/IMAGE). La valeur par défaut est linuxkit et ne doit être utilisée que pour les paquets core maintenus par le projet LinuxKit.

arches spécifie la liste des images que l’outil pourra trouver pour construire le manifeste multi-architectures.

La clé network indique le besoin de connexion internet depuis la construction.

Dans le but de la démonstration, la confiance dans le contenu (i.e. la signature de l’image) est désactivée.

Enfin, la configuration réalisée lors de l’exécution sera fournie dans la clé config. Ces valeurs seront fixées à la valeur org.mobyproject.config dans l’image et utilisées par Linuxkit comme configuration des conteneurs par défaut. Ainsi, les packages peuvent être utilisés sans information complémentaire sur les besoins en capabilites, points de montages, etc.

Pour plus d’informations et de mises à jour, cf. la documenation officielle.

Image OCI du module

Ensuite, il est nécessaire d’avoir un Dockerfile pour spécifier comment constuire l’image OCI:

FROM alpine AS mirror

RUN mkdir -p /out/etc/apk && cp -r /etc/apk/* /out/etc/apk/
RUN apk add --no-cache --initdb -p /out \
    alpine-baselayout \
    busybox \
    bash \
    ca-certificates \
    musl \
    tini \
    openldap \
    openldap-back-mdb \
    openldap-clients \
    && true
# we really do not want a rogue inittab here
RUN rm -rf /out/etc/inittab
COPY scripts/* /out/etc/openldap/
COPY slapd.sh /out/

FROM scratch
ENTRYPOINT ["/sbin/tini","-s","-v","--"]
WORKDIR /
COPY --from=mirror /out/ /
CMD ["/slapd.sh"]

L’ensemble des sources est disponible sur le github de ObjectifLibre.

Construction du module

Pour construire le package, il suffit d’utiliser la commande suivante depuis le dossier source :

$ linuxkit pkg build .

Par défaut, le tag de l’image est le HEAD de l’arborescence git.

Pour utiliser le module, vous avez simplement besoin de changer la référence de l’image pour le service ldap dans le fichier yaml pour utiliser l’image que l’on vient de construire.

Vous pouvez également partager votre module en poussant l’image dans le hub avec docker ou la commande LinuxKit:

$ linuxkit pkg push .

Note

Sur linux, veillez à ne pas utiliser les docker-credentials-helper puisque cet outil va chercher les utilisateur/mot de passe encodés en base64 dans ~/.docker/config.json afin de copier l’image sur le hub.

Note

Pour le mutli-architecture, vous devez construire et pousser l’image séquentiellement pour chaque plateforme. L’outil va mettre à jour le manifeste multi-architecture à chaque passe.

Conclusion

LinuxKit est une tentative de fournir le même mécanisme simple de développement et exécution, quel que soit l’hôte (physique ou virtuel), que celui proposé par Docker pour les conteneurs.

L’équipe derrière LinuxKit a fait un travail remarquable dans temps restreint (le projet n’a pas encore un an) et le projet est déjà utilisé dans des produits publics de Docker et IBM.

Le projet présente les défaut de sa jeunesse : l’api et les package ne sont pas encore stables, et le manque de bonne documentation peut représenter un obstacle pour un nouveau venu.

Cependant, l’approche proposée par LinuxKit est clairement dans la lignée du DevOps et nous vous invitons à garder un œil dessus.