Consul

consul es una solución que permite a los equipos gestionar de forma segura las conexiones de red entre servicios y entornos multi-nube, proporcionando descubrimiento de servicios, malla de servicios, gobernanza de tráfico, actualización automática de infraestructura de red y otras funcionalidades.
Documentación oficial: Consul by HashiCorp
Código abierto: hashicorp/consul
Consul es una herramienta de descubrimiento y registro de servicios de código abierto desarrollada por HashiCorp, utiliza el algoritmo de elección Raft y está escrita en Go, lo que hace que su despliegue sea muy ligero. Consul tiene las siguientes características:
- Descubrimiento de servicios
- Registro de servicios
- Comprobación de estado
- Almacenamiento clave-valor
- Multi-datacenter
En realidad, consul puede hacer más que solo descubrimiento de servicios, también puede funcionar como centro de configuración distribuido. Hay muchas otras herramientas de código abierto similares, como zookeeper, nacos, que no se presentarán en detalle aquí.
Instalación
Para Ubuntu, ejecute los siguientes comandos 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 consulO también puede descargar el paquete de instalación correspondiente desde el sitio web oficial Install Consul. Dado que consul está desarrollado en Go, el paquete de instalación en sí es solo un archivo binario ejecutable, por lo que la instalación es bastante conveniente. Después de una instalación exitosa, ejecute el siguiente comando para verificar la versión.
$ consul versionSi la salida es normal, no hay 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)Inicio rápido
A continuación se presenta cómo configurar rápidamente un solo nodo de consul. Generalmente, un solo nodo se usa para pruebas durante el desarrollo. Si un solo nodo funciona sin problemas, es muy probable que un clúster de múltiples nodos también funcione. Configurar un solo nodo es muy simple, solo requiere un comando:
$ consul agent -dev -bind=192.168.48.141 -data-dir=/tmp/consul -ui -node=dev01Generalmente habrá la siguiente salida:
==> 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}]"Una breve explicación de los parámetros:
agentes un subcomando, es el comando principal de consul.consul agentejecuta un nuevo agente de consul. Cada nodo es un agente.deves el modo de ejecución del agente. Hay tres modos en total:dev,client,server.bindes la dirección de comunicación de red local (LAN), el puerto predeterminado es 8301. Generalmente, este valor es la dirección IP interna del servidor.advertisees la dirección de comunicación de red de área amplia (WAN), el puerto predeterminado es 8302. Generalmente, este valor es la dirección IP externa del servidor.data-dires el directorio de almacenamiento de datos.config-dires el directorio de configuración. Consul leerá todos los archivos json en este directorio.bootstrapindica que el servidor actual entra en modo de arranque. Se votará a sí mismo durante la elección Raft. No puede haber más de un servidor en este modo en el clúster.bootstrap-expectes el número esperado de servidores en el clúster. Hasta que no se alcance este número, el clúster no comenzará la elección. No se puede usar al mismo tiempo quebootstrap.retry-joindespués de que el agente se inicia, intentará constantemente unirse a los nodos especificados. También admite los siguientes métodos de descubrimiento de servicios de proveedores:aliyun aws azure digitalocean gce hcp k8s linode mdns os packet scaleway softlayer tencentcloud triton vsphereuiejecuta la interfaz web.nodees el nombre del nodo de ejecución, debe ser único en el clúster.
TIP
Para más explicaciones sobre los parámetros del agente, visite Agents - CLI Reference | Consul | HashiCorp Developer. Tenga en cuenta que algunos parámetros solo están disponibles en la versión empresarial.
Después de ejecutar correctamente, visite 127.0.0.1:8500 para navegar por la interfaz web.

El ícono de dev01 es una estrella, lo que indica que es el nodo líder.
Al salir, para permitir que otros nodos detecten la salida del nodo actual, no se recomienda finalizar forzosamente el proceso. Puede usar el comando:
consul leaveO
consul force-leaveTambién puede usar ctrl+c para permitir que el agente de consul salga elegantemente.
Conceptos
Este es un diagrama de un clúster de consul, dividido en dos partes: el plano de control y el plano de datos. Consul solo se encarga del plano de control, dividido en clúster de servicios y clientes. El clúster de servicios se divide en seguidores y líderes. En general, el clúster de consul en el diagrama constituye un centro de datos. A continuación se explican algunos términos:
- Agent (Agente): o sería más apropiado llamarlo nodo. Cada agente es un proceso guardián que se ejecuta durante mucho tiempo. Exponen interfaces HTTP y DNS, y son responsables de las comprobaciones de estado y la sincronización de servicios.
- Server (Agente de servicio): como servidor de consul, sus responsabilidades incluyen participar en la elección Raft, mantener el estado del clúster, responder consultas, intercambiar datos con otros centros de datos y reenviar consultas a líderes y otros centros de datos.
- Client (Agente cliente): el cliente es sin estado en comparación con el servidor. No participa en la elección Raft. Su única función es reenviar todas las solicitudes al servidor. Lo único que participa en relación con el fondo es el reenvío de rumores de red local (LAN gossip pool).
- Leader (Líder): el líder es el jefe de todos los servidores, y solo puede haber un líder. El líder se elige mediante el algoritmo de elección Raft. Cada líder tiene su propio mandato. Durante su mandato, cualquier solicitud recibida por otros servidores debe informarse al líder, por lo que los datos del líder son los más actualizados.
- Gossip (Rumor): Consul está construido sobre Serf (otro producto de la misma empresa). Utiliza el protocolo gossip, que está diseñado para la comunicación aleatoria entre nodos, similar a UDP. Consul usa este protocolo para notificarse mutuamente en el clúster de servicios.
- Data Center (Centro de datos): un clúster de consul dentro de una red local se llama centro de datos. Consul admite múltiples centros de datos. La forma de comunicación entre múltiples centros de datos es WAN gossip.
TIP
Puede obtener más información sobre más vocabulario y términos en Glossary | Consul | HashiCorp Developer.
En un clúster de consul, el número de servidores debe controlarse estrictamente, ya que participan directamente en LAN gossip y WAN gossip, elección Raft y deben almacenar datos. Cuantos más servidores, mayor es el costo de comunicación. El número de clientes puede ser mayor sin problema, ya que solo son responsables del reenvío y no participan en la elección, ocupando muy pocos recursos. En el clúster del diagrama, cada servicio se registra en el servidor a través del cliente. Si un servidor falla, el cliente buscará automáticamente otros servidores disponibles.
Ejemplo de configuración de clúster
A continuación se presenta un ejemplo simple de configuración de un clúster de consul de múltiples nodos. Primero prepare cuatro máquinas virtuales:

De las cuatro máquinas virtuales, tres son servidores y una es cliente. La recomendación oficial es que el número de servidores sea preferiblemente impar y preferiblemente mayor o igual a tres. Aquí usaremos vm00-vm02 como servidores y vm03 como cliente.
Para el servidor, ejecute el siguiente comando para crear un agente de servidor:
consul agent -server -bind=vm_address -client=0.0.0.0 -data-dir=/tmp/consul/ -node=agent_name -uiPara el cliente, ejecute el siguiente comando para crear un agente de cliente:
consul agent -client=0.0.0.0 -bind=vm_address -data-dir=/tmp/consul/ -node=agent_name -uiLos comandos ejecutados son 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.138Explicación de algunos parámetros:
client:0.0.0.0significa permitir solicitudes de todas las fuentes. Si solo hay parámetro client sin parámetro server, representa que el agente se ejecutará en modo cliente.
Después de que todos los agentes se ejecuten correctamente, la función de retry-join es equivalente a ejecutar automáticamente el comando join. Si falla, seguirá intentando. El tiempo de reintento predeterminado es de 30 segundos:
$ consul join 192.168.48.138Después de completar el join, cada nodo conoce la existencia del otro. Dado que vm00 especificó el modo bootstrap, será el líder predeterminado. Si no se especifica el modo bootstrap, el nodo especificado en el join será el líder predeterminado. Antes de que se elija el líder, el clúster no puede funcionar normalmente. El acceso a la interfaz web devolverá 500 y algunos comandos tampoco funcionarán correctamente. Si hay un nodo en el clúster que especifica el modo bootstrap, entonces ningún otro nodo en el clúster debe especificar el modo bootstrap. Al mismo tiempo, otros nodos no deben usar el parámetro bootstrap-expect. Si se usa, se deshabilitará automáticamente.
En este momento, ejecute el siguiente comando en el nodo líder (en realidad, en este momento se puede verificar en cualquier nodo) para ver la información de los miembros del centro de datos:
$ 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: nombre del nodo
- Address: dirección de comunicación
- Status:
alivesignifica activo,leftsignifica fuera de línea - Type: tipo de agente, dos modos: server y client
- Build: versión de consul utilizada por el nodo. Consul puede funcionar con nodos de diferentes versiones dentro de un cierto rango
- Protocol: se refiere a la versión del protocolo Raft utilizado. Este protocolo debe ser consistente en todos los nodos
- DC: Data Center, centro de datos. Todos los nodos en la salida pertenecen al centro de datos dc1
- Partition: partición a la que pertenece el nodo, función de la versión empresarial. Cada nodo solo puede comunicarse con nodos de la misma partición
- Segment: segmento de red al que pertenece el nodo, función de la versión empresarial

De la misma manera, si desea que un nodo salga, debe usar consul leave para permitir que el nodo salga elegantemente y notifique a otros nodos que va a salir. En el caso de múltiples nodos, la salida elegante de un nodo es particularmente importante, ya que esto está relacionado con la consistencia de los datos.
TIP
Durante la demostración, las máquinas virtuales tenían todos los firewalls desactivados. En un entorno de producción real, por razones de seguridad, se deben habilitar. Para ello, debe prestar atención a todos los puertos utilizados por consul: Required Ports | Consul | HashiCorp Developer.
A continuación, pruebe brevemente la consistencia de los datos. Agregue los siguientes datos en la máquina virtual vm00:
$ consul kv put sys_confg {"name":"consul"}
Success! Data written to: sys_confgDespués de guardar, acceda a otros nodos a través de la API HTTP y descubrirá que los datos también existen (el valor está codificado 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}]De hecho, la función de descubrimiento y registro de servicios proporcionada por consul se transmite a otros nodos a través del protocolo gossip. Y cuando cualquier nodo se une al centro de datos actual, todos los nodos detectarán este cambio.
Ejemplo de configuración de múltiples centros de datos
Prepare cinco máquinas virtuales. vm00-vm02 son el clúster del ejemplo anterior y pertenecen al centro de datos dc1. No los toque. vm03-vm04 pertenecen al centro de datos dc2. El centro de datos es dc1 por defecto cuando se inicia el agente.

TIP
Aquí, para la demostración, solo se configuran servidores, omitiendo los clientes.
Primero inicie vm03 respectivamente, estableciéndolo como el líder predeterminado:
$ consul agent -server -datacenter=dc2 -bind=192.168.48.141 -client=0.0.0.0 -data-dir=/tmp/consul/ -node=agent04 -ui -bootstrapInicie vm04, permitiéndole unirse automáticamente 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.141En este momento, verifique members en vm00 y 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>Se puede ver que el campo DC es diferente. Dado que aquí es una demostración con máquinas virtuales, todas están en el mismo segmento de red. En la realidad, dos centros de datos pueden ser clústeres de servidores en ubicaciones diferentes. A continuación, permita que cualquier nodo de dc1 se una a cualquier nodo de dc2. Aquí haremos que vm01 se una a vm03:
$ consul join -wan 192.168.48.141
Successfully joined cluster by contacting 1 nodes.Después de que el join sea exitoso, ejecute el comando para ver los members de la red de área amplia (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
dc1Siempre que cualquier nodo de dc1 se una a cualquier nodo de dc2, todos los nodos de ambos centros de datos detectarán este cambio. Al verificar members, también puede ver los nodos de ambos centros de datos.
A continuación, intente agregar un dato KV en el nodo vm00:
$ consul kv put name consul
Success! Data written to: nameIntente leer los datos en el nodo vm01. Puede ver que los datos dentro del mismo centro de datos están sincronizados:
$ consul kv get name
consulLuego intente leer los datos en vm03 en un centro de datos diferente. Descubrirá que los datos entre diferentes centros de datos no están sincronizados:
$ consul kv get name
Error! No key exists at: nameSi desea sincronizar datos entre múltiples centros de datos, puede consultar hashicorp/consul-replicate: Consul cross-DC KV replication daemon.
Registro y descubrimiento de servicios

Hay dos formas de registrar servicios en consul: registro mediante archivo de configuración y registro mediante API. Para facilitar las pruebas, aquí preparamos de antemano un servicio Hello World (ejemplo del artículo gRPC), desplegado en dos instancias en diferentes ubicaciones. Para el método de registro mediante archivo de configuración, puede consultar Register external services with Consul service discovery | Consul | HashiCorp Developer. Aquí solo se presentará el registro a través de la API HTTP.
TIP
Para servicios locales (junto con el cliente de consul), puede usar directamente el registro de servicio del agente. De lo contrario, debe usar el registro de catálogo para registrarse.
Consul proporciona SDK para la API HTTP. Para SDK en otros idiomas, consulte Libraries and SDKs - HTTP API | Consul | HashiCorp Developer. Aquí descargamos la dependencia de Go:
go get github.com/hashicorp/consul/apiRegistre activamente el servicio en consul cuando el servicio se inicie y cancele el registro del servicio en consul cuando el servicio se cierre. A continuación se muestra un ejemplo:
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{
// Debe ser único
ID: "hello-service1",
Service: "hello-service",
// Desplegado en dos instancias, un puerto es 8080, el otro es 8081
Port: 8080,
}
)
// Registrar servicio
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 de servicio
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()
// Escuchar puerto
listen, err := net.Listen("tcp", ":8080")
if err != nil {
panic(err)
}
// Crear servidor gRPC
server := grpc.NewServer(
grpc.Creds(insecure.NewCredentials()),
)
// Registrar servicio
pb.RegisterSayHelloServer(server, &HelloRpc{})
log.Println("server running...")
// Ejecutar
err = server.Serve(listen)
if err != nil {
panic(err)
}
}El código del cliente utiliza un resolvedor personalizado de consul para consultar el servicio correspondiente en el centro de registro y resolverlo a una dirección 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,
// Estrategia de round-robin
ServiceConfig: c.cc.ParseServiceConfig(
`{"loadBalancingPolicy":"round_robin"}`),
})
}
func (c ConsulResolver) ResolveNow(options resolver.ResolveNowOptions) {
c.resolve()
}
func (c ConsulResolver) Close() {
}El cliente registra el resolvedor al iniciarse:
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 personalizado de consul
myresolver.NewConsulResolverBuilder("192.168.48.138:8500"),
)
}
func main() {
// Establecer conexión, sin verificación de cifrado
conn, err := grpc.Dial("consul:hello-service",
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
if err != nil {
panic(err)
}
defer conn.Close()
// Crear cliente
client := hello2.NewSayHelloClient(conn)
for range time.Tick(time.Second) {
// Llamada remota
helloRep, err := client.Hello(context.Background(), &hello2.HelloReq{Name: "client"})
if err != nil {
panic(err)
}
log.Printf("received grpc resp: %+v", helloRep.String())
}
}Primero inicie el servidor, luego inicie el cliente. Hay dos servidores que proporcionan el mismo servicio, solo que las direcciones son diferentes. La estrategia de equilibrio de carga del cliente es round-robin. Se puede ver por el intervalo de tiempo de los registros del servidor que la estrategia está funcionando.
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"Este es un caso simple de uso de consul combinado con gRPC para implementar el registro y descubrimiento de servicios.
