Consul

consul é uma solução que permite às equipes gerenciar conexões de rede de forma segura entre serviços e em ambientes multi-cloud e multi-provisionamento, oferecendo funcionalidades como descoberta de serviços, service mesh, governança de tráfego, atualização automática de infraestrutura de rede e muito mais.
Documentação oficial: Consul by HashiCorp
Código fonte aberto: hashicorp/consul
Consul é uma ferramenta de descoberta e registro de serviços de código aberto da empresa HashiCorp, que utiliza o algoritmo de eleição Raft. A ferramenta em si é desenvolvida em Go, tornando sua implantação muito leve. O Consul possui as seguintes características:
- Descoberta de serviços
- Registro de serviços
- Verificação de saúde
- Armazenamento chave-valor
- Multi-datacenter
Na verdade, o consul pode fazer mais do que apenas descoberta de serviços, também pode ser usado como centro de configuração distribuída. Existem muitas outras ferramentas de código aberto do mesmo tipo, como zookeeper, nacos, que não serão apresentadas em detalhes aqui.
Instalação
Para Ubuntu, execute os comandos abaixo para instalar usando 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 consulOu você pode baixar no site oficial Install Consul o pacote de instalação correspondente. Como o consul é desenvolvido em go, o pacote de instalação em si é apenas um arquivo executável binário, e a instalação é bastante conveniente. Após a instalação bem-sucedida, execute o seguinte comando para verificar a versão.
$ consul versionSe a saída for normal, não há problema
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)Início Rápido
Abaixo está como configurar rapidamente um único nó consul. Geralmente, um único nó é usado para testes durante o desenvolvimento. Se um único nó funcionar sem problemas, é provável que um cluster de vários nós também não tenha problemas. A configuração de um único nó é muito simples, requer apenas uma linha de comando
$ consul agent -dev -bind=192.168.48.141 -data-dir=/tmp/consul -ui -node=dev01Geralmente haverá a seguinte saída
==> 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}]"Uma breve explicação dos significados
agenté um subcomando, é o comando principal do consul,consul agentexecuta um novo agente consul, cada nó é um agente.devé o modo de execução do agent, existem três tipos no total:dev,client,serverbind, endereço de comunicação da rede local, porta padrão 8301, geralmente este valor é o endereço interno do servidoradvertise, endereço de comunicação da rede de longa distância, porta padrão 8302, geralmente este valor é o endereço externo do servidordata-dir, diretório de armazenamento de dadosconfig-dir, diretório de configuração, o consul lê todos os arquivos json no diretóriobootstrap, marca o servidor atual para entrar no modo de bootstrap, votando em si mesmo durante a eleição raft, não pode haver mais de um servidor neste modo no clusterbootstrap-expect, o número esperado de servidores no cluster, até que o número especificado seja atingido, o cluster não iniciará a eleição e votação, não pode ser usado simultaneamente combootstrap.retry-join, após o agent iniciar, ele continuará tentando se juntar aos nós especificados, também suporta os seguintes métodos de descoberta de serviços de provedoresaliyun aws azure digitalocean gce hcp k8s linode mdns os packet scaleway softlayer tencentcloud triton vsphereui, executa a interface Webnode, nome do nó de execução, deve ser único no cluster.
TIP
Para mais explicações sobre parâmetros do agent, vá para Agents - CLI Reference | Consul | HashiCorp Developer , note que alguns parâmetros só estão disponíveis na versão empresarial.
Após executar com sucesso, acesse 127.0.0.1:8500 para navegar na interface Web.

O ícone dev01 é uma estrela, indicando que é o nó líder.
Ao sair, para permitir que outros nós percebam a saída do nó atual, não é recomendado forçar a terminação do processo, use o comando
consul leaveou
consul force-leaveTambém pode usar ctrl+c para sair graciosamente do consul agent.
Conceitos
Este é um diagrama de um cluster consul, dividido em duas partes: plano de controle e plano de dados. O consul é responsável apenas pelo plano de controle, dividido em cluster de serviços e clientes, o cluster de serviços é dividido em seguidores e líderes, em geral, o cluster consul na figura constitui um datacenter. Abaixo está a explicação de alguns termos
- Agent (Agente): ou seria mais apropriado chamar de nó, cada agent é um processo de longa duração que expõe interfaces HTTP e DNS, responsável por verificações de saúde e sincronização de serviços.
- Server (Agente de Serviço): Como um servidor consul, suas responsabilidades incluem participar da eleição Raft, manter o estado do cluster, responder a consultas, trocar dados com outros datacenters e encaminhar consultas para líderes e outros datacenters.
- Client (Agente Cliente): client é stateless em comparação com o server, não participa da eleição Raft, sua única função é encaminhar todas as solicitações para o server, a única coisa que participa relacionada ao background é o pool de gossip LAN (LAN gossip pool).
- Leader (Líder): leader é o chefe de todos os servers, e só pode haver um líder, leader é eleito através do algoritmo de eleição Raft, cada líder tem seu próprio mandato, durante o mandato, qualquer solicitação recebida por outros servers deve ser passada ao leader, então os dados do leader são os mais atualizados.
- Gossip (Rumor): Consul é construído sobre Serf (outro produto da mesma empresa), usa o protocolo gossip, que é especializado em comunicação aleatória entre nós, similar ao UDP, consul usa este protocolo para notificação mútua entre clusters de serviços.
- Data Center (Datacenter): Um cluster consul dentro de uma rede local é chamado de datacenter, consul suporta múltiplos datacenters, o modo de comunicação entre múltiplos datacenters é WAN gossip.
TIP
Mais vocabulário e termos podem ser encontrados em Glossary | Consul | HashiCorp Developer
No cluster consul, o número de servers deve ser estritamente controlado, pois eles participam diretamente do LAN gossip e WAN gossip, eleição raft e precisam armazenar dados, quanto mais servers, maior o custo de comunicação. Enquanto o número de clients pode ser maior sem problemas, eles são apenas responsáveis pelo encaminhamento, não participam da eleição e ocupam poucos recursos, no cluster da figura, vários serviços se registram no server através do client, se um server falhar, o client procurará outros servers disponíveis.
Exemplo de Configuração de Cluster
Abaixo está um exemplo simples de configuração de cluster consul de múltiplos nós, primeiro prepare quatro máquinas virtuais

Das quatro máquinas virtuais, três servers e um client, a recomendação oficial é que o número de servers seja ímpar e preferencialmente maior ou igual a três. Aqui vm00-vm02 são usados como servers e vm03 como client.
Para o server, execute o seguinte comando para criar o agent server
consul agent -server -bind=vm_address -client=0.0.0.0 -data-dir=/tmp/consul/ -node=agent_name -uiPara o client, execute o seguinte comando para criar o agent client
consul agent -client=0.0.0.0 -bind=vm_address -data-dir=/tmp/consul/ -node=agent_name -uiOs comandos executados são respectivamente
# 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.138Algumas explicações de parâmetros
client,0.0.0.0significa permitir solicitações de todas as origens, se houver apenas o parâmetro client sem o parâmetro server, representa que o agent executará no modo client.
Depois que todos os agents estiverem em execução, a função de retry-join é equivalente a executar automaticamente o comando join, e continuará tentando após falhar, o tempo de retry padrão é 30s
$ consul join 192.168.48.138Após a conclusão do join, cada nó conhece a existência do outro, como vm00 especificou o modo bootstrap, ele é o líder padrão, se o modo bootstrap não for especificado, o nó especificado por todos os nós durante o join é o líder padrão, antes que o líder seja eleito, o cluster não pode funcionar normalmente, acessar a interface web retornará 500, e alguns comandos também não funcionarão corretamente. Se um nó no cluster especificar o modo bootstrap, então nenhum outro nó no cluster deve especificar o modo bootstrap, e outros nós também não devem usar o parâmetro bootstrap-expect, se usado será automaticamente desabilitado.
Neste ponto, execute o comando para visualizar as informações dos membros do datacenter no nó líder (na verdade, neste momento qualquer nó pode visualizar)
$ 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 do nó
- Address, endereço de comunicação
- Status,
alivesignifica ativo,leftsignifica offline - Type, tipo de agent, dois modos server e client
- Build, versão do consul usada pelo nó, consul pode trabalhar com nós de diferentes versões dentro de um certo escopo
- Protocol, refere-se à versão do protocolo Raft usada, este protocolo deve ser consistente em todos os nós
- DC, Data Center, datacenter, todos os nós na saída pertencem ao datacenter dc1
- Partition, partição à qual o nó pertence, funcionalidade da versão empresarial, cada nó só pode se comunicar com nós na mesma partição
- Segment, segmento ao qual o nó pertence, funcionalidade da versão empresarial

Da mesma forma, se quiser que um nó saia, deve usar consul leave para sair graciosamente e notificar outros nós que está prestes a sair, para múltiplos nós, a saída graciosa do nó é particularmente importante, pois isso está relacionado à consistência dos dados.
TIP
As máquinas virtuais desativaram todos os firewalls durante a demonstração, em ambientes de produção reais, por razões de segurança, eles devem ser ativados, para isso deve-se prestar atenção a todas as portas usadas pelo consul: Required Ports | Consul | HashiCorp Developer.
Em seguida, teste brevemente a consistência dos dados, adicione os seguintes dados na máquina virtual vm00
$ consul kv put sys_confg {"name":"consul"}
Success! Data written to: sys_confgApós salvar, acesse outros nós através da API HTTP e descobrirá que os dados também existem (o value é codificado em 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}]De fato, a funcionalidade de descoberta e registro de serviços fornecida pelo consul é transmitida para outros nós através do protocolo gossip, e quando qualquer nó se junta ao datacenter atual, todos os nós perceberão essa mudança.
Exemplo de Configuração de Multi-Datacenter
Prepare cinco máquinas virtuais, vm00-vm02 são o cluster do exemplo anterior, pertencem ao datacenter dc1, não mexa nele, vm03-vm04 pertencem ao datacenter dc2, o datacenter é dc1 por padrão quando o agent é iniciado.

TIP
Aqui para demonstração, apenas o server é configurado, omitindo o client.
Primeiro inicie vm03 respectivamente, tornando-o o líder padrão
$ consul agent -server -datacenter=dc2 -bind=192.168.48.141 -client=0.0.0.0 -data-dir=/tmp/consul/ -node=agent04 -ui -bootstrapInicie vm04, faça com que ele se junte automaticamente ao nó 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.141Neste ponto, verifique members em vm00 e vm03 respectivamente
# 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>Pode-se ver que o campo DC é diferente, como aqui é uma demonstração com máquina virtual, então estão todos na mesma sub-rede, na realidade dois datacenters podem ser clusters de servidores em locais diferentes. Em seguida, faça com que qualquer nó de dc1 se junte a qualquer nó de dc2, aqui faça vm01 se juntar a vm03
$ consul join -wan 192.168.48.141
Successfully joined cluster by contacting 1 nodes.Após o join bem-sucedido, execute o comando para visualizar members da rede de longa distância
$ 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
dc1Basta que qualquer nó de dc1 se junte a qualquer nó de dc2, todos os nós de ambos os datacenters perceberão essa mudança, ao visualizar members também é possível ver os nós de ambos os datacenters.
Em seguida, tente adicionar um dado KV no nó vm00
$ consul kv put name consul
Success! Data written to: nameTente ler os dados no nó vm01, pode-se ver que os dados do mesmo datacenter estão sincronizados
$ consul kv get name
consulDepois vá tentar ler os dados em vm03 em um datacenter diferente, descobrirá que os dados de datacenters diferentes não estão sincronizados.
$ consul kv get name
Error! No key exists at: nameSe quiser sincronização de dados entre múltiplos datacenters, pode consultar hashicorp/consul-replicate: Consul cross-DC KV replication daemon.
Registro e Descoberta de Serviços

Existem duas maneiras de registrar serviços no consul: registro por arquivo de configuração e registro por API. Para facilitar o teste, prepare antecipadamente um serviço Hello World (exemplo do artigo gRPC), implante duas cópias em locais diferentes. Para o método de registro por arquivo de configuração, consulte Register external services with Consul service discovery | Consul | HashiCorp Developer, aqui será apresentado apenas o registro através da API HTTP.
TIP
Para serviços locais (junto com o consul client), pode-se usar diretamente o registro de serviço do agent, caso contrário, deve-se usar o catalog register para registro.
O consul fornece SDKs de API HTTP, SDKs de outras linguagens podem ser consultados em Libraries and SDKs - HTTP API | Consul | HashiCorp Developer. Aqui baixe a dependência go
go get github.com/hashicorp/consul/apiRegistre ativamente o serviço no consul quando o serviço for iniciado e cancele o registro do serviço no consul quando o serviço for desligado, abaixo está um exemplo.
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 ser único
ID: "hello-service1",
Service: "hello-service",
// Implante duas cópias, uma com porta 8080, outra com porta 8081
Port: 8080,
}
)
// Registrar serviço
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)
}
// Cancelar registro do serviço
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()
// Escutar porta
listen, err := net.Listen("tcp", ":8080")
if err != nil {
panic(err)
}
// Criar servidor grpc
server := grpc.NewServer(
grpc.Creds(insecure.NewCredentials()),
)
// Registrar serviço
pb.RegisterSayHelloServer(server, &HelloRpc{})
log.Println("server running...")
// Executar
err = server.Serve(listen)
if err != nil {
panic(err)
}
}O código do cliente usa o resolvedor personalizado do consul para consultar o serviço correspondente no centro de registro e resolvê-lo para o endereço real.
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,
// Estratégia de round-robin
ServiceConfig: c.cc.ParseServiceConfig(
`{"loadBalancingPolicy":"round_robin"}`),
})
}
func (c ConsulResolver) ResolveNow(options resolver.ResolveNowOptions) {
c.resolve()
}
func (c ConsulResolver) Close() {
}O cliente registra o resolvedor ao iniciar
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() {
// Registrar builder
resolver.Register(
// Registrar resolvedor consul personalizado
myresolver.NewConsulResolverBuilder("192.168.48.138:8500"),
)
}
func main() {
// Estabelecer conexão, sem verificação de criptografia
conn, err := grpc.Dial("consul:hello-service",
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
if err != nil {
panic(err)
}
defer conn.Close()
// Criar cliente
client := hello2.NewSayHelloClient(conn)
for range time.Tick(time.Second) {
// Chamada remota
helloRep, err := client.Hello(context.Background(), &hello2.HelloReq{Name: "client"})
if err != nil {
panic(err)
}
log.Printf("received grpc resp: %+v", helloRep.String())
}
}Primeiro inicie o servidor, depois inicie o cliente, existem dois servidores, fornecendo o mesmo serviço, apenas com endereços diferentes, a estratégia de balanceamento de carga do cliente é round-robin, pode-se ver a partir do intervalo de tempo dos logs do servidor que a estratégia foi efetivada.
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"Acima está um caso simples de uso do consul combinado com gRPC para implementar registro e descoberta de serviços.
