Applications et infrastructures cloud dynamiques avec Consul Service Discovery

Magazine
Marque
SysOps Pratique
Numéro
149
Mois de parution
mai 2025
Spécialité(s)


Résumé

Consul est un outil open source de Hashicorp permettant de faciliter la découverte de services réseaux et leur configuration dynamique dans des environnements distribués. Il permet de connecter les applications et services entre eux, de gérer la configuration à l’échelle et d’assurer la haute disponibilité des services grâce à son mécanisme de clustering et de supervision.


Body

1. Fonctionnement

Consul repose sur une architecture distribuée avec deux types de nœuds principaux : les serveurs et les agents clients.

Les serveurs Consul sont responsables de stocker l'état du cluster, d'exécuter l'élection du leader et de répondre aux requêtes des clients. Pour assurer un fonctionnement en mode haute disponibilité, le service Consul est mis en cluster. Pour des questions de quorum, cela exige un minimum de trois serveurs. Le protocole RAFT est utilisé par Consul pour assurer la cohérence des données entre les serveurs du cluster. Raft fonctionne en élisant un leader parmi les serveurs du cluster. Ce leader est responsable de la gestion des écritures et de la réplication des données aux autres serveurs appelés followers. Si le leader devient indisponible, un nouveau leader est automatiquement élu via un processus de vote parmi les nœuds restants.

Un des mécanismes clés de Consul est le protocole Gossip, utilisé pour la communication entre les nœuds. Il repose sur un algorithme de dissémination d’informations pair à pair, permettant aux nœuds d'échanger des informations sur leur état et de détecter les pannes rapidement. Ce protocole assure une mise à jour efficace des données dans tout le cluster et permet d’améliorer la tolérance aux pannes en évitant les points uniques de défaillance.

Les agents clients, quant à eux, s'exécutent sur chaque machine et servent d'interface entre les services et les serveurs Consul, facilitant ainsi l'enregistrement et la découverte des services.

Pour la suite de la démonstration, je vais déployer trois serveurs Consul sous Ubuntu 24.04 et deux agents.

Nom

IP

Rôle

consul01

192.168.69.11

Serveur Consul

consul02

192.168.69.12

Serveur Consul

consul03

192.168.69.13

Serveur Consul

www01

192.168.69.14

Serveur Apache

www02

192.168.69.15

Serveur Apache

lb01

192.168.69.31

Load Balancer HAProxy

lb02

192.168.69.32

Load Balancer HAProxy

2. Installation du cluster

Hashicorp propose des paquets précompilés pour différentes distributions ainsi que des binaires. Il n’y a pas spécifiquement de dépendances. Pour une installation sous Ubuntu 24.04, c’est très simple :

wget -O - https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install consul

Et passons maintenant à la configuration de notre serveur dans le fichier /etc/consul.d/consul.hcl. Cela implique de définir dans le retry_join, les IP des serveurs consul et dans advertise_addr, l’IP locale du serveur.

datacenter = "dc1"
data_dir = "/opt/consul"
client_addr = "0.0.0.0"
ui_config{
  enabled = true
}
server = true
bind_addr = "0.0.0.0"
advertise_addr = "192.168.69.11"
bootstrap_expect = 3
retry_join = ["192.168.69.11","192.168.69.12","192.168.69.13"]

Si tout s’est bien passé, nous pouvons vérifier que notre cluster est bien formé et identifier le leader avec la commande consul operator raft list-peers.

consul operator raft list-peers
Node     ID                                   Address            State     Voter RaftProtocol Commit Index Trails Leader By
consul01 6afa79d7-205d-ab39-bc39-9aa18008bb21 192.168.69.11:8300 leader    true  3            33160        -
consul02 77366d78-f9f8-a58f-c2d8-416c7b7e55a8 192.168.69.12:8300 follower  true  3            33160        0 commits
consul03 eba7ef57-1cb6-b024-cbec-2cfecda89efe 192.168.69.13:8300 follower  true  3            33160        0 commits

Le fait d’avoir activé l’interface graphique permet de visualiser l’état du cluster et les services en se connectant sur le port 8500 du service (voir Figure 1).

image-20250422100740-1

Figure 1

La jonction du cluster a été jusqu’ici permise sans sécurité. Il convient donc de chiffrer le protocole Gossip. Consul permet d’utiliser une clé de chiffrement partagée pour garantir que seuls les nœuds autorisés peuvent rejoindre le cluster. Il convient donc de générer une clé :

consul keygen
O+G5qhy5xDlZXgpRSIsODUPwwq/BiNbWLLgWvBxY320=

Puis de l’ajouter au fichier de configuration de consul à la ligne :

encrypt = "O+G5qhy5xDlZXgpRSIsODUPwwq/BiNbWLLgWvBxY320="

3. Les services Consul

Les services Consul constituent des entités exposées sur le réseau dans le but d’être consommées par des applications. L’exposition de services réseaux permet de les rendre découvrables de manière dynamique pour d’autres services. La vérification de l’état de santé des services permet de gérer la disponibilité de celui-ci et éventuellement de basculer sur un autre nœud qui fournit ce même service, mais celui-ci fonctionnel.

Nous allons ajouter un premier service sur nos deux agents www01 et www02. Pour l’installation des agents, c’est la même chose avec la variable server positionnée à false. Par conséquent, il n’est pas utile de détailler le processus d’installation.

Vérifions déjà que nos nœuds ont rejoint le cluster consul avec la commande consul members :

Node     Address            Status  Type   Build  Protocol  DC  Partition  Segment
consul01 192.168.69.11:8301 alive   server 1.20.4 2         dc1 default    <all>
consul02 192.168.69.12:8301 alive   server 1.20.4 2         dc1 default    <all>
consul03 192.168.69.13:8301 alive   server 1.20.4 2         dc1 default    <all>
www01    192.168.69.14:8301 alive   client 1.20.4 2         dc1 default    <default>
www02    192.168.69.15:8301 alive   client 1.20.4 2         dc1 default    <default>

Sur chacun de ces nœuds, j’ai déployé un service apache2 sans configuration particulière.

Sur les deux nœuds www, nous allons donc créer un fichier /etc/consul.d/service-apache.hcl contenant :

service {
  name = "service-apache"
  id = "service-apache-80"
  port = 80
 
  check {
    id       = "check-service-apache"
    http     = "http://localhost:80"
    method   = "GET"
    interval = "1s"
    timeout = "1s"
  }
}

L’ID et le nom du service sont forcément uniques parmi les machines qui exposent un même service. Il en va de la logique de disponibilité du service exposé par Consul. Il peut y avoir toutefois plusieurs health checks associés à un même service, donc les délais de vérification sont à adapter à la criticité, la rapidité de bascule attendue ainsi qu’au temps d’exécution de ce health check. Pour que le health check soit pris en compte, il est nécessaire de redémarrer Consul.

En fonction des types de services à exposer, Consul propose plusieurs types de checks pour vérifier l’état du service : TCP, UDP, HTTP pour les plus basiques. De façon plus élaborée, à la manière d’une solution de supervision, il est également possible de faire appel à un script externe.

Pour exposer par exemple le service SSH sur www01, un check TCP fera l’affaire. J’ai pris l’habitude d’exposer le SSH sur mes machines, ce qui me donne un état déjà très basique de mes nodes. Cela donnerait donc :

service {
  id   = "ssh-www01"
  name = "ssh"
  port = 22
 
  checks = {
    name     = "SSH Health Check"
    tcp      = "localhost:22"
    interval = "60s"
    timeout = "10s"
  }
}

Pour afficher les services connus :

consul catalog services
consul
service-apache
ssh

Vérifions maintenant les nœuds exposants le service appelé service-apache :

consul catalog nodes -service=service-apache
Node   ID        Address        DC
www01  de718bd9  192.168.69.14  dc1
www02  e96d0308  192.168.69.15  dc1

4. Interroger Consul

Jusqu’ici nous avons utilisé les commandes consul pour configurer et interroger l’état du cluster et les services Consul. Dans le cas d’une application en micro services, Consul propose deux modes d’interrogation pour identifier les services : le DNS et une API REST.

Consul offre un service DNS sur le port 8600, dynamiquement peuplé depuis le catalogue des services. Celui-ci est donc mis à jour lors du changement d’état de ceux-ci. Les services sont exposés dans une pseudo zone service.consul :

dig @consul01 -p 8600 service-apache.service.consul +short
192.168.69.14
192.168.69.15

Si on simule une panne ou une maintenance sur www01 :

consul maint -enable
Node maintenance is now enabled

On constate que consul a mis à jour les enregistrements retournés :

dig @consul01 -p 8600 service-apache.service.consul +short
192.168.69.15

Pour sortir www01 de la maintenance :

consul maint -disable
Node maintenance is now disabled

Et les deux nœuds sont de retour :

dig @consul01 -p 8600 service-apache.service.consul +short
192.168.69.14
192.168.69.15

D’une autre manière et comme je l’indiquais juste avant, Consul offre une API mise à jour de manière tout aussi dynamique :

curl -s -XGET http://consul01:8500/v1/catalog/service/service-apache | jq '.[] | .Node,.Address'
"www01"
"192.168.69.14"
"www02"
"192.168.69.15"

5. Le KV Store de Consul : stockage clé-valeur distribué

Le KV Store de Consul est un service distribué permettant de stocker des paires clé-valeur de manière simple et scalable. Il est souvent utilisé pour stocker des configurations dynamiques que les services peuvent récupérer en temps réel, partager des métadonnées entre services ou encore conserver des secrets et informations sensibles. C’est d’ailleurs le backend historique de Hashicorp Vault.

Contrairement à une base de données traditionnelle, le KV Store de Consul fonctionne comme un registre hiérarchique, similaire à un système de fichiers, où chaque clé peut être organisée en répertoires logiques.

L'ajout d'une valeur dans le KV Store se fait via la CLI Consul :

consul kv put config/database/url "postgres://user:password@db:5432"
Success! Data written to: config/database/url

Pour récupérer la valeur associée :

consul kv get config/database/url
postgres://user:password@db:5432

Il est également possible de récupérer toutes les clés d'un préfixe donné :

consul kv get -recurse config/
config/database/url:postgres://user:password@db:5432

Enfin, pour la suppression :

consul kv delete config/database/url
Success! Deleted key: config/database/url

L’un des atouts du KV Store est la possibilité de surveiller les changements en temps réel. Par exemple, un service peut être configuré pour écouter les modifications d'une clé donnée et réagir automatiquement.

En CLI :

consul kv put config/database/url "postgres://user:password@db:5432"
Success! Data written to: config/database/url
consul watch -type=key -key=config/database/url
{
    "Key": "config/database/url",
    "CreateIndex": 39597,
    "ModifyIndex": 39597,
    "LockIndex": 0,
    "Flags": 0,
    "Value": "cG9zdGdyZXM6Ly91c2VyOnBhc3N3b3JkQGRiOjU0MzI=",
    "Session": ""
}

Ou via l’API REST en envoyant une requête avec un index de modification pour être notifié uniquement si la valeur change :

curl "http://localhost:8500/v1/kv/config/database/url?wait=10s&index=123&pretty"
[
    {
        "LockIndex": 0,
        "Key": "config/database/url",
        "Flags": 0,
        "Value": "cG9zdGdyZXM6Ly91c2VyOnBhc3N3b3JkQGRiOjU0MzI=",
        "CreateIndex": 39597,
        "ModifyIndex": 39597
    }
]

6. Sauvegarde et restauration

Le fait que les données Consul soient répliquées entre les serveurs n’est pas une protection contre une perte de données en cas de défaillance. Comme tout système gérant des données, il est crucial de mettre en place un mécanisme de sauvegarde et de restauration.

La sauvegarde consul crée un snapshot des données Consul comprenant les services, les ACL, les tokens et les données du KV Store.

Pour créer une sauvegarde, il suffit d’utiliser la commande consul snapshot :

sudo consul snapshot save /backup/consul-snapshot.snap
Saved and verified snapshot to index 39733

Une sauvegarde n’a de valeur que si elle est faite régulièrement, il conviendra donc d’automatiser cette tâche avec un cron job pour une sauvegarde régulière. Par exemple, pour réaliser une sauvegarde quotidienne à 2h06 du matin avec une purge à 30 jours :

6 2 * * * consul snapshot save /backup/consul-$(date +\%F).snap
9 2 * * * find /backup -type f -name *,snap -mtime +7 -exec rm -rf {} \;

En cas de panne ou de corruption des données, une restauration rapide peut être effectuée sur le node qui est le leader du cluster avec la commande :

sudo consul snapshot restore /backup/consul-snapshot.snap
Restored snapshot

7. Sécurisation des accès avec les ACL

Les ACL (Access Control Lists) sont essentielles pour sécuriser les accès aux services et aux données stockées dans Consul. Elles permettent d’attribuer des permissions granulaires aux utilisateurs et aux applications en fonction de rôles définis. Par défaut, les ACL sont désactivées dans Consul et jusqu’ici nous avions libre accès à l’ensemble des données Consul que ce soit en web ou en CLI.

Pour les activer, il faut modifier le fichier de configuration du serveur Consul (/etc/consul.d/consul.hcl) et ajouter le bloc ci-dessous puis redémarrer le service consul :

acl {
  enabled = true
  default_policy = "deny"
  enable_token_persistence = true
}

La stratégie par défaut est de refuser l’accès. On commence donc par générer un bootstrap token, qui aura tous les droits sur le cluster et qu’il faut donc stocker dans un espace sécurisé :

consul acl bootstrap
AccessorID:       70143125-5590-e3d5-ce0e-6336c9ea8a77
SecretID:         0721e136-917a-257f-9c30-87ae351cfc8c
Description:      Bootstrap Token (Global Management)
Local:            false
Create Time:      2025-03-04 22:08:19.205136748 +0000 UTC
Policies:
   00000000-0000-0000-0000-000000000001 – global-management

Sans authentification, il n’est plus possible d’interagir avec consul :

consul acl token list
Failed to retrieve the token list: Unexpected response code: 403 (rpc error making call: Permission denied: anonymous token lacks permission 'acl:read'. The anonymous token is used implicitly when a request does not specify a token.)

Sauf à utiliser le secret ID du bootstrap token, cela vaut aussi pour l’interface web :

export CONSUL_HTTP_TOKEN="0721e136-917a-257f-9c30-87ae351cfc8c"
consul acl token list
AccessorID:       70143125-5590-e3d5-ce0e-6336c9ea8a77
SecretID:         0721e136-917a-257f-9c30-87ae351cfc8c
Description:      Bootstrap Token (Global Management)
Local:            false
Create Time:      2025-03-04 22:08:19.205136748 +0000 UTC
Policies:
   00000000-0000-0000-0000-000000000001 - global-management
 
AccessorID:       00000000-0000-0000-0000-000000000002
SecretID:         anonymous
Description:      Anonymous Token
Local:            false
Create Time:      2025-03-04 22:06:22.31000713 +0000 UTC

Les ACL fonctionnent avec un système de politiques (policies) qui définissent les droits associés aux ressources consul. Les droits sont simples : read, write, list, deny. Les permissions quant à elles s’appliquent sur les ressources service, node, key, acl, etc..

Par exemple, une politique autorisant uniquement la lecture des configurations KV peut être définie comme suit dans un fichier rules.hcl :

# noeuds commençant par www
node_prefix "www" {
  policy = "write"
}
 
# service service-apache en écriture
service "service-apache-80" {
  policy = "write"
}

La politique de permissions doit ensuite être créée depuis ce fichier de définition dans Consul :

consul acl policy create -name "policy-www" -rules @rules.hcl

Une fois la politique créée dans Consul, un jeton d’accès ayant les droits d’utiliser ces permissions doit être créé. Ce jeton d’accès (token) sera ensuite utilisé par les machines et applications pour s’authentifier et obtenir des autorisations d’accès aux données stockées dans Consul. La création d’un token associé à cette politique se fait de la manière suivante :

consul acl token create -policy-name "policy-www"
AccessorID:       a57f292c-8eba-9a8c-e534-74bc28d744a4
SecretID:         48fec504-5a1d-77a7-4249-168913da8dc8
Description:
Local:            false
Create Time:      2025-03-04 23:01:58.210621515 +0000 UTC
Policies:
   20c3cf19-7902-ce6c-21e3-fa8e5b416a08 - policy-www

Le token généré devra être utilisé par les services qui doivent accéder aux données KV avec cette restriction. Il est possible d’exploiter un token directement dans la configuration de l’agent avec cette configuration :

acl = {
  enabled = true
  tokens = {
    default = "20c3cf19-7902-ce6c-21e3-fa8e5b416a08"
  }
}

8. Consul Template

Consul Template est un outil permettant de générer automatiquement des fichiers de configuration en fonction des données stockées dans Consul. Consul Template surveille les données stockées dans Consul et met à jour la configuration en cas de changement. La configuration devient ainsi dynamique en fonction des changements de topologie de l’infrastructure. Un cas d’usage fréquent est la mise à jour de la configuration de HAProxy.

Commençons par créer un fichier /etc/consul-template/haproxy.ctmpl contenant :

frontend http_front
    bind *:80
    default_backend http_back
 
backend http_back
{{ range service "service-apache" }}
    server {{ .Name }} {{ .Address }}:{{ .Port }} check
{{ end }}

Le format est classique, c’est du Jinja2 utilisé dans les templates Ansible. Ce template boucle sur les services enregistrés dans consul appelés service-apache-80 et ajoute dynamiquement chaque instance. Une version plus évoluée utiliserait les clés du KV Store pour peupler de manière plus complète la configuration de HAProxy.

Générons un fichier de configuration pour consul-template qui se sourcera sur le template pour générer la configuration HAProxy. Je vais ici utiliser le token de bootstrap pour garder l’article concis, mais en cas réel, il est requis de mettre en place des ACL et d’utiliser un token dédié. Voici ce à quoi ressemble donc notre fichier /etc/consul-template/config.hcl :

consul {
  addresses = ["http://192.168.69.11:8500", "http://192.168.69.12:8500", "http://192.168.69.13:8500"]
  token = "0721e136-917a-257f-9c30-87ae351cfc8c"
}
 
template {
  source      = "/etc/consul-template/haproxy.ctmpl"
  destination = "/etc/haproxy/haproxy.cfg"
}

Générons la configuration dynamique depuis consul avec consul-template :

sudo consul-template -config="/etc/consul-template/config.hcl"

En production, un service systemd sera à créer. Vérifions la configuration générée :

cat /etc/haproxy/haproxy.cfg
frontend http_front
    bind *:80
    default_backend http_back
 
backend http_back
    server service-apache 192.168.69.14:80 check
    server service-apache 192.168.69.15:80 check

Félicitations, nous avons un HAProxy, basique certes, mais entièrement dynamique si on doit ajouter des serveurs Apache pour gérer la charge et dont aucune ligne de configuration ou playbook ansible n’a été nécessaire pour prendre en compte les changements.

Conclusion

Voici pour ce tour d’horizon sur Consul qui je l’espère aura évoqué votre intérêt. Il resterait encore beaucoup à dire comme le chiffrement des communications, les tags, détailler davantage les ACL et se plonger dans les fonctionnalités de service mesh.