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)