Consul

Consul est une solution qui permet aux équipes de gérer de manière sécurisée les connexions réseau entre les services dans des environnements préinstallés et multicloud. Il offre une série de fonctionnalités telles que la découverte de services, le maillage de services, la gestion du trafic et la mise à jour automatique de l'infrastructure réseau.
Documentation officielle : Consul by HashiCorp
Dépôt open source : hashicorp/consul
Consul est un outil de découverte et d'enregistrement de services open source de l'entreprise HashiCorp, utilisant l'algorithme d'élection Raft. L'outil lui-même est développé en langage Go, ce qui le rend très léger à déployer. Consul a les caractéristiques suivantes :
- Découverte de services
- Enregistrement de services
- Contrôle de santé
- Stockage clé-valeur
- Centres de données multiples
En réalité, Consul peut faire plus que de la découverte de services, il peut aussi servir de centre de configuration distribué. Il existe de nombreux autres outils open source similaires comme Zookeeper, Nacos, nous ne les présenterons pas davantage ici.
Installation
Pour Ubuntu, exécutez les commandes suivantes pour installer avec apt
$ 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 consulVous pouvez aussi télécharger le package d'installation correspondant sur le site officiel Install Consul. Comme Consul est développé en Go, le package d'installation ne contient qu'un seul fichier exécutable binaire, ce qui rend l'installation très pratique. Après une installation réussie, exécutez la commande suivante pour vérifier la version.
$ consul versionUne sortie normale signifie qu'il n'y a pas de problème
Consul v1.16.1
Revision e0ab4d29
Build Date 2023-08-05T21:56:29Z
Protocol 2 spoken by default, understands 2 to 3 (agent will automatically use protocol >2 when speaking to compatible agents)Démarrage rapide
Voici comment construire rapidement un nœud Consul unique. Généralement, un nœud unique est utilisé pour les tests pendant le développement. Si l'utilisation d'un nœud unique ne pose pas de problème, il y a de fortes chances qu'un cluster multi-nœuds fonctionne également correctement. La construction d'un nœud unique est très simple, il suffit d'une seule commande
$ consul agent -dev -bind=192.168.48.141 -data-dir=/tmp/consul -ui -node=dev01La sortie sera généralement la suivante
==> Starting Consul agent...
Version: '1.16.1'
Build Date: '2023-08-05 21:56:29 +0000 UTC'
Node ID: 'be6f6b8d-9668-f7ff-8709-ed57c72ffdec'
Node name: 'dev01'
Datacenter: 'dc1' (Segment: '<all>')
Server: true (Bootstrap: false)
Client Addr: [127.0.0.1] (HTTP: 8500, HTTPS: -1, gRPC: 8502, gRPC-TLS: 8503, DNS: 8600)
Cluster Addr: 192.168.48.141 (LAN: 8301, WAN: 8302)
Gossip Encryption: false
Auto-Encrypt-TLS: false
ACL Enabled: false
Reporting Enabled: false
ACL Default Policy: allow
HTTPS TLS: Verify Incoming: false, Verify Outgoing: false, Min Version: TLSv1_2
gRPC TLS: Verify Incoming: false, Min Version: TLSv1_2
Internal RPC TLS: Verify Incoming: false, Verify Outgoing: false (Verify Hostname: false), Min Version: TLSv1_2
==> Log data will now stream in as it occurs:
2023-08-25T17:23:33.763+0800 [DEBUG] agent.grpc.balancer: switching server: target=consul://dc1.be6f6b8d-9668-f7ff-8709-ed57c72ffdec/server.dc1 from=<none> to=<none>
2023-08-25T17:23:33.767+0800 [INFO] agent.server.raft: initial configuration: index=1 servers="[{Suffrage:Voter ID:be6f6b8d-9668-f7ff-8709-ed57c72ffdec Address:192.168.48.141:8300}]"Expliquons brièvement la signification
agentest une sous-commande, c'est la commande principale de Consul.consul agentlance un nouvel agent Consul, chaque nœud est un agent.dev, est le mode de fonctionnement de l'agent, il y a trois modes au total :dev,client,serverbind, adresse de communication LAN, le port par défaut est 8301, généralement cette valeur est l'adresse IP interne du serveuradvertise, adresse de communication WAN, le port par défaut est 8302, généralement cette valeur est l'adresse IP externe du serveurdata-dir, répertoire de stockage des donnéesconfig-dir, répertoire de stockage de la configuration, Consul lit tous les fichiers JSON du répertoirebootstrap, indique que le serveur actuel entre en mode bootstrap, il se donnera un vote lors de l'élection Raft. Il ne peut pas y avoir plus d'un serveur dans ce mode dans le clusterbootstrap-expect, nombre attendu de serveurs dans le cluster. Avant d'atteindre ce nombre, le cluster ne commencera pas l'élection. Ne peut pas être utilisé avecbootstrap.retry-join, après le démarrage de l'agent, il essaiera continuellement de rejoindre le nœud spécifié. Supporte également les méthodes de découverte des fournisseurs suivantsaliyun aws azure digitalocean gce hcp k8s linode mdns os packet scaleway softlayer tencentcloud triton vsphereui, lance l'interface Webnode, nom du nœud, doit être unique dans le cluster.
TIP
Pour plus d'informations sur les paramètres de l'agent, consultez Agents - CLI Reference | Consul | HashiCorp Developer. Notez que certains paramètres ne sont disponibles que dans la version entreprise.
Une fois lancé avec succès, accédez à 127.0.0.1:8500 pour voir l'interface Web.

L'icône de dev01 est une étoile, ce qui indique qu'il s'agit du nœud leader.
Pour quitter, afin que les autres nœuds puissent percevoir la sortie du nœud actuel, il n'est pas recommandé de tuer le processus de force. Utilisez plutôt la commande
consul leaveou
consul force-leaveVous pouvez aussi utiliser ctrl+c pour laisser l'agent Consul quitter proprement.
Concepts
Ceci est un schéma d'un cluster Consul. L'image est divisée en deux parties : le plan de contrôle et le plan de données. Consul ne gère que le plan de contrôle, divisé en cluster de serveurs et clients. Le cluster de serveurs est lui-même divisé en followers et leader. Dans l'ensemble, le cluster Consul de l'image constitue un centre de données. Voici l'explication de quelques termes :
Agent (Agent) : Ou plus appropriément appelé nœud. Chaque agent est un processus démon de longue durée qui expose des interfaces HTTP et DNS, responsable des contrôles de santé et de la synchronisation des services.
Server (Serveur) : En tant que serveur Consul, ses responsabilités principales sont : participer aux élections Raft, maintenir l'état du cluster, répondre aux requêtes, échanger des données avec d'autres centres de données, et transmettre les requêtes au leader et aux autres centres de données.
Client (Client) : Le client est sans état par rapport au serveur. Il ne participe pas aux élections Raft. Son seul travail est de transmettre toutes les requêtes au serveur. La seule chose liée au backend à laquelle il participe est le pool de gossip LAN.
Leader (Leader) : Le leader est le chef de tous les serveurs, et il ne peut y en avoir qu'un seul. Le leader est élu via l'algorithme Raft. Chaque leader a son propre mandat. Pendant son mandat, tout autre serveur qui reçoit une requête doit en informer le leader, donc les données du leader sont les plus à jour.
Gossip (Rumeur) : Consul est construit sur Serf (un autre produit de la même entreprise). Il utilise le protocole gossip, un protocole dédié à la communication aléatoire entre nœuds, similaire à UDP. Consul utilise ce protocole pour la notification mutuelle entre les clusters de services.
Data Center (Centre de données) : Un cluster Consul dans un réseau local est appelé un centre de données. Consul supporte les centres de données multiples. Le mode de communication entre centres de données multiples est le gossip WAN.
TIP
Plus de vocabulaire et termes peuvent être consultés sur Glossary | Consul | HashiCorp Developer.
Dans un cluster Consul, le nombre de serveurs doit être strictement contrôlé car ils participent directement au gossip LAN et WAN, aux élections Raft, et doivent stocker des données. Plus il y a de serveurs, plus les coûts de communication sont élevés. En revanche, le nombre de clients peut être plus important sans problème. Ils ne font que transmettre les requêtes, ne participent pas aux élections et consomment très peu de ressources. Dans le cluster de l'image, les différents services s'enregistrent auprès du serveur via les clients. Si un serveur tombe en panne, le client recherche automatiquement un autre serveur disponible.
Exemple de construction de cluster
Construisons un exemple simple de cluster Consul multi-nœuds. Préparons d'abord quatre machines virtuelles

Parmi les quatre machines virtuelles, trois serveurs et un client. La recommandation officielle est que le nombre de serveurs soit impair et idéalement supérieur ou égal à trois. Ici, vm00-vm02 seront des serveurs, vm03 sera un client.
Pour les serveurs, exécutez la commande suivante pour créer un agent serveur
consul agent -server -bind=vm_address -client=0.0.0.0 -data-dir=/tmp/consul/ -node=agent_name -uiPour les clients, exécutez la commande suivante pour créer un agent client
consul agent -client=0.0.0.0 -bind=vm_address -data-dir=/tmp/consul/ -node=agent_name -uiLes commandes exécutées sont respectivement
# vm00
consul agent -server -bind=192.168.48.138 -client=0.0.0.0 -data-dir=/tmp/consul/ -node=agent01 -ui -bootstrap
# vm01
consul agent -server -bind=192.168.48.139 -client=0.0.0.0 -data-dir=/tmp/consul/ -node=agent02 -ui -retry-join=192.168.48.138
# vm02
consul agent -server -bind=192.168.48.140 -client=0.0.0.0 -data-dir=/tmp/consul/ -node=agent03 -ui -retry-join=192.168.48.138
# vm03
consul agent -bind=192.168.48.140 -client=0.0.0.0 -data-dir=/tmp/consul/ -node=agent03 -ui -retry-join=192.168.48.138Explication de certains paramètres
client,0.0.0.0signifie accepter toutes les requêtes de toutes les sources. S'il n'y a que le paramètre client sans le paramètre server, cela signifie que l'agent fonctionnera en mode client.
Une fois tous les agents lancés, le rôle de retry-join équivaut à exécuter automatiquement la commande join. En cas d'échec, il continuera à essayer, avec un temps de réessai par défaut de 30s
$ consul join 192.168.48.138Une fois le join terminé, chaque nœud connaît l'existence des autres. Puisque vm00 a spécifié le mode bootstrap, il est le leader par défaut. Si aucun mode bootstrap n'est spécifié, le nœud spécifié lors du join devient le leader par défaut. Avant que le leader ne soit élu, le cluster ne peut pas fonctionner normalement, l'accès à l'interface Web renverra 500, et certaines commandes ne fonctionneront pas non plus. Si un nœud du cluster a spécifié le mode bootstrap, aucun autre nœud ne devrait spécifier ce mode, et les autres nœuds ne devraient pas utiliser le paramètre bootstrap-expect. S'ils l'utilisent, il sera automatiquement désactivé.
À ce moment, sur le nœud leader (en fait, n'importe quel nœud peut voir), exécutez la commande pour voir les informations des membres du centre de données
$ consul members
Node Address Status Type Build Protocol DC Partition Segment
agent01 192.168.48.138:8301 alive server 1.16.1 2 dc1 default <all>
agent02 192.168.48.139:8301 alive server 1.16.1 2 dc1 default <all>
agent03 192.168.48.140:8301 alive server 1.16.1 2 dc1 default <all>
client01 192.168.48.141:8301 alive client 1.16.1 2 dc1 default <default>- Node, nom du nœud
- Address, adresse de communication
- Status,
alivesignifie en vie,leftsignifie hors ligne - Type, type d'agent, modes serveur et client
- Build, version Consul utilisée par ce nœud. Consul peut être compatible dans une certaine mesure avec des nœuds de versions différentes
- Protocol, version du protocole Raft utilisée, ce protocole doit être identique pour tous les nœuds
- DC, Data Center, centre de données. Tous les nœuds de la sortie appartiennent au centre de données dc1
- Partition, partition à laquelle appartient le nœud, fonctionnalité de la version entreprise. Chaque nœud ne peut communiquer qu'avec les nœuds de la même partition
- Segment, segment réseau auquel appartient le nœud, fonctionnalité de la version entreprise

De même, si vous souhaitez qu'un nœud quitte, vous devriez utiliser consul leave pour permettre au nœud de quitter proprement et informer les autres nœuds de son départ. Pour un cluster multi-nœuds, la sortie propre des nœuds est particulièrement importante car elle affecte la cohérence des données.
TIP
Les pare-feu ont été désactivés sur les machines virtuelles pour la démonstration. Dans un environnement de production réel, par sécurité, ils devraient être activés. Pour cela, consultez tous les ports utilisés par Consul : Required Ports | Consul | HashiCorp Developer.
Testons ensuite la cohérence des données. Sur la machine virtuelle vm00, ajoutons les données suivantes
$ consul kv put sys_confg {"name":"consul"}
Success! Data written to: sys_confgAprès enregistrement, via l'API HTTP, accédez aux autres nœuds et vous verrez que les données existent également (la valeur est encodée en base64)
$ curl http://192.168.48.138:8500/v1/kv/sys_confg
[{"LockIndex":0,"Key":"sys_confg","Flags":0,"Value":"ewogICJuYW1lIjoiY29uc3VsIgp9","CreateIndex":2518,"ModifyIndex":2518}]
$ curl http://192.168.48.139:8500/v1/kv/sys_confg
[{"LockIndex":0,"Key":"sys_confg","Flags":0,"Value":"ewogICJuYW1lIjoiY29uc3VsIgp9","CreateIndex":2518,"ModifyIndex":2518}]
$ curl http://192.168.48.140:8500/v1/kv/sys_confg
[{"LockIndex":0,"Key":"sys_confg","Flags":0,"Value":"ewogICJuYW1lIjoiY29uc3VsIgp9","CreateIndex":2518,"ModifyIndex":2518}]En fait, la fonction de découverte et d'enregistrement de services fournie par Consul diffuse aux autres nœuds via le protocole gossip, et lorsque n'importe quel nœud rejoint le centre de données actuel, tous les nœuds perçoivent ce changement.
Exemple de construction multi-centres de données
Préparez cinq machines virtuelles. vm00-vm02 est le cluster de l'exemple précédent, appartenant au centre de données dc1, qu'on laisse tel quel. vm03-vm04 appartiennent au centre de données dc2. Le centre de données par défaut au démarrage de l'agent est dc1.

TIP
Pour cette démonstration, seuls les serveurs sont construits, sans clients.
D'abord, démarrez vm03 comme leader par défaut
$ consul agent -server -datacenter=dc2 -bind=192.168.48.141 -client=0.0.0.0 -data-dir=/tmp/consul/ -node=agent04 -ui -bootstrapDémarrez vm04 pour qu'il rejoigne automatiquement le nœud vm03
$ consul agent -server -datacenter=dc2 -bind=192.168.48.142 -client=0.0.0.0 -data-dir=/tmp/consul/ -node=agent05 -ui -retry-join=192.168.48.141Maintenant, regardez les members sur vm00 et vm03 respectivement
# vm00-vm02
$ consul members
Node Address Status Type Build Protocol DC Partition Segment
agent01 192.168.48.138:8301 alive server 1.16.1 2 dc1 default <all>
agent02 192.168.48.139:8301 alive server 1.16.1 2 dc1 default <all>
agent03 192.168.48.140:8301 alive server 1.16.1 2 dc1 default <all>
# vm03-vm04
$ consul members
Node Address Status Type Build Protocol DC Partition Segment
agent04 192.168.48.141:8301 alive server 1.16.1 2 dc2 default <all>
agent05 192.168.48.142:8301 alive server 1.16.1 2 dc2 default <all>On peut voir que le champ DC est différent. Comme c'est une démonstration sur machines virtuelles, tout est dans le même segment réseau. En réalité, deux centres de données pourraient être des clusters de serveurs dans des lieux différents. Ensuite, faites rejoindre n'importe quel nœud de dc1 à n'importe quel nœud de dc2. Ici, faisons rejoindre vm01 à vm03
$ consul join -wan 192.168.48.141
Successfully joined cluster by contacting 1 nodes.Après le join réussi, exécutez la commande pour voir les members WAN
$ consul members -wan
Node Address Status Type Build Protocol DC Partition Segment
agent01.dc1 192.168.48.138:8302 alive server 1.16.1 2 dc1 default <all>
agent02.dc1 192.168.48.139:8302 alive server 1.16.1 2 dc1 default <all>
agent03.dc1 192.168.48.140:8302 alive server 1.16.1 2 dc1 default <all>
agent04.dc2 192.168.48.141:8302 alive server 1.16.1 2 dc2 default <all>
agent05.dc2 192.168.48.142:8302 alive server 1.16.1 2 dc2 default <all>
$ consul catalog datacenters
dc2
dc1Il suffit qu'un nœud de dc1 rejoigne n'importe quel nœud de dc2 pour que tous les nœuds des deux centres de données perçoivent ce changement. Vous pouvez aussi voir les nœuds des deux centres de données en consultant les members.
Ensuite, essayons d'ajouter une donnée KV sur le nœud vm00
$ consul kv put name consul
Success! Data written to: nameSur le nœud vm01, essayons de lire les données. On peut voir que les données du même centre de données sont synchronisées
$ consul kv get name
consulPuis allons sur vm03 d'un centre de données différent pour essayer de lire les données. On constate que les données des différents centres de données ne sont pas synchronisées.
$ consul kv get name
Error! No key exists at: nameSi vous souhaitez synchroniser les données entre plusieurs centres de données, vous pouvez consulter hashicorp/consul-replicate: Consul cross-DC KV replication daemon.
Enregistrement et découverte de services

Il y a deux façons d'enregistrer des services Consul : l'enregistrement par fichier de configuration et l'enregistrement par API. Pour faciliter les tests, nous avons préparé un service Hello World (l'exemple de l'article gRPC), déployé en deux exemplaires à des endroits différents. Pour l'enregistrement par fichier de configuration, consultez Register external services with Consul service discovery | Consul | HashiCorp Developer. Ici, nous présentons uniquement l'enregistrement via l'API HTTP.
TIP
Pour les services locaux (co-localisés avec le client Consul), vous pouvez utiliser directement agent service pour l'enregistrement. Sinon, vous devriez utiliser catalog register.
Consul fournit un SDK pour l'API HTTP. Pour les SDK dans d'autres langages, consultez Libraries and SDKs - HTTP API | Consul | HashiCorp Developer. Ici, téléchargez la dépendance Go
go get github.com/hashicorp/consul/apiAu démarrage du service, enregistrez-le activement auprès de Consul. À l'arrêt du service, désenregistrez-le auprès de Consul. Voici un exemple.
package main
import (
consulapi "github.com/hashicorp/consul/api"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
pb "grpc_learn/helloworld/hello"
"log"
"net"
)
var (
server01 = &consulapi.AgentService{
// Doit rester unique
ID: "hello-service1",
Service: "hello-service",
// Déployé en deux exemplaires, un sur le port 8080, un sur le port 8081
Port: 8080,
}
)
// Enregistrer le service
func Register() {
client, _ := consulapi.NewClient(&consulapi.Config{Address: "192.168.48.138:8500"})
_, _ = client.Catalog().Register(&consulapi.CatalogRegistration{
Node: "hello-server",
Address: "192.168.2.10",
Service: server01,
}, nil)
}
// Désenregistrer le service
func DeRegister() {
client, _ := consulapi.NewClient(&consulapi.Config{Address: "192.168.48.138:8500"})
_, _ = client.Catalog().Deregister(&consulapi.CatalogDeregistration{
Node: "hello-server",
Address: "192.168.2.10",
ServiceID: server01.ID,
}, nil)
}
func main() {
Register()
defer DeRegister()
// Écouter le port
listen, err := net.Listen("tcp", ":8080")
if err != nil {
panic(err)
}
// Créer le serveur gRPC
server := grpc.NewServer(
grpc.Creds(insecure.NewCredentials()),
)
// Enregistrer le service
pb.RegisterSayHelloServer(server, &HelloRpc{})
log.Println("server running...")
// Exécuter
err = server.Serve(listen)
if err != nil {
panic(err)
}
}Le code client utilise un résolveur personnalisé Consul pour interroger le service correspondant auprès du registre et le résoudre en adresse réelle.
package myresolver
import (
"fmt"
consulapi "github.com/hashicorp/consul/api"
"google.golang.org/grpc/resolver"
)
func NewConsulResolverBuilder(address string) ConsulResolverBuilder {
return ConsulResolverBuilder{consulAddress: address}
}
type ConsulResolverBuilder struct {
consulAddress string
}
func (c ConsulResolverBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) {
consulResolver, err := newConsulResolver(c.consulAddress, target, cc)
if err != nil {
return nil, err
}
consulResolver.resolve()
return consulResolver, nil
}
func (c ConsulResolverBuilder) Scheme() string {
return "consul"
}
func newConsulResolver(address string, target resolver.Target, cc resolver.ClientConn) (ConsulResolver, error) {
var reso ConsulResolver
client, err := consulapi.NewClient(&consulapi.Config{Address: address})
if err != nil {
return reso, err
}
return ConsulResolver{
target: target,
cc: cc,
client: client,
}, nil
}
type ConsulResolver struct {
target resolver.Target
cc resolver.ClientConn
client *consulapi.Client
}
func (c ConsulResolver) resolve() {
service := c.target.URL.Opaque
services, _, err := c.client.Catalog().Service(service, "", nil)
if err != nil {
c.cc.ReportError(err)
return
}
var adds []resolver.Address
for _, catalogService := range services {
adds = append(adds, resolver.Address{Addr: fmt.Sprintf(fmt.Sprintf("%s:%d", catalogService.Address, catalogService.ServicePort))})
}
c.cc.UpdateState(resolver.State{
Addresses: adds,
// Stratégie de round-robin
ServiceConfig: c.cc.ParseServiceConfig(
`{"loadBalancingPolicy":"round_robin"}`),
})
}
func (c ConsulResolver) ResolveNow(options resolver.ResolveNowOptions) {
c.resolve()
}
func (c ConsulResolver) Close() {
}Le client enregistre le résolveur au démarrage
package main
import (
"context"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/resolver"
"grpc_learn/helloworld/client/myresolver"
hello2 "grpc_learn/helloworld/hello"
"log"
"time"
)
func init() {
// Enregistrer le builder
resolver.Register(
// Enregistrer le résolveur Consul personnalisé
myresolver.NewConsulResolverBuilder("192.168.48.138:8500"),
)
}
func main() {
// Établir la connexion, sans chiffrement
conn, err := grpc.Dial("consul:hello-service",
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
if err != nil {
panic(err)
}
defer conn.Close()
// Créer le client
client := hello2.NewSayHelloClient(conn)
for range time.Tick(time.Second) {
// Appel distant
helloRep, err := client.Hello(context.Background(), &hello2.HelloReq{Name: "client"})
if err != nil {
panic(err)
}
log.Printf("received grpc resp: %+v", helloRep.String())
}
}Démarrez d'abord le serveur, puis le client. Il y a deux serveurs qui fournissent le même service, mais avec des adresses différentes. La stratégie d'équilibrage de charge du client est le round-robin. On peut voir dans les logs du serveur que la stratégie fonctionne, en regardant l'intervalle de temps.
2023/08/29 17:39:54 server running...
2023/08/29 21:03:46 received grpc req: name:"client"
2023/08/29 21:03:48 received grpc req: name:"client"
2023/08/29 21:03:50 received grpc req: name:"client"
2023/08/29 21:03:52 received grpc req: name:"client"
2023/08/29 21:03:54 received grpc req: name:"client"
2023/08/29 21:03:56 received grpc req: name:"client"
2023/08/29 21:03:58 received grpc req: name:"client"
2023/08/29 21:04:00 received grpc req: name:"client"Ceci est un exemple simple d'enregistrement et de découverte de services utilisant Consul combiné avec gRPC.
