Consul

Consul è una soluzione che consente ai team di gestire in modo sicuro le connessioni di rete tra servizi e attraverso ambienti multi-piattaforma e multi-cloud. Fornisce funzionalità come scoperta dei servizi, service mesh, governance del traffico e aggiornamenti automatici dell'infrastruttura di rete.
Documentazione ufficiale: Consul by HashiCorp
Repository open source: hashicorp/consul
Consul è uno strumento open source per la scoperta e registrazione dei servizi sviluppato da HashiCorp. Utilizza l'algoritmo di elezione Raft ed è scritto in Go, rendendolo molto leggero da distribuire. Consul ha le seguenti caratteristiche:
- Scoperta dei servizi
- Registrazione dei servizi
- Health check
- Archiviazione chiave-valore
- Multi-datacenter
In realtà, Consul può fare più della semplice scoperta dei servizi; può anche essere utilizzato come centro di configurazione distribuito. Esistono molti altri strumenti open source simili come Zookeeper e Nacos, che non verranno trattati ulteriormente qui.
Installazione
Per Ubuntu, eseguire i seguenti comandi per installare tramite 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 consulIn alternativa, è possibile scaricare dal sito ufficiale Install Consul il pacchetto di installazione corrispondente. Poiché Consul è sviluppato in Go, il pacchetto di installazione è un singolo file binario eseguibile, rendendo l'installazione molto semplice. Dopo un'installazione riuscita, eseguire il seguente comando per verificare la versione:
$ consul versionUn output normale indica che non ci sono problemi:
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)Guida Rapida
Di seguito viene illustrato come configurare rapidamente un singolo nodo Consul. Generalmente, un singolo nodo viene utilizzato per i test durante lo sviluppo. Se un singolo nodo funziona correttamente, molto probabilmente anche un cluster multi-nodo funzionerà senza problemi. La configurazione di un singolo nodo è molto semplice e richiede solo un comando:
$ consul agent -dev -bind=192.168.48.141 -data-dir=/tmp/consul -ui -node=dev01L'output tipico è il seguente:
==> 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}]"Breve spiegazione dei parametri:
agentè il sottocomando, il comando principale di Consul.consul agentesegue un nuovo agente Consul. Ogni nodo è un agente.devè la modalità di esecuzione dell'agente. Esistono tre modalità:dev,client,server.bindè l'indirizzo di comunicazione LAN, porta predefinita 8301. Generalmente questo valore è l'indirizzo interno del server.advertiseè l'indirizzo di comunicazione WAN, porta predefinita 8302. Generalmente questo valore è l'indirizzo esterno del server.data-dirè la directory di archiviazione dei dati.config-dirè la directory di archiviazione della configurazione. Consul leggerà tutti i file json nella directory.bootstrapindica che il server corrente entra in modalità bootstrap e voterà per se stesso durante l'elezione Raft. Nel cluster, non può esserci più di un server in questa modalità.bootstrap-expectè il numero previsto di server nel cluster. Finché non viene raggiunto il numero specificato, il cluster non inizierà l'elezione. Non può essere usato contemporaneamente abootstrap.retry-joindopo l'avvio dell'agente, continuerà a tentare di unirsi ai nodi specificati. Supporta anche i seguenti metodi di scoperta dei servizi:aliyun aws azure digitalocean gce hcp k8s linode mdns os packet scaleway softlayer tencentcloud triton vsphereuiesegue l'interfaccia Web.nodeè il nome del nodo di esecuzione e deve essere unico nel cluster.
TIP
Per ulteriori spiegazioni sui parametri dell'agente, consultare Agents - CLI Reference | Consul | HashiCorp Developer. Si noti che alcuni parametri sono disponibili solo nella versione Enterprise.
Dopo un'esecuzione riuscita, accedere a 127.0.0.1:8500 per visualizzare l'interfaccia Web.

L'icona di dev01 è una stella, indicando che è il nodo leader.
Per uscire, per consentire agli altri nodi di rilevare l'uscita del nodo corrente, non è consigliabile terminare forzatamente il processo. È possibile utilizzare il comando:
consul leaveoppure
consul force-leaveIn alternativa, premere ctrl+c per consentire all'agente Consul di uscire correttamente.
Concetti
Questo è un diagramma di un cluster Consul, diviso in due parti: piano di controllo e piano dati. Consul è responsabile solo del piano di controllo, diviso in cluster di servizi e client. Il cluster di servizi è ulteriormente diviso in follower e leader. Complessivamente, il cluster Consul costituisce un datacenter. Di seguito vengono spiegati alcuni termini:
- Agent (Agente): o più appropriatamente chiamato nodo. Ogni agente è un processo daemon in esecuzione prolungata che espone interfacce HTTP e DNS, responsabile dei controlli di integrità e della sincronizzazione dei servizi.
- Server (Agente Server): come server Consul, le sue responsabilità includono partecipare all'elezione Raft, mantenere lo stato del cluster, rispondere alle query, scambiare dati con altri datacenter e inoltrare query ai leader e ad altri datacenter.
- Client (Agente Client): rispetto al server, il client è senza stato. Non partecipa all'elezione Raft e il suo unico compito è inoltrare tutte le richieste al server. L'unica cosa a cui partecipa relativa al backend è il gossip forwarding LAN (LAN gossip pool).
- Leader: il leader è a capo di tutti i server e può esserci solo un leader. Il leader viene eletto attraverso l'algoritmo di elezione Raft. Ogni leader ha un proprio mandato e durante il mandato, qualsiasi richiesta ricevuta dagli altri server deve essere inoltrata al leader, quindi i dati del leader sono i più tempestivi e aggiornati.
- Gossip: Consul è costruito su Serf (un altro prodotto della stessa azienda) e utilizza il protocollo gossip, dedicato alla comunicazione casuale tra nodi, simile a UDP. Consul utilizza questo protocollo per notificarsi reciprocamente nel cluster di servizi.
- Data Center (Datacenter): un cluster Consul all'interno di una rete LAN è chiamato datacenter. Consul supporta più datacenter e il metodo di comunicazione tra più datacenter è il WAN gossip.
TIP
Per ulteriori vocaboli e termini, consultare Glossary | Consul | HashiCorp Developer.
Nel cluster Consul, il numero di server dovrebbe essere strettamente controllato poiché sono direttamente coinvolti nel LAN gossip e WAN gossip, nell'elezione Raft e nell'archiviazione dei dati. Più server ci sono, maggiore è il costo di comunicazione. Il numero di client può essere maggiore senza problemi, poiché sono solo responsabili dell'inoltro e non partecipano all'elezione, occupando pochissime risorse. Nel cluster mostrato nel diagramma, i vari servizi si registrano nel server tramite client. Se un server si guasta, i client cercheranno autonomamente altri server disponibili.
Esempio di Configurazione del Cluster
Di seguito viene illustrato un semplice esempio di configurazione di un cluster Consul multi-nodo. Preparare prima quattro macchine virtuali:

Delle quattro macchine virtuali, tre sono server e una è client. Il consiglio ufficiale è che il numero di server dovrebbe essere preferibilmente dispari e almeno tre. Qui vm00-vm02 sono server e vm03 è client.
Per i server, eseguire il seguente comando per creare un agente server:
consul agent -server -bind=vm_address -client=0.0.0.0 -data-dir=/tmp/consul/ -node=agent_name -uiPer i client, eseguire il seguente comando per creare un agente client:
consul agent -client=0.0.0.0 -bind=vm_address -data-dir=/tmp/consul/ -node=agent_name -uiI comandi eseguiti sono rispettivamente:
# 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.138Spiegazione di alcuni parametri:
client:0.0.0.0accetta richieste da tutte le origini. Se è presente solo il parametro client senza il parametro server, l'agente funzionerà in modalità client.
Dopo che tutti gli agenti sono in esecuzione, la funzione di retry-join equivale all'esecuzione automatica del comando join, che continuerà a riprovare in caso di fallimento. Il tempo di retry predefinito è 30s:
$ consul join 192.168.48.138Dopo il completamento del join, ogni nodo è a conoscenza dell'esistenza degli altri. Poiché vm00 è stato specificato in modalità bootstrap, è il leader predefinito. Se non è specificata la modalità bootstrap, il nodo specificato durante il join da tutti i nodi è il leader predefinito. Prima che il leader venga eletto, il cluster non può funzionare correttamente, l'accesso all'interfaccia Web restituirà 500 e alcuni comandi non funzioneranno correttamente. Se un nodo nel cluster è configurato in modalità bootstrap, nessun altro nodo nel cluster dovrebbe essere configurato in modalità bootstrap. Allo stesso tempo, nessun altro nodo dovrebbe utilizzare il parametro bootstrap-expect. Se utilizzato, verrà automaticamente disabilitato.
A questo punto, sul nodo leader (in realtà è possibile verificare su qualsiasi nodo), eseguire il seguente comando per visualizzare le informazioni sui membri del datacenter:
$ 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: nome del nodo
- Address: indirizzo di comunicazione
- Status:
aliveindica attivo,leftindica offline - Type: tipo di agente, due modalità: server e client
- Build: versione di Consul utilizzata dal nodo. Consul può funzionare con nodi di versioni diverse entro un certo range
- Protocol: si riferisce alla versione del protocollo Raft utilizzata. Questo protocollo dovrebbe essere coerente su tutti i nodi
- DC: Data Center. Tutti i nodi nell'output appartengono al datacenter dc1
- Partition: partizione a cui appartiene il nodo, funzionalità Enterprise. Ogni nodo può comunicare solo con nodi nella stessa partizione
- Segment: segmento di rete a cui appartiene il nodo, funzionalità Enterprise

Allo stesso modo, se si desidera che un nodo esca, è necessario utilizzare consul leave per consentire al nodo di uscire correttamente e notificare agli altri nodi che sta per uscire. Nel caso di più nodi, l'uscita corretta di un nodo è particolarmente importante poiché riguarda la coerenza dei dati.
TIP
Durante la dimostrazione, tutti i firewall delle macchine virtuali sono stati disattivati. Nell'ambiente di produzione reale, per motivi di sicurezza, dovrebbero essere abilitati. Pertanto, è necessario prestare attenzione a tutte le porte utilizzate da Consul: Required Ports | Consul | HashiCorp Developer.
Successivamente, testare brevemente la coerenza dei dati. Aggiungere i seguenti dati sulla macchina virtuale vm00:
$ consul kv put sys_confg {"name":"consul"}
Success! Data written to: sys_confgDopo il salvataggio, accedere agli altri nodi tramite HTTP API per verificare che i dati esistano anche lì (il valore è codificato in 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}]In effetti, le funzionalità di scoperta e registrazione dei servizi fornite da Consul vengono trasmesse agli altri nodi tramite il protocollo gossip. Quando qualsiasi nodo si unisce al datacenter corrente, tutti i nodi percepiscono questo cambiamento.
Esempio di Configurazione Multi-Datacenter
Preparare cinque macchine virtuali. vm00-vm02 sono il cluster dell'esempio precedente e appartengono al datacenter dc1, non verranno modificati. vm03-vm04 appartengono al datacenter dc2. Il datacenter è dc1 per impostazione predefinita all'avvio dell'agente.

TIP
Qui, per la dimostrazione, vengono configurati solo i server, omettendo i client.
Avviare prima vm03 come leader predefinito:
$ consul agent -server -datacenter=dc2 -bind=192.168.48.141 -client=0.0.0.0 -data-dir=/tmp/consul/ -node=agent04 -ui -bootstrapAvviare vm04 e farlo unire automaticamente al nodo 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.141A questo punto, verificare i membri su vm00 e vm03 rispettivamente:
# 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>È possibile vedere che il campo DC è diverso. Poiché questa è una dimostrazione con macchine virtuali, sono tutte nella stessa subnet. Nella realtà, due datacenter potrebbero essere cluster di server in luoghi diversi. Successivamente, fare in modo che un nodo qualsiasi di dc1 si unisca a un nodo qualsiasi di dc2. Qui vm01 si unisce a vm03:
$ consul join -wan 192.168.48.141
Successfully joined cluster by contacting 1 nodes.Dopo il join riuscito, eseguire il comando per visualizzare i membri 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
dc1Finché un nodo qualsiasi di dc1 si unisce a un nodo qualsiasi di dc2, tutti i nodi di entrambi i datacenter percepiscono questo cambiamento. Quando si visualizzano i membri, è possibile vedere i nodi di entrambi i datacenter.
Successivamente, provare ad aggiungere un dato KV sul nodo vm00:
$ consul kv put name consul
Success! Data written to: nameProvare a leggere i dati sul nodo vm01. È possibile vedere che i dati all'interno dello stesso datacenter sono sincronizzati:
$ consul kv get name
consulPoi provare a leggere i dati su vm03 in un datacenter diverso. Si scopre che i dati tra datacenter diversi non sono sincronizzati:
$ consul kv get name
Error! No key exists at: nameSe si desidera la sincronizzazione dei dati tra più datacenter, è possibile consultare hashicorp/consul-replicate: Consul cross-DC KV replication daemon.
Registrazione e Scoperta dei Servizi

Esistono due modi per registrare i servizi in Consul: registrazione tramite file di configurazione e registrazione tramite API. Per comodità di test, viene preparato in anticipo un servizio Hello World (un esempio dall'articolo gRPC), distribuito in due copie in posizioni diverse. Per la registrazione tramite file di configurazione, consultare Register external services with Consul service discovery | Consul | HashiCorp Developer. Qui viene introdotta solo la registrazione tramite HTTP API.
TIP
Per i servizi locali (insieme al client Consul), è possibile utilizzare direttamente la registrazione del servizio agent. Altrimenti, è necessario utilizzare la registrazione del catalogo.
Consul fornisce SDK per le API HTTP. Per gli SDK di altri linguaggi, consultare Libraries and SDKs - HTTP API | Consul | HashiCorp Developer. Qui viene scaricata la dipendenza per Go:
go get github.com/hashicorp/consul/apiRegistrare attivamente il servizio con Consul all'avvio del servizio e deregistrare il servizio da Consul alla chiusura. Di seguito è riportato un esempio:
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{
// Deve essere unico
ID: "hello-service1",
Service: "hello-service",
// Distribuito in due copie, una con porta 8080, l'altra con porta 8081
Port: 8080,
}
)
// Registra il servizio
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)
}
// Deregistra il servizio
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()
// Ascolta la porta
listen, err := net.Listen("tcp", ":8080")
if err != nil {
panic(err)
}
// Crea il server gRPC
server := grpc.NewServer(
grpc.Creds(insecure.NewCredentials()),
)
// Registra il servizio
pb.RegisterSayHelloServer(server, &HelloRpc{})
log.Println("server running...")
// Esegui
err = server.Serve(listen)
if err != nil {
panic(err)
}
}Il codice client utilizza un risolutore personalizzato di Consul per interrogare il registro dei servizi e risolvere gli indirizzi reali.
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,
// Strategia di round-robin
ServiceConfig: c.cc.ParseServiceConfig(
`{"loadBalancingPolicy":"round_robin"}`),
})
}
func (c ConsulResolver) ResolveNow(options resolver.ResolveNowOptions) {
c.resolve()
}
func (c ConsulResolver) Close() {
}Il client registra il risolutore all'avvio:
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() {
// Registra il builder
resolver.Register(
// Registra il risolutore Consul personalizzato
myresolver.NewConsulResolverBuilder("192.168.48.138:8500"),
)
}
func main() {
// Stabilisce la connessione, senza verifica crittografica
conn, err := grpc.Dial("consul:hello-service",
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
if err != nil {
panic(err)
}
defer conn.Close()
// Crea il client
client := hello2.NewSayHelloClient(conn)
for range time.Tick(time.Second) {
// Chiamata remota
helloRep, err := client.Hello(context.Background(), &hello2.HelloReq{Name: "client"})
if err != nil {
panic(err)
}
log.Printf("received grpc resp: %+v", helloRep.String())
}
}Avviare prima il server, poi il client. Ci sono due server che forniscono lo stesso servizio ma con indirizzi diversi. La strategia di bilanciamento del carico del client è round-robin, come si può vedere dall'intervallo di tempo nei log del server che la strategia è efficace.
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"Questo è un semplice caso d'uso di Consul combinato con gRPC per implementare la registrazione e la scoperta dei servizi.
