Consul : de A à Y
Il est maintenant indéniable qu’Hashicorp est une entreprise qui révolutionne l’univers de l’IAC. Entre Terraform qui est adopté par la plupart des clouds providers, Packer et Vagrant pour produire des machines ou des environnements de développement, ou encore Nomad comme alternative à Kubernetes : Hashicorp a su s’imposer via des outils fiables et efficaces.
J’oriente mes sujets d’apprentissages en fonction des tendances du moment et de mes besoins professionnels, et comme la principale évolution de la décennie (dans l’univers du développement) est la gestion des micro-services, je me suis orienté vers Kubernetes qui propose un environnement très favorable à cette pratique.
Mais Kubernetes n’est pas libre de toute contrainte, et je peux comprendre que certains milieux ne soient pas prêts à passer le cap.
C’est pour proposer une alternative à Kubernetes-Istio que j’ai commencé à apprendre Consul.
Je vais alors vous parler de Consul de A à Y, car je ne suis pas encore arrivé à la lettre Z (et je ne pense pas que j’y arriverai un jour).
Qu’est ce que Consul ?
Consul est l’outil développé par Hashicorp dans sa catégorie “Réseau”, mais ne vous découragez pas : aucune notion de réseau n’est nécéssaire à son utilisation.
En simplifiant à l’extrême : Consul permet d’interconnecter des services.
Celui-ci dispose de 2 modes de fonctionnement : agent et serveurs.
Les agents doivent identifier les services présents sur une machine, et les ajouter au catalogue de service. Les serveurs doivent orienter les requêtes d’un service à un autre.
Par exemple, je dispose de 2 micro-services : un service A qui propose un dashboard dont les informations sont récupérées depuis le service B, et le service B qui propose un API REST pour récupérer ces informations.
En temps normal : il est nécéssaire d’indiquer l’IP:PORT du service B au service A. Mais qu’en est-il du cas où nous voulons redonder le service B ? Quelle IP devrons nous donner au dashboard ? Il serait nécéssaire d’installer un HAProxy (ou autre) qui devrait rediriger en s’assurant quelles machines sont fonctionnelles ou pas. Et les HealthChecks de HAProxy ne sont pas forcement très permissifs : HAProxy ne peut pas voir l’état de santé complet de la machine…
(À noter qu’il est possible de bricoler HAProxy pour qu’il utilise le catalogue de Consul. Je laisserai un lien pour intégrer Consul et HAProxy en bas de l’article)
Sur Kubernetes, un “service” pointe forcément vers un Pod dont le liveness/readiness montre qu’il est en bonne santé (parmis une liste de pods de la même application). Avec Consul : l’agent installé va vérifier si le micro-service et le système sont fonctionnels et si les conditions sont bonnes, l’agent fait remonter que le service est disponible et le serveur va rediriger le service A vers la bonne machine.
Pour rediriger le trafic, Consul peut le faire de 3 manières différentes:
- par DNS - Consul héberge un serveur DNS redirigeant vers une IP pointant vers le micro-service demandé.
- par API/SDK - L’application doit faire une requête en REST pour obtenir l’IP du micro-service.
- par un sidecar proxy - Créant un tunnel dont le flux et l’accès sont gérés par Consul (qui peut autoriser ou non la communication entre les services).
Lorsqu’on passe par le DNS ou le sidecar, l’application n’a pas conscience de Consul.
Prenons le cas où nous passons par DNS :
- Le service A demande l’IP du domaine service-b.service.consul (Consul va directement comprendre quel service est concerné).
- Consul va vérifier si le service-b est fonctionnel (application/système).
- L’agent remonte le status de Consul.
- Consul donne l’IP du service-B.
- Le service A communique avec le B.
Information
Petite précision : Consul ne va pas vérifier en temps réel que le service B est en bonne santé. Il va constamment actualiser le statut des services, donc à la moindre requête : il sait d’avance vers quelle machine rediriger.
C’est l’usage le plus simple de Consul, mais ne vous inquietez pas : nous n’allons pas nous arreter à ça ! Il manque encore de la haute-disponibilité, des règles pour autoriser (ou pas) les services à communiquer et une bonne dose de sécurité.
Mais avant d’en arriver là : il va falloir créer notre environnement de travail.
Créer notre cluster Consul
Je n’ai parlé que de serveur Consul, mais il est possible de faire fonctionner Consul en cluster. Pour assurer un minimum de haute-dispo, il nous faudra minimum 3 noeuds :
Nombre de serveur | Taille du Quorum | Perte de serveur tolérée |
---|---|---|
1 | 1 | 0 |
2 | 2 | 0 |
3 | 2 | 1 |
4 | 3 | 1 |
5 | 3 | 2 |
6 | 4 | 2 |
7 | 4 | 3 |
Installons alors nos 3 noeuds.
Nom | IP | Description |
---|---|---|
consul-server-01 | 192.168.1.151 | Machine 01 du cluster |
consul-server-02 | 192.168.1.152 | Machine 02 du cluster |
consul-server-03 | 192.168.1.153 | Machine 03 du cluster |
Consul n’a presque aucune dépendance et est sous la forme d’un simple binaire.
Je crée 3 machines sous AlpineOS et on passe direct à l’installation.
Consul est également disponible sur la majorité des distributions Linux, voici la procédure pour l’installer sur une machine Debian.
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [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
Dans mon cas, j’utilise Alpine (comme précisé ci-dessus), voici comment j’installe Consul sur une machine Alpine:
apk add consul
mkdir -p /etc/consul.d/
rm -r /etc/consul/
mkdir -p /opt/consul
chown consul:consul /etc/consul.d/ /opt/consul
echo 'consul_opts="agent -config-dir=/etc/consul.d"' > /etc/conf.d/consul
Le package Alpine est un peu vieux, alors avec ces commandes : je place les fichiers de configuration dans /etc/consul.d/
et le dossier pour la persistence sera dans /opt/consul
(ce qui correspond à la documentation actuelle de Consul).
Créons maintenant la configuration de notre premier noeud :
datacenter = "coffee"
data_dir = "/opt/consul"
client_addr = "0.0.0.0"
ui_config{
enabled = true
}
connect {
enabled = true
}
server = true
bind_addr = "0.0.0.0"
bootstrap_expect=1
advertise_addr = "192.168.1.151" # à changer pour mettre l'IP de la machine
retry_join = ["192.168.1.151", "192.168.1.152", "192.168.1.153"]
Avertissement
Vous devrez adapter l’IP en fonction de votre noeud.
Ce fichier va créer notre premier noeud qui va lui-même créer le datacenter “coffee”. Il est possible de gérer plusieurs zones géographiques avec Consul, mais nous n’allons pas nous en occuper dans cet article (peut-être dans un prochain).
En utilisant ce fichier:
- Vous déclarez que le noeud est un type “serveur”.
- Vous activez l’interface WEB sur ce noeud (l’UI).
- Vous autorisez l’utilisation d’un Mesh proxy ‘Consul Connect’ (nous verrons ça plus tard).
- Vous rejoignez les clusters des noeuds suivants : “192.168.1.151”, “192.168.1.152” et “192.168.1.153”.
Peuplons ensuite les configurations des autres noeuds et en allant sur l’interface web, nous devrions avoir une “Server fault tolerance” à 1.
Information
Si l’UI est activée, l’interface est disponible sur le port 8500 en http.
Nous pouvons aussi voir les machines présentes dans le cluster via la commande consul members
:
# consul members
Node Address Status Type Build Protocol DC Partition Segment
consul-server-01 192.168.1.151:8301 alive server 1.16.0 2 coffee default <all>
consul-server-02 192.168.1.152:8301 alive server 1.15.4 2 coffee default <all>
consul-server-03 192.168.1.153:8301 alive server 1.15.4 2 coffee default <all>
Ajouter un client au cluster
Il existe 2 principales méthodes pour joindre une machine à un cluster :
- Via le fichier de configuration.
- Via la CLI.
La méthode que nous allons toujours privilégier est celle du fichier de configuration.
J’installe alors une nouvelle machine Alpine via la même procédure que pour les serveurs et je place le fichier de configuration suivant :
datacenter = "coffee"
data_dir = "/opt/consul"
client_addr = "0.0.0.0"
server = false
bind_addr = "0.0.0.0"
advertise_addr = "192.168.1.120" # IP de la machine cliente
retry_join = ["192.168.1.151", "192.168.1.152", "192.168.1.153"]
Après avoir démarré le service Consul, nous pouvons voir que la machine est bien présente dans le registre :
Cette machine accueillera un micro-service ne servant qu’à compter (je l’ai donc nommé “counting”).
Une première chose sympatique à tester est de récupérer l’IP grâce au nom de la machine :
# dig @192.168.1.151 -p 8600 counting.node.consul ANY
; <<>> DiG 9.18.1-1ubuntu1.2-Ubuntu <<>> @192.168.1.151 -p 8600 counting.node.consul ANY
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 14895
;; flags: qr aa rd; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;counting.node.consul. IN ANY
;; ANSWER SECTION:
counting.node.consul. 0 IN A 192.168.1.120
counting.node.consul. 0 IN TXT "consul-network-segment="
;; Query time: 4 msec
;; SERVER: 192.168.1.151#8600(192.168.1.151) (TCP)
;; WHEN: Thu Jul 27 11:00:58 CEST 2023
;; MSG SIZE rcvd: 101
Je n’ai pas l’usage de cette fonctionnalité mais cela permet de tester assez facilement qu’une machine est bien présente sur Consul (sans devoir passer par l’UI).
Je tiens également à vous partager cette phrase présente dans la documentation de Consul :
An agent which is already part of a cluster may join an agent in a different cluster, causing the two clusters to be merged into a single cluster.
En français : Si un agent est déjà présent dans un cluster et que nous l’ajoutons à un second cluster, ceux-ci fusionneront pour n’en créer qu’un. (Il est d’ailleurs préférable de n’avoir qu’un cluster Consul par zone géographique, et d’isoler les services si nécéssaires).
Ajouter un service
Voici le tableau représentant les machines de notre cluster :
Nom | IP | Description |
---|---|---|
consul-server-01 | 192.168.1.151 | Machine 01 du cluster |
consul-server-02 | 192.168.1.152 | Machine 02 du cluster |
consul-server-03 | 192.168.1.153 | Machine 03 du cluster |
counting | 192.168.1.120 | Micro-Service ‘Counting’ |
dashboard | 192.168.1.79 | Micro-Service ‘Dashboard’ |
Comme premier test, je vais installer un serveur web sur la machine counting et l’ajouter à notre catalogue Consul.
apk add lighttpd
echo "Hello, je suis la machine 'counting'" > /var/www/localhost/htdocs/index.html
service lighttpd start
Le serveur web est fonctionnel sur la machine counting, maintenant je dois informer de l’existence de ce service à notre agent Consul.
Toujours sur la machine counting, je vais créer le fichier /etc/consul.d/lighttpd.hcl
.
service {
name = "lighttpd"
id = "lighttpd-1"
port = 80
check {
id = "lighttpd-check"
http = "http://localhost:80"
method = "GET"
interval = "1s"
timeout = "1s"
}
}
Avec ce fichier, Consul va apprendre l’existence d’une application sur le port 80 ainsi que d’un moyen de vérifier l’état de santé de ce service.
À retenir :
- L’id doit être unique (pour identifier l’instance ciblée).
- Le nom doit être le même si plusieurs machines hébergent le même service.
- Un healthcheck permet de s’assurer que Consul ne nous redirige pas vers une application morte.
- Il est possible d’ajouter plusieurs healthchecks.
Information
Ces healthchecks peuvent se présenter sous plusieurs formes : requête HTTP, TCP, UDP ou via un script shell. Je vous laisse avec la documentation officielle qui propose de nombreux exemples : ici
Je peux maintenant interroger Consul pour obtenir l’IP du micro-service.
- DNS:
# curl lighttpd.service.consul # en utilisant 192.168.1.151:8600 en serveur DNS
Hello, je suis la machine 'counting'
- ou via l’API:
➜ ~ curl -s http://192.168.1.152:8500/v1/catalog/service/lighttpd | jq
[
{
"ID": "f7fef3ac-ca76-5818-824d-63a08d9d5482",
"Node": "counting",
"Address": "192.168.1.120",
"Datacenter": "coffee",
"TaggedAddresses": {
"lan": "192.168.1.120",
"lan_ipv4": "192.168.1.120",
"wan": "192.168.1.120",
"wan_ipv4": "192.168.1.120"
},
"NodeMeta": {
"consul-network-segment": ""
},
"ServiceKind": "",
"ServiceID": "lighttpd-1",
"ServiceName": "lighttpd",
"ServiceTags": [],
"ServiceAddress": "",
"ServiceWeights": {
"Passing": 1,
"Warning": 1
},
"ServiceMeta": {},
"ServicePort": 80,
"ServiceSocketPath": "",
"ServiceEnableTagOverride": false,
"ServiceProxy": {
"Mode": "",
"MeshGateway": {},
"Expose": {}
},
"ServiceConnect": {},
"CreateIndex": 1834,
"ModifyIndex": 1834
}
]
Haute disponibilité d’un service
Maintenant, nous allons ajouter une autre machine à notre Consul, celle-ci se nomme dashboard (son nom trouvera son sens plus tard).
Je configure son config.hcl
:
datacenter = "coffee"
data_dir = "/opt/consul"
client_addr = "0.0.0.0"
server = false
bind_addr = "0.0.0.0"
advertise_addr = "192.168.1.79" # IP de la machine cliente
retry_join = ["192.168.1.151", "192.168.1.152", "192.168.1.153"]
J’installe lighttpd :
apk add lighttpd
echo "Hello, je suis la machine 'dashboard'" > /var/www/localhost/htdocs/index.html
service lighttpd start
Et j’ajoute le service sur Consul :
service {
name = "lighttpd"
id = "lighttpd-2"
port = 80
check {
id = "lighttpd-check"
http = "http://localhost:80"
method = "GET"
interval = "1s"
timeout = "1s"
}
}
Nous avons maintenant deux instances du même service lighttpd sur deux machines différentes :
Si j’interroge le service, Consul me redirigera aléatoirement sur l’une des deux machines :
# curl lighttpd.service.consul
Hello, je suis la machine 'dashboard'
En revanche, si j’arrête le serveur web sur la machine dashboard via service lighttpd stop
, Consul remarquera une indisponibilité du service grace au healthcheck.
Et à la prochaine requête : Consul nous redirigera vers la machine counting contenant le même service.
- Par DNS :
# curl lighttpd.service.consul
Hello, je suis la machine 'counting'
- Ou par l’API :
# curl -s http://192.168.1.152:8500/v1/catalog/service/lighttpd | jq -r '.[0].Address'
192.168.1.120
Ce cas pratique nous montre comment héberger deux fois la même application et assurer une redondance.
En résumant :
- En ajoutant plusieurs applications du même nom : Consul nous redirigera toujours vers une instance fonctionnelle.
- Les healthchecks déterminent quelles machines peuvent accepter la requête.
- Il est possible d’obtenir l’IP d’un service ciblé via DNS ou l’API.
Il est également possible de rajouter du “poids” à une instance pour qu’elle soit plus solicitée que les autres (voir ici).
Mise à niveau sans downtime
Jusque là, lorsque nous interrogions Consul : notre seule condition est d’avoir un service en bonne santé.
Mais il est également possible de conditionner notre requête en imposant une application disposant un certain tag.
Pour cela, il est nécéssaire d’utiliser une ‘requête préparée’ (Prepared Query) qui permet d’imposer plusieurs conditions.
Modifions le service lighttpd
de la machine dashboard
pour y ajouter les tags “prod” et “v2”.
service {
name = "lighttpd"
id = "lighttpd-2"
port = 80
tags = ["prod", "v2"]
check {
id = "lighttpd-check"
http = "http://localhost:80"
method = "GET"
interval = "1s"
timeout = "1s"
}
}
J’en profite aussi pour changer le fichier index.html
de cette machine pour mettre le texte : ‘Production (v2)’.
Ajoutons maintenant les tags “prod” et “v1” sur le service lighttpd
de la machine counting
et changeons le fichier index.html
en ‘Production (v1)’.
Les tags d’un service sont visibles depuis l’interface web de Consul :
Créer une requête préparée
Créons un fichier front-production.json
qui définiera le service à contacter ainsi que ses tags.
{
"Name": "front-production",
"Service": {
"Service": "lighttpd",
"Tags": [
"v1",
"prod"
]
}
}
Puis envoyons ce fichier à l’API de Consul pour qu’il le traite :
#curl --request POST --data @front-production.json http://192.168.1.151:8500/v1/query
{"ID":"f2f116c3-e741-e5c9-8f8e-9c3693855826"}
Une réponse indiquant l’ID de la requête nous est retournée.
Remarque
Si vous avez perdu l’ID de la requête, vous pouvez lister les requêtes préparées sur l’endpoint /v1/query. (ex: http://192.168.1.151:8500/v1/query`)
# curl http://192.168.1.151:8500/v1/query | jq
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 457 100 457 0 0 130k 0 --:--:-- --:--:-- --:--:-- 148k
[
{
"ID": "f2f116c3-e741-e5c9-8f8e-9c3693855826",
"Name": "front-production",
"Session": "",
"Token": "",
"Template": {
"Type": "",
"Regexp": "",
"RemoveEmptyTags": false
},
"Service": {
"Service": "lighttpd",
"SamenessGroup": "",
"Failover": {
"NearestN": 0,
"Datacenters": null,
"Targets": null
},
"OnlyPassing": false,
"IgnoreCheckIDs": null,
"Near": "",
"Tags": [
"prod",
"v2"
],
"NodeMeta": null,
"ServiceMeta": null,
"Connect": false,
"Peer": ""
},
"DNS": {
"TTL": ""
},
"CreateIndex": 3568,
"ModifyIndex": 3814
}
]
Nous pouvons maintenant interroger la requête préparée via le DNS de Consul :
nslookup front-production.query.consul
Server: 127.0.0.53
Address: 127.0.0.53#53
Non-authoritative answer:
Name: front-production.query.consul
Address: 192.168.1.120
Ou l’API :
curl -s http://192.168.1.151:8500/v1/query/f2f116c3-e741-e5c9-8f8e-9c3693855826/execute | jq -r '.Nodes[0].Node.Address'
Une fois la requête préparée, les clients doivent être redirigés vers cette dernière et verront l’application “v1” (présente sur la machine counting
).
➜ curl http://front-production.query.consul/
Production (v1)
Mise à jour d’une requête préparée
Maintenant, pour passer de la “v1” à la “v2” de notre application, il suffit de modifier le fichier front-production.json
pour changer le tag “v1” en “v2”.
{
"Name": "front-production",
"Service": {
"Service": "lighttpd",
"Tags": [
"v2", // <- ICI
"prod"
]
}
}
Puis d’envoyer le fichier à l’API de Consul en utilisant la méthode PUT et l’ID de la requête préparée : /v1/query/<ID>
(sinon une nouvelle requête préparée sera créée).
curl --request PUT --data @front-production.json http://192.168.1.151:8500/v1/query/f2f116c3-e741-e5c9-8f8e-9c3693855826
Essayons maintenant d’interroger la nouvelle version de la requête préparée :
➜ Consul curl -s http://front-production.query.consul/
Production (v2)
Nous pouvons alors remarquer que l’application vient d’être mise à jour sans aucun downtime.
Applications en microservices
Dans cette partie, nous allons voir comment Consul peut nous aider à gérer des applications en microservices.
Les microservices sont des applications qui sont découpées en plusieurs services. Chaque service est indépendant et peut être déployé sur une machine différente. (ex: un service pour la base de données, un service pour l’API, un service pour le front-end, etc…)
Les serveurs counting et dashboard vont héberger deux services différents: un service front (dashboard) et un service rest (counting).
Je vais utiliser l’application développée par Hashicorp pour l’exemple : demo-consul-101.
Je dépose les binaires directement sur les serveurs pour simplifier l’exemple, je n’utilise pas de gestionnaire de paquets ou de conteneurs.
Sans Consul
Dans un premier temps, nous allons voir comment fonctionne l’application sans Consul.
Nous démarrons l’application counting sur le port 9003 de la machine counting. Celle-ci est indépendante et ne nécéssite pas un autre service pour fonctionner.
counting:~# PORT=9003 ./counting &
L’application counting est maintenant disponible sur le port 9003 de la machine counting.
L’application dashboard demande à l’API de counting le nombre de requêtes effectuées sur le service. Il faut donc que l’application dashboard connaisse l’adresse de l’API de counting.
dashboard:~# PORT=9002 COUNTING_SERVICE_URL=http://192.168.1.120:9003 ./dashboard
Cette méthode ne permet pas de gérer la haute disponibilité de l’application counting. Si le service counting tombe en panne, l’application dashboard ne pourra plus fonctionner.
Avec Consul (DNS)
Maintenant, utilisons ce que nous avons vu précédemment pour rendre l’application counting disponible via Consul.
J’ajoute le service counting sur notre catalogue Consul :
# /etc/consul.d/counting.hcl
service {
name = "counting"
id = "counting-1"
port = 9003
check {
id = "counting-check"
http = "http://localhost:9003/health"
method = "GET"
interval = "1s"
timeout = "1s"
}
}
Nous pouvons démarrer le service dashboard en utilisant le DNS de Consul pour trouver l’adresse de l’API de counting.
dashboard:~# PORT=9002 COUNTING_SERVICE_URL=http://counting.service.consul:9003 ./dashboard
Ainsi, il est possible de créer un service counting sur plusieurs machines et de les ajouter au catalogue Consul. L’application dashboard pourra alors interroger le service counting via le DNS de Consul et sera redirigée vers un des services counting disponibles.
Consul avec un sidecar (mesh proxy)
Dans cette partie, nous allons voir comment utiliser Consul avec un sidecar, cette méthode permet de rendre une application disponible via un tunnel sécurisé.
Il est possible d’utiliser Envoy ou Consul Connect pour créer un tunnel sécurisé entre les services (voir Consul Connect).
Information
Un sidecar est une application qui est exécutée sur la même machine. Il permet de fournir des fonctionnalités supplémentaires ou de modifier le comportement d’une application.
L’avantage est que l’usage d’un sidecar permet de chiffrer le trafic entre les services. Il est aussi possible de créer des règles pour autoriser ou non les services à communiquer entre eux.
Par simplicité, nous allons utiliser Consul Connect pour créer un tunnel sécurisé du service dashboard vers le service counting.
Avertissement
À noter qu’il faut que Consul Connect soit activé sur les serveurs Consul avec la configuration suivante :
connect {
enabled = true
}
Commençons par modifier le fichier /etc/consul.d/counting.hcl
de la machine counting :
service {
name = "counting"
id = "counting-1"
port = 9003
connect {
sidecar_service {}
}
check {
id = "counting-check"
http = "http://localhost:9003/health"
method = "GET"
interval = "1s"
timeout = "1s"
}
}
Démarrons (si ce n’est pas déjà fait) le service counting :
PORT=9003 ./counting &
Nous démarrons ensuite le sidecar sur la machine counting pour qu’il soit accessible via Consul Connect :
consul connect proxy -sidecar-for counting-1 &
On passe maintenant sur la machine dashboard.
Créons le fichier /etc/consul.d/dashboard.hcl
:
service {
name = "dashboard"
id = "dashboard-1"
port = 9002
check {
id = "dashboard-check"
http = "http://localhost:9002/health"
method = "GET"
interval = "1s"
timeout = "1s"
}
connect {
sidecar_service {
proxy {
upstreams = [
{
destination_name = "counting"
local_bind_port = 5000
}
]
}
}
}
}
Le service dashboard va utiliser le port 5000 pour communiquer avec le service counting via Consul Connect (le port de Counting n’est pas à préciser puisque Consul le connait déjà dans son catalogue).
Celui-ci va directement se connecter au service counting et va créer un tunnel sécurisé entre le service counting et Consul.
Information
À noter que le sidecar n’a pas besoin de connaître quelle machine va communiquer avec lui.
Notre sidecar (de la machine dashboard) va utiliser le port 5000 qui sera redirigé vers le service counting via Consul Connect.
Démarrons le service dashboard et le sidecar :
PORT=9002 COUNTING_SERVICE_URL=http://localhost:5000 ./dashboard &
consul connect proxy -sidecar-for dashboard-1 &
Et voilà, notre application dashboard utilise maintenant Consul Connect pour communiquer avec le service counting.
Avertissement
L’IP utilisée par le sidecar sera 127.0.0.1 par défault. Aucun risque de collision avec les autres services ou d’exposition sur le réseau.
L’usage de Consul Connect permet également de gérer les autorisations entre les services. Il est possible de créer des règles pour autoriser ou non les services à communiquer entre eux.
Par défault : tous les services peuvent communiquer entre eux, mais il est recommandé de créer une règle wildcard pour interdire toutes les communications et de créer les autorisations par la suite. Ces règles se nomment des intentions.
Depuis le service dashboard, on peut voir que counting est défini en tant qu’upstream :
Dans l’onglet Intention de Consul, les règles de communication entre les services sont visibles.
Nous allons créer une première règle pour interdire toutes les communications entre les services :
Si nous essayons de communiquer avec le service counting depuis le service dashboard, nous obtenons une erreur :
Pour autoriser la communication entre nos deux micro-services, il faut créer la règle suivante :
Information
Je n’autorise QUE dashboard à communiquer avec counting et non l’inverse, counting ne pourra pas contacter dashboard de lui-même.
À partir de ce moment, la communication entre les services est autorisée.
Astuce
Il est aussi possible de créer ces règles via l’API de Consul ou via le CLI.
Création de l’intention wildcard :
# consul intention create -deny "*" "*"
Created: * => * (deny)
Création de l’intention entre les services dashboard et counting :
# consul intention create dashboard counting
Created: dashboard => counting (allow)
Liste des intentions :
# consul intention list
ID Source Action Destination Precedence
1fee94ca-e8f9-b6fb-a348-30415b36363a dashboard allow counting 9
ea8574f3-13cb-6c47-2c63-920caa12364f * deny * 5
Stocker des données (kv store)
Dans cette partie, nous allons voir comment stocker des données dans Consul.
Consul dispose d’un KV store (registre key:value ou clé:valeur en français) qui permet de stocker des données comme des méta-données, des configurations, des informations sur l’environnement, etc.
Je commencerai par un disclaimer : Consul n’est pas fait pour stocker des données sensibles. Ne stockez aucune clé privée, mot de passe, etc. dans Consul !
Pour cela, il existe des solutions comme Vault qui permettent de stocker des données sensibles.
Avertissement
Les objets stockés dans Consul KV sont stockés en clair et peuvent faire une taille maximale de 512Ko.
Pour enregistrer une donnée, il est possible de passer par tous les clients Consul (API, CLI, UI, etc.).
Par UI :
En CLI :
consul kv put test/kv "Bonjour"
consul kv put test/kv @fichier.txt # Pour enregistrer le contenu d'un fichier
- Par l’API :
curl -X PUT "localhost:8500/v1/kv/test/kv" -d "Bonjour ! "
curl -X PUT "localhost:8500/v1/kv/test/kv" -d "@fichier.txt" # Pour enregistrer le contenu d'un fichier
Pour récupérer une donnée, il est possible de passer par l’API, le CLI ou l’UI.
# curl -s http://127.0.0.1:8500/v1/kv/administrator_metadata | jq -r '.[].Value | @base64d'
{
"administrators": [
{
"name": "Quentin",
"email": "[email protected]",
"username": "thebidouilleur",
"role": "Administrator"
},
{
"name": "Nougat",
"email": "[email protected]",
"username": "nougat",
"role": "Cat",
"birthday": "2016-05-20"
}
]
}
consul kv get test/kv
Astuce
Il est possible de voir toutes les clés stockées dans Consul via l’UI ou via la commande suivante :
consul kv get -recurse
Consul Template
Consul Template est un outil qui permet de générer des fichiers de configuration à partir des données stockées dans Consul KV.
Il prend en input une template et génère un fichier à partir des données récupérées.
Créons un fichier template.ctmpl
qui contient la template suivante :
Bonjour {{ key "admin/name" }}
Votre travail est {{ key "admin/job" }}, et votre animal de compagnie est un {{ key "admin/pet_type" }} nommé {{ key "admin/pet" }}
Ce fichier appelle des clés stockées dans notre Consul qui doivent être créées :
consul kv put admin/name "Quentin"
consul kv put admin/pet "Nougat" # Personne n'a le droit de critiquer le nom de mon chat ;)
consul kv put admin/pet_type "chat"
consul kv put admin/job "System Administrator"
Maintenant, nous allons lancer Consul Template avec la commande suivante :
consul-template -template="template.ctmpl:output.txt"
Celui-ci va générer un fichier output.txt
avec le contenu suivant :
# cat output.txt
Bonjour Quentin
Votre travail est System Administrator, et votre animal de compagnie est un chat nommé Nougat
Le meilleur est que tant que Consul Template est lancé, il va surveiller les données stockées dans Consul et mettre à jour le fichier de configuration si les données changent.
L’idée est alors de générer des fichiers de configuration pour des services comme Nginx, HAProxy, etc. et de les redémarrer automatiquement si ces données sont altérées.
Pour simplifier la configuration de Consul Template, il est possible de créer un fichier de configuration config.hcl
pour définir le chemin de la template, le fichier de sortie et même une commande à exécuter après chaque mise à jour du KV Store.
template {
source = "/opt/consul/templates/template-nginx.ctmpl"
destination = "/etc/nginx/nginx.conf"
command = "service nginx restart"
}
Puis on relance Consul Template avec la commande suivante :
consul-template -config=config.hcl
EnvConsul
EnvConsul est un outil qui permet de récupérer des données stockées dans Consul et de les injecter dans l’environnement d’un processus.
Tout comme Consul Template, il est assez simple à utiliser.
Nous créons les même clés que dans la partie précédente :
consul kv put admin/name "Quentin"
consul kv put admin/pet "Nougat" # Toujours pas le droit de critiquer
consul kv put admin/pet_type "chat"
consul kv put admin/job "System Administrator"
Maintenant, nous lançons la commande envconsul -prefix admin/ [ma commande]
, celle-ci va s’exécuter avec les variables d’environnement que nous avons précédemment stockées dans Consul.
Par exemple avec la commande env
:
#envconsul -upcase -sanitize -prefix admin "env"
USER=root
PAGER=most
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/0/bus
PET=Nougat
PET_TYPE=Cat
XDG_SESSION_CLASS=user
TERM=xterm-256color
XDG_SESSION_TYPE=tty
HOME=/root
OLDPWD=/root
SHELL=/bin/bash
LOGNAME=root
XDG_RUNTIME_DIR=/run/user/0
PS1=\[\033[1;31m\]\u\[\033[0;37m\]@\[\033[1;32m\]\h\[\033[00m\]:\[\033[1;34m\]\w\[\033[00m\] (\d - \t)\n\$
SSH_TTY=/dev/pts/2
LS_OPTIONS=--color=auto
SHLVL=1
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
LANG=fr_FR.UTF-8
LESSOPEN=| /usr/share/source-highlight/src-hilite-lesspipe.sh %s
_=/usr/bin/envconsul
NAME=Quentin
LESS= -R
XDG_SESSION_ID=178
LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=00:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.avif=01;35:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.webp=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:*~=00;90:*#=00;90:*.bak=00;90:*.old=00;90:*.orig=00;90:*.part=00;90:*.rej=00;90:*.swp=00;90:*.tmp=00;90:*.dpkg-dist=00;90:*.dpkg-old=00;90:*.ucf-dist=00;90:*.ucf-new=00;90:*.ucf-old=00;90:*.rpmnew=00;90:*.rpmorig=00;90:*.rpmsave=00;90:
JOB=System Administrator
PWD=/root/envconsul
MOTD_SHOWN=pam
Les arguments -upcase
et -sanitize
permettent de mettre en majuscule les clés et de remplacer les caractères invalides par des underscores.
On retrouve bel et bien nos variables d’environnement stockées dans le KV store de Consul.
Pour une application, c’est le même fonctionnement : voici un script en Golang qui va afficher les variables d’environnement (stockées dans consul dans notre cas).
package main
import (
"fmt"
"os"
)
func main() {
nom := os.Getenv("NAME")
pet := os.Getenv("PET")
petType := os.Getenv("PET_TYPE")
job := os.Getenv("JOB")
if nom == "" || pet == "" || petType == "" || job == "" {
fmt.Println("Certaines variables d'environnement ne sont pas définies.")
return
}
message := fmt.Sprintf("Bonjour %s\n\nVotre travail est %s, et votre animal de compagnie est un %s nommé %s.", nom, job, petType, pet)
fmt.Println(message)
for {
time.Sleep(3 * time.Minute)
}
}
Si je lance sans envconsul, j’obtiens une erreur puisque les variables d’environnement ne sont pas définies.
# ./mon-application
Certaines variables d'environnement ne sont pas définies.
En utilisant envconsul, j’obtiens le résultat attendu.
# envconsul -upcase -sanitize -prefix admin/ ./mon-application
Bonjour Quentin
Votre travail est System Administrator, et votre animal de compagnie est un chat nommé Nougat.
EnvConsul permet alors de fournir les variables d’environnement à une application en récupérant ces données dans notre Consul.
Le meilleur dans tout ça : envconsul va mettre à jour les variables d’environnement du processus lancé si celles-ci sont modifiées dans le KV store et redémarrer l’application automatiquement.
Chiffrer les échanges
Dans cette partie, nous allons voir comment rajouter une couche de sécurité à notre cluster Consul.
Par défaut, Consul ne chiffre pas les échanges entre les agents Consul (serveurs inclus) ce qui peut poser des problèmes de sécurité. En conditions réelles, il est obligatoire de mettre en place une sécurité contre un MITM.
Créer un certificat pour chaque agent Consul permet d’authentifier ces derniers et de chiffrer les échanges entre eux.
Une méthode possible est de générer une autorité de certification (CA) et de créer des certificats pour chaque agent Consul.
Consul peut faire office de CA nativement sans avoir à installer d’autres outils.
Générer une CA et des certificats pour les serveurs
Sur un des serveurs Consul, nous allons générer une CA et des certificats pour les serveurs Consul.
consul tls ca create
Nous obtenons alors deux fichiers : consul-agent-ca.pem
et consul-agent-ca-key.pem
à conserver précieusement.
Générons maintenant les certificats pour les autres serveurs Consul (chaque serveur doit avoir son propre certificat).
# consul tls cert create -server -dc coffee
==> WARNING: Server Certificates grants authority to become a
server and access all state in the cluster including root keys
and all ACL tokens. Do not distribute them to production hosts
that are not server nodes. Store them as securely as CA keys.
==> Using consul-agent-ca.pem and consul-agent-ca-key.pem
==> Saved coffee-server-consul-0.pem
==> Saved coffee-server-consul-0-key.pem
J’exécute la commande deux fois pour obtenir trois paires de certificats.
total 36
-r--r----- 1 consul root 227 29 juil. 12:20 coffee-server-consul-0-key.pem
-r--r----- 1 consul root 977 29 juil. 12:20 coffee-server-consul-0.pem
-r--r----- 1 consul root 227 29 juil. 12:34 coffee-server-consul-1-key.pem
-r--r----- 1 consul root 973 29 juil. 12:34 coffee-server-consul-1.pem
-r--r----- 1 consul root 227 29 juil. 12:34 coffee-server-consul-2-key.pem
-r--r----- 1 consul root 977 29 juil. 12:34 coffee-server-consul-2.pem
-r--r----- 1 consul root 227 29 juil. 12:14 consul-agent-ca-key.pem
-r--r----- 1 consul root 1078 29 juil. 12:14 consul-agent-ca.pem
-r--r----- 1 consul consul 1018 29 juil. 12:30 consul.hcl
Avertissement
Ces fichiers étant sensibles, je vous invite à restreindre les permissions de ces derniers pour que seul l’utilisateur consul
puisse les lire.
Depuis notre poste local, récupérons le fichier consul-agent-ca.pem
en vue d’une copie sur les autres serveurs Consul.
cd $(mktemp -d)
scp [email protected]:/etc/consul.d/consul-agent-ca.pem .
scp consul-agent-ca.pem [email protected]:/etc/consul.d/
scp consul-agent-ca.pem [email protected]:/etc/consul.d/
Nous allons maintenant récupérer les fichiers coffee-server-consul-1-key.pem
et coffee-server-consul-1.pem
et les envoyer sur consul-server-02
.
scp [email protected]:/etc/consul.d/{coffee-server-consul-1-key.pem,coffee-server-consul-1.pem} .
scp {coffee-server-consul-1-key.pem,coffee-server-consul-1.pem} [email protected]:/etc/consul.d/
Même chose pour le serveur consul-server-03
avec les fichiers coffee-server-consul-2-key.pem
et coffee-server-consul-2.pem
.
scp [email protected]:/etc/consul.d/{coffee-server-consul-2-key.pem,coffee-server-consul-2.pem} .
scp {coffee-server-consul-2-key.pem,coffee-server-consul-2.pem} [email protected]:/etc/consul.d/
Puis, nous allons créer le fichier de configuration tls.hcl
sur chaque serveur Consul pour activer le chiffrement TLS.
Voici le fichier sur la machine server-consul-01
, pensez à changer le nom des fichiers des certificats en fonction de la machine.
tls {
defaults {
ca_file = "/etc/consul.d/consul-agent-ca.pem"
cert_file = "/etc/consul.d/coffee-server-consul-0.pem"
key_file = "/etc/consul.d/coffee-server-consul-0-key.pem"
verify_incoming = true
verify_outgoing = true
}
internal_rpc {
verify_server_hostname = true
}
}
auto_encrypt {
allow_tls = true
}
Avec le fichier de configuration précédent, les échanges entre les serveurs Consul seront chiffrés et un agent avec un certificat non-valide ne pourra pas rejoindre le cluster en tant que serveur.
À noter que ces certificats seront utilisés pour les communications RPC entre les agents Consul ainsi que pour les communications HTTP (UI et API).
Vous pouvez utiliser votre propre CA locale pour générer les certificats. La seule contrainte est que les certificats doivent provenir d’une même CA.
Vault peut également être utilisé pour générer les certificats.
Activer le chiffrement sur les clients
Maintenant que les serveurs communiquent entre eux de manière sécurisée, nous allons activer le chiffrement sur les clients.
Pour rappel, voici les IPs des machines :
Nom | IP | Description |
---|---|---|
consul-server-01 | 192.168.1.151 | Machine 01 du cluster |
consul-server-02 | 192.168.1.152 | Machine 02 du cluster |
consul-server-03 | 192.168.1.153 | Machine 03 du cluster |
counting | 192.168.1.120 | Micro-Service ‘Counting’ |
dashboard | 192.168.1.79 | Micro-Service ‘Dashboard’ |
De même que pour les serveurs, nous allons générer un certificat pour chaque client Consul.
consul tls cert create -client -dc coffee
==> Using consul-agent-ca.pem and consul-agent-ca-key.pem
==> Saved coffee-client-consul-0.pem
==> Saved coffee-client-consul-0-key.pem
Nous exécutons la commande deux fois pour obtenir deux paires de certificats. Une paire pour chaque machine : counting
et dashboard
. Une fois les certificats générés, nous les récupèrons sur notre poste local ainsi que le fichier consul-agent-ca.pem
(le même que pour les serveurs).
cd $(mktemp -d)
scp [email protected]:/etc/consul.d/consul-agent-ca.pem .
scp [email protected]:/etc/consul.d/{coffee-client-consul-0.pem,coffee-client-consul-0-key.pem,coffee-client-consul-1.pem,coffee-client-consul-1-key.pem} .
Nous envoyons la paire de certificats coffee-client-consul-0.pem
et coffee-client-consul-0-key.pem
sur la machine counting
(192.168.1.120) et la paire coffee-client-consul-1.pem
et coffee-client-consul-1-key.pem
sur la machine dashboard
(192.168.1.79). Nous devons également envoyer le fichier consul-agent-ca.pem
sur les deux machines.
scp {coffee-client-consul-0.pem,coffee-client-consul-0-key.pem} [email protected]:/etc/consul.d/
scp {coffee-client-consul-1.pem,coffee-client-consul-1-key.pem} [email protected]:/etc/consul.d/
scp consul-agent-ca.pem root@{192.168.1.120,192.168.1.79}:/etc/consul.d/
Nous allons maintenant créer le fichier de configuration tls.hcl
sur chaque client Consul pour activer le chiffrement TLS.
tls {
defaults {
ca_file = "/etc/consul.d/consul-agent-ca.pem"
cert_file = "/etc/consul.d/coffee-client-consul-0.pem"
key_file = "/etc/consul.d/coffee-client-consul-0-key.pem"
verify_incoming = true
verify_outgoing = true
}
internal_rpc {
verify_server_hostname = true
}
}
Avertissement
N’oubliez pas de changer le nom des fichiers en fonction de la machine.
Avec le fichier de configuration précédent, les échanges entre les clients Consul seront chiffrés et un agent avec un certificat non-valide ne pourra pas rejoindre le cluster en tant que client.
Chiffrement du Gossip
Le Gossip (wikipedia) est un protocole de communication utilisé par Consul pour la découverte des agents. Celui-ci va informer les agents Consul de la présence d’un membre dans le cluster ou de sa suppression. Il est indispensable pour le bon fonctionnement de Consul.
Par défaut, le Gossip n’est pas chiffré. Nous allons donc activer le chiffrement de celui-ci.
Il est important de noter que le chiffrement du Gossip est indépendant du chiffrement TLS. Le chiffrement du Gossip n’utilise pas de certificat, c’est un chiffrement symétrique qui sera le même pour tous les agents Consul.
Créons alors notre clé de chiffrement via la CLI Consul.
# consul keygen
45xtK+iezZHI21U7dTzG6QHFqPiT+XzSksBfj2zWWvY=
Nous allons paramétrer le chiffrement du Gossip sur les serveurs et les clients. Pour cela, nous allons créer le fichier gossip.hcl
sur chaque machine.
encrypt = "45xtK+iezZHI21U7dTzG6QHFqPiT+XzSksBfj2zWWvY="
encrypt_verify_incoming = true
encrypt_verify_outgoing = true
Une fois le fichier créé, nous allons redémarrer les agents Consul sur chaque machine. Vous n’aurez qu’une petite interruption de service le temps de configurer les clés sur chaque noeud.
Astuce
Il est aussi possible de configurer le chiffrement du Gossip sans interruption !
Pour cela, entrez la clé de chiffrement mais n’activez pas le chiffrement incoming/outgoing:
encrypt = "45xtK+iezZHI21U7dTzG6QHFqPiT+XzSksBfj2zWWvY="
encrypt_verify_incoming = false
encrypt_verify_outgoing = false
Cela permettra aux agents de pouvoir déchiffrer les messages du Gossip mais pas de les chiffrer (les machines peuvent donc continuer à communiquer avec le cluster).
Activons maintenant le chiffrement des messages sortants :
encrypt_verify_outgoing = true
Les agents peuvent envoyer du traffic chiffré, ainsi que recevoir du traffic chiffré et non-chiffré (toujours sans aucune indisponibilité).
Enfin, on peut activer le chiffrement des messages entrants (et refuser les messages non-chiffrés) :
encrypt_verify_incoming = true
Distribuer une nouvelle clé de chiffrement
Admettons maintenant que notre clé est compromise par un noeud infecté. Nous allons donc devoir la changer sur l’ensemble du cluster.
Plutôt que de devoir modifier tous les agents Consul, nous allons simplement distribuer la nouvelle clé de chiffrement sur chaque noeud à partir d’un des serveurs.
Il est possible de voir les clés de chiffrement utilisées par les agents Consul via la commande consul keyring -list
.
# cat /etc/consul.d/gossip.hcl
encrypt = "45xtK+iezZHI21U7dTzG6QHFqPiT+XzSksBfj2zWWvY="
encrypt_verify_incoming = true
encrypt_verify_outgoing = true
# consul keyring -list
==> Gathering installed encryption keys...
WAN:
45xtK+iezZHI21U7dTzG6QHFqPiT+XzSksBfj2zWWvY= [3/3]
coffee (LAN):
45xtK+iezZHI21U7dTzG6QHFqPiT+XzSksBfj2zWWvY= [5/5]
Information
La clé de chiffrement n’est pas forcément la même pour le WAN et le LAN. Ici, les clés WAN sont stockées sur les serveurs et les clés LAN sur toutes les machines.
Maintenant, nous allons créer une nouvelle clé de chiffrement et la distribuer sur chaque noeud en utilisant la commande consul keyring
.
# consul keygen
DKUBMOmE/vospumAajBrsTvEEFPWVSBn7mdrKP+h3oQ=
# consul keyring -install "DKUBMOmE/vospumAajBrsTvEEFPWVSBn7mdrKP+h3oQ="
==> Installing new gossip encryption key...
# consul keyring -list
==> Gathering installed encryption keys...
WAN:
45xtK+iezZHI21U7dTzG6QHFqPiT+XzSksBfj2zWWvY= [3/3]
DKUBMOmE/vospumAajBrsTvEEFPWVSBn7mdrKP+h3oQ= [3/3]
coffee (LAN):
45xtK+iezZHI21U7dTzG6QHFqPiT+XzSksBfj2zWWvY= [5/5]
DKUBMOmE/vospumAajBrsTvEEFPWVSBn7mdrKP+h3oQ= [5/5]
Puis supprimons l’ancienne clé de chiffrement sur chaque noeud.
# consul keyring -remove "45xtK+iezZHI21U7dTzG6QHFqPiT+XzSksBfj2zWWvY="
==> Removing gossip encryption key...
error: Unexpected response code: 500 (10 errors occurred:
* WAN error: 3/3 nodes reported failure
* consul-server-01.coffee: Removing the primary key is not allowed
* consul-server-02.coffee: Removing the primary key is not allowed
* consul-server-03.coffee: Removing the primary key is not allowed
* coffee (LAN) error: 5/5 nodes reported failure
* consul-server-01: Removing the primary key is not allowed
* counting: Removing the primary key is not allowed
* consul-server-03: Removing the primary key is not allowed
* consul-server-02: Removing the primary key is not allowed
* dashboard: Removing the primary key is not allowed)
Une erreur est apparue car Consul ne vous permettra pas de supprimer une clé de chiffrement si elle est utilisée. Nous devons d’abord indiquer de quelle clé de chiffrement nous souhaitons faire usage.
# consul keyring -use "DKUBMOmE/vospumAajBrsTvEEFPWVSBn7mdrKP+h3oQ="
==> Changing primary gossip encryption key...
# consul keyring -remove "45xtK+iezZHI21U7dTzG6QHFqPiT+XzSksBfj2zWWvY="
==> Removing gossip encryption key...
Nous avons désormais une nouvelle clé de chiffrement sur l’ensemble du cluster.
# consul keyring -list
==> Gathering installed encryption keys...
WAN:
DKUBMOmE/vospumAajBrsTvEEFPWVSBn7mdrKP+h3oQ= [3/3]
coffee (LAN):
DKUBMOmE/vospumAajBrsTvEEFPWVSBn7mdrKP+h3oQ= [5/5]
Grâce à cette méthode, nous pouvons changer la clé de chiffrement sur l’ensemble du cluster sans avoir à modifier les fichiers de configuration sur chaque noeud. Hashicorp propose même un exemple de script Bash que vous pouvez exécuter régulièrement pour changer la clé de chiffrement.
#!/usr/bin/env bash
# Setup Consul address info
export CONSUL_HTTP_ADDR="http://localhost:8500"
# Generate the new key
NEW_KEY=`consul keygen`
# Install the key
consul keyring -install ${NEW_KEY}
# Set as primary
consul keyring -use ${NEW_KEY}
# Retrieve all keys used by Consul
KEYS=`curl -s ${CONSUL_HTTP_ADDR}/v1/operator/keyring`
ALL_KEYS=`echo ${KEYS} | jq -r '.[].Keys| to_entries[].key' | sort | uniq`
for i in `echo ${ALL_KEYS}`; do
if [ $i != ${NEW_KEY} ] ; then
consul keyring -remove $i
fi
done
Créer des ACLs
Depuis le début de cet article, nous utilisons la CLI Consul, ainsi que l’interface web comme bon nous semble. Cependant, dans un environnement de production, il est important de limiter l’accès à Consul et de définir des permissions pour les utilisateurs.
Sur Consul, nous avons les ACLs (à ne pas confondre avec les Intentions pour les services). Les ACLs permettent de définir des permissions pour les utilisateurs et les applications.
Activer les ACLs
Lors de la première activation des ACLs, nous allons paramétrer une politique de base par défaut. Il est obligatoire (pour le moment) de définir une politique par défaut qui autorise les échanges, sous peine de ne plus pouvoir accéder à Consul.
Nous allons alors nous connecter sur chaque serveur consul et créer le fichier acl.hcl
avec le contenu suivant :
# /etc/consul.d/acl.hcl
acl {
enabled = true
default_policy = "allow"
down_policy = "extend-cache"
}
On répète l’opération sur chaque serveur Consul et on redémarre le service Consul.
Une fois les ACLs activées, nous allons créer le token de bootstrap. Ce token dispose des droits absolus sur Consul. Il est donc important de le conserver précieusement.
# consul acl bootstrap
AccessorID: 4e5ad15a-84f4-b7aa-c5d4-66561f931860
SecretID: 26ccba2f-b97f-31ba-6447-b5f3e2cf7858
Description: Bootstrap Token (Global Management)
Local: false
Create Time: 2023-07-29 16:10:56.933406514 +0200 CEST
Policies:
00000000-0000-0000-0000-000000000001 - global-management
Par défaut, lorsque nous ne sommes pas authentifiés, nous avons accès à toutes les fonctionnalités de Consul qui ne concernent pas les ACLs (grâce à la default_policy
que nous avons défini plus haut).
Si nous essayons d’accéder à une fonctionnalité qui concerne les ACLs, nous obtenons une erreur :
# consul acl token list
Failed to retrieve the token list: Unexpected response code: 403 (Permission denied: anonymous token lacks permission 'acl:read'. The anonymous token is used implicitly when a request does not specify a token.)
Pour palier à ce problème, nous pouvons utiliser le token de bootstrap. Cependant, ce token est très puissant et nous ne devons pas l’utiliser pour les opérations courantes (nous allons régler ce problème par la suite).
Définissons une variable d’environnement CONSUL_HTTP_TOKEN
dont la valeur est le SecretID du token de bootstrap.
#export CONSUL_HTTP_TOKEN=26ccba2f-b97f-31ba-6447-b5f3e2cf7858
#consul acl token list
AccessorID: 4e5ad15a-84f4-b7aa-c5d4-66561f931860
SecretID: 26ccba2f-b97f-31ba-6447-b5f3e2cf7858
Description: Bootstrap Token (Global Management)
Local: false
Create Time: 2023-07-29 16:10:56.933406514 +0200 CEST
Policies:
00000000-0000-0000-0000-000000000001 - global-management
AccessorID: 00000000-0000-0000-0000-000000000002
SecretID: anonymous
Description: Anonymous Token
Local: false
Create Time: 2023-07-29 16:07:21.146019865 +0200 CEST
La policy global-management
(00000000-0000-0000-0000-000000000001) permet d’accéder à toutes les fonctionnalités de Consul, il n’est pas possible de l’éditer ou de la supprimer (en revanche, il est possible de la renommer).
Une fois notre token de bootstrap créé, nous allons modifier le fichier de configuration acl.hcl
sur chaque serveur Consul pour que la default_policy
soit deny
.
Ainsi, si nous ne sommes pas authentifié, nous n’avons aucun droit et aucun accès.
acl {
enabled = true
default_policy = "deny"
down_policy = "extend-cache"
}
Après avoir redémarré les services Consul, la commande consul members
ne devrait rien nous renvoyer si nous ne définissons pas la variable d’environnement CONSUL_HTTP_TOKEN
avec la valeur du SecretID du token de bootstrap.
root@consul-server:~ (dim. juil. 30 - 15:40:06)
# unset CONSUL_HTTP_TOKEN
root@consul-server:~ (dim. juil. 30 - 15:40:11)
# consul members
root@consul-server:~ (dim. juil. 30 - 15:40:12)
# export CONSUL_HTTP_TOKEN=26ccba2f-b97f-31ba-6447-b5f3e2cf7858
root@consul-server:~ (dim. juil. 30 - 15:40:15)
# consul members
Node Address Status Type Build Protocol DC Partition Segment
consul-server-01 192.168.1.151:8301 alive server 1.16.0 2 coffee default <all>
consul-server-02 192.168.1.152:8301 alive server 1.15.4 2 coffee default <all>
consul-server-03 192.168.1.153:8301 alive server 1.15.4 2 coffee default <all>
counting 192.168.1.120:8301 alive client 1.15.4 2 coffee default <default>
dashboard 192.168.1.79:8301 alive client 1.15.4 2 coffee default <default>
Astuce
Si vous perdez le token de bootstrap, il est possible de réinitialiser les ACLs en ligne de commande. Vous pouvez consulter la documentation officielle pour plus d’informations : access-control-troubleshoot
Créer des policies
Une policy est un ensemble de règles qui définissent les permissions d’un utilisateur ou d’une application. Il sera possible de lier un token à une (ou plusieurs) policy.
Les différentes permissions sont les suivantes :
- read (autoriser à lire les données, mais pas à les modifier)
- write (autoriser à lire et modifier les données)
- deny (interdire l’accès à une ressource)
- list (autoriser à lister les ressources KV)
Il est possible d’appliquer ces permissions sur les ressources suivantes :
- agent (pour les opérations sur l’agent)
- node (pour les opérations sur les noeuds)
- service (pour les opérations sur le catalogue de services)
- key (pour les opérations sur le KV store)
- session (pour les opérations sur les sessions)
- operator (pour les opérations sur le cluster)
- acl (pour les opérations sur les policies et tokens)
Pour créer une policy, nous allons utiliser la commande consul acl policy create
:
Mais avant ça, nous allons pouvoir décrire notre policy dans un fichier .hcl
et l’appliquer avec la commande consul acl policy create -name <nom de la policy> -rules <fichier .hcl>
.
Astuce
Si la permission par défaut est deny, il vous faudra une policy pour rejoindre le cluster ou pour créer un service.
Par exemple :
node "counting" {
policy = "write"
}
service "counting" {
policy = "write"
}
Mise en place d’ACL pour les applications
Après l’activation des ACLs et de la policy par défaut deny
, les applications counting et dashboard ne sont plus présentes dans le catalogue de services et ne peuvent plus communiquer avec Consul Connect.
Nous allons commencer par créer une policy pour l’application counting, qui aura les permissions pour rejoindre le cluster en tant que counting et pour créer un service counting.
# counting.hcl
node "counting" {
policy = "write"
}
service_prefix "counting" {
policy = "write"
intentions = "read"
}
Quant à la la policy pour l’application dashboard dashboard.hcl
, qui aura les permissions pour rejoindre le cluster en tant que dashboard, pour créer un service dashboard, pour lire les services commençant par counting et enfin pour lire les informations des noeuds commençant par counting.
# dashboard.hcl
node "dashboard" {
policy = "write"
}
service_prefix "dashboard" {
policy = "write"
}
service_prefix "counting" {
policy = "read"
}
node_prefix "counting" {
policy = "read"
}
Nous appliquons maintenant les deux policies avec la commande consul acl policy create -name <nom de la policy> -rules <fichier .hcl>
.
#consul acl policy create -name counting-policy -rules @counting.hcl
#consul acl policy create -name dashboard-policy -rules @dashboard.hcl
ID: fb76be74-aaa4-8388-3ce0-c143ee21ce2e
Name: counting-policy
Description:
Datacenters:
Rules:
# counting.hcl
node "counting" {
policy = "write"
}
service_prefix "counting" {
policy = "write"
intentions = "read"
}
---
ID: 0fbb5905-2203-6462-56f6-e65eb2a0243b
Name: dashboard-policy
Description:
Datacenters:
Rules:
# dashboard.hcl
node "dashboard" {
policy = "write"
}
service_prefix "dashboard" {
policy = "write"
}
service_prefix "counting" {
policy = "read"
}
node_prefix "counting" {
policy = "read"
}
Nous allons maintenant créer un token correspondant à chaque application, en lui associant la policy correspondante.
consul acl token create -description "Token pour machine 'counting'" -policy-id fb76be74-aaa4-8388-3ce0-c143ee21ce2e
consul acl token create -description "Token pour machine 'dashboard'" -policy-id 0fbb5905-2203-6462-56f6-e65eb2a0243b
Nous obtenons les tokens suivants :
df228746-fb24-837d-cbc3-c4ad80aff27a
pour l’application counting2748f8e2-f260-76a1-325f-81363490c757
pour l’application dashboard
Nous créons ensuite un fichier de configuration sur les deux machines avec le token correspondant.
acl = {
enabled = true
tokens = {
default = "2748f8e2-f260-76a1-325f-81363490c757"
}
}
Après avoir redémarré les deux applications, nous pouvons maintenant accéder à l’application dashboard et counting.
Grâce à ces ACLs, chaque machine dispose du minimum de permissions pour fonctionner.
Différente manière d’utiliser un token
En UI, il vous faudra vous connecter en donnant un token valide. Vous pouvez utiliser le token de bootstrap ou un token que vous avez créé.
En ligne de commande, il existe plusieurs manières de spécifier un token :
- en utilisant la variable d’environnement
CONSUL_HTTP_TOKEN
- en utilisant l’option
-token
et en spécifiant le token - en utilisant l’option
-token-file
et en spécifiant le chemin vers le fichier contenant le token
Et par l’API REST, il vous faudra spécifier le token dans le header X-Consul-Token
.
curl --silent --header "X-Consul-Token: d74b1733-b368-51b0-85d7-f5e06cea804d" http://localhost:8500/v1/kv/apps/eCommerce/version
Modifier les permissions par défaut
Il se peut que vous souhaitiez modifier les permissions par défaut pour permettre des actions sans devoir s’authentifier.
Par exemple, nous souhaitons permettre à toutes les machines de lister les services et les noeuds. Par défaut, voici les réponses obtenues :
# consul members
# consul catalog services
No services match the given query - try expanding your search.
Pour donner cette autorisation, nous pouvons créer une policy qui aura les permissions read
sur les services et les noeuds.
Pour l’appliquer à toutes les requêtes non-authentifiées, nous devons lier cette policy au token Anonymous.
Celui-ci est créé par défaut, et possède l’ID 00000000-0000-0000-0000-000000000002
.
Depuis l’UI, nous devons alors éditer le token Anonymous et lui associer la policy default-permissions.
# consul members
Node Address Status Type Build Protocol DC Partition Segment
consul-server-01 192.168.1.151:8301 alive server 1.16.0 2 coffee default <all>
consul-server-02 192.168.1.152:8301 alive server 1.15.4 2 coffee default <all>
consul-server-03 192.168.1.153:8301 alive server 1.15.4 2 coffee default <all>
counting 192.168.1.120:8301 alive client 1.15.4 2 coffee default <default>
dashboard 192.168.1.79:8301 alive client 1.15.4 2 coffee default <default>
# consul catalog services
consul
counting
counting-sidecar-proxy
dashboard
dashboard-sidecar-proxy
(Bonus) Créer un token pour créer des snapshots
Pour créer un token qui aura les permissions pour générer des snapshots, il faut ajouter une policy qui aura les permissions write
sur les ACLs.
consul acl policy create -name "snapshot-policy" -rules 'acl = "read"'
consul acl token create -description "Snapshot Token" -policy-name "snapshot-policy"
J’aurais bien aimé pouvoir faire mes sauvegardes avec une policy qui aurait uniquement la permission read
sur les snapshots, mais cela semble impossible.
# consul snapshot save toto
Error saving snapshot: Unexpected response code: 403 (Permission denied: token with AccessorID '3bfc0b27-8a11-c791-3cab-0b3359b43e93' lacks permission 'acl:write')
Conclusion
J’avais prévu de faire un chapitre sur le fait d’interconnecter plusieurs clusters Consul et présenter Consul dans un cluster Kubernetes, mais après ces > 1700 lignes de markdown et le temps que j’ai passé à reproduire les différents cas, je pense que je vais m’arrêter là.
Surtout pour un article qui n’intéressera qu’un publique très restreint.
Il reste néanmoins plus qu’assez de matière pour faire d’autres articles (Consul dans Kubernetes, Extraction de métriques dans Consul Connect, Datacenter Federation, etc.), mais je pense que je vais faire une pause sur Consul pour le moment et me concentrer sur une potentielle certification HashiCorp (si j’en ai le temps et la foi).
J’espère que cet article vous aura plu et qu’il vous aura permis de mieux comprendre Consul et ses fonctionnalités !
Je ne le partage pas beaucoup, mais je possède une page Ko-Fi vous permettant de me faire un don (sans créer de compte) si vous souhaitez (et pouvez vous le permettre) soutenir financièrement mes projets.