Skip to main content

Service Discovery con Consul

Come i microservizi si trovano e comunicano tra loro usando Consul.

Cos'è Consul?

Consul è un tool di HashiCorp che fornisce:

  • Service Discovery - I servizi si registrano e si trovano automaticamente
  • Health Checking - Verifica che i servizi siano funzionanti
  • Key/Value Store - Configurazione distribuita
  • Service Mesh - Comunicazione sicura tra servizi (opzionale)

Registrazione Automatica

Quando definisci un service block nel job Nomad, il servizio viene registrato automaticamente in Consul:

service {
name = "auth"
port = "http"

tags = [
"api",
"v1"
]

check {
type = "http"
path = "/health"
interval = "10s"
timeout = "3s"
}
}

Verifica Registrazione

# Lista servizi registrati
consul catalog services

# Dettagli di un servizio
consul catalog nodes -service=auth

# Health status
consul health checks auth

Service Discovery nei Servizi

Usando DNS

Consul espone i servizi via DNS sulla porta 8600:

# Query DNS
dig @127.0.0.1 -p 8600 auth.service.consul

# Risultato
auth.service.consul. 0 IN A 10.0.1.15
auth.service.consul. 0 IN A 10.0.1.16

Configurazione DNS nel Container

task "devices" {
driver = "docker"

config {
image = "gcr.io/visla-gps/devices:latest"

# Usa Consul come DNS
dns_servers = ["${attr.unique.network.ip-address}"]
}

env {
# Usa nome DNS Consul
AUTH_SERVICE_URL = "http://auth.service.consul:8080"
}
}

Usando HTTP API

// Go example
package main

import (
"fmt"
consul "github.com/hashicorp/consul/api"
)

func main() {
client, _ := consul.NewClient(consul.DefaultConfig())

// Query per il servizio auth
services, _, _ := client.Health().Service("auth", "", true, nil)

for _, entry := range services {
fmt.Printf("Auth service at %s:%d\n",
entry.Service.Address,
entry.Service.Port)
}
}

Template Consul in Nomad

Nomad può generare configurazioni dinamiche usando i dati di Consul:

Service Discovery Template

task "api-gateway" {
template {
data = <<EOF
# upstream per nginx/envoy
{{ range service "auth" }}
upstream auth {
server {{ .Address }}:{{ .Port }};
}
{{ end }}

{{ range service "devices" }}
upstream devices {
server {{ .Address }}:{{ .Port }};
}
{{ end }}
EOF
destination = "local/upstream.conf"
change_mode = "signal"
change_signal = "SIGHUP"
}
}

Config dal KV Store

task "app" {
template {
data = <<EOF
{{ with secret "kv/data/app/config" }}
LOG_LEVEL={{ .Data.data.log_level }}
FEATURE_FLAGS={{ .Data.data.features }}
{{ end }}
EOF
destination = "secrets/config.env"
env = true
}
}

Health Checks

Tipi di Health Check

TipoUsoEsempio
httpAPI RESTGET /health
tcpPorte TCPConnessione riuscita
scriptScript customexit 0 = healthy
grpcgRPC healthStandard gRPC health

Esempio Completo

service {
name = "auth"
port = "http"

# HTTP check - controlla endpoint /health
check {
name = "HTTP Health"
type = "http"
path = "/health"
interval = "10s"
timeout = "3s"
}

# TCP check - verifica che la porta sia aperta
check {
name = "TCP Port"
type = "tcp"
interval = "10s"
timeout = "2s"
}

# Script check - controllo custom
check {
name = "Database Connection"
type = "script"
command = "/app/check-db.sh"
interval = "30s"
timeout = "10s"
}
}

Health Check Endpoint Consigliato

// handlers/health.go
func HealthHandler(db *sql.DB, redis *redis.Client) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
status := map[string]string{
"status": "healthy",
}

// Check database
if err := db.Ping(); err != nil {
status["database"] = "unhealthy"
status["status"] = "unhealthy"
} else {
status["database"] = "healthy"
}

// Check Redis
if err := redis.Ping(r.Context()).Err(); err != nil {
status["redis"] = "unhealthy"
status["status"] = "unhealthy"
} else {
status["redis"] = "healthy"
}

if status["status"] == "unhealthy" {
w.WriteHeader(http.StatusServiceUnavailable)
}

json.NewEncoder(w).Encode(status)
}
}

Traefik + Consul Integration

Traefik legge automaticamente i servizi da Consul e configura il routing:

Tags per Traefik

service {
name = "auth"
port = "http"

tags = [
# Abilita Traefik per questo servizio
"traefik.enable=true",

# Routing rule
"traefik.http.routers.auth.rule=PathPrefix(`/api/auth`)",

# Entrypoint (http/https)
"traefik.http.routers.auth.entrypoints=websecure",

# TLS
"traefik.http.routers.auth.tls=true",
"traefik.http.routers.auth.tls.certresolver=letsencrypt",

# Middleware (opzionale)
"traefik.http.routers.auth.middlewares=strip-api",
"traefik.http.middlewares.strip-api.stripprefix.prefixes=/api"
]
}

Consul UI

Accedi all'UI Consul per visualizzare:

  • Servizi registrati
  • Health status
  • Nodi del cluster
  • Key/Value store
http://<consul-server>:8500/ui

Consul UI


Best Practices

  1. Usa sempre health checks - Senza health checks, Consul non può rimuovere istanze non funzionanti

  2. Timeout appropriati - Health check timeout < interval

  3. Naming convention - Usa nomi consistenti per i servizi

  4. Tags per versioning - Aggiungi tag come v1, canary per routing avanzato

  5. Monitor health - Configura alerting su servizi unhealthy

# Alert quando un servizio è unhealthy
consul watch -type=checks -state=critical /scripts/alert.sh