LightCodeSysadmin Open Source

Docker pour un petit serveur

Après avoir parlé des conteneurs au sens large dans l’article précédent, nous allons maintenant nous pencher sur le cas de Docker, un gestionnaire de conteneurs avancé. Je présenterai d’abord les fonctionnalités de Docker indispensables pour la suite de l’article, ensuite je me pencherai sur l’utilisation de Docker sur un serveur personnel. Nous verrons que dans ce domaine, Docker permet de nous faciliter la vie lors de nos déploiements.

Quelques fonctionnalités de Docker

Si vous avez lu mon dernier article sur les conteneurs, vous devriez savoir que Docker est un gestionnaire de conteneurs plutôt avancé. Comme les autres, ils utilisent les principes d’isolation du noyau Linux pour créer des conteneurs isolés du reste du système. Mais, Docker permet d’aller plus loin grâce à trois de ses fonctionnalités : les liens (links), les volumes et les registres.

Les liens

Les liens permettent d’interconnecter les différents conteneurs entre eux. Par exemple, si vous voulez connecter un conteneur “site web” et un conteneur “base de données”, vous devrez créer un lien indiquant au conteneur “site web” l’adresse du conteneur “base de données”, le lien permettra également d’autoriser la communication entre les deux conteneurs. Par exemple, créons un conteneur exécutant MySQL ayant pour nom wikidb :

# docker run -d --name wikidb -e MYSQL_ROOT_PASSWORD=mot-de-passe-secret mysql

Notre application, MediaWiki, dont l’image se nomme synctree/mediawiki, a besoin d’être connecté avec une base de donnée de ce type. La documentation nous indique que nous devons utiliser un lien :

# docker run -d --name wiki --link wikidb:mysql synctree/mediawiki

Ici, le lien créé wikidb:mysql, fait pointé le conteneur wikidb vers un nom utilisable à l’intérieur de l’application, ici ce nom est mysql (ce nom nous est imposé par la documentation car il est utilisé par le wiki pour se connecter à la base de données).

Les volumes

Dans Docker, on a pour principe de dire que les conteneurs sont des entités éphémères. Lorsqu’un utilisateur crée un conteneur, il s’exécute puis il s’arrête. Il peut ensuite être supprimé ou recréé. Dans ces cas là, tout son contenu est amené à disparaître et cela arrive plus souvent qu’on le croit. C’est pour cette raison que les données contenues dans le conteneur ne doivent en aucun cas être des données essentielles. Lorsque l’on veut changer de serveur, on ne pourra pas déplacer le conteneur tel quel. Le même problème peut arriver lorsque le conteneur est mis à jour.

Il faut en réalité placer les données importantes à l’extérieur du conteneur supportant l’application. Un moyen de stocker les données utilisateurs importantes est de créer un point de montage entre le conteneur et l’hôte. Vos données seront stockées sur l’hôte, elles pourront être lues et modifiées par le conteneur, mais lors de la suppression de celui-ci, les données persisteront sur l’hôte. L’outil permettant cela au sein de Docker se nomme “les volumes”. Si vous configurez correctement les volumes et les conteneurs, vous devriez pouvoir exécuter et redémarrer votre conteneur sans perte de données et sans avoir à faire d’autres opérations.

Les registres

Docker est un système de conteneur basé sur l’instanciation d’images. Une image comporte un ensemble de fichiers ainsi que des métadonnées utilisées lors de l’instanciation. Les images fonctionnent grâce à un système de couches gérées par un système de fichiers spécial. Une image est constituée d’une suite de couches, chaque couche se base sur la précédente en la modifiant. Lors de la création d’un conteneur, celui-ci va utiliser l’image qu’on lui a donnée en lecture-seule sur laquelle le conteneur va ajouter une couche en lecture-écriture propre à ce conteneur. Les images ne sont donc jamais modifiées par les conteneurs et sont mutualisées entres tous les conteneurs. La construction d’une image est réalisée la plupart du temps grâce à un fichier Dockerfile, ce fichier est composé d’une suite d’instructions qui sera interprétée par la commande docker build.

J’ai utilisé des images dans mes commandes docker run. Lors de l’exécution de ces commandes, les images sont directement téléchargées sur le registre. Un registre n’est rien d’autre qu’un dépôt contenant des images. Le registre Docker officiel, que l’on peut consulter l’interface web de celui-ci via ce site, est celui utilisé par défaut. Il est possible d’installer un registre sur son propre serveur.

Docker sur un petit serveur ?

Si vous possédez un petit serveur personnel, qu’il soit physique ou virtuel, pour auto-héberger des services ou des sites web, Docker peut se révéler utile afin de faciliter l’administration de ce serveur. En effet, Docker ne nécessite pas d’avoir de gros moyens pour être utilisé et peut parfaitement trouver sa place pour faire de l’hébergement amateur.

D’abord, Docker permet un déploiement plus simple des applications que d’installer les applications directement sur son serveur. La gestion des dépendances peut parfois être laborieuse et compliquée lorsque plusieurs applications dépendent d’un même logiciel (ou bibliothèque) dans plusieurs versions différentes. Dans le cas d’un conteneur, les dépendances sont directement embarquées avec l’application ce qui élimine ce problème. Les conteneurs Linux peuvent fonctionner sur n’importe quelle machine faisant fonctionner Docker, à condition toutefois d’utiliser la même architecture de processeur (x86, ARM…). Les registres permettent d’utiliser des images créées par d’autres utilisateurs rendant très accessible l’installation d’application. La gestion de vos applications sous la forme de conteneur permet également de supprimer les applications facilement et d’isoler les différents composants applicatifs.

Lorsque l’on administre un serveur, on est confronté au problème de la sauvegarde. Plusieurs choses doivent être sauvegardées sur un serveur : les données “utilisateurs”, elles ont été produites à l’aide de vos applications, ça peut être des bases de données, des fichiers téléchargés… Ce que l’on oublie de sauvegarder la plupart du temps, ou que l’on ne sauvegarde pas volontairement car c’est un travail laborieux : la configuration des applications. Elle est indispensable quand on doit réinstaller le serveur. Docker permet de retrouver la maîtrise du stockage des données grâce aux volumes. Pour chacun de vos conteneurs, vous aurez juste à vous souvenir des données indispensables à sauvegarder et vous pouvez les mettre dans un répertoire particulier. Vous aurez ensuite simplement à sauvegarder ce répertoire sur un serveur distant. Les fichiers de configuration peuvent être soit directement dans le conteneur et ne jamais être modifié, dans ce cas on n’a rien à faire. Soit les fichiers de configuration sont modifiés au cours de l’exécution et dans ce cas là, vous aurez besoin de faire un volume spécial pour la configuration. Le revers de la médaille est qu’il faut que vos conteneurs supportent l’utilisation des conteneurs. Lorsque vous montez un volume la première fois, le répertoire sera vide, il faut que votre conteneur soit capable de gérer les données de base automatiquement dans ce cas là. Nous l’avons vu précédemment avec la base de données, la base de données est créée au démarrage du conteneur si celle-ci n’existe pas.

Concernant la sauvegarde des applications avec Docker, il suffit simplement de garder une liste des images utilisées ou directement le code qui a permis de réaliser les images. La réinstallation d’un serveur utilisant Docker est donc beaucoup plus simple.

Architecture

Installation de l’hôte

Nous allons parler de l’installation de l’hôte qui fera tourner Docker. Docker peut s’installer soit sur une machine physique soit sur une machine virtuelle. Toutes les distributions Linux peuvent accueillir Docker, mais il existe deux familles de distributions qu’il faut privilégier :

  • Les distributions spécialisées : légères, elles ont été créées pour optimiser au maximum la machine qui accueillera les conteneurs. Vous ne trouverez dessus que le minimum nécessaire pour faire fonctionner un hôte sous Docker. Elles n’ont même pas de gestionnaire de paquets installé, il faut utiliser des conteneurs pour tout faire. Il existe plusieurs distributions de ce type : CoreOS, la première à être apparue. Les créateurs de distributions plus traditionnelles en ont également créé : Project Atomic de RedHat ou encore Ubuntu Core de Cannonical.
  • On trouve ensuite les distributions classiques pour serveur, celles que l’on a l’habitude de voir : CentOS, Debian, Ubuntu… Dans le cas de l’installation d’un serveur Docker, il est préférable d’installer la distribution sans l’interface graphique et de limiter les paquets installés au strict minimum. Je vous conseille d’installer une distribution dites “serveur” et de supprimer les services inutilisés (les commandes htop et netstat -tlnpu sont vos amis).

Parmi ces deux catégories de distribution, vous allez devoir choisir entre la robustesse des distributions spécialisées dans Docker et la flexibilité des distributions classiques. Si vous utilisez une distribution classique, je vous conseil de limiter les applications non conteneurisées et d’utiliser au maximum Docker. Il peut également être intéressant d’utiliser Ansible pour automatiser l’installation de l’hôte. Cette automatisation vous permettra d’installer votre serveur complètement automatique.

Reverse proxy HTTP

Cette partie vous sera très utile si vous installez plusieurs conteneurs fournissant des services HTTP différents fonctionnant sur le port 80. Il n’est évidemment possible d’ouvrir qu’une seule fois le port 80 sur votre machine (sauf si vous disposez de plusieurs IP) et vous voulez quand même faire tourner tous vos conteneurs sur ce même port. C’est ici qu’intervient le reverse proxy. Son fonctionnement est simple : on intercale le proxy entre les clients et les serveurs web. Les clients ne s’adressent plus aux serveurs web mais au proxy qui va refaire une requête HTTP vers le bon serveur web. Pour savoir quel serveur web consulter, le proxy utilise le champ Host situé dans les requêtes HTTP qui contient le nom de domaine utilisé pour accéder au site web. Les serveurs web pourront écouter sur différents ports qui seront présents dans la configuration du proxy.

Vous allez voir qu’avec Docker il est possible d’automatiser le proxy grâce à un conteneur particulier qui est capable de modifier sa configuration en fonction des conteneurs démarrés. L’image en question se nomme jwilder/nginx-proxy, on l’instancie simplement avec cette commande :

# docker run -d --name proxy --publish 80:80 jwilder/nginx-proxy

Une fois que ce conteneur est lancé, nous n’avons plus besoin de le modifier, il est autonome. Vous pouvez voir qu’il faut faire pointer le port 80 de votre serveur Docker vers le port 80 du conteneur. Lorsque nous lançons nos conteneurs ayant des serveurs web :

# docker run -d --name wiki --link wikidb:mysql -e VIRTUAL_HOST=wiki.local synctree/mediawiki

Le conteneur de proxy utilise les variables d’environnement des serveurs démarrés pour générer sa configuration. Dans notre exemple, on utilise la variable d’environnement VIRTUAL_HOST. Elle permet de marquer ce conteneur comme étant un serveur web et contient le nom DNS que l’on utilisera pour accéder au service.

Administrer ses conteneurs

Docker Compose

Docker Compose est un outil permettant de gérer ses conteneurs grâce à un fichier de configuration. La commande Docker est pratique pour des usages basiques mais elle peut devenir très longue et devient compliquée à retenir. C’est également plus pratique dans le cas où vous voulez démarrer plusieurs conteneurs en même temps en gérant les liens dépendances.

D’abord, créons un fichier définissant des services dans un répertoire créé pour l’occasion nommé “wiki”. Dans ce répertoire, on crée le fichier docker-compose.yml. C’est le nom par défaut utilisé par Docker Compose, en utilisant ce nom, on n’aura pas besoin de le préciser dans les commandes. Ce fichier est écrit en YAML, un langage de représentation de données. En se référant à la documentation en ligne on peut écrire ce premier fichier :

wikidb:
    image: mysql
    environment:
    MYSQL_ROOT_PASSWORD: mon-mot-de-passe-root
    MYSQL_DATABASE: wiki
    MYSQL_USER: wiki
    MYSQL_PASSWORD: mot-de-passe-bdd
    volumes:
    - /data/wikidb/db:/var/lib/mysql
wiki:
    image: synctree/mediawiki
    links:
    - wikidb:mysql
    ports:
    - "80:80"
    environment:
    MEDIAWIKI_DB_NAME: wiki
    MEDIAWIKI_DB_USER: wiki
    MEDIAWIKI_DB_PASSWORD: mot-de-passe-bdd
    volumes:
    - /data/wiki/html:/var/www/html

Je vous conseille de vous familiariser avec YAML car ce format devient incontournable car il est utilisé dans beaucoup de nouveaux outils. Dans Docker Compose, on appelle les conteneurs “services”, mon exemple en comporte deux regroupés sous la forme de paragraphes : le premier pour la base de données et le second pour le serveur web comportant l’application MediaWiki.

Regardons en détails le deuxième paragraphe. Comme vous pouvez le constater, il commence par wiki:, ceci est une clé permettant de nommer le service. Ensuite, le bloc indenté suivant contient un dictionnaire (aussi appelé “objet”) contenant un ensemble d’attributs :

  • image: synctree/mediawiki : on définit l’image qui sera utilisée pour créer le conteneur ;
  • links : on établi la liste des liens, chaque entrée de la liste est présentée comme on le ferait dans la commande Docker ;
  • ports : liste des ports du conteneur, ils s’écrivent dans le même format que la commande Docker ;
  • environment : ici, on peut préciser une liste de variables d’environnement qui seront injectées dans le conteneur lors de son exécution. Elles permettent de définir une liste de paramètre visant à personnaliser son exécution ;
  • volumes : on donne une liste de volumes dans le même format qu’avec la commande Docker ;

Pour lancer les deux conteneurs simultanément en mode démon :

# docker-compose up -d

Après avoir tapé cette commande, il est possible que votre installation ne fonctionne pas. En effet, Docker Compose va exécuter les deux conteneurs en créant d’abord le conteneur MySQL puis le conteneur de wiki. Cet ordre est défini par les liens (links), il faut que le conteneur auquel on se lie soit déjà démarré pour créer un lien. Si le conteneur de wiki ne démarre pas, c’est simplement parce qu’il ne peut pas se connecter à la base de données car celle-ci n’a pas encore eu le temps de démarrer. La première fois que le conteneur MySQL va démarrer, il va créer la base de données, et pendant ce temps là, il n’est pas possible de se connecter à la base de données. Cette opération prenant du temps, lorsque le conteneur de wiki va démarrer, la base de données de sera pas opérationnelle. Et dans ce cas, le conteneur de wiki va tester la connexion à la base de données et va s’arrêter si la connexion est échouée.

La seconde fois que l’on lancera les deux conteneurs, le conteneur MySQL n’aura plus besoin de créer sa base de données puisqu’elle est déjà accessible via le volume. La base de données démarre suffisamment vite pour que le conteneur de wiki puisse effectuer son test et là votre installation va fonctionner. Pour palier à ce problème, je vous conseille de démarrer les conteneurs de base de données avant et de laisser le temps aux scripts de créer les fichiers nécessaires et ensuite de démarrer les applications. On peut vérifier le fonctionnement de notre installation avec la commande docker-compose ps :

# docker-compose ps
    Name                   Command               State         Ports
---------------------------------------------------------------------------
root_wiki_1     /entrypoint.sh apache2-for...    Up      0.0.0.0:80->80/tcp
root_wikidb_1   /entrypoint.sh mysqld            Up      3306/tcp

En quelques minutes, nous venons de mettre en place un MediaWiki utilisant une base de données MySQL.

Maestro-ng

Maestro-ng est un programme permettant également de définir des conteneurs en YAML comme Docker Compose. Maestro adopte un style différent de management des conteneurs. En effet, le programme peut être utilisé pour créer les conteneurs sur des hôtes différents et à distance. Vous pouvez définir une série de services et de conteneurs et choisir sur quel hôte vous voulez les lancer. Ensuite, vous pouvez afficher leur statut, les stopper… L’outil reste toutefois assez simple à mettre en place puisqu’il suffit d’écrire un fichier YAML permettant de décrire les hôtes qui accueilleront les conteneurs, ainsi que les conteneurs eux-mêmes et les services. La communication entre Maestro et les hôtes peut se faire soit via un tunnel SSH, soit en se connectant directement à l’API Docker tournant sur votre hôte. Il est donc aisé de faire une architecture sécurisée.

Maestro s’installe avec le gestionnaire de dépendance Python, pip, en téléchargeant le logiciel sur GitHub :

# pip install --upgrade git+git://github.com/signalfuse/maestro-ng

Reprenons l’exemple du wiki avec Maestro cette fois-ci. Le fichier YAML se nomme cette fois-ci maestro.yaml, il se décompose en plusieurs parties.

La première partie se trouve sous la clé ships et référence les machines qui accueillerons les conteneurs :

ships:
    local:
    ip: localhost
    socket_path: /var/run/docker.sock

On ajoute l’hôte nommé “local”, on utilise la clé socket_path pour dire à Maestro comment se connecter à Docker. La deuxième partie de la configuration permet de définir des services et des instances :

services:
    wikidb:
    image: mysql
    instances:
        c-wikidb:
        ship: local
        volumes:
            /data/mysql-wiki/db: /var/lib/mysql
        env:
            MYSQL_ROOT_PASSWORD: mon-mot-de-passe-root
            MYSQL_DATABASE: wiki
            MYSQL_USER: wiki
            MYSQL_PASSWORD: mot-de-passe-bdd
    wiki:
    image: synctree/mediawiki
    requires:
        - wikidb
    instances:
        c-wiki:
        ship: local
        links:
            c-wikidb: mysql
        ports:
            http: 80
        env:
            MEDIAWIKI_DB_NAME: wiki
            MEDIAWIKI_DB_USER: wiki
            MEDIAWIKI_DB_PASSWORD: mot-de-passe-bdd
        volumes:
            /data/mediawiki/html: /var/www/html

Je ne vais pas détailler complètement la configuration, elle est assez similaire à celle vue précédemment avec Docker Compose. La principale différence avec Maestro, on créé d’abord des services et ensuite on définit les conteneurs qui seront instanciés. On retrouve aussi une clé requires qui permet de créer des dépendances. Le conteneur wiki a besoin de wikidb.

Puis vous pouvez démarrer le service wiki comme ceci :

# maestro start -d wiki

L’option -d permet de démarrer les conteneurs en respectant les dépendances données avec le mot clé requires.

Note : le même problème qu’avec Docker Compose va se produire, il est préférable d’attendre que la base de données ait terminé de se créer.

Conclusion

Cet article m’aura permis de vous présenter comment utiliser Docker sur un petit serveur personnel. Vous aurez notamment appris ce que permet de faire Docker en général, pour voir ensuite quelques éléments d’architecture, pour finir sur des outils de gestion de conteneurs basés sur Docker pour terminer.

L’univers de Docker est riche et évolue sans cesse, beaucoup de nouvelles technologies apparaissent, permettant ainsi d’augmenter le potentiel de Docker. Les outils utilisés varient en fonction de la taille de votre installation et en fonction des différentes contraintes que vous avez. D’autres articles arriveront prochainement pour parler d’autres technologies émergentes liées à Docker.