Docker : comment ça marche ?
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 :
- Un GitLab
- Un forum, NodeBB
- Un cloud complet, Cozy
- Un blog, Ghost
- Un serveur de chat, Rocket.chat
- Un serveur Mumble
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 :
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
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;'"
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.