Docker : comment ça marche ?

docker mars 25, 2016

Note aux lecteurs
Cet article est vieillissant et son contenu doit être révisé. Attention donc : les explications, commandes systèmes et autres informations peuvent dorénavant être incorrectes ou inexactes.


I - Serveur dédié

Je loue un serveur dédié ayant des performances très correctes :

  • Processeur i3-2130 CPU @ 3.4GHz 2 coeurs et 4 threads avec une enveloppe thermique (la puissance de chauffe) de 65 Watts
  • 8 Go de RAM
  • 2 To de disque dur

Cela me permet de tester et d'y mettre pleiiiiiin de services :


II - Docker

Ces services tournent tous avec docker (ils sont donc conteneurisés, de la pseudo virtualisation). C'est très pratique, car je peux lancer plusieurs services à priori identiques, mais avec des versions différentes et pour des utilisations différentes.

Par exemple, si j'ai besoin de redis (une base de donnés) pour gitlab et pour un autre service (un site web, ou autre chose), je peux créer deux containers redis qui auront un nom différent et seront dédiés à un seul service.

Le fait de conteneuriser avec docker ne demande pas plus de ressources car ce n'est pas de la virtualisation à part entière. Explication en image :

img
source

Il est assez facile de comprendre qu'une application virtualisée est bien plus lourde car elle demande un OS dédié. En revanche, un système utilisant docker se base sur le noyau (kernel) et sur l'OS hôte. Pour cette raison il est (pour le moment ?) impossible de faire tourner dans un container docker une application provenant d'un autre système d'exploitation ayant besoin d'un noyau différent (Windows par exemple).

Les avantages sont multiples :

  • Lancement très rapide des containers
  • Déploiement facilité et compatible sur beaucoup de systèmes
  • Système hôte propre : chaque service est installé dans un container et non sur l'hôte

A - Comment on fait ?

Il y a plusieurs possibilité de déployer un service sur une machine.

1. L'image

Une image, c'est un peu comme un snapshot d'une machine virtuelle, c'est un instantané d'un système à un instant t. Elle contient un mini système d'exploitation (Debian, Ubuntu, Alpine Linux, etc...) et une application spécifique.
C'est à partir d'une image que l'on peut lancer un container (et donc un service). Pour ce faire, il n'y a que deux choix (à ma connaissance) :

1.1. Construire l'image sur l'hôte ( build )

Pour construire une image, on peut partir sur une image de base, qui sera ensuite complétée par les services et dépendances dont nous avons besoin, où from scratch (de zéro). Si vous voulez construire une image d'un service distribué sur le docker hub, le fichier permettant de la construire peut être fourni. Le fichier en question se nomme Dockerfile.

En voici un exemple pour construire une image nginx-reverse-proxy :

# Image déjà existante sur le docker hub, sur laquelle se baser
FROM nginx:1.9.12
MAINTAINER Jason Wilder mail@jasonwilder.com 

# Installation de wget, et installation/mise à jour des certificats
RUN apt-get update \
 && apt-get install -y -q --no-install-recommends \
    ca-certificates \
    wget \
 && apt-get clean \
 && rm -r /var/lib/apt/lists/*

# Configuration de Nginx et application d'un correctif pour les serveurs avec des noms trop longs
RUN echo "daemon off;" >> /etc/nginx/nginx.conf \
 && sed -i 's/^http {/&\n    server_names_hash_bucket_size 128;/g' /etc/nginx/nginx.conf

# Installation de Forego
RUN wget -P /usr/local/bin https://godist.herokuapp.com/projects/ddollar/forego/releases/current/linux-amd64/forego \
 && chmod u+x /usr/local/bin/forego

ENV DOCKER_GEN_VERSION 0.4.2

RUN wget https://github.com/jwilder/docker-gen/releases/download/$DOCKER_GEN_VERSION/docker-gen-linux-amd64-$DOCKER_GEN_VERSION.tar.gz \
 && tar -C /usr/local/bin -xvzf docker-gen-linux-amd64-$DOCKER_GEN_VERSION.tar.gz \
 && rm /docker-gen-linux-amd64-$DOCKER_GEN_VERSION.tar.gz

COPY . /app/
WORKDIR /app/

ENV DOCKER_HOST unix:///tmp/docker.sock

VOLUME ["/etc/nginx/certs"]

ENTRYPOINT ["/app/docker-entrypoint.sh"]
CMD ["forego", "start", "-r"]

# Arrivé ici, l'image aura été créée

source

Pour l'installer, il faut taper la commande docker build -t nginx ..
C'est une tâche qui peut prendre plus ou moins de temps suivant la puissance de la machine (entre 5 minutes et 20 minutes pour des services un peu plus lourds).

1.2. Récupérer une image des dépôts officiels (docker hub)

La solution permettant un déploiement bien plus rapide. Il suffit de demander à docker de récupérer l'image disponible sur le docker hub :

docker pull jwilder/nginx-proxy

Le temps de la télécharger et l'image est sur votre serveur !

2. Lancer des services

Une fois l'image récupérée (ou construite), il faut la lancer (captain obvious, le retour). Encore une fois, il y a plusieurs possibilités !

2.1. Docker-compose.yml

Un fichier docker-compose.yml sera interprété par l'utilitaire éponyme afin de lancer le service en question avec les bons paramètres.

En voici un petit exemple pour le lancement d'un container nginx.


# Sur quelle image on se base, présente sur le serveur ou non (sera alors téléchargée)
image: nginx

  # Permet de passer un dossier sur l'hôte pour la sauvegarde des données
  volumes:
   - ./mysite.template:/etc/nginx/conf.d/mysite.template

  # Quels ports sont exposés (ici 80 à l'exterieur, bindé vers 80, idem pour le 443)
  ports:
   - "80:80"
   - "443:443"

  # Passage de variable(s) d'environnement au container (peut aider à mieux générer des configurations auto)
  environment:
   - NGINX_HOST=foobar.com
   - NGINX_PORT=80

  # Commandes à lancer au démarrage
  command: /bin/bash -c "envsubst < /etc/nginx/conf.d/mysite.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'"

source

Bien plus d'informations à ce propos sur la page officielle.

2.2. A la main, ou avec un script bash

Depuis que j'utilise docker, il y a eu beaucoup d'évolution. Je préfère lancer mes services avec un script, ce qui me donne la possibilité de supprimer les containers existants, mettre à jour les images puis relancer mes services.
Mais je tente aussi de plus en plus le lancement depuis des docker-compose.yml. Cependant, je rencontre certains problèmes qui sont dû à l'évolution de docker, qui semble vouloir privilégier les nouveautés à la stabilité (pour la mise en production).

echo -e "\n\t Arret du reverse proxy nginx \n"
docker stop nginx-proxy                # Arret du container
docker rm nginx-proxy                  # Suppression du container

echo -e "\n\t Mise à jour de l' image \n"
docker pull jwilder/nginx-proxy:latest # :latest n'est pas obligatoire. On peut passer ici une version précise si elle existe.


echo -e "\n\t Lancement du reverse proxy nginx \n"
docker run -d \                        # -d pour lancer le service comme un daemon
  --restart=always \                   # En cas d'arrêt, docker essayera de relancer le container
  -p 80:80 -p 443:443 \                # Exposition des ports
  --name nginx-proxy \                 # Nom que je donne au container
  --env 'DEFAULT_HOST=www2.lmilcent.com' \ # Variable d'environnement SPECIFIQUE au container
  --env 'MAX_UPLOAD_SIZE=200M'\        # Idem
  -v /path/to/directory/conf.d \       # Exposer le dossier contenant les configurations
  -v /etc/nginx/vhost.d \              # idem : autre fichier de conf
  -v /usr/share/nginx/html \           # idem : fichiers d'erreurs html 
  -v /path/to/directory/certs:/etc/nginx/certs:ro \  # <Dossier hôte où sont les certificats pour le https>:<dossier du container>:<lecture seule (read only)>
  -v /var/run/docker.sock:/tmp/docker.sock:ro \      # Authorise toutes les connexions locales (utilisé pour joindre les autres services)
  -v /path/to/directory/my_proxy.conf:/etc/nginx/conf.d/my_proxy.conf:ro \ # idem : autre fichier de conf
    jwilder/nginx-proxy                # L'image utilisée pour créer le container

# Commande pour afficher tous les container arrêtés ("-a") ou non
docker ps -a

3. Les commandes utiles

Docker propose plusieurs commandes très pratiques pour gérer les différents containers. Les commandes les plus courantes (dans mon cas) sont :

3.1. Afficher tous les containers, arrêtés ou non

docker ps -a

$ docker ps -a
CONTAINER ID        IMAGE                                    COMMAND                  CREATED             STATUS              PORTS                                      NAMES
059e77f4ed31        rocket.chat                              "node main.js"           17 hours ago        Up 17 hours         3000/tcp                                   rocketchat
9be08d923da0        mongo                                    "/entrypoint.sh --sma"   17 hours ago        Up 17 hours         27017/tcp                                  mongo-rocketchat
b411d034d0de        benlubar/nodebb                          "/bin/sh -c 'node app"   36 hours ago        Up 36 hours         4567/tcp                                   forum-nodebb_1
27b3690884a8        redis:alpine                             "redis-server"           36 hours ago        Up 36 hours         6379/tcp                                   redis-nodebb_1
c77cf8578738        jrcs/letsencrypt-nginx-proxy-companion   "/bin/bash /app/entry"   37 hours ago        Up 37 hours                                                    2_nginx-companion
f6d41ffa6361        jwilder/nginx-proxy                      "/app/docker-entrypoi"   37 hours ago        Up 37 hours         0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp   1_nginx-proxy
4704ef5851ec        ghost:latest                             "/entrypoint.sh npm s"   2 days ago          Up 2 days           2368/tcp                                   ghost
0854cd086e4c        mariadb:latest                           "/docker-entrypoint.s"   2 days ago          Up 2 days           3306/tcp                                   ghost-mariadb
6163e3bf35b3        sameersbn/gitlab:latest                  "/sbin/entrypoint.sh "   3 days ago          Up 3 days           80/tcp, 443/tcp, 0.0.0.0:10022->22/tcp     GitLab
91ad35e1d1b0        sameersbn/redis:latest                   "/sbin/entrypoint.sh"    3 days ago          Up 3 days           6379/tcp                                   gitlab-redis
3e68b8ec374b        sameersbn/postgresql:latest              "/sbin/entrypoint.sh"    3 days ago          Up 3 days           5432/tcp                                   gitlab-postgresql
c5831465d967        5d9e4c176cfd                             "/usr/local/bin/super"   4 days ago          Up 36 hours         80/tcp, 443/tcp                            cozy

3.2 Arrêter / Supprimer / Relancer

$ docker stop <nom ou ID du container>
$ docker restart <nom ou ID du container>
$ docker rm <nom ou ID du container>

Nota : Pour supprimer un container, il faut que celui-ci soit arrêté. Sinon docker vous le rappellera gentimement !
Vous pouvez insister : docker rm -f <conteneur>.

3.3 Les logs

Parce qu'il est très pratique de pouvoir accéder aux messages affichés dans la console d'un container pour le débugger ou simplement connaître son état :

$ docker logs <nom ou ID du container>

Bien faire attention au s de logs !

Astuce : vous pouvez voir les logs en temps réel en ajoutant l'option -f.

3.4 Accéder au terminal d'un container

Pour accéder (voire modifier) les fichiers des containers, docker permet d'accéder au terminal.

$ docker exec -ti <nom ou ID du container> bash

bash peut être remplacé par /bin/sh ou tout autre programme permettant de lancer un terminal. En fait, cette commande permet d'autres utilisations comme exécuter une commande précise dans un container.

docker exec -ti <nom ou ID du container> <commande à exécuter>

3.5 Afficher les images locales

A force de déployer des services, il arrive toujours un moment où toutes les images téléchargées commencent à prendre de la place. Et bien souvent, elles ne sont plus forcément utilisées.
df -m permet de visualiser la mémoire libre sur le disque (-m pour afficher en mégaoctets).

Sys. de fichiers blocs de 1M Utilisé Disponible Uti% Monté sur
/dev/sda2            1875646   32728    1747619   2% /
udev                      10       0         10   0% /dev
tmpfs                   1581     153       1428  10% /run
tmpfs                   3952       4       3949   1% /dev/shm
tmpfs                      5       0          5   0% /run/lock
tmpfs                   3952       0       3952   0% /sys/fs/cgroup

Pour afficher les images locales:

docker images

$ docker images
REPOSITORY                               TAG                 IMAGE ID            CREATED             SIZE
jrcs/letsencrypt-nginx-proxy-companion   latest              28748c03a4f1        21 hours ago        68.13 MB
sameersbn/gitlab                         latest              951a95511f96        29 hours ago        714 MB
benlubar/nodebb                          latest              54cc06df228b        2 days ago          753.5 MB
rocket.chat                              latest              9102aa5c3391        4 days ago          737.7 MB
jwilder/nginx-proxy                      latest              8670fdde7d38        4 days ago          242.7 MB
sameersbn/postgresql                     latest              d3f084c4b808        6 days ago          231.4 MB
sameersbn/redis                          latest              100e6eb3355d        6 days ago          196.5 MB
cozy/full                                latest              11c4577b8d05        7 days ago          1.219 GB
mysql                                    latest              7a5866c2edbf        13 days ago         361.3 MB
mariadb                                  latest              e3bf4cfec82a        13 days ago         347.2 MB
nginx                                    latest              af4b3d7d5401        2 weeks ago         190.5 MB
postgres                                 latest              a10b46bba0df        2 weeks ago         264.6 MB
mongo                                    latest              e79b013480eb        2 weeks ago         309.8 MB
redis                                    alpine              50405530a7e5        3 weeks ago         15.95 MB
alpine                                   latest              70c557e50ed6        3 weeks ago         4.798 MB
ghost                                    latest              81dd302221ec        3 weeks ago         353.9 MB
debian                                   jessie              f50f9524513f        3 weeks ago         125.1 MB
<none>                                   <none>              5d9e4c176cfd        7 weeks ago         1.222 GB
dockerui/dockerui                        latest              903724e089de        7 weeks ago         6.13 MB
extra/mumble                             latest              f1a145dc7c5b        8 weeks ago         40.8 MB
debian                                   latest              9a02f494bef8        8 weeks ago         125.1 MB
ubuntu                                   latest              3876b81b5a81        9 weeks ago         187.9 MB
centos                                   centos7             61b442687d68        3 months ago        196.6 MB
phusion/baseimage                        latest              772dd063a060        3 months ago        305.1 MB

Pour supprimer une image, vous devez faire docker rmi <id de l'image>.

Et comme vous pouvez le remarquer, quelques fois, des images <none> sont enregistrées. Explication ici. Pour les supprimer, vous pouvez faire docker rmi $(docker images -f "dangling=true" -q).

Après avoir supprimé les images voulues, vous pouvez refaire un df -m et constater la place libérée !

3.6 Sauvegarder un conteneur

Lorsqu'un conteneur est créé et lancé, il peut arriver d'y faire des modifications. Mais lorsque le conteneur est effacé, ces modifications disparaissent !
Pour les conserver, ou créer des sauvegardes à un instant T de l'état d'un conteneur, docker fournit une possibilité de faire des commit. Cette commande créée une nouvelle image à partir du conteneur actuel.

docker commit -a "Auteur" -m "message optionnel" <ID du conteneur ou nom> <Tag à donner à l'image>

3.7 Exporter une image vers un dossier

On peut exporter cette nouvelle image pour la déplacer vers un autre serveur (par exemple), ou faire des tests dans un environnement de développement.

docker save -o <chemin> <id ou nom de l'image>

Cette commande va créer un fichier .tar.

3.8 Importer une image

Une fois l'image copiée vers la nouvelle machine, il faut la charger dans docker pour l'utiliser.

docker load -i <image exportée>.tar

3.9 Afficher les ressources utilisées par conteneur

Il est pratique de connaitre la consommation de chaque service conteneurisé, voire de la limiter.

Cette commande permet d'afficher en temps réel la consommation de tous les conteneurs qui tournent.

docker stats $(docker ps | awk '{if(NR>1) print $NF}')

B - Persistance des données

Le but d'un container docker est de contenir un seul service qui va s'exécuter. Puis lorsqu'il sera arrêté et effacé toutes ses données seront supprimées.

Mais alors ...

1. Comment sont gérées les données ?

Et bien c'est simple jamy : comme je l'ai dit plus haut, toutes les données sont supprimées une fois le container effacé. C'est le but d'un container, être éphémère.

2. Comment les sauvegarder ?

Il existe une ruse option permettant de fournir un dossier de l'hôte au container, qui sera utilisé dans celui-ci de manière transparente. Ainsi, à chaque démarrage, l'existence du dossier est vérifiée. Il sera créé automatiquement au premier lancement.

Pendant son exécution, si le container utilise le dossier renseigné lors de sa création, les données seront automatiquement sauvegardées sur l'hôte. C'est ce que j'ai fait juste au dessus, pour un fichier : -v /path/to/directory/my_proxy.conf:/etc/nginx/conf.d/my_proxy.conf:ro. Le fichier local my_proxy.conf est utilisé dans le container.

Plus d'informations sur le site de docker !

C - La gestion des réseaux

Une récente mise à jour de docker permet de gérer plus finement les réseaux. On peut en créer, avec accès externe ou seulement interne (à docker), et même gérer l'adressage IP pour créer de réelles architectures à la main (utilisation d'un plugin dédié, exemple).

1. Créer un réseau interne

Dans ce cas, le réseau créé n'aura pas accès à internet (ou le réseau de l'hôte) et sera cantonné à docker.

docker network create --internal <nom>.

2. Créer un réseau avec accès externe

Ici, le réseau créé aura accès à l'extérieur.

docker network create <nom>.


III - Conclusion

Docker est pour moi très pratique et finalement assez facile à prendre en main, une fois que l'on s'est un peu cassé les dents dessus ! ;)

J'ai subi quelques pertes de données suite à des suppressions de containers et une mauvaise configuration des volumes, mais on apprend de ses erreurs ! Et grâce à docker je peux mieux gérer les services qui tournent sur mon serveur, les ressources utilisées et surtout les fichiers utiles et inutiles. Ce qui me permet de garder un OS le plus propre possible.


Image par Torulus de Pixabay

Mots clés

Super ! Vous vous êtes inscrit avec succès.
Super ! Effectuez le paiement pour obtenir l'accès complet.
Bon retour parmi nous ! Vous vous êtes connecté avec succès.
Parfait ! Votre compte est entièrement activé, vous avez désormais accès à tout le contenu.