Docker : leçon 2

Anne docker

Après la leçon 1, voilà mes notes prises lors de la seconde leçon sur Docker.

Fabriquer une image

Interactivement

$ docker commit <container id> <repository:tag>
  • le tag par défaut est latest
  • le nom du dépôt est souvent composé du nom du registry (basé par exemple sur le nom de l’utilisateur ou dockerhub) et du nom de l’application.

Imaginons par exemple que je souhaite construire une image avec tous mes outils préférés déjà installés :

$ docker run -i -t ubuntu:14.04 /bin/bash
$ apt-get install -y ...                  # tous mes outils préférés
$ exit
$ docker ps -a                            # pour récupérer d'id
$ docker commit <id> anne/ma_base:1.0
$ docker images                           # on doit voir la nouvelle image

Pour utiliser cette nouvelle image :

docker run -i -t anne/ma_base:1.0 /bin/bash

A partir d’un fichier de configuration

Un Dockerfile est fichier contenant des instructions pour construire une nouvelle image.

# Un petit exemple
FROM  ubuntu:14.04
RUN   apt-get install -y vim

Attention, chaque commande conduit à construire une nouvelle image. Il peut donc être préférable de les regrouper :

RUN apt-get update \
 && apt-get install -y vim git ...

L’intérêt d’avoir les images intermédiaires, c’est que si on ajoute des lignes au Dockerfile par la suite, il n’aura pas besoin de refaire les premières étapes car il les aura sauvegardé, et il sait les retrouver.

Pour construire l’image :

$ docker build -t <repository:tag> <contexte>

Le contexte indique où chercher le Dockerfile. Par exemple le contexte est . pour le répertoire courant :

$ vim Dockerfile
$ docker build -t anne/test:1.0 .
$ docker images

L’instruction CMD donne la commande à lancer quand on utilise le container. Elle n’est pas exécutée lors de la création de l’image. Si on ne donne pas de commande dans le Dockerfile, il lance la commande par défaut du container de départ. Par exemple, pour Ubuntu, il s’agit de la commande bash. Il faut noter que la commande peut être donnée lors de l’utilisation du container. C’est alors cette dernière qui a la priorité.

Donc si on utilise l’image précédente, il ne se passe rien :

docker run anne/test:1.0

Si on ajoute une commande au Dockerfile :

CMD  ["ping", "127.0.0.1", "-c", "10"]

Et qu’on construit et lance une nouvelle image :

$ docker build -t anne/test:1.1 .
$ docker run anne/test:1.1

On voit les ping s’exécuter. On peut surcharger cette commande en faisant :

$ docker run anne/test:1.1 echo "coucou"

Cette fois, plus de ping, mais juste coucou.

La commande ENTRYPOINT est similaire à CMD, sauf qu’on ne peut pas la surcharger, et que par contre, on peut lui passer des arguments. Si on remplace la commande CMD dans le Dockerfile par :

ENTRYPOINT ["ping"]

Et qu’on construit et lance une nouvelle image :

$ docker build -t anne/test:1.2 .
$ docker run anne/test:1.2

Ça râle, car ping attend des arguments, mais on peut alors faire :

docker run anne/test:1.2 127.0.0.1 -c 5

Gestion des images et des containers

Pour arrêter un container qui tourne en arrière plan :

$ docker stop <id|nom>

(pour trouver l’identificateur ou le nom, docker ps avec éventuellement -a).

Pour le redémarrer :

 $ docker start <id|nom>

On peut lancer un nouveau processus dans container qui tourne avec :

$ docker exec -it <id|nom> bash

Cette fois, quand on quitte bash, le container ne s’arrête pas de tourner car il ne s’agit pas du processus 1.

Nettoyage

Pour détruire complètement un container (qui est arrêté) :

$ docker rm <id|nom>

Pour détruire localement une image :

docker rmi <image_id|repo:tag>

Par exemple:

docker images
docker rmi anne/test:1.0

Transfert sur DockerHub

Sur DockerHub, on peut créer des dépôts publiques ou privés. On les crée à partir de l’interface web. Pour pousser (push) une image, il faut que le nom de dépôt soit le même que sur DockerHub.

Si ce n’est pas le cas, on peut renommer l’image avec :

$ docker tag anne/test:1.0 anne/exercices:1.0

(attention, c’est plutôt une copie qu’un renommage).

On peut ensuite faire :

docker push anne/exercices:1.0

(la première fois, il décliner son identité et mot de passe).

On peut vérifier sur l’interface web de DockerHub que l’image est bien là.

Volumes

Les volumes sur des répertoires persistants du container. Les données sont indépendantes du cycle de vie du container. Quand on modifie une image, les données persistantes ne seront pas modifiées. Elles persistent même si le container est détruit, et peuvent être partagées entre plusieurs container. Pour monter un volume lorsqu’on lance un container, on utilise l’option -v :

$ docker run -d -v /mon_volume anne/test:1.2

Les volumes être associés à des répertoires sur l’hôte :

$ docker run -i -t -v /data/src:/test/src anne/test:1.2

Ici, /data/src est le répertoire sur la machine hôte, et /test/src le point de montage dans le container.

Bien sûr, on peut spécifier ça dans le Dockerfile. Il y a plusieurs syntaxes :

VOLUME /mon_volume
VOLUME /www/site1.com /www/site2.com
VOLUME ["vol_1", "vol_2"]

Par contre, cela ne permet pas de monter un répertoire de l’hôte, car les Dockerfile sont conçus pour pouvoir être déplacés, et exécuter sur différentes machines.

Les volumes permettent de stocker des données que l’on peut garder indépendamment du container, comme des fichiers de log par exemple. Ça permet aussi de partager des données entre plusieurs container.

Attention, monter des répertoires de l’hôte n’est à utiliser que lors de tests. Ce n’est pas à utiliser en production car cela lie fortement le container à la machine hôte, ce qui n’est pas souhaitable.

Exemple :

$ docker run -i -t -v /volume  ubuntu:14.04 /bin/bash
# ls
# echo "toto" > /volume/test
# cat /volume/test
# exit
$ docker ps -a
$ docker commit <id> voltest:1.0
$ docker run -i -t voltest:1.0 bash
# ls /volume

On observe que le répertoire /volume est bien là, mais il est vide, car les données ne sont pas exportées.

Créer un volume de données

Pour créer un volume et mettre des données dedans, on fait :

$ docker create -v /volume --name mes_donnees busybox

L’image busybox est juste une petite image toute simple utilisée comme base.

On peut vérifier que le répertoire /volume est vide :

$ docker run --rm --volumes-from mes_donnees ubuntu ls -la /volume

(--rm permet de créer un container temporaire qui est automatiquement détruit après usage).

$ docker run -ti --rm --volumes-from mes_donnees ubuntu bash
# ls volume
# echo "coucou" > /volume/test
# cat /volume/test
# exit
$ docker run -ti --rm --volumes-from mes_donnees ubuntu bash
# ls /volume
# cat /volume/test
# exit

Cette fois, les données sont bien toujours là.

On peut voir le container de donné en faisant :

$ docker ps -a

Pour un exemple d’utilisation des volumes, voir .

Attention, lorsqu’on crée un volume de données, l’image de base (ici busybox) n’a pas d’importance. Du coup, il est intéressant d’utiliser la même image que le container utilisateur. Comme ça, on n’a besoin que d’une seule image, et en plus, on a la même environnement (groupes, utilisateurs, etc).

Réseau

Brancher les ports

Pour parler à des applications web dans le container, il faut associer les ports. Pour exemple, si le serveur nginx écoute sur le port 80, on peut le faire correspondre au port 8085 sur la machine hôte avec l’option -p :

$ docker run -d -p 8085:80 nginx:1.7

On voit l’association avec docker ps. On peut ensuite vérifier qu’on a bien accès au serveur :

$ firefox localhost:8085

L’option -P le numéro de port sur l’hôte est choisi automatiquement

parmis les grands nombres, mais seuls les ports spécifiés par la commande EXPOSE seront associés. Par exemple :

FROM ubuntu:14.04
RUN apt-get update && apt-get install -y nginx
EXPOSE 80 443
CMD ["nginx", "-g", "daemon off;"]

Associer des containers

On peut aussi lier des containers : le container destinataire a alors accès aux données du container source. La liaison utilise les noms des containers. Il est donc préférable de spécifier le nom plutôt que de laisser docker les choisir comme précédemment. On va d’abord créer le container source :

$ docker run -d --name database postgres

Puis créer le container destination en le liant au précédent :

$ docker run -it --name website --link database:db ubuntu:14.04 /bin/bash

On peut voir dans le fichier /etc/hosts que le nom db est associé à une adresse IP. Si on sort maintenant du container website, et que l’on cherche l’adresse IP du container source en faisant :

$ docker instpect database | grep IPAddress

On peut vérifier que c’est la même.

On remarque aussi que quand on lie deux containers source -> destination, on n’est pas obligé d’exposer les ports de la destination. Ceci ne seront donc accessible que de la source.

Intégration continue

La fin du cours explique comment connecter GitHub et DockerHub pour faire des compilations automatiques, mais ça ne me concerne pas pour l’instant…

La suite, pour ma part, est plutôt de voir comment utiliser Jenkins dans Docker.

Voir aussi :