Créer une image Docker de MongoDB pour Raspberry

Comme déjà cité dans un précédent article, je me suis abondamment appuyé sur l'ouvrage de Jean-Philippe GOUIGOUX pour découvrir Docker. Malheureusement, les exemples cités ne fonctionne pas lorsque Docker est installé sur un Raspberry Pi. Le présent article reproduit l'exercice de création d'une image MongoDB adapté à l'architecture ARMv7 du Raspberry en utilisant comme image de base, l'image de Rasbian Stretch créée dans l'article intitulé "Créer une image Docker de Raspbian Stretch (v9)", celle-ci étant dotée des utilitaires nécessaires à l'exercice.
Il va y avoir trois étages de travail dans cet exercices :

  • Les opérations à exécuter dans la machine hôte repérées par le prompt pi@Cluster01.
  • Les opérations à exécuter dans le container repérées par le prompt root@ContMongoDB colorées en bleu.
  • Les opération à exécuter dans le client MongoDB, lui-même lancé dans le container, colorées en orange.
De plus, pour améliorer la lisibilités des ces étages, chacun d'eux sera légèrement indenté par rapport à son processus parent.

Démarrer une instance de l'image rpi-raspbian:stretch

Contrairement à ce qui a été fait dans l'article précédent, nous allons démarrer l'instance de Raspbian, non pas avec l'option --rm qui permettait de nettoyer Docker lorsqu'on la quittait, mais en lui donnant un nom avec l'option --name mongodb. Ce qui permettra de la repérer facilement et de la redémarrer, en conservant toutes les modifications qui y seront faites, par la commande docker start -i mongodb.
pi@Cluster01:~ $ docker run -it --name mongodb -h ContMongoDB aplimouzin/stretch
root@ContMongoDB:/# exit
exit
pi@Cluster01:~ $ docker ps -a
CONTAINER ID        IMAGE                        COMMAND                  CREATED             STATUS                      PORTS               NAMES
84838544da3d        resin/rpi-raspbian:stretch   "/usr/bin/entry.sh /…"   7 seconds ago       Exited (0) 14 seconds ago                      mongodb
pi@Cluster01:~ $  docker start -i mongodb
root@ContMongoDB:/#

Installation de MongoDB

L'installation de MongoDB sur une architecture ARMv7 se fait comme d'habitude à l'aide de la commande apt-get install. Comme l'image de Rasbian servant de base peut être ancienne, on la fera précéder prudemment par les commandes apt-get update et apt-get upgrade pour ne pas avoir un décalage de dépendance dans les paquets. A remarquer, que la connexion à l'instance se fait en mode root. Il est donc inutile d'utiliser la commande sudo avec apt-get.
root@ContMongoDB:/# apt-get update

. . .

Reading package lists... Done
root@ContMongoDB:/# apt-get upgrade
Reading package lists... Done
Building dependency tree
Reading state information... Done
Calculating upgrade... Done

 . . .

root@ContMongoDB:/# apt-get install mongodb
Reading package lists... Done
Building dependency tree
Reading state information... Done

. . .

Setting up libmongoclient0:armhf (1.1.2-5) ...
Setting up libmongoclient-dev:armhf (1.1.2-5) ...
Setting up mongodb-dev (2:1.1.2-5) ...
Setting up mongodb (1:2.4.14-4) ...
Processing triggers for libc-bin (2.24-11+deb9u3) ...
Processing triggers for systemd (232-25+deb9u4) ...
root@ContMongoDB:/#

Tester l'installation de MongoDB

Si l'installation est correcte, le service peut être lancé. Il sera aisé d'en vérifier le fonctionnement grâce à la commande ps de Rasbian qui affiche tous les processus en cours (à ne pas confondre avec la commande docker ps de Docker qui affiche toutes les instances de containers chargées).
root@ContMongoDB:/# mongod --config /etc/mongodb.conf &
[1] 436
root@ContMongoDB:/# all output going to: /var/log/mongodb/mongodb.log

root@ContMongoDB:/# ps
  PID TTY          TIME CMD
    1 pts/0    00:00:00 entry.sh
   16 pts/0    00:00:00 tini
   17 pts/0    00:00:00 bash
  436 pts/0    00:00:00 mongod
  447 pts/0    00:00:00 ps
root@ContMongoDB:/#
Le & en fin de commande permet de faire fonctionner MongoDB en tache de fond et de reprendre la main. Ce qui permet de faire des requêtes en utilisant un client MongoDB (commande mongo). On peut alors commencer à peupler la base en y insérant des items dans la collection Personnes (qui est créée automatiquement à l'insertion du premier item).
root@ContMongoDB:/# mongo
MongoDB shell version: 2.4.14
connecting to: test
Welcome to the MongoDB shell.
For interactive help, type "help".
For more comprehensive documentation, see
        http://docs.mongodb.org/
Questions? Try the support group
        http://groups.google.com/group/mongodb-user
Server has startup warnings:
Thu Nov  1 17:58:45.078 [initandlisten]
Thu Nov  1 17:58:45.078 [initandlisten] ** NOTE: This is a 32 bit MongoDB binary.
Thu Nov  1 17:58:45.078 [initandlisten] **       32 bit builds are limited to less than 2GB of data (or less with --journal).
Thu Nov  1 17:58:45.078 [initandlisten] **       See http://dochub.mongodb.org/core/32bit
Thu Nov  1 17:58:45.078 [initandlisten]
> use mydb
switched to db mydb
> db.Personnes.insert({nom:"Lagaffe",prenom:"Gaston"})
> db.Personnes.insert({nom:"Franquin",prenom:"André"})
> db.Personnes.insert({nom:"Jean",prenom:"De Mesmeker"})
> db.Personnes.find()
{ "_id" : ObjectId("5bdb3f7aa5896ea8d340bf34"), "nom" : "Lagaffe", "prenom" : "Gaston" }
{ "_id" : ObjectId("5bdb43689578a99e00c0c9e6"), "nom" : "Franquin", "prenom" : "André" }
{ "_id" : ObjectId("5bdb438d9578a99e00c0c9e7"), "nom" : "Jean", "prenom" : "De Mesmeker" }
> exit
bye
root@ContMongoDB:/#

Donner une persistance à l'image MongoDB

Avant de pérenniser l'image MongoDB, il faut la purger des manipulations de tests effectuées dans le paragraphe précédent. Il suffit, pour cela, de supprimer la collection Personnes.
root@ContMongoDB:/# mongo
MongoDB shell version: 2.4.14
connecting to: test
Welcome to the MongoDB shell.
For interactive help, type "help".
For more comprehensive documentation, see
        http://docs.mongodb.org/
Questions? Try the support group
        http://groups.google.com/group/mongodb-user
Server has startup warnings:
Thu Nov  1 17:58:45.078 [initandlisten]
Thu Nov  1 17:58:45.078 [initandlisten] ** NOTE: This is a 32 bit MongoDB binary.
Thu Nov  1 17:58:45.078 [initandlisten] **       32 bit builds are limited to less than 2GB of data (or less with --journal).
Thu Nov  1 17:58:45.078 [initandlisten] **       See http://dochub.mongodb.org/core/32bit
Thu Nov  1 17:58:45.078 [initandlisten]
> use mydb
switched to db mydb
> db.Personnes.drop()
true
> exit
bye
root@ContMongoDB:/# exit
exit
pi@Cluster01:~ $
Pour donner une persistance à une image Docker, on utilise la commande commit en passant  en premier paramètre le nom de l'instance (mongodb) donné par l'option --name et en second paramètre le nom que l'on veut donner à l'image. Pour pouvoir exporter cette image sur hub.docker.com, on respectera la convention en préfixant le nom de l'image avec le nom du compte docker de l'auteur (aplimouzin/mongodb).
pi@Cluster01:~ $ docker commit mongodb aplimouzin/mongodb
sha256:a00ecc022f591a4587d1d569a8d38753ff463b62ccf1b8cf5a326548d97e8190
pi@Cluster01:~ $ docker images
REPOSITORY           TAG                 IMAGE ID            CREATED              SIZE
aplimouzin/mongodb   latest              a00ecc022f59        About a minute ago   1.17GB
resin/rpi-raspbian   stretch             21dc8fc1377f        8 days ago           139MB
resin/rpi-raspbian   latest              6e68cc6f3192        8 days ago           128MB
hello-world          latest              b7012647d53a        7 weeks ago          1.64kB
pi@Cluster01:~ $ 
En utilisant la commande images de Docker, on voit que l'image aplimouzin/mongodb est bien présente dans la liste. Cette image peut être utilisée à son tour comme n'importe quelle autre image. Attention à ne pas confondre les deux exit. Le premier quitte le client MongoDB pour revenir au Rasbian du container. Le second quitte le Rasbian du container pour revenir au Rasbian du Raspberry. A noter qu'un troisième exit terminerait la session ouverte sur le Raspberry Pi et fermerait le client ssh (putty) utilisé.

Utilisation de l'image MongoDB créée en mode interactif

L'utilisation de l'image MongoDB en mode interactif ne diffère pas de ce qui a été fait avec l'image Rasbian. Il suffit d'utiliser la commande run de Docker avec l'option -it.
pi@Cluster01:~ $ docker run -it -h ContMongoDB aplimouzin/mongodb
root@ContMongoDB:/# ps
  PID TTY          TIME CMD
    1 pts/0    00:00:00 entry.sh
   16 pts/0    00:00:00 tini
   17 pts/0    00:00:00 bash
   18 pts/0    00:00:00 ps
root@ContMongoDB:/# exit
exit
pi@Cluster01:~ $ 
En lançant la commande ps dans le container, on a la désagréable surprise de constater que le processus MongoDB n'est pas lancé alors qu'il fonctionnait dans l'instance avant qu'on ne la transforme en image. Pour qu'elle soit run au démarrage de container, il faut ajouter l'instruction utilisée dans le paragraphe précédent mongod --config /etc/mongodb.conf & dans le script /etc/bash.bashrc qui est exécuté au démarrage de Rasbian. Cette opération devra être effectué sur l'instance dans laquelle on a installée MongoDB. Si l'instance utilisée (mongodb dans notre exemple), il suffit de la redémarrer (docker start -i mongodb). Sinon il suffit de ré-instancier l'image MongoDB créée avec l'instruction commit de Docker (docker run -it --name mongodb -h ContMongoDB aplimouzin/mongodb).
pi@Cluster01:~ $ docker ps -a
CONTAINER ID        IMAGE                        COMMAND                  CREATED             STATUS                      PORTS               NAMES
552ddfb5f53e        aplimouzin/mongodb           "/usr/bin/entry.sh /…"   31 minutes ago      Exited (0) 30 minutes ago                       gallant_boyd
25670bb7332a        resin/rpi-raspbian:stretch   "/usr/bin/entry.sh /…"   16 hours ago        Exited (0) 14 hours ago                         mongodb
pi@Cluster01:~ $ docker start -i mongodb
root@ContMongoDB:/# echo "/usr/bin/mongod --config /etc/mongodb.conf &" >> /etc/bash.bashrc
root@ContMongoDB:/# tail /etc/bash.bashrc
                elif [ -x /usr/share/command-not-found/command-not-found ]; then
                   /usr/share/command-not-found/command-not-found -- "$1"
                   return $?
                else
                   printf "%s: command not found\n" "$1" >&2
                   return 127
                fi
        }
fi
/usr/bin/mongod --config /etc/mongodb.conf &
root@ContMongoDB:/# exit
exit
pi@Cluster01:~ $
Cette modification effectuée, il suffit de relancer la commande commit de Docker pour recréer une nouvelle image. Attention toutefois à bien utiliser l'instance qui a été modifiée. En effet dans notre exemple, il y a deux instances présentes dans Docker, gallant_boy qui correspond à l’instanciation de l'image MongoDB testée, et mongodb qui est l'instance utilisée pour installer MongoDB et sur laquelle, dans notre exemple, la modification de bash.bashrc a été effectuée. Attention aussi à supprimer l'image MongoDB précédente avec la commande rmi de Docker avant le commit pour éviter toute ambiguïté.
Cette abondance de précision fera certainement hurler de rire les experts de Docker. Mais je vous certifie que, lorsqu'on découvre Docker, ce n'est pas du luxe. La confusion entre image et instance est tellement facile, comme la confusion entre classe et variable en Java ou en C# lorsqu'on débute.
pi@Cluster01:~ $ docker images
REPOSITORY           TAG                 IMAGE ID            CREATED             SIZE
aplimouzin/mongodb   latest              a00ecc022f59        15 hours ago        1.17GB
resin/rpi-raspbian   stretch             21dc8fc1377f        9 days ago          139MB
resin/rpi-raspbian   latest              6e68cc6f3192        9 days ago          128MB
hello-world          latest              b7012647d53a        7 weeks ago         1.64kB
pi@Cluster01:~ $ docker rmi -f aplimouzin/mongodb
Untagged: aplimouzin/mongodb:latest
Deleted: sha256:a00ecc022f591a4587d1d569a8d38753ff463b62ccf1b8cf5a326548d97e8190
pi@Cluster01:~ $ docker commit raspbian_stretch aplimouzin/mongodb
sha256:61e8317400b9953e3e6f1ac03bb8c62583dacbc8b14c39f21db486d856c27df4
pi@Cluster01:~ $ docker run -it -h ContMongoDB aplimouzin/mongodb
all output going to: /var/log/mongodb/mongodb.log
root@ContMongoDB:/# ps
  PID TTY          TIME CMD
    1 pts/0    00:00:00 entry.sh
   16 pts/0    00:00:00 tini
   17 pts/0    00:00:00 bash
   18 pts/0    00:00:06 mongod
   29 pts/0    00:00:00 ps
root@ContMongoDB:/#
Cette fois, le processus mongod est bien run.

Utilisation de l'image MongoDB créée à partir de la machine hôte

L'utilisation d'un container MongoDB en mode interactif n'a pas vraiment d’intérêt. Dans le cadre de Docker, l'intérêt d'un container MongoDB est de pouvoir l'utiliser à partir d'une machine tierce, voir à partir d'un ou plusieurs autres containers équipés du client MongoDB. Dans l'exemple qui va suivre, c'est à partir de la machine hôte que l'on va faire des requêtes sur le container MongoDB. Celui-ci devra être lancé auparavant.

Modification de la configuration de l'image MongoDB

Dans un premier temps, il est nécessaire de modifier la configuration du container MongoDB pour qu'il puisse traiter des requêtes à la base de données de l'extérieur de celle-ci. Voici la liste des opérations à effectuer :
pi@Cluster01:~ $ docker start -i mongodb
root@ContMongoDB:/# nano /etc/mongodb.conf
root@ContMongoDB:/# exit
exit
pi@Cluster01:~ $ docker commit mongodb aplimouzin/mongodb
sha256:75f9867038a81393430669da446d3f60ca5df82ab82967cef6c86c5456ef3c14
pi@Cluster01:~ $
  • Redemarrer l'instance mongodb si elle a été quittée par un exit en utilisant la commande docker start -i mongodb.
  • Modifier le fichier /etc/mongodb.conf avec un éditeur de texte comme nano (installé dans l'image qui a servi de base).
  • Quitter le container modifié par la commande exit.
  • Mettre à jour l'image aplimouzin/mongodb par un nouveau commit.
La modification du fichier /etc/mongodb.conf consiste à supprimer le paramètre bind_ip en le commentant par un # :
# mongodb.conf

# Where to store the data.
dbpath=/var/lib/mongodb

#where to log
logpath=/var/log/mongodb/mongodb.log

logappend=true

#bind_ip = 127.0.0.1
#port = 27017

# Enable journaling, http://www.mongodb.org/display/DOCS/Journaling
journal=true

# Enables periodic logging of CPU utilization and I/O wait
#cpu = true

. . .


Installation d'un client MongoDB sur la machine hôte

Comme pour toute installation sur le Rasberry Pi, on va utiliser la commande apt-get install, que l'on fera précéder des commandes apt-get update et apt-get upgrade pour ne pas brouiller les dépendances entre les paquets installés. Le client MongoDB se trouve dans le paquet mongodb-clients.
pi@Cluster01:~ $ sudo apt-get update

. . .

pi@Cluster01:~ $ sudo upgrade

. . .

pi@Cluster01:~ $ sudo apt-get install mongodb-clients
Lecture des listes de paquets... Fait
Construction de l'arbre des dépendances
Lecture des informations d'état... Fait
The following additional packages will be installed:
  libboost-filesystem1.58.0 libboost-program-options1.58.0 libboost-system1.58.0 libboost-thread1.58.0 libpcap0.8 libpcrecpp0v5 libv8-3.14.5
Les NOUVEAUX paquets suivants seront installés :
  libboost-filesystem1.58.0 libboost-program-options1.58.0 libboost-system1.58.0 libboost-thread1.58.0 libpcap0.8 libpcrecpp0v5 libv8-3.14.5 mongodb-clients
0 mis à jour, 8 nouvellement installés, 0 à enlever et 0 non mis à jour.
Il est nécessaire de prendre 0 o/30,4 Mo dans les archives.
Après cette opération, 103 Mo d'espace disque supplémentaires seront utilisés.
Souhaitez-vous continuer ? [O/n] O

. . .

Paramétrage de libpcap0.8:armhf (1.8.1-3) ...
Paramétrage de mongodb-clients (1:2.4.14-4) ...
Traitement des actions différées (« triggers ») pour libc-bin (2.24-11+deb9u3) ...
pi@Cluster01:~ $ 

Démarrage d'un container MongoDB en arrière-plan

Pour pouvoir utiliser le container MongoDB à partir de la machine hôte (le Raspberry Pi), il faut que le container soit run lorsqu'on a la main sur la machine hôte. Or, dans les opérations précédentes, lorsqu'on quittait le container mongodb, celui-ci s'arrête comme le montre la commande ps de Docker.
pi@Cluster01:~ $ docker run -it -h ContMongoDB aplimouzin/mongodb
root@ContMongoDB:/# exit
exit
pi@Cluster01:~ $ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
pi@Cluster01:~ $ 
Il faut donc pouvoir lancer la container en arrière-plan et que celui-ci continue à tourner lorsqu'on reprend la main sur la machine hôte. Pour que ce soit possible, on va changer les options de la commande run de Docker. Au lieu d'utiliser l'option -i qui démarre le container en mode interactif, on va la remplacer par l'option -d qui permet de démarrer un container en mode détaché, c'est à dire en arrière-plan.
pi@Cluster01:~ $ docker run -dt --name mongodb -h ContMongoDB aplimouzin/mongodb
pi@Cluster01:~ $ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
124ced29e636 aplimouzin/mongodb "/usr/bin/entry.sh /…" 9 minutes ago Up 7 seconds mongodb
pi@Cluster01:~ $ 
Cette fois, à l'issue de la commande run de Docker, la main revient immédiatement sur la machine hôte. Et la commande ps de Docker mentionne un container mongodb run depuis quelques secondes.

Connexion au container MongoDB à partir de la machine hôte

Pour pouvoir se connecter au container avec le client MongDB, il faut connaitre l'adresse IP qui lui a été allouée. L'usage de la commande ifconfig n'est pas approprié. L'utiliser sur la machine hôte mentionnerait l'adresse IP de celle-ci, mais pas celle du container. Et il est impossible d'utiliser ifconfig dans le container puisque qu'on n'a plus la main dessus. Le seul moyen de trouver l'adresse IP du container MongoDB en activité et d'utiliser la commande inspect de Docker sur celui-ci pour récupérer son paramètre IPAdress. Le résultat de la commande inspect est assez long. On va extraire du résultat uniquement les lignes contenant IP avec la commande grep.
pi@Cluster01:~ $ docker inspect mongodb | grep IP
            "LinkLocalIPv6Address": "",
            "LinkLocalIPv6PrefixLen": 0,
            "SecondaryIPAddresses": null,
            "SecondaryIPv6Addresses": null,
            "GlobalIPv6Address": "",
            "GlobalIPv6PrefixLen": 0,
            "IPAddress": "172.17.0.2",
            "IPPrefixLen": 16,
            "IPv6Gateway": "",
                    "IPAMConfig": null,
                    "IPAddress": "172.17.0.2",
                    "IPPrefixLen": 16,
                    "IPv6Gateway": "",
                    "GlobalIPv6Address": "",
                    "GlobalIPv6PrefixLen": 0,
pi@Cluster01:~ $ 
Dans l'exemple ci-dessus, on voit que l'adresse IP allouée au container MongoDB en activité est 172.17.0.2. Pour se connecter, à la base MongoDB du container, il suffit d'invoquer le client MongoDB comme on l'avait fait à l'intérieur du container, mais en ajoutant une option --host. Il est possible alors de répéter les opérations pour la tester.
pi@Cluster01:~ $ mongo --host 172.17.0.2
MongoDB shell version: 2.4.14
connecting to: 172.17.0.2:27017/test
Welcome to the MongoDB shell.
For interactive help, type "help".
For more comprehensive documentation, see
        http://docs.mongodb.org/
Questions? Try the support group
        http://groups.google.com/group/mongodb-user
Server has startup warnings:
Sat Nov  3 15:34:39.360 [initandlisten]
Sat Nov  3 15:34:39.360 [initandlisten] ** NOTE: This is a 32 bit MongoDB binary.
Sat Nov  3 15:34:39.360 [initandlisten] **       32 bit builds are limited to less than 2GB of data (or less with --journal).
Sat Nov  3 15:34:39.360 [initandlisten] **       See http://dochub.mongodb.org/core/32bit
Sat Nov  3 15:34:39.360 [initandlisten]
> use mydb
switched to db mydb
> db.Personnes.insert({nom:"Lagaffe",prenom:"Gaston"})
> db.Personnes.find()
{ "_id" : ObjectId("5bddcb217eaf91ddf5e7959f"), "nom" : "Lagaffe", "prenom" : "Gaston" }
> exit
bye
pi@Cluster01:~ $ 
A noter que le client MongDB est lancé à partir de la machine hôte, d'où la couleur violette utilisée pour ne pas faire de confusion avec la couleur orange, lorsque le client était lancé dans le container lui-même. A noter également que la commande exit rend la main à la machine hôte cette fois et non au container.

Conclusion

L'image MongoDB crée dans ce chapitre est complètement opérationnelle. Cependant, s'il elle peut être utilisée dans un contexte local dans docker, elle n'est pas encore prête à être diffusée ou partagée sur le hub.docker.com. Quelques fonctionnalités manquent encore :
  1. Les données relatives à la base de données sont stockées sur le container. Lorsque l'on arrête le container et qu'on le supprime avec la commande rm de Docker, le données enregistrées dans la base de données sont perdues. Il faudrait pour cela pouvoir utiliser un répertoire externe au container pour stocker les données.
  2. On ne connait pas a priori l'adresse IP allouée au container. Pour la connaitre, il est nécessaire d'utiliser la commande inspect de docker. Ce qui ne permet d'automatiser l'utilisation de MongoDB puisque cette adresse IP peut changer. Il faudrait pouvoir faire un lien entre le port qui expose MongoDB (27017) et un port local à la machine hôte de Docker qui, elle, aura toujours la même adresse IP.
  3. L'utilisateur pour lequel fonctionne MongoDB est l'utilisateur root, ce qui n'est pas recommandé en terme de securité, puisque celui-ci à tous les droits sur l'ensemble des fichiers du container. Il faudrait que MongoDB ne fonctionne que pour un utilisateur ou un groupe d'utilisateur connu, mais restreint dont les droits seraient limités au lancement et à l'utilisation de l'application MongoDB.
Pour répondre à ces problèmes, l'utilisation d'une image compilée à partir d'un fichier Dockerfile est nécessaire. Cela fera prochaine l'objet d'un autre article.

Bibliographie

Docker
Prise en main et mise en pratique d'une architecture micro-services
Jean-Philippe GOUIGOUX
ENI
Ce livre décrit comment utiliser docker pour embarquer divers types de containers.

Commentaires

Posts les plus consultés de ce blog

Gérer la mise en veille

Configurer VSCode pour programmer et déboguer à distance sur Raspberry Pi

Créer un nouvel utilisateur Raspbian