Skip to main content

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​

ScenarioArchitettura Consigliata
Usi 1NCE VPN e vuoi sicurezza massimaServizi Stateful - VM dedicata
Vuoi scalare orizzontalmente il decoderQuesta 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:

  1. GPS invia primo pacchetto con IMEI
  2. Decoder verifica IMEI nel database
  3. 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:

MetricaDescrizioneAlert
decoder_connections_activeConnessioni TCP attive> 2000 per istanza
decoder_messages_per_secondThroughput< 10 (se atteso > 100)
decoder_errors_totalErrori parsing> 10/min
jvm_memory_used_bytesMemoria 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​

AspettoVPN + VM SingolaDNS + 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)