Scaling Decoder Senza VPN
Questa guida spiega come scalare orizzontalmente il decoder GPS senza utilizzare OpenVPN, usando DNS e TCP Load Balancer.
Quando Usare Questa Architettura
| Scenario | Architettura Consigliata |
|---|---|
| Usi 1NCE VPN e vuoi sicurezza massima | Servizi Stateful - VM dedicata |
| Vuoi scalare orizzontalmente il decoder | Questa guida - LB + Nomad |
| Hai GPS con SIM diverse (non 1NCE) | Questa guida - LB + Nomad |
| Hai 5000+ GPS e serve alta disponibilità | Questa guida - LB + Nomad |
Architettura
┌─────────────────────────────────────────────────────────────────────┐
│ │
│ GPS Devices │
│ (Qualsiasi connettività: 1NCE, Vodafone, Tim, etc.) │
│ │ │
│ │ DNS: decoder.visla.com → 34.xx.xx.xx │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ TCP Load Balancer (Layer 4) │ │
│ │ IP Statico: 34.xx.xx.xx │ │
│ │ Porta: 5052 │ │
│ │ │ │
│ │ ⚙️ session_affinity: CLIENT_IP │ │
│ │ ⚙️ timeout: 86400s (24 ore) │ │
│ └────────────────────────┬─────────────────────────────────────┘ │
│ │ │
│ ┌─────────────────┼─────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ Decoder │ │ Decoder │ │ Decoder │ │
│ │ Instance 1 │ │ Instance 2 │ │ Instance 3 │ │
│ │ Nomad │ │ Nomad │ │ Nomad │ │
│ └─────┬──────┘ └─────┬──────┘ └─────┬──────┘ │
│ │ │ │ │
│ └─────────────────┼─────────────────┘ │
│ ▼ │
│ ┌────────────────┐ │
│ │ Memorystore │ │
│ │ (Redis) │ │
│ └────────────────┘ │
│ │
│ Nomad Cluster │
└─────────────────────────────────────────────────────────────────────┘
Come Funziona CLIENT_IP Affinity
Il problema con le connessioni TCP persistenti è che un GPS deve rimanere connesso alla stessa istanza per tutta la durata della sessione.
Soluzione: Hash dell'IP Client
GPS-A (IP: 80.1.2.3) ──► hash("80.1.2.3") % 3 = 0 ──► Instance 1
GPS-B (IP: 80.1.2.4) ──► hash("80.1.2.4") % 3 = 1 ──► Instance 2
GPS-C (IP: 80.1.2.5) ──► hash("80.1.2.5") % 3 = 2 ──► Instance 3
GPS-D (IP: 80.1.2.6) ──► hash("80.1.2.6") % 3 = 0 ──► Instance 1
Risultato: Lo stesso GPS va sempre alla stessa istanza, finché il numero di istanze non cambia.
Cosa Succede al Failover
Situazione normale (3 istanze):
┌─────────┬────────────┐
│ GPS │ Istanza │
├─────────┼────────────┤
│ GPS-A │ Instance 1 │
│ GPS-B │ Instance 2 │
│ GPS-C │ Instance 3 │
└─────────┴────────────┘
Instance 2 muore → Load Balancer ricalcola:
┌─────────┬────────────┬─────────────┐
│ GPS │ Prima │ Dopo │
├─────────┼────────────┼─────────────┤
│ GPS-A │ Instance 1 │ Instance 1 │ ✅ Invariato
│ GPS-B │ Instance 2 │ Instance 1 │ 🔄 Riconnesso
│ GPS-C │ Instance 3 │ Instance 3 │ ✅ Invariato
└─────────┴────────────┴─────────────┘
[!TIP] I GPS sono progettati per riconnettersi automaticamente in caso di disconnessione. Il failover causa solo pochi secondi di interruzione.
Implementazione
1. Creare IP Statico
# Crea IP statico per il Load Balancer
gcloud compute addresses create decoder-lb-ip \
--region=europe-west1
# Visualizza l'IP allocato
gcloud compute addresses describe decoder-lb-ip \
--region=europe-west1 \
--format="get(address)"
# Output esempio: 34.76.123.45
2. Configurare DNS
Aggiungi un record A nel tuo DNS provider:
decoder.visla.com A 34.76.123.45 TTL: 300
3. Creare Health Check
gcloud compute health-checks create tcp decoder-health \
--port=5052 \
--check-interval=10s \
--timeout=5s \
--healthy-threshold=2 \
--unhealthy-threshold=3
4. Creare Backend Service
gcloud compute backend-services create decoder-backend \
--protocol=TCP \
--health-checks=decoder-health \
--session-affinity=CLIENT_IP \
--timeout=86400s \
--global
[!IMPORTANT]
--timeout=86400s(24 ore) è fondamentale per connessioni TCP long-lived dei GPS.
5. Creare Instance Group
# Crea Instance Group (unmanaged per Nomad)
gcloud compute instance-groups unmanaged create decoder-group \
--zone=europe-west1-b
# Aggiungi i nodi Nomad al gruppo
gcloud compute instance-groups unmanaged add-instances decoder-group \
--zone=europe-west1-b \
--instances=nomad-client-1,nomad-client-2,nomad-client-3
# Configura named port
gcloud compute instance-groups unmanaged set-named-ports decoder-group \
--zone=europe-west1-b \
--named-ports=decoder:5052
# Aggiungi al backend service
gcloud compute backend-services add-backend decoder-backend \
--instance-group=decoder-group \
--instance-group-zone=europe-west1-b \
--global
6. Creare Forwarding Rule
gcloud compute forwarding-rules create decoder-lb \
--address=decoder-lb-ip \
--ports=5052 \
--backend-service=decoder-backend \
--global
7. Configurare Firewall
# Permetti traffico dal Load Balancer ai nodi
gcloud compute firewall-rules create allow-decoder-lb \
--allow=tcp:5052 \
--source-ranges=130.211.0.0/22,35.191.0.0/16 \
--target-tags=nomad-client
# Permetti health checks
gcloud compute firewall-rules create allow-decoder-health \
--allow=tcp:5052 \
--source-ranges=35.191.0.0/16,130.211.0.0/22 \
--target-tags=nomad-client
Nomad Job
job "decoder" {
datacenters = ["dc1"]
type = "service"
group "decoder" {
count = 3 # Numero di istanze
# Spread su nodi diversi per HA
constraint {
operator = "distinct_hosts"
value = "true"
}
network {
port "decoder" {
static = 5052 # Porta fissa richiesta per TCP LB
}
}
service {
name = "decoder"
port = "decoder"
tags = ["tcp", "gps"]
check {
type = "tcp"
interval = "10s"
timeout = "2s"
}
}
task "decoder" {
driver = "docker"
config {
image = "gcr.io/visla-gps/decoder:latest"
ports = ["decoder"]
}
template {
data = <<EOF
REDIS_URL=redis://{{ key "config/redis/host" }}:6379
JAVA_OPTS=-Xmx4g -XX:+UseG1GC -XX:MaxGCPauseMillis=200
NETTY_WORKER_THREADS=8
EOF
destination = "secrets/.env"
env = true
}
resources {
cpu = 2000 # 2 vCPU
memory = 4096 # 4 GB RAM
}
# Graceful shutdown per connessioni attive
kill_timeout = "30s"
}
# Rolling update: 1 alla volta
update {
max_parallel = 1
min_healthy_time = "30s"
healthy_deadline = "5m"
auto_revert = true
}
# Restart policy
restart {
attempts = 3
interval = "5m"
delay = "15s"
mode = "delay"
}
}
}
Configurazione GPS
Opzione A: DNS (Consigliata)
SERVER,decoder.visla.com,5052#
APN,iot.1nce.net#
Opzione B: IP Diretto
SERVER,34.76.123.45,5052#
APN,iot.1nce.net#
[!TIP] Usa il DNS per flessibilità. Se l'IP cambia, basta aggiornare il record DNS senza riconfigurare i GPS.
Sicurezza
Senza VPN, la porta 5052 è esposta su Internet. Ecco come proteggerla:
1. Firewall - Solo IP noti
Se i GPS usano SIM 1NCE, limita agli IP 1NCE:
gcloud compute firewall-rules create allow-decoder-1nce \
--allow=tcp:5052 \
--source-ranges=185.48.172.0/22,185.48.176.0/22 \
--target-tags=nomad-client \
--priority=1000
# Nega tutto il resto
gcloud compute firewall-rules create deny-decoder-all \
--action=DENY \
--rules=tcp:5052 \
--source-ranges=0.0.0.0/0 \
--target-tags=nomad-client \
--priority=2000
2. Autenticazione IMEI
Il decoder già implementa autenticazione basata su IMEI:
- GPS invia primo pacchetto con IMEI
- Decoder verifica IMEI nel database
- IMEI non valido → Connessione chiusa
// Nel decoder (già implementato)
DeviceSession getDeviceSession(Channel channel, Object msg) {
String imei = parseImei(msg);
Device device = deviceManager.lookup(imei);
if (device == null) {
channel.close(); // Connessione rifiutata
return null;
}
return createSession(channel, device);
}
3. Rate Limiting
Aggiungi Cloud Armor per protezione DDoS:
gcloud compute security-policies create decoder-policy
gcloud compute security-policies rules create 1000 \
--security-policy=decoder-policy \
--action=throttle \
--rate-limit-threshold-count=100 \
--rate-limit-threshold-interval-sec=60 \
--conform-action=allow \
--exceed-action=deny-429
gcloud compute backend-services update decoder-backend \
--security-policy=decoder-policy \
--global
Monitoring
Metriche Chiave
# Connessioni attive per istanza
docker exec decoder netstat -an | grep :5052 | grep ESTABLISHED | wc -l
# Memory usage
docker stats decoder --no-stream
# Logs
docker logs decoder --tail 100 -f
Grafana Dashboard
Metriche da monitorare:
| Metrica | Descrizione | Alert |
|---|---|---|
decoder_connections_active | Connessioni TCP attive | > 2000 per istanza |
decoder_messages_per_second | Throughput | < 10 (se atteso > 100) |
decoder_errors_total | Errori parsing | > 10/min |
jvm_memory_used_bytes | Memoria JVM | > 80% heap |
Scaling
Scaling Orizzontale
# Scala a 5 istanze
nomad job scale decoder decoder 5
# Verifica
nomad job status decoder
Scaling Automatico (HPA)
# Nel job Nomad
scaling {
enabled = true
min = 2
max = 10
policy {
evaluation_interval = "30s"
cooldown = "5m"
check "cpu" {
source = "nomad-apm"
query = "avg_cpu"
strategy "target-value" {
target = 70
}
}
}
}
Confronto Architetture
| Aspetto | VPN + VM Singola | DNS + Load Balancer |
|---|---|---|
| Scaling | ⬆️ Solo verticale | ↔️ Orizzontale |
| HA | ⚠️ Single point of failure | ✅ Multi-istanza |
| Sicurezza | ✅ Tunnel cifrato | ⚠️ Firewall + IMEI auth |
| Complessità | ⚠️ Config VPN | ✅ Standard GCP |
| Costo | ~€100/mese | ~€120/mese (+ LB €20) |
| Comunicazione bidirezionale | ✅ Sì | ❌ Solo GPS → Server |
| Max GPS consigliati | ~2000 | ~10000+ |
Checklist Implementazione
- Creare IP statico GCP
- Configurare record DNS
- Creare Health Check TCP
- Creare Backend Service con CLIENT_IP affinity
- Creare Instance Group con nodi Nomad
- Creare Forwarding Rule
- Configurare Firewall rules
- Deploy Nomad job decoder
- Testare connessione GPS
- Configurare monitoring/alerting
- Testare failover (kill un'istanza)