Comme je l’évoquais dans un précédent billet, il y a tout lieu de penser que les architectures de microservices et polyglottes constituent un horizon proche des Systèmes d’Information d’Entreprise, quand ce n’est pas déjà une réalité pour de grands acteurs du web (Netflix, Airbnb).
Dans ce contexte, Docker apparait comme une véritable bénédiction tant ses qualités sont en symbiose parfaite avec ce nouveau paradigme. Il répond remarquablement aux besoins :
- d’isolation de l’environnement d’exécution d’un service
- de déploiement rapide et sans couture
- de portabilité et de scalabilité
- de compatibilité avec des solutions d’hébergement dans un Cloud privé, public ou hybride
Cependant, la communication entre conteneurs Docker répartis sur plusieurs machines hôtes n’est vraiment pas triviale à mettre en oeuvre.
Dans le cas d’une architecture constituée d’un très grand nombre de microservices dockerisés, la difficulté peut conduire au naufrage…
Il existe deux approches pour traiter ce problème complexe :
- le paradigme SDN (Software-Defined Networking), notamment mis en oeuvre par Weave, Open vSwitch ou Socketplane, permet de construire un réseau virtuel de conteneurs répartis sur plusieurs hôtes Docker. A ce propos, l’acquisition récente de Socketplane par Docker Inc semble annoncer l’apparition prochaine d’une sorte “d’API réseau” standardisée dans l’écosystème Docker.
- la découverte de service est une technique permettant de trouver une ressource (application, service) sur un réseau. Cette technique n’est pas spécifique au monde Docker bien sûr (songez au DNS) mais elle a fait ses preuves dans cet écosystème grâce à l’adoption grandissante de Consul et SkyDNS2 / etcd, des solutions de découverte de service, distribuées et hautement disponibles. On ne résout pas l’intégralité des problèmes de communication inter-conteneurs, mais disons que ce modèle est suffisant dans la plupart des cas d’utilisation.
C’est cette dernière approche que je vous propose d’étudier car elle a le mérite d’être peu intrusive et plus simple à mettre en oeuvre qu’un SDN.
En pratique, nous verrons comment :
- installer et configurer un cluster Consul (sur un environnement provisionnable localement avec Vagrant)
- enregistrer des services dockerisés
- communiquer avec un service depuis un conteneur Docker, sans connaitre sa localisation physique (adresse IP ou FQDN)
Consul : présentation
Consul est une solution de découverte de service, distribuée et hautement disponible, développée par la société Hashicorp (dont le fondateur est le créateur de Vagrant).
Par ailleurs, Consul est également :
- un datastore distribué de type clé/valeur pour le stockage d’éléments de configuration
- un système de supervision de services (bilan de santé, détection de pannes)
Consul est disponible sous Linux, Windows et Mac OS mais seule l’utilisation sous Linux est recommandée en production.
Le schéma ci-dessous décrit l’architecture d’un cluster constitué de 3 noeuds hébergeant chacun un agent Consul et un hôte Docker. J’ai également représentés sur ce schéma les services dockerisés déployés (svc1
, svc2
et svc3
), dont certains sont redondants (svc1
et svc2
) :
Note : Je mets à disposition deux configurations Vagrant pour provisionner localement ce cluster et expérimenter vous même les exemples illustrant cet article. Pour les impatients, consultez directement le paragraphe suivant.
Un agent Consul est un composant essentiel chargé d’enregistrer les services, répondre aux requêtes, collecter des informations du cluster etc.
S’exécutant sur chaque noeud du cluster, il peut fonctionner en mode client ou serveur. Le cluster doit disposer d’au moins un agent serveur. Au delà, un mécanisme d’élection du leader du cluster permet d’attribuer ce rôle à un des agents serveur. En production, il est recommandé d’avoir de 3 à 5 agents serveur par cluster pour éviter toute perte de données en cas de panne d’un des serveurs.
Chaque agent supporte 3 protocoles de communication :
- RPC (port par défaut
8400
) - HTTP (port par défaut
8500
) : une API RESTful est exposée - DNS (port par défaut `8600)
Les ports 8300
, 8301
et 8302
sont des ports internes réservés à la communication entre agents.
Configuration Vagrant
Pour accompagner la lecture de cet article, deux configurations du cluster sont provisionnables localement via Vagrant :
- Configuration partielle : services Dockers
svc1
,svc2
etsvc3
provisionnés mais pas de cluster Consul :
Repartez de cette configuration si vous souhaiter reproduire les exemples de l’article.
- Configuration complète : services Dockers
svc1
,svc2
etsvc3
provisionnés, cluster Consul configuré et provisionné :
Note : Le temps de construction et de démarrage du cluster en partant de zéro est long. Compter plusieurs minutes.
Mise en oeuvre du cluster
Commençons par démarrer l’agent du premier noeud en mode serveur sur node1
dont l’IP privée est 172.20.20.10
:
L’agent revendique le rôle de leader du cluster :
On notera que :
- le flag
-node
permet d’attribuer un nom à l’agent (node1
dans le cas présent) - le flag
-server
permet de configurer l’agent en mode serveur - le flag
-bootstrap-expect 1
est suivi du nombre d’agent serveur devant être actifs avant que le cluster ne soit opérationnel. Dans notre cas, on aurait pu utiliser le flag-bootstrap
car il n’y a qu’un agent serveur. - le flag
-client
définit l’adresse client de l’agent exposant les interfaces DNS, HTTP et RPC. - le flag
-advertise
définit l’adresse cluster de l’agent, c’est-à-dire l’adresse IP atteignable par les autres agents du cluster.
Note : On peut également externaliser les paramètres de l’agent dans un fichier de configuration au format JSON et activer l’agent via la commande
consul agent -config-file conf/node1.json
:
Sur le deuxième noeud (node2
dont l’IP privée est 172.20.20.20
), démarrons l’agent consul en mode client :
A ce stade, le deuxième agent n’a toujours pas rejoint le cluster. Pour ce faire, utiliser la commande consul join
suivie de l’adresse IP du leader (node1
) :
Procédons de même sur le troisième noeud (node3.local
dont l’IP privée est 10.10.0.3
) mais en une seule commande grâce au flag -join
:
Pour connaître l’état du cluster Consul, utiliser la commande consul members
sur n’importe quel noeud :
Note : si un des agents client venait à tomber, son statut passerait de
alive
àleft
.
Voilà. Le cluster est maintenant opérationnel.
Enregistrement, désenregistrement et découverte de services par DNS
Chaque agent expose une API HTTP permettant d’enregistrer, désenregistrer ou consulter les services qu’il administre.
On peut consulter la définition d’un service (nom, adresse IP, port) via l’API HTTP ou l’API DNS (rappelons que chaque agent fait aussi office de resolver DNS).
Enregistrement
Enregistrons les 2 instances du service svc1
des noeuds node1
et node2
(port 8081
) :
La déclaration manuelle des services peut sembler fastidieuse à bien des égards.
Dans le prochain article de la série, nous mettrons en place une solution “plug and play” permettant d’enregistrer automatiquement un service dockerisé au démarrage du conteneur et de désenregistrement de celui-ci lorsque qu’il s’arrête.
Découverte par DNS
Un service est déclaré dans le DNS Consul sous la forme d’un enregistrement SRV (ou enregistrement de service) suffixé par .service.consul
.
Ainsi, les noms pleinement qualifiés des 3 services dockerisés seront :
srv1.service.consul
srv2.service.consul
srv3.service.consul
Interrogeons le DNS Consul, via l’agent sur node1
/172.20.20.10
, pour localiser le service svc1.service.consul
:
On observe que le service est bien déclaré sur 172.20.20.10:8081
et 172.20.20.20:8081
:
On remarquera que l’ordre d’apparition des adresses IP lors de la résolution du nom de service est aléatoire :
En requêtant une fraction de seconde plus tard, l’ordre a changé :
Il est ainsi possible de répartir aléatoirement la charge sur l’une ou l’autre instance du service, ouvrant la voie à une API RESTful scalable horizontalement.
Désenregistrement
Le point de terminaison /v1/agent/service/deregister/
exposé par l’API HTTP de l’agent permet de désenregistrer un service dont il a la charge :
Communiquer avec un service depuis un conteneur Docker
Voyons comment appeler le service dockerisé svc1.service.consul
déployé sur node1
et node2
depuis un conteneur Docker situé sur node3
.
Nous utiliserons l’image Docker tutum/curl
de l’hébergeur Tutum pour la démonstration :
- Le flag
--dns
est suivi de l’adresse IP de l’agent Consul du noeud 1 utilisé comme resolver DNS.
Cela ne fonctionne que si l’agent est configuré pour écouter les requêtes DNS sur le port 53 (le port DNS Consul par défaut étant 8600).
- Le flag
--dns-search
permet de forcer les recherches DNS sur le domaine passé en paramètre.