Docker 1.6 et son écosystème

Mon, 1 June, 2015 (3100 Words)
Cet article est disponible sur le Blog de Zenika à l'adresse suivante : http://blog.zenika.com/index.php?post/2015/06/01/Docker-1-6-et-son-ecosysteme. Cet publication me sert de mirroir / sauvegarde.

Introduction

Le 28 octobre dernier, nous avions parlé de la sortie de Docker 1.3, des évolutions entre la version 1 et cette dernière et de son écosystème. Je vous proposes de remettre ça, bientôt 6 mois après, avec un peu le même plan : les principales nouveautés entre la version 1.3 et 1.6 (et il y en a ;-)), l'évolution de l'écosystème qui gravite autour et un peu de social avec les meetups et évènements qui se sont passés depuis.

docker_container_engine_logo.png

Rappel ultra rapide, Docker est une plate-forme ouverte à destination des développeurs et administrateurs systèmes visant à faciliter la construction et le déploiement d'applications distribuées. De manière moins marketing, l'idée derrière Docker est d'automatiser le déploiement d'environnements sous forme de conteneurs légers, portables et auto-suffisants ; les conteneurs permettant d'isoler l'exécution des applications dans des contextes d'exécution. Pour ce faire, Docker, écrit en Go, reprend les bases de LXC, utilise les fonctionnalités du noyau Linux (CGroups, Namespaces, …) et se base initialement sur un système de fichier "en oignons" AUFS ; D'autres backends sont supportés également comme BTRFS ou devicemapper (LVM).

Ovelay filesystem storage driver (1.4.0)

La release 1.4.0 de Docker (et la 1.3.3 en parallèle) a surtout été une gigantesque bugfix party, histoire de rendre les fonctionnalités arrivées auparavant plus stable — la release note se trouve ici.

La principale nouveautée de cette version est l'apparation d'un nouveau storage driver, il s'agit d'OverlayFs. Il s'agit d'un mécanisme de montage permettant de superposer dans un répertoire le contenu de plusieurs autres répertoires.

Initialement Docker est basé Aufs qui fait, pour simplifier, la même chose. Le problème avec aufs est qu'il n'est pas intégré dans le noyau Linux (i.e. dans les sources officielles), contrairement à OverlayFS qui a fait son apparition avec le noyau 3.18. Il était donc nécessaire de disposer d'un noyau patché ; Le noyau Linux de Debian et de sess dérivés (Ubuntu, etc.) ont ce patchset aufs de base mais ce n'est pas le cas de toutes les distributions. L'idée de cette intégration est assez simple : supporter le maximum de distributions en se basant sur une feature du noyau. C'est donc bien évidemment le driver d'avenir pour Docker ; attention cependant, la peinture est encore un peu fraîche ;-P.

Support d'IPv6 (1.5.0)

Les adresses IPv4 commencent a se faire rare, il est donc important que Docker supporte IPv6. C'est désormais le cas avec la version 1.5.0, même si ce n'est pas activé par défaut. Pour activer le support de l'IPv6 (en plus de l'IPv4), il faut ajouter le flag --ipv6 au daemon. Docker va donc mettre en place le bridge docker0 avec en plus un IPv6 en mode local, avec l'adresse fe80::1.

Par défaut les containers qui seront créés n'auront qu'une adresse locale. Pour avoir une adresse IPv6 routable à votre conteneur, il est nécessaire de lui préciser un sous-réseau (subnet) dans lequel il va piocher son adresse. Cela se fait grâce à l'argument --fixed-cidr-v6.

docker -d --ipv6 --fixed-cidr-v6="2001:db8:1::/64"

Comme je ne suis pas un pro de l'IPv6, pour plus d'information, et si l'anglais ne vous fait pas peur, c'est dans la documentation "networking" de Docker.

Conteneurs en lecture seule (1.5.0)

Une autre fonctionnalité assez sympathique qui est arrivé avec cette version 1.5.0 est les conteneurs en lecture seule — c'est Michael Crosby qui s'est occupé d'implémenter ça. L'intérêt des conteneurs en lecture seule est de permettre de contrôler où l'application à l'intérieur de votre conteneur peut écrire ou modifier des fichiers. En combinant ceci avec les volumes, vous pouvez vous assurez des emplacements dans lesquels votre conteneur va persister des états ou données (le/les volumes), puisqu'il ne sera pas possible d'écrire ailleurs de toute façon.

Pour activer cette fonctionnalité, c'est l'argument --read-only.

docker run --read-only -v /volume/writable busybox touch /volume/writable

Une autre utilisation des conteneurs en lecture seule est que cela donne la possibilité de faire du debug post-mortem d'un conteneur (en production par exemple). Cela nous permet de redémarrer un conteneur qui aurait planté, en lecture seule avec le système de fichier dans l'état du crash.

Les labels pour le « daemon », les images et les conteneurs (1.6.0)

One Meta Data to Rule Them All

Une des deux fonctionnalités très attendue de la récente version 1.6.0 sont les labels. En un mot, et pour le faire « à-la » le seigneur des anneaux, les labels peuvent se résumer en "Une metadata pour les gouverner tous" (ça le fait vachement mieux en anglais en fait).

Les labels s'appliquent sur le daemon, les images et les conteneurs. C'est un peu un mélange entre des tags et des variables d'environnements puisque il s'agit d'un couple clé/valeur.

L'ajout de label sur le daemon se fait grâce à l'argument — roulement de tambour — --label (\o/). La principale utilité pour l'instant est son utilisation conjointe avec Swarm dont nous parlerons un peu plus bas ; mais en deux mots, cela permet de filtrer les engines sur lesquels on va taper.

# Souvent, c'est dans DOCKER_OPTS du fichier /etc/default/docker
docker -d -H unix://var/run/docker.sock --label storage=ssd --label type=laptop

L'ajout d'un label sur une image se fait dans le fichier Dockerfile, et l'ajout d'un label sur un conteneur, grâce à l'argument --label pour rester cohérent. Construisons une image inutile mais en lui appliquant un label :

FROM busybox
# Support du multi-line pour LABEL
LABEL vendor=zenika \
      com.zenika.lang=golang \
      com.zenika.version=0.1
CMD ["echo", "zenika"]

Nous allons maintenant construire cette image et lancer un conteneur à partir de cette dernière avec un autre label.

$ docker build -t zenikaapp .
# […]
$ docker run --name test --label com.zenika.foo=bar zenikaapp
zenika

L'idée c'est que maintenant, lorsque l'on va regarder la liste d'images ou de conteneurs à disposition sur notre engine, nous allons pouvoir filtrer par label, comme suit :

$ docker images --filter "label=vendor=zenika" --filter "label=com.zenika.lang=golang"
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
zenikaapp           latest              66ffda023118        43 seconds ago      2.433 MB
$ docker ps -a --filter "label=com.zenika.foo=bar"
CONTAINER ID        IMAGE               COMMAND             CREATED              []   NAMES
37e9a37caf57        zenikaapp:latest    "echo zenika"       About a minute ago   []   test
# On peut également regarder les labels avec inspect
$ docker inspect -f "{{json .ContainerConfig.Labels }}" zenikaap
{"com.zenika.lang":"golang","com.zenika.version":"0.1","vendor":"zenika"}
$ docker inspect -f "{{json .Config.Labels }}" test
{"com.zenika.foo":"bar","com.zenika.lang":"golang","com.zenika.version":"0.1","vendor":"zenika"}

On peut imaginer beaucoup d'usage de ces labels. Par example, avec Rancher, ils sont utilisés pour faciliter la configuration du load-balancer (ici) — ils utilisent un label io.rancher.service.provides qui permettra à ce dernier de trouver automatiquement ces petits. Je vous laisse imaginer vos propres use-cases.

Il y a une partie de la documentation qui parle exclusivement des labels, avec une petite partie sur les best-practice de nommage des labels, c'est ici.

Logging drivers \o/ (1.6.0)

Un gros reproche qui était fait à Docker était sa gestion très simpliste des logs des conteneurs. Plusieurs critiques étaient faites :

  1. Tout faire sortir sur stdout et stderr n'est pas vraiment une habitude de nos jours, surtout dans des langages comme Java où l'utilisation de logger (Log4j, Slf4j, …) est très répandue. Cela rend le portage vers docker de certaines applications un peu plus fastidieux.
  2. Il n'y avait aucun mécanisme de rotation de logs — et comme en plus le dossier dans lequel les logs étaient écris est un peu enfoui dans /var/lib/docker, cela pouvait poser quelques problème si des conteneurs étaient un peu trop bavards..
  3. La solution utilisée pour sauvegarder ces logs et pourquoi pas les centraliser (avec ELK par exemple), était d'utiliser un volume, souvent partagé entre applications, et de démarrer un conteneur pour gérer cette analyse, centralisation, …. Non seulement ce n'est pas très optimal, mais cela nécessitait de configurer chaque application (donc chaque conteneur) — et docker logs perdait tout son intérêt.

Avec la version 1.6.0, les logging driver permettent une gestion des logs un peu plus optimale, ou au moins plus flexible. Il est donc maintenant possible de préciser le logging driver à utiliser. Ils en existent 3 pour l'instant :

  1. json-file correspond au comportement par défaut de Docker avant la 1.6 et reste la valeur par défaut
  2. syslog qui permet de connecter les logs de nos conteneurs dans notre vénérable syslog (ou en tout cas quelqu'un qui parl le syslog).
  3. none qui est le magicien puisqu'il nous permet de faire taire complètement un conteneur o/.

Il est possible de définir le logging driver à deux endroits :

  1. sur le daemon pour la valeur par défaut de tous les conteneurs.
$ docker -d --log-driver="json-file"
# Pour faire taire les conteneurs par défaut
$ docker -d --log-driver="none"
  1. en option de la command run (ou de la commande create).
$ docker run --log-driver=syslog ubuntu \
  /bin/bash -c 'while true; do echo "Hello"; sleep1; done'
$ tail -f /var/log/syslog
# […]
May 28 17:39:01 dev1 docker[116314]: 0e5b67244c00: Hello
# […]

Une option --log-opts est également présente pour passer des options additionnelles au driver si celui-ci les supporte. Notons également qu'un driver pour systemd devrait arriver avec la version 1.7.

La pull-request ayant permis d'intégrer cette fonctionnalité se trouve ici.

Client Windows natif (1.6.0)

Enfin, on s'en doutait un peu après l'annonce du partenariat entre Docker Microsoft, ça bosse beaucoup pour porter Docker vers Windows. La première étape était de fournir un client natif pour Windows. C'est chose faite avec cette version 1.6. Maintenant beaucoup de travail est effectué pour rendre le engine plus portable, il n'y a qu'à suivre un peu les pull-request avec un tag os/windows ou encore cette très récente pull-request avec un titre plutôt évocateur : « Windows: The real Windows exec driver is here ».

Divers

Il y a pas mal d'autres options qui sont arrivées depuis la version 1.3.0, nous allons en parcourir certaines rapidement — parce que sinon cet article va faire 100 pages ;-p :

  • Stats (1.5.0) : une commande stats (et donc une API derrière) permet de récupérer quelques statistiques par conteneur, c'est simple pour l'instant.
  • Depuis la version 1.5.0 il est possible de spécifier le ficher Dockerfile grâce à l'option -f de la commande build — jusqu'à maintenant docker regardait uniquement le dossier spécifié et cherchait le fichier Dockerfile. Cela permet donc, par exemple, d'avoir plusieurs Dockerfile dans un dossier.
$ docker build -t monimage -f backend.Dockerfile .
# […] Build the backend
$ docker build -t monimage -f frontend.Dockerfile .
# […] Build the frontend
  • Le registry voit son API passer en V2, principalement pour améliorer les transferts. L'implémentation officielle a été réécrite en Go (à la place de Python) et se nomme maintenant distribution.
  • La commande commit est dotée, depuis la version 1.6.0, d'une option =--change qui permet d'appliquer une instruction supportée par les Dockerfile — voir ici.
  • Docker a publié un petit document « Docker Image Specification » qui a pour but de définir le format des images utilisées par Docker, permettant à d'autres notamment des potentiels conccurents, d'implémenter des images qui seraient compatible.

À venir

L'une des principales nouveautés qui devrait arriver avec la version 1.7 de Docker, c'est l'intégration d'une nouvelle stack réseau avec l'intégration du projet libnetwork, si tout se passe bien. On pourra noter également de nouveaux logging driver, avec notamment un rollover driver ou encore le systemd driver. On peut noter également l'arrivée d'un filesystem driver pour ZFS (voici la pull-request). Le Docker Birthday étant passé par là, beaucoup de corrections de bugs, de nouvelles petites fonctionnalités, une meilleure couverture de code par les tests unitaires (o/).

La RC1 est disponible depuis le 28 mai, ici et la pull-request associée, donc à vos tests !

Écosystème

Trois projets « Docker » sont apparus depuis le dernier article : Compose, Swarm et Machine. Présentons les très rapidement.

Compose

Compose est le nouveau nom de Fig. Fig était développé par Orchard qui a été racheté par Docker. Pour rappel, l'idée est de définir son environnement via un fichier YAML, que ce soit pour le code sur lequel nous travaillons mais également les services externes desquels notre application dépend (Base de données, ''Message queue'', etc.).

web:
  build: .
  command: lein run
  links:
   - db
  ports:
   - "8000:8000"
db:
  image: postgres

Compose est en version 1.2 — depuis la version 1.0, la majorité des modifications sont des corrections de bugs et des ajouts pour suivre les modifications et nouvelles fonctionnalités de Docker (env-file, dns_search, add_host, restart, volumes_from, net, …). La commande est maintenant docker-compose à la place de fig et le fichier docker-compose.yml à la place de fig.yml — pour des raisons de rétro-compatibilité, Compose continue de lire les fig.yml.

Swarm & Machine

Deux nouveaux projets ont fait leur apparition dans l'escarcelle de Docker Inc. : Swarm et Machine. Swarm est le clustering à moyenne échelle vu par Docker. Machine permet de provisionner Docker sur différents providers : amazon aws, google compute engine, azure, Virtualbox pour ne citer qu'eux — mais beaucoup d'autres sont déjà supportés.

Voilà ce que Jérôme Petazzoni dit à propos de Swarm :

Un système de cluster utilisant l’API Docker et compatible avec tous les outils de l’écosystème maison. On peut utiliser les commandes classiques Docker pour piloter le cluster

L'idée principale de Swarm est Je veux parler à mon cluster Docker de la même façon que je parle avec mon daemon Docker. Cela se traduit assez simplement par : Swarm expose la même API que docker. C'est une idée simple et terriblement puissante puisque cela veut dire que je peux administrer mon cluster avec les mêmes commandes que j'utilise quand je travaille en local. Swarm a pour but de piloter des clusters d'une taille relativement petite (moins de 1000 machines). Pour les clusters de plus grande taille, il existe de très bonnes solutions, comme Mesos, et ce n'est pas le but de Docker Inc. de venir les concurrencer, bien au contraire.

Pour faire simple, swarm c'est un manager et des agents (un par engine) — les agents s'enregistrent auprès du master par le biais d'un service discovery. Swarm dispose d'un petit service de discovery mais qui n'est là que pour la démo ; il est possible et conseillé de le connecter à des solutions existantes, pour l'instant consul et etcd.

Un bout de code vaut mieux qu'un long discours, voici comment bootstraper un cluster Swarm, avec l'aide de Machine pour être funky.

# On crée un cluster simple avec son id
$ swarm create
50862dcedd53c2f584adfb00e85bac4b
# On démarre des agents
$ docker-machine create -d azure --swarm --swarm-discovery token://50862dcedd53c2f584adfb00e85bac4b node1
INFO[0000] Creating SSH key...
# […]
$ docker-machine create -d digitalocean --swarm --swarm-discovery token://50862dcedd53c2f584adfb00e85bac4b node2
INFO[0000] Creating SSH key...
# […]
# On démarre le master
$ docker-machine create -d virtualbox --swarm --swarm-master --swarm-discovery token://50862dcedd53c2f584adfb00e85bac4b manager

Maintenant que l'on dispose d'un petit cluster, en pointant dessus (merci Machine) on va pouvoir lancer des commandes docker.

$ $(docker-machine env --swarm manager)
# Le bon vieux info
$ docker info
Containers: 4
Nodes: 3
 manager: 192.168.99.103:2376
   Containers: 2
   Reserved CPUs: 0 / 4
   Reserved Memory: 0 B / 999.9 MiB
 node1: 45.55.160.223:2376
   Containers: 1
   Reserved CPUs: 0 / 1
   Reserved Memory: 0 B / 490 MiB
 node2: swarm-nwde2.cloudapp.net:2376
   Containers: 1
   Reserved CPUs: 0 / 1
   Reserved Memory: 0 B / 1.639 GiB
# On démarre des nginx
$ for i in `seq 1 3`; do docker run -d -p 80:80 nginx; done
$ docker ps
CONTAINER ID    IMAGE       COMMAND                ... PORTS                                NAMES
9bff07d8ee18    nginx:1.7   "nginx -g 'daemon of   ... 443/tcp, 104.210.33.180:80->80/tcp   node1/loving_torvalds
457ed59c9bb3    nginx:1.7   "nginx -g 'daemon of   ... 443/tcp, 45.55.160.223:80->80/tcp    node2/drunk_swartz
6013be18cdbc    nginx:1.7   "nginx -g 'daemon of   ... 443/tcp, 192.168.99.103:80->80/tcp   manager/condescending_galileo

On voit qu'on a démarré nginx sur les 3 nœuds. Swarm a quelques stratégies pour démarrer un conteneur sur un nœud ou l'autre :

  • spread va éparpiller les conteneurs pour que chaque nœud en ait le moins possible (répartis).
  • binpack va faire l'inverse (tout sur le même nœud jusqu'à ce que ses ressources soient épuisés).
  • random qui fait au pif.

Il est également possible de mettre des contraintes lors du lancement d'un conteneur, en utilisant le flag -e de docker run (-e = variables d'environnement).

# Démarrer postgres sur un host qui a le label storage=ssd
$ docker run -d -e constraint:storage==ssd --name postgres postgres
# Démarrer redis à coté du conteneur dont le nom est postgres
$ docker run -d -e affinity:container==postgres --name redis redis
# Démarer backend où tu veux, mais comme les links sont des contraintes
# implicites il démarrera sur le même host que postgres ET redis
# (ou ne démarrera pas ces derniers ne sont pas au même endroit)
$ docker run -d --link redis:redis --link postgres:db --name backend backend

On pourrait faire un article dédié à Swarm (ce qui sera probablement le cas dans un avenir assez proche) donc je vous laisse le découvrir via github.

Levées de fonds et acquisitons

Docker est sur toutes les lèvres en ce moment. Il est donc normal que cela attire également des capitaux. Le 14 avril dernier, Docker annonçait une nouvelle levée de fonds de 95 millions de dollars. Après celle de 40 millions en Septembre 2014, on peut se dire que Docker Inc. a de beaux jours à venir.

Docker Inc. « mange » aussi quelques startups, puisque après Orchard, qui éditait fig (devenu docker-compose), ils ont fait l'acquisition de Socketplane et Kitematic. Kitematic est un outil desktop qui permet de facilement utiliser Docker sous Mac OS X, une belle application, un peu « clickodrome » ;-P. Socketplane est une solution réseau qui connectait Open vSwitch avec Docker — nulle doute que la récente libnetwork vient de là.

Évènements

Nous allons finir avec une liste non-exhaustive et un peu orientée des évènements marquants qui se sont passés ces derniers mois :

  • Le Docker Tour de France, avec notament un hackathon organisé à l'Epitech, où notre Mario Loriedo national a bootstrapé son projet Sublime-docker avec Mike Bright et à du coup gagné sa place à la DockerCon de 2015.
  • Les Nightclazz découverte et avancée hébergé par Zenika, présenté par Mario Loriedo et moi-même ;-).
  • La DockerCon Europe.
  • Le Docker Birthday, gigantesque Open-source-athon tout autour du monde — une véritable réussite, tant au niveau de l'organisation (des évènements, la préparation en amont des issues, etc.) que de ce qu'il en est ressorti.