Servizi Stateful (Decoder + VPN)
Questa guida affronta la sfida di scalare servizi che mantengono connessioni TCP persistenti, come il Decoder GPS con OpenVPN.
Il Problemaβ
I dispositivi GPS stabiliscono connessioni TCP long-lived con il decoder. Questo crea diverse sfide:
- Connessioni persistenti: Un GPS mantiene la stessa connessione per ore/giorni
- Stato della sessione: Il decoder traccia quale device Γ¨ connesso su quale socket
- 1NCE VPN Limit: Solo 1 tunnel VPN attivo per account 1NCE
β A quale istanza si connette il GPS?
β
βββββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββ
β Nomad Cluster β β
β βΌ β
β βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ β
β β Decoder + β β Decoder + β β Decoder + β β
β β OpenVPN Client β β OpenVPN Client β β OpenVPN Client β β
β β Instance 1 β β Instance 2 β β Instance 3 β β
β ββββββββββ¬βββββββββ ββββββββββ¬βββββββββ ββββββββββ¬βββββββββ β
β β β β β β β β
β ββββββββββββββββββββ΄βββββββββββββββββββ β
β β β
β Solo 1 puΓ² connettersi! β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Differenza Stateless vs Statefulβ
| Tipo | Esempio | ScalabilitΓ |
|---|---|---|
| Stateless | Auth, Positions, Geofences | β Facile - Load Balancer standard |
| Stateful | Decoder, WebSocket | β οΈ Richiede sticky sessions o architettura dedicata |
Architettura Consigliata: Single Entry Pointβ
Il decoder rimane su una VM dedicata (non in Nomad), mentre tutti gli altri servizi scalano liberamente:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β
β GPS Devices β
β (100.64.x.x via 1NCE) β
β β β
β βΌ β
β βββββββββββββββ β
β β 1NCE VPN β βββ Solo 1 tunnel possibile β
β β Tunnel β β
β ββββββββ¬βββββββ β
β β β
β βΌ β
β ββββββββββββββββββββββββββββββββββββββββ β
β β VM Dedicata (Compute Engine) β β
β β n1-standard-4 (4 vCPU, 15GB RAM) β β
β β β β
β β ββββββββββββββ ββββββββββββββββββ β β
β β β OpenVPN ββββ Decoder β β β
β β β Client β β (Traccar) β β β
β β ββββββββββββββ βββββββββ¬βββββββββ β β
β β β β β
β β Redis XADD positions β β
β βββββββββββββββββββββββββββββ¬βββββββββββ β
β β β
β βΌ β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Memorystore (Redis) β β
β β (Gestito da GCP) β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β ββββββββββββββββββββββΌβββββββββββββββββββββ β
β βΌ βΌ βΌ β
β ββββββββββββββ ββββββββββββββ ββββββββββββββ β
β β WebSocket β β Positions β β DB Persist β β
β β x3 istanze β β x3 istanze β β x2 istanze β β
β ββββββββββββββ ββββββββββββββ ββββββββββββββ β
β β
β Nomad Cluster (tutti scalabili) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
PerchΓ© Questa Architettura Funzionaβ
-
Decoder non serve scalare orizzontalmente:
- GiΓ ottimizzato per 2000+ connessioni
- CPU-bound solo per parsing (basso overhead)
- Scala verticalmente (piΓΉ CPU/RAM sulla VM)
-
Disaccoppiamento via Redis:
- Decoder pubblica su Redis Stream
- Tutti gli altri servizi consumano da Redis
- Redis Γ¨ il "bus" centrale che abilita scaling
-
CompatibilitΓ 1NCE VPN:
- Solo 1 VM ha il client VPN
- Rispetta il limite di 1 connessione
Implementazioneβ
VM Decoder (Compute Engine)β
# Crea VM dedicata per decoder
gcloud compute instances create decoder-vm \
--project=visla-gps-prod \
--zone=europe-west1-b \
--machine-type=n1-standard-4 \
--image-family=cos-stable \
--image-project=cos-cloud \
--boot-disk-size=50GB \
--boot-disk-type=pd-ssd \
--tags=decoder
# Nessuna porta pubblica necessaria (VPN!)
# Solo SSH per amministrazione
gcloud compute firewall-rules create allow-ssh-decoder \
--allow tcp:22 \
--source-ranges=<YOUR_IP>/32 \
--target-tags=decoder
Docker Compose (sulla VM Decoder)β
version: '3.8'
services:
openvpn-client:
image: dperson/openvpn-client
container_name: openvpn-client
cap_add:
- NET_ADMIN
devices:
- /dev/net/tun
volumes:
- ./openvpn-config:/vpn:ro
environment:
FIREWALL: ''
ROUTE: '100.64.0.0/10'
restart: unless-stopped
healthcheck:
test: ["CMD", "ping", "-c", "1", "10.66.5.1"]
interval: 30s
timeout: 10s
retries: 3
decoder:
image: gcr.io/visla-gps/decoder:latest
container_name: decoder
depends_on:
- openvpn-client
# Condivide la rete con OpenVPN per accedere ai GPS
network_mode: "service:openvpn-client"
environment:
# Redis gestito (Memorystore)
REDIS_URL: redis://10.0.0.5:6379
# Performance tuning
JAVA_OPTS: "-Xmx8g -XX:+UseG1GC -XX:MaxGCPauseMillis=200"
NETTY_WORKER_THREADS: "16"
restart: unless-stopped
logging:
driver: "json-file"
options:
max-size: "50m"
max-file: "5"
Connessione a Redis Gestitoβ
La VM decoder si connette a Memorystore tramite Private IP:
# Ottieni IP Memorystore
gcloud redis instances describe visla-redis \
--region=europe-west1 \
--format="get(host)"
# Output: 10.0.0.5
Configura la VM per accedere alla VPC:
# La VM deve essere nella stessa VPC di Memorystore
gcloud compute instances create decoder-vm \
--network=visla-vpc \
--subnet=visla-subnet-europe \
# ... altri parametri
Scaling del Decoderβ
Scaling Verticaleβ
Il decoder scala verticalmente. Ecco le raccomandazioni:
| GPS Devices | Machine Type | vCPU | RAM | Costo/mese |
|---|---|---|---|---|
| 1-500 | n1-standard-2 | 2 | 7.5 GB | ~β¬50 |
| 500-1500 | n1-standard-4 | 4 | 15 GB | ~β¬100 |
| 1500-3000 | n1-standard-8 | 8 | 30 GB | ~β¬200 |
| 3000+ | n1-highmem-8 | 8 | 52 GB | ~β¬280 |
Tuning JVMβ
# docker-compose.yml
decoder:
environment:
JAVA_OPTS: >
-Xmx${HEAP_SIZE:-8g}
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:+ParallelRefProcEnabled
-XX:+UseStringDeduplication
NETTY_WORKER_THREADS: "${WORKER_THREADS:-16}"
Monitoring Risorseβ
# SSH nella VM
gcloud compute ssh decoder-vm --zone=europe-west1-b
# Monitor in tempo reale
docker stats decoder
# Connessioni TCP attive
docker exec decoder netstat -an | grep ESTABLISHED | wc -l
Alternative: Senza VPN 1NCEβ
Se non usi 1NCE VPN o vuoi scalare orizzontalmente il decoder, ecco le alternative:
Opzione A: Load Balancer TCP con Sticky Sessionsβ
GPS Devices βββΊ TCP Load Balancer βββΊ Decoder Instances
(Client IP Affinity)
# Terraform - GCP TCP Load Balancer
resource "google_compute_backend_service" "decoder" {
name = "decoder-backend"
protocol = "TCP"
port_name = "decoder"
# Sticky sessions: stesso GPS β stessa istanza
session_affinity = "CLIENT_IP"
# Timeout lungo per connessioni persistenti
timeout_sec = 86400 # 24 ore
backend {
group = google_compute_instance_group_manager.decoder.instance_group
}
health_checks = [google_compute_health_check.decoder.id]
}
resource "google_compute_health_check" "decoder" {
name = "decoder-health"
tcp_health_check {
port = 5052
}
check_interval_sec = 10
timeout_sec = 5
healthy_threshold = 2
unhealthy_threshold = 3
}
Nomad Job per Decoder (senza VPN):
job "decoder" {
datacenters = ["dc1"]
type = "service"
group "decoder" {
count = 3 # Multiple istanze
network {
port "decoder" {
static = 5052 # Porta fissa per load balancer
}
}
service {
name = "decoder"
port = "decoder"
check {
type = "tcp"
interval = "10s"
timeout = "2s"
}
}
task "decoder" {
driver = "docker"
config {
image = "gcr.io/visla-gps/decoder:latest"
ports = ["decoder"]
}
env {
REDIS_URL = "redis://{{ key "config/redis/host" }}:6379"
JAVA_OPTS = "-Xmx4g -XX:+UseG1GC"
}
resources {
cpu = 2000 # 2 vCPU
memory = 4096 # 4 GB
}
}
}
}
Opzione B: Sharding per Range IPβ
Partiziona i GPS su decoder specifici:
GPS IP 100.64.0.0/12 β decoder-1.visla.com
GPS IP 100.64.16.0/12 β decoder-2.visla.com
GPS IP 100.64.32.0/12 β decoder-3.visla.com
[!WARNING] Questa opzione richiede configurazione manuale dei GPS e non Γ¨ consigliata per la maggior parte dei casi.
Failover e Alta DisponibilitΓ β
VM Singola: Rischiβ
Con una singola VM decoder, un failure causa downtime. Mitigazioni:
-
Preemptible VM Monitoring:
# Alert se la VM va giΓΉ
gcloud monitoring policies create \
--condition="compute.googleapis.com/instance/uptime < 1" \
--notification-channels=<CHANNEL_ID> -
Auto-Restart:
# Restart automatico se la VM crasha
gcloud compute instances set-scheduling decoder-vm \
--automatic-restart -
Instance Template + Health Check:
# Managed Instance Group con auto-healing
gcloud compute instance-groups managed create decoder-mig \
--template=decoder-template \
--size=1 \
--health-check=decoder-health \
--initial-delay=300
Cosa Succede Durante Failoverβ
- GPS si disconnette β Connessione TCP chiusa
- GPS riprova β Nuova connessione alla nuova istanza
- Stato ripristinato β GPS invia primo pacchetto, decoder riconosce device
[!TIP] I GPS sono progettati per riconnettersi automaticamente. Un failover di 1-2 minuti Γ¨ generalmente accettabile.
Costi Totali Architetturaβ
| Componente | Configurazione | Costo/Mese |
|---|---|---|
| VM Decoder | n1-standard-4 | ~β¬100 |
| Cloud SQL | db-custom-2-8192, HA | ~β¬130 |
| Memorystore | 2GB Standard HA | ~β¬90 |
| Nomad Cluster (3 nodi) | n1-standard-2 Γ 3 | ~β¬150 |
| Load Balancer | Per altri servizi | ~β¬20 |
| Network Egress | ~10GB/mese | ~β¬1 |
| Totale | ~β¬491 |
Checklist Implementazioneβ
- Creare VM dedicata per decoder
- Configurare OpenVPN client con credenziali 1NCE
- Creare Memorystore Redis
- Configurare peering VPC per Redis
- Deploy decoder con Docker Compose
- Verificare connessione VPN (
docker logs openvpn-client) - Testare connessione GPS
- Configurare alerting su VM
- Deploy altri servizi su Nomad
- Configurare consumatori Redis (positions, db-persister, websocket)