docker - arrêtez d'utiliser des conteneurs data-only

18/04/2016

Le voilà le titre racoleur. Et en le lisant vous êtes en train de vous dire que je vais aller à l’encontre des bonnes pratiques. Vous vous trompez !

Non mais sérieusement, je sais qu’on vous a répété maintes fois “utilisez un conteneur data-only”, et je comprends bien l’intérêt. Cela-dit, je vais faire durer un peu le suspens et commencer par réexpliquer ce qu’est un “volume” et ce que fais un conteneur “data-only”. Je vous donnerai enfin la meilleure option pour vous en passer, et pour éviter de vous torturer la tête: non je ne vais pas non plus faire un montage de volume local tout bête. Mais je ne vais pas non plus rendre plus complexe l’opération.

En clair, on va se passer de conteneur data-only, que ce soit avec docker ou docker-compose tout en gardant la souplesse de l’opération.

EDIT: Merci à Adirelle, dans les commentaires, d’avoir précisé que les volumes nommés sont une “nouveauté” de Docker 1.9, ce qui explique l’utilisation massive de conteneurs data-only avant cette version

EDIT: Merci aussi à cedvanet pour sa question: Effectivement je n’ai pas été clair sur le fait que les conteneurs “host:conteneurs” ne sont pas le sujet de cet article, ils sont bel et bien très utiles, par exemple pour développer en partageant les sources entre votre IDE et votre conteneur, ou pour placer de la configuration. De plus, effectivement, il arrive que des “data-only containers” soient utiles, le titre du billet est peut-être trop brutal. Ce que je tente de vous démontrer n’est pas qu’un data-only container est “inutile” ou “à ne jamais utiliser”, mais qu’il faut les utiliser que certains cas.

C’est quoi un volume ?

Docker propose des volumes depuis le début. Le principe est parfois mal compris et très souvent mal utilisé. Il existe deux types de volumes très utilisés:

  • les volumes “host:conteneur”
  • les volumes “conteneur:conteneur”

Ces noms sont donnés à titre indicatif et n’existent pas dans la documentation de docker, c’est juste pour vous donner une définition simple.

Le premier, “host-conteneur” est un bête montage de répertoire local que vous choisissez vers le conteneur. Pour faire simple:

$ docker run -it --rm -v ~/Documents:/opt/docs alpine sh
> ls -la /opt/docs

Vous voyez dans le répertoire “/opt/docs” du conteneur les fichiers et répertoires “~/Documents”. C’est simple et c’est pratique. Par exemple quand on travaille en PHP, Python ou je ne sais quel autre langage, il suffit de monter les sources du projet dans le conteneur qui aura loisir de démarrer votre projet.

L’autre type de volume est plus sournois. Une image va définir un volume qui pourra alors être monté dans d’autres conteneurs. Outre le fait que la majeure partie des gens n’ont pas conscience de ce qui découle de déclarer un volume dans une image (et, brisons le suspens, c’est la finalité de l’article), c’est certainement l’une des notions les plus intéressantes de Docker.

Prenons une image simple:

FROM alpine
# on déclare un volume
VOLUME /opt/data
# pas la peine le de le laisser tourner
# les volumes sont accessibles même
# quand le conteneur ne tourne plus
CMD ["true"]

Maintenant démarrons un conteneur avec cette image:

$ docker build -t $USER/dataexample .
$ docker run -it -d --name data $USER/dataexample

Et lions les volumes de ce conteneur dans un nouveau conteneur:

$ docker run --rm -it --volumes-from data alpine sh
> touch /opt/data/foo
> exit

Maintenant, créons un nouveau conteneur qui monte, une fois de plus, les volumes du conteneur “data”

$ docker run --rm -it --volumes-from data alpine sh
> ls /opt/data
> foo

Vous l’avez compris. Le fait de déclarer un volume dans une image permet de partager un répertoire depuis le conteneur vers d’autres conteneurs.

Maintenant, pour bien poser l’idée, le conteneur “data” est un conteneur “data-only”. Il ne fait rien d’autre que de garder les données d’un répertoire qu’il a déclaré comme étant un volume.

Sans passer par la case “image”, on peut tout à faire créer un conteneur “data-only” directement:

docker run -it --volume /opt/data --name data2 busybox

Le fait de ne pas donner de point de montage local va créer un volume.

Ok, mais il stocke où alors ?

Et justement… Voilà ce qui me chagrine avec l’utilisation de conteneurs “data-only”.

Le volume est bel et bien stocké, par défaut, sur votre système. Sauf que peu d’utilisateurs en ont conscience.

Si je ne déclare pas de volume, les fichiers sont stockés dans le conteneur lui-même (en fait, dans le répertoire /var/lib/docker/SHA_IMAGE…). Mais quand on déclare un volume, docker va créer un répertoire prévu à cet effet. Que l’on soit bien clair, Docker déclare un volume, et vous pouvez le voir par vous-même:

$ docker -d run -it --name data -v /opt/data busybox
$ docker inspect  --format "{{ .Mounts }}" data
[{ab099654cc0be... /var/lib/docker/volumes/ab099654cc0be.../_data /opt/data local  true }]

$ docker volume inspect ab099654cc0be...
[
    {
        "Name": "ab099654cc0be...",
        "Driver": "local",
        "Mountpoint": "/var/lib/docker/volumes/ab099654cc0be.../_data"
    }
]

Et voilà où je veux en venir. Supprimez le conteneur “data”, et relancez la commande d’inspection de volume:

docker stop data
docker rm data

Vous venez de supprimer le conteneur, mais son volume est toujours là…

docker volume inspect ab099654cc0bef6c8b0f18e3fb8efe2b564b39832cd1db6b277f4c92bf84272c
[
    {
        "Name": "ab099654cc0be...",
        "Driver": "local",
        "Mountpoint": "/var/lib/docker/volumes/ab099654cc0be.../_data"
    }
]

On vient de créer un orphelin.

Autre souci, je ne peux plus monter ce volume:

$ docker run --rm -it --volumes-from data busybox
docker: Error response from daemon: No such container: data

Donc, voilà la première raison qui me pousse à ne pas utiliser des conteneurs “data-only”, mais plutôt d’utiliser un volume.

Ok, utilisons un volume

Maintenant que vous avez compris le principe, sachez que docker peut vous permettre de créer un volume sans passer par un conteneur. C’est là que je veux en venir !

Prenons un exemple que j’utilise beaucoup, un conteneur mongo dont je ne veux pas perdre les données. J’utilisais, avant, un répertoire perso pour y stocker les données.

Plus tard, j’ai utilisé un conteneur data-only, ce qui fonctionne bien, je ne dis pas le contraire. L’image mongo déclare un volume “/data/db”, donc je n’avais qu’à faire:

docker run --name mongodata -v /data/db -it -d busybox
docker run --rm -it --volumes-from mongodata mongo

Oui, mais c’est bête !

Voilà la meilleure façon de faire:

docker volume create --name mongodata
docker run --rm -it -v mongodata:/data/db mongo

Cette fois, je définis où je monte le volume, et je n’ai qu’un seul conteneur qui tourne: le conteneur mongo. Le volume est local (par défaut) et se trouve ici:

$ docker volume inspect mongodata
[
    {
        "Name": "mongodata",
        "Driver": "local",
        "Mountpoint": "/var/lib/docker/volumes/mongodata/_data"
    }
]

C’est bien plus efficace et ça limite largement le nombre de conteneurs à démarrer. On notera aussi la facilité à retrouver les volumes par leurs noms.

et avec docker-compose ?

Docker-compose est vraiment un outil pratique et j’attendais avec impatience l’arrivée d’une version “2” qui me permettais de jouer avec un peu plus d’options. Et justement, depuis cette version, nous pouvons créer des volumes.

Exemple:

version: '2'
services:
    database:
        image: mongo
        volumes:
        - mongodata:/data/db
volumes:
    mongodata:
        driver: local

Et voilà, un conteneur, un volume, pas la peine de créer un conteneur simplement pour garder des données au chaud.

Les drivers

Par défaut nous utilisons des volumes locaux. Mais il existe d’autres drivers qui permettent de monter le volume autrement que sur votre disque.

La liste est déjà conséquente et se trouve ici: https://docs.docker.com/engine/extend/plugins/

J’ai utilisé, pour le fun https://github.com/gondor/docker-volume-netshare qui permet d’utiliser NFS (entre autre). Ça marche bien !

Suites aux commentaires

J’ai reçu pas mal de commentaires sur Twitter, Google plus et ici qui sont trés intéressants. Je vais donc repréciser un point:

Effectivement, il peut arriver que, à l’inverse, un “volume nommé” tel que je le présente ne soit pas la panacée. Un “data-only container” peut avoir tout son intérêt, par exemple pour partager un volume avec beaucoup de conteneurs avec en plus un accès “host”.

Clairement, je ne suis pas là pour juger les utilisateurs de conteneurs data-only, ni pour définir une règle. Ce que je tenais à démontrer ici, dans ce billet de blog, c’est simplement que pour pas mal de cas, la plupart même, un conteneur “data-only” n’est pas une évidence.

En clair:

  • vous développez et devez avoir accès au répertoire de travail localement: faites un volume host:container
  • vous voulez partager un volume avec beaucoup de conteneurs et avoir accès localement aux données: faites un conteneur “data-only”
  • vous voulez persister des données mais y avoir accès localement n’est que ponctuel ou anecdotique: créez un volume nommé

Donc

Donc voilà, le titre, je le répète, “arrêtez d’utiliser des conteneurs data-only”, c’est en fait rarement utile et surtout c’est moins efficace. Docker gère très bien les volumes en tant qu’entité.

Ça peut vous intéresser aussi


Docker Apache Mysql PHP

Ce matin un collègue me demande “comment tu ferais ...


Rendre homogène une équipe de dev avec docker et docker-compose

Quand on veut bosser avec Docker sur des projets plus ...


Docker, Xvfb et links

Un conteneur = un service, c’est une règle qui ...


Alpine ou comment faire une image Docker très légère

Puisque ces temps-ci je met à profit mes compétences CoreOS/Docker ...

Merci de m'aider à financer mes services

Si vous avez apprécié cet article, je vous serai reconnaissant de m'aider à me payer une petite bière :)

Si vous voulez en savoir plus sur l'utilisation de flattr sur mon blog, lisez cette page: Ayez pitié de moi

Commentaires

Ajouter un commentaire

Astik - 19/05/2017

Effectivement, on ne parle pas de data que produit le container, mais bien de data que consomme le container. L’idée est de ne pas avoir à repackager l’invariant (ici le système) et ne repackager que ce qui est modifié entre chaque build de la PIC (ici les applications).

En gros, j’aimerais faire comme avec un point de montage “host vers container” mais avec la possibilité de pousser le dossier partagé du host vers un registry. De cette façon, avec docker compose, je lui demande de mettre à jour la version du composant et hop le tour est joué.

Le problème pourrait être similaire avec un site statique : j’ai une image contenant tout le nécessaire pour un site web (apache, …) et je ne souhaite mettre que quelques fichiers statiques. En développement, j’utiliserais un point de montage comme tu le décris dans ton article. L’idée serait de faire le même genre de montage mais à partir d’un élément provenant du registry.

Apparemment, je ne semble pas être le seul à être curieux sur le sujet. On trouve des discussions similaires comme ici : https://github.com/moby/moby/issues/7583

Pour l’orchestrateur, je suis moyennement tenté, ça me sort beaucoup de ma zone de confiance =D J’aurais aimé tout gérer avec docker-compose. Maintenant, peut être que ce que je veux faire n’est pas compatible avec la philosophie Docker =‘(

(désolé pour la typo dès le premier mot dans le message précédent, il fallait lire “salut” =D)

Metal3d - 19/05/2017

Là je vois surtout que t’as besoin d’un orchestrateur type kubernetes ou openshift.

Par contre les volumes nommés sont là pour stoquer les données générées par ton appli. Là tu génères une appli externe et une image que tu mets dans un registre. On parle pas de data ou alors j’ai pas tout compris ton archives.

Astik - 19/05/2017

Saut,

J’ai un use case potentiel pour un container / volume data, mais j’avoue être un peu perdu avec Docker présentement.

Actuellement, nous avons plusieurs applications java qui tournent sur la même stack : java8 + spring boot, le tout dans un docker. A chaque fois qu’on build l’application sur la PIC (jenkins), une nouvelle image est construite (contenant l’application et la jdk) et est déployée sur un registry privé. Ensuite, un script demande à mettre à jour l’environnement cible qui ira qui ira lui-même chercher la dernière image sur le registry.

Le problème est que l’on a pas mal d’application (archi micro-service) et pas mal de build sur différents environnements.

Du coup, je pensais utiliser quelques chose comme des contenants (container ? volume ?) pour monter l’application dans une image java générique :

  • 1 image générique pour le système
  • 1 contenants par application et par build pour les micro-service
  • 1 docker compose qui rapatrie la dernière image du système ainsi que la dernière image pour un micro-service précis, puis montage du contenant dans l’image système

Le contenant micro-service serait read-only par rapport à l’image système, pas besoin d’y stocker de donnée (et donc pas besoin de gérer un backup car on l’a déjà dans notre registry)

On aurait alors, des “contenants” assez léger et une image générique qui ne changerait quasiment pas. Je pense que cela devrait alléger notre stockage sur le registry mais aussi alléger les déploiements.

Est-ce que ce scénario a du sens ? Est-ce que Docker peut répondre à cela ? et dans ce cas, data volume nommé ? container avec configuration particulière ?

J’avoue ne pas trouver d’information sur la possibilité d’héberger des data volume nommés sur un registry privé.

Merci d’avance pour toute piste

Metal3d - 05/04/2017

@Alain, j’ai du mal à comprendre ce qui te coince mais si je comprend bien, tu utilises un DOC pour stoquer des données que tu veux ensuite transporter et/ou monter dans un autre conteneur.

Un volume nommé est un répertoire (situé dans /var/lib/docker/volumes par défaut) que tu peux inspecter (docker volume inspect ...) et copier, synchroniser, ou archiver. Au lieu de déplacer un conteneur, tu déplaceras un répertoire/archive.

Autre solution, utiliser un autre driver que celui par défaut (local).

C’est le principe utilisé sur Kubernetes (et par effet de cause, par OpenShift), tu peux écrire des données dans un NFS, un GlusterFS, Ceph, etc… Car les conteneurs démarrent sur plusieurs machines dont les volumes ne seraient plus accessibles depuis le conteneur si il se voyait redémarrer sur un autre noeud.

Personnellement je n’ai pas du tout l’utilité de créer des volumes transportables - je fais en sorte que mon image ou mon entrypoint se charge de créer les données, et comme j’utilise OpenShift et Kubernetes, mes volumes sont sur GlusterFS ou NFS dans le pire des cas. Donc les volumes sont accessibles depuis n’importe quel noeud.

Mais si tu as un exemple concret je serai super content de réfléchir à la question. Car là, je dois bien avouer que j’ai du mal à saisir le besoin :) (et j’estime ne pas tout savoir, d’où mon intérêt)

Metal3d - 05/04/2017

@Quentin, le chemin du mountpoint indique où les sources sont accessible à l’intérieur du conteneur. Pour rappel, un conteur utilise sa propre racine et ne voit pas le chemin de l’hôte. Pour être plus clair:

# le répertoire "/dir/to/A" est accessible dans le conteneur
# via le répertoire /B
docker run ... -v /dir/to/A:/B

# et dans le cas d'un volume "nommé":
# le volume "A" est accessible dans le conteneur
# via le répertoire /B
docker run ... -v A:/B
Quentin - 03/04/2017

Très sympas cet article. Par contre je ne comprend pas à quoi correspond le chemin du moutpoint.

Alain Bdlp - 14/11/2016

Merci pour la réponse! Ma question portait plutôt sur “comment remplir un docker volume (pas un DOC) avec des données statiques” gérées en configuration et qui sont ensuite “montée” lors du “docker run” de l’image qui a besoin de ces données de configuration ? Le DOC remplissait ce Use Case, avec les volumes cela me paraît moins évident et c’était ma question quelles sont les “best practises” recommandées pour ce UC s’il ne faut plus utiliser les DOC. merci pour le temps passé!

Metal3d - 09/11/2016

@Alain Bdlp je n’ai pas bien compris les commandes que tu voulais montrer (il aurait fallu les mettre entre triple backtick, c’est du markdown) mais je vais tenter de répondre à la dernière question.

En fait, oui, un volume est au sens littéral un volume de données. Et tu peux faire un amalgame entre deux concepts:

  • le volume au sens littéral du terme, celui qui te permet de monter des données “host” vers “conteneur”
  • le volume déclaré dans une image qui te permet de partager un répertoire de conteneur à conteneur

Les volumes nommés (et volumes hôtes) ne sont pas déclarés dans un Dockerfile, c’est à l’instanciation d’un conteneur que tu le défini (via l’opption “-v” ou “–volume”)

Alors que le volume déclaré dans un Dockerfile, partage un répertoire depuis le conteneur vers un autre conteneur. Par exemple, si je définis dans mon Dockerfile “VOLUME /data”, que j’en fais une image et que je crée un conteneur “foo”, alors si je crée un autre conteneur (quelque soit l’image), je peux utiliser le volume de cette manière:

$ docker run -it --volumes-from=foo alpine bash
# je suis dans le conteneur "alpine", 
# j'a accès au répertoire /data venant de foo
$ > ls /data

En espérant avoir clarifié le concept :)

Alain Bdlp - 09/11/2016

Article intéressant qui permet de comprendre la différence entre les DataOnlyConteneur et les Volumes. Néanmoins j’ai un Use Case sur mon projet ou j’ai l’impression que le DataOnly container reste intéressant… Je voulais utiliser les DOC pour transporter des données de configuration d’une plateformeA (ou je gère la configuration en GC ) vers une plateformeB qui les utilise pour les monter sur un ligne Docker run.. plateformeA : docker run -it -d –name ConfigV1.0.0 /space/config/V1.0.0/configFile push ConfigV1.0.0 in plateformeB.registry plateformeB : docker run –rm -it –volumes-from ConfigV1.0.0 “dockerimage which need config V1.0.0” Ma comprehension est que les volumes servent à créer(partager) des données sur un media persistent (host ou autre dependant du volume driver) mais pas de transporter des données (exemple dans une image…) Est ce que j’ai râté quelque chose dans ma compréhension des volumes pour mon UC de “partage de donnée de configuration” ? Y a t’il un moyen “simple” d’implementer mon UC avec des volumes ? Merci!

Metal3d - 19/04/2016

Suite à pas mal de commentaires dont je vous remercie, j’ai adapté un peu le contenu et ajouté une section + des “edits” dans l’entête. Merci à tous pour vos retours

Metal3d - 19/04/2016

@Mr T c’est en cours :)

Mr T - 19/04/2016

Très intéressant … ça mérite une petite traduction en Anglais pour faire passer le message à toute la communeauté!

Merci

Metal3d - 18/04/2016

@adrielle j’avais modifié l’article cet après-midi pour justement utiliser “true”. On a du se croiser :)

Oui je n’ai.pas précisé que les volumes nommés sont récents. Tu as raison je modifierai l’article en conséquence. Je te remercie :)

Adirelle - 18/04/2016

Tu n’étais pas obligé de maintenir le conteneur “data-only” up: un contenur avec /bin/true suffisait largement. Il est vrai qu’il est plus simple d’utiliser des volumnes nommés mais c’est une fonction apportée par la version 1.9 de Docker, d’où l’usage très répandu des conteurs de données.

Ajouter un commentaire

(*) Votre e-mail ne sera ni revendu, ni rendu public, ni utilisé pour vous proposer des mails commerciaux. Il n'est utilisé que pour vous contacter en cas de souci avec le contenu du commentaire, ou pour vous prévenir d'un nouveau commentaire si vous avez coché la case prévue à cet effet.