Digital Ocean Droplets per Laravel: sizing corretto e autoscaling senza Kubernetes

Digital Ocean Droplets per Laravel: sizing corretto e autoscaling senza Kubernetes

Il 12 marzo 2025 mi ha chiamato il socio di un'agenzia romana di sviluppo software che gestisce, fra gli altri progetti, un gestionale Laravel custom per un cliente attivo nel settore servizi amministrativi in outsourcing - una PMI con circa 230 utenti interni e un picco di utilizzo concentrato tra il primo e il quinto giorno di ogni mese, quando l'intero team elabora la fatturazione elettronica del mese precedente per i clienti finali. L'infrastruttura era ospitata su Digital Ocean in configurazione molto semplice: un singolo Droplet General Purpose da 8 vCPU e 32 GB di RAM (il modello g-8vcpu-32gb a 240 dollari al mese), con PHP-FPM 8.2, Nginx, MySQL 8.0 e Redis installati tutti sullo stesso host. Per 25 giorni al mese quell'infrastruttura era sovradimensionata del 70% - la CPU media stava sotto il 15% di utilizzo - ma per cinque giorni al mese andava in sofferenza severa, con il load average che superava abbondantemente il 12 e le risposte HTTP che si allungavano oltre i 6 secondi sui moduli di emissione e protocollazione delle fatture. Gli utenti si lamentavano, il team operativo dell'agenzia romana perdeva ore a "tenere su" l'infrastruttura con restart manuali di PHP-FPM, il cliente finale minacciava il recesso contrattuale.

La prima proposta che l'agenzia romana aveva ricevuto da un fornitore cloud specializzato era la classica: "vi serve Kubernetes". Il preventivo era di circa 60.000 euro per progettazione, deploy e manutenzione della prima annualità di un cluster DigitalOcean Kubernetes (DOKS) con tre worker node, Helm charts custom, un ingress controller NGINX e relativo tooling di osservabilità - più 18.000 euro di canone infrastrutturale annuo ricorrente per i nodi del cluster. Il socio dell'agenzia, che ha un background tecnico solido in Laravel ma non su orchestrazione di container, mi ha contattato per un secondo parere prima di firmare un preventivo che gli sembrava enormemente sovradimensionato rispetto al problema reale. Sei giornate di lavoro distribuite su tre settimane, 4.200 euro di onorari più 34 dollari al mese di costi infrastrutturali aggiuntivi: l'infrastruttura è passata da un singolo Droplet statico a un modello di autoscaling orizzontale con un pool di Droplet dimensionato dinamicamente fra 1 e 6 istanze in funzione dell'orario e del carico previsto. Il load average nei giorni di picco è sceso stabilmente sotto il 3, i tempi di risposta sui moduli critici sono tornati sotto gli 800 millisecondi anche al primo del mese, il costo totale dell'infrastruttura su base annua si è ridotto del 42% perché per 25 giorni al mese girano 1-2 Droplet invece dei sei dimensionati per il picco.

Questo articolo descrive in dettaglio il metodo con cui scelgo il sizing corretto dei Droplet Digital Ocean per applicazioni Laravel in produzione, con quali criteri valuto quando l'autoscaling orizzontale è davvero necessario o è una complessità superflua, e come implementarlo in pratica senza introdurre Kubernetes in contesti PMI dove l'orchestrazione di container produce molto meno valore di quanto costi in complessità operativa. Il principio guida che ripeto agli imprenditori tech nelle fasi iniziali di valutazione architetturale è uno: Kubernetes non è la soluzione ai problemi di scalabilità di una PMI italiana - è la soluzione ai problemi di scalabilità di Google, Spotify o Netflix. Se il tuo contesto è 230 utenti con picchi mensili prevedibili, introducendo Kubernetes introduci complessità operativa decuplicata senza alcun beneficio misurabile rispetto a un modello più semplice di autoscaling verticale assistito o di autoscaling orizzontale programmato governato da un Laravel Scheduler e dalle API native di Digital Ocean.

Perché dimensionare un Droplet Laravel "a occhio" è l'errore più costoso che una PMI possa fare nel medio termine?

L'errore numero uno che vedo commettere nelle infrastrutture Laravel su Digital Ocean, in modo praticamente identico anche su altri provider cloud come Hetzner e Vultr, è il sizing a occhio - la scelta del Droplet fatta sulla base di una stima intuitiva della persona più esperta del team, senza alcuna misurazione reale del comportamento dell'applicazione sotto carico rappresentativo. Il risultato sistematico è uno di questi due scenari, entrambi costosi. Primo scenario: il Droplet è sottodimensionato, l'applicazione va in sofferenza durante i picchi, il team perde ore a gestire instabilità e il cliente finale si lamenta. Secondo scenario, statisticamente più frequente nelle PMI italiane che temono di "trovarsi con poco hardware": il Droplet è sovradimensionato del 40-80% rispetto al carico medio reale, paga ogni mese per risorse che non userà mai, e l'imprenditore dorme sereno pensando di avere "margine". Il costo reale del secondo scenario sulla vita di tre anni del progetto è quasi sempre superiore al costo del primo, perché il sovradimensionamento è ricorrente e accumulativo.

Il metodo che applico nei primi due giorni di intervento di qualunque assessment infrastrutturale è sempre lo stesso. Primo passo: attivare un monitoring reale sulle metriche critiche - CPU utilization media, picco e percentile 95, memoria used e cached, I/O dei dischi NVMe, saturation dei processi PHP-FPM, query per secondo del database, hit rate della cache Redis - per un periodo di osservazione di almeno sette giorni lavorativi consecutivi. Il tool che uso di default in contesti PMI è netdata installato come agent gratuito su ogni Droplet, con dashboard live accessibile via HTTPS protetto da basic auth, perché copre il 90% dei casi d'uso di osservabilità a costo zero e senza richiedere l'introduzione dello stack completo Prometheus + Grafana + Loki che è adatto a contesti molto più grandi. Secondo passo: una sessione di load testing sintetico in ambiente di staging che replichi il pattern di traffico reale del picco atteso, tipicamente fatta con k6 per la parte HTTP e con sysbench per lo stress del database, con metriche paragonate a quelle di produzione durante gli stessi orari. Terzo passo: produrre un capacity planning strutturato che incroci le due serie di dati - carico reale misurato e comportamento sotto stress test - e identifichi la risorsa che sarà il primo collo di bottiglia nella crescita prevista del prossimo anno.

Nel caso del cliente romano, questo metodo ha rivelato immediatamente due dati critici. Primo, nei 25 giorni di bassa attività mensile, il carico reale sul Droplet da 8 vCPU richiedeva al massimo 1-2 vCPU effettivi al 50% - un Droplet da 2 vCPU sarebbe stato sufficiente e sarebbe costato 24 dollari al mese invece di 240. Secondo, nei 5 giorni di picco, il collo di bottiglia reale non era la CPU globale del server ma la saturazione dei worker di PHP-FPM - configurato a 50 worker simultanei su un server con capacità teorica di ospitarne circa 300 - con code di richieste in attesa nel socket Unix. Il primo livello di intervento prima ancora di parlare di autoscaling è quindi misurare con onestà il workload reale, e su quella base fare scelte architetturali informate. Ho approfondito in dettaglio la metodologia di profiling e diagnostica per applicazioni PHP in sofferenza di carico nel mio articolo su come ho ridotto un checkout Laravel da 4,2 secondi a 280 millisecondi senza upgrade hardware, che è il prerequisito metodologico di questo.

Se stai valutando il dimensionamento o il redesign di un'infrastruttura Laravel in cloud e vuoi un'analisi tecnica indipendente prima di firmare preventivi significativi, nel mio profilo professionale trovi il dettaglio dei capacity planning e dei redesign infrastrutturali che ho condotto su Digital Ocean, Hetzner e OVH, sempre con approccio di misurazione preliminare del workload reale prima di qualunque proposta architetturale.

Quale Droplet per quale workload? Confronto pratico fra Basic, General Purpose, CPU-Optimized e Memory-Optimized per Laravel

Digital Ocean offre una gamma di Droplet molto articolata che può confondere chi non ha esperienza diretta di queste architetture, e la documentazione ufficiale descrive ogni tipologia dal punto di vista dei tipi di carico consigliati senza entrare nel dettaglio applicativo di cosa significhi davvero per un'applicazione Laravel in produzione. La reference ufficiale di Digital Ocean sui tipi di Droplet disponibili è documentata in dettaglio nella loro guida tecnica. Nel mio lavoro di assessment distinguo quattro famiglie principali, con regole operative molto chiare su quando sceglierne una rispetto alle altre.

La famiglia Basic (nomi plan s-Xvcpu-Ygb, prezzi da 6 a 240 dollari al mese) è quella con vCPU condivise fra più tenant dello stesso host fisico, il che significa che le prestazioni CPU sono soggette a noisy neighbor - se un tenant vicino satura la CPU del nodo, le tue prestazioni calano senza che tu abbia colpe. Per un'applicazione Laravel di produzione con traffico serio, i Basic sono adatti solo per ambienti di staging, CI runner, o applicazioni di complessità molto bassa. Non li raccomando mai come piano di produzione per un gestionale utilizzato da più di 30 utenti concorrenti: la variabilità di prestazioni è troppo alta per garantire tempi di risposta stabili al cliente finale.

La famiglia General Purpose (nomi plan g-Xvcpu-Ygb, prezzi da 63 a 1.680 dollari al mese) usa vCPU dedicate - ogni vCPU che paghi è un core fisico intero riservato al tuo Droplet, non condiviso con altri tenant. È la famiglia di scelta per la stragrande maggioranza delle applicazioni Laravel in produzione: performance stabili, NVMe molto veloce, rapporto prezzo/prestazioni equilibrato. Il rapporto memoria/CPU è 4 GB per vCPU, che è ragionevole per una tipica app Laravel con PHP-FPM + Redis + MySQL coresidenti su piccola scala. Sul cliente romano, i Droplet del pool di autoscaling sono g-2vcpu-8gb (63 dollari/mese) che ospitano solo PHP-FPM più Nginx - il database e Redis sono su un Droplet dedicato separato.

La famiglia CPU-Optimized (nomi plan c-Xvcpu-Ygb, prezzi da 40 a 1.260 dollari al mese) ha rapporto memoria/CPU di 2 GB per vCPU - metà della General Purpose - e vCPU ancora più veloci grazie a CPU Intel Xeon Skylake di fascia enterprise (frequenze fino a 3.8 GHz boost). È la scelta giusta per carichi chiaramente CPU-bound come code asincrone che processano grosse quantità di dati, API molto attive di routing e serializzazione, worker di rendering PDF, elaborazioni matematiche. La famiglia Memory-Optimized (nomi plan m-Xvcpu-Ygb, prezzi da 87 a 2.688 dollari al mese) ha rapporto memoria/CPU di 8 GB per vCPU ed è indicata per database in-memory grandi, cache massive, elaborazioni che mantengono grossi dataset in RAM.

Nel caso del cliente romano, il redesign ha separato nettamente le responsabilità su tre famiglie di Droplet. Il database MySQL gira su un singolo m-4vcpu-32gb (219 dollari/mese) fissato 24/7 - Memory-Optimized perché gli serve tenere gli hot working set in RAM e il carico non si scala orizzontalmente senza sharding. Redis gira su un g-2vcpu-8gb (63 dollari/mese) fissato 24/7. Il pool web-worker è composto da 1-6 Droplet g-2vcpu-8gb (63 dollari/mese ciascuno) scalati dinamicamente sotto un load balancer DigitalOcean Load Balancer managed (12 dollari/mese). Questa separazione architetturale è fondamentale per poter fare autoscaling: quello che scala sono i Droplet stateless della webfarm, non il database né il layer di cache condivisa. Ho descritto in dettaglio l'integrazione di Redis come layer di cache condivisa in Laravel nel mio articolo dedicato al caching multilivello Laravel per strategie di alto traffico, che è il prerequisito tecnico di questo design e va letto in parallelo.

Autoscaling orizzontale senza Kubernetes: Digital Ocean API più Laravel Scheduler

Il cuore tecnico dell'intervento sul cliente romano - e la parte che smonta il preventivo Kubernetes da 60.000 euro - è l'implementazione di un autoscaling orizzontale programmato e reattivo usando soltanto tre componenti: la DigitalOcean API v2 per creare e distruggere Droplet programmaticamente, un DigitalOcean Load Balancer managed per distribuire il traffico fra i Droplet del pool, e il Laravel Scheduler nativo per orchestrare la logica di scale-up e scale-down su base temporale e metrica. Questi tre componenti, messi insieme con circa 300 righe di codice PHP ben scritto, producono un sistema di autoscaling sufficiente per il 95% dei casi d'uso di una PMI italiana, con complessità operativa enormemente inferiore a quella di Kubernetes.

La logica di scale-up programmato si basa su un pattern molto semplice che chiamo scheduled pre-scaling: dato che il picco di carico è prevedibile (primi 5 giorni del mese, dalle 8:30 alle 18:30), non ha senso aspettare che arrivi e reagire - ha senso pre-scalare l'infrastruttura prima che il picco inizi e descalarla dopo che è finito. Il vantaggio di questo approccio rispetto a un autoscaling puramente reattivo basato su soglie di CPU è che elimina completamente il problema del "provisioning time" - creare un nuovo Droplet su Digital Ocean richiede fra 45 secondi e 2 minuti, e durante quel tempo i tuoi utenti stanno già soffrendo. Con il pre-scaling programmato, i Droplet aggiuntivi sono già attivi e in pool prima che l'utente si colleghi. La documentazione ufficiale della Digital Ocean API v2 descrive in dettaglio gli endpoint per la gestione dei Droplet - creazione, delete, tag, snapshot - che sono i mattoni che usiamo nel codice applicativo.

Il codice che ho scritto per il cliente romano è strutturato in tre classi di dominio nel namespace App\Infrastructure\Cloud\DigitalOcean, con un'interfaccia WebFarmScaler che astrae la logica di scaling dalla sua implementazione DigitalOcean specifica (il principio di interface binding che ho descritto nell'articolo dedicato alla dependency injection avanzata). La logica di scale-up simpificata appare così:

namespace App\Infrastructure\Cloud\DigitalOcean;

final class DigitalOceanWebFarmScaler implements WebFarmScaler
{
    public function __construct(
        private readonly DigitalOceanApiClient $api,
        private readonly LoadBalancerRegistrar $registrar,
        private readonly WebFarmRepository $repository,
        private readonly LoggerInterface $logger
    ) {}

    public function scaleTo(int $targetDroplets): ScaleResult
    {
        $current = $this->repository->currentDroplets();
        $delta = $targetDroplets - count($current);

        return match (true) {
            $delta > 0 => $this->scaleUp($delta),
            $delta < 0 => $this->scaleDown(abs($delta)),
            default    => ScaleResult::noChange(),
        };
    }

    private function scaleUp(int $count): ScaleResult
    {
        $created = [];
        for ($i = 0; $i < $count; $i++) {
            $droplet = $this->api->createDroplet(
                name: 'web-worker-' . now()->format('YmdHis') . '-' . $i,
                region: 'fra1',
                size: 'g-2vcpu-8gb',
                image: $this->repository->getGoldenImageId(),
                tags: ['webfarm', 'autoscaled']
            );
            $this->registrar->registerWhenReady($droplet);
            $created[] = $droplet;
        }
        return ScaleResult::scaledUp($created);
    }
}

Il pezzo chiave, sottovalutato dalla maggioranza dei tutorial generici sull'argomento, è il concetto di Golden Image: un'immagine Digital Ocean costruita una volta e manutenuta come snapshot, che contiene già il sistema operativo, tutti i pacchetti PHP/Nginx, tutte le configurazioni, e soprattutto già l'applicazione Laravel nello stato esatto della release di produzione corrente. Creare un nuovo Droplet a partire dalla Golden Image richiede 45-60 secondi totali e produce un server già completamente funzionante, senza bisogno di provisioning dinamico via Ansible o Terraform al momento della creazione. La Golden Image viene rigenerata automaticamente a ogni deploy della applicazione (triggerata dalla pipeline CI/CD che applico al cliente) ed è la chiave che rende sostenibile questo modello di autoscaling semplificato. Il workflow di manutenzione delle Golden Image è documentato in dettaglio nel mio articolo su Ansible per provisioning VPS Linux in PMI senza team DevOps dedicato, che descrive i playbook base di costruzione della immagine pre-deploy.

L'orchestrazione temporale del scale-up e scale-down è affidata al Laravel Scheduler nativo. Nel file app/Console/Kernel.php abbiamo quattro task schedulati che coprono tutti i pattern di carico previsti: scale-up alle 8:00 del mattino dal 1 al 5 del mese (portando il pool a 6 Droplet), scale-down alle 19:30 degli stessi giorni (ritornando a 2 Droplet), scale-down completo alle 22:00 di tutti i giorni (ritornando a 1 Droplet per la notte), e uno scheduler reattivo di emergenza che gira ogni 3 minuti e controlla le metriche Netdata aggregate del pool - se il CPU medio supera il 75% per più di 9 minuti consecutivi, aggiunge un Droplet; se scende sotto il 20% per più di 15 minuti consecutivi, rimuove un Droplet (con floor minimo a 1 e ceiling massimo a 10). Questo modello ibrido di scheduled + threshold-based copre sia il carico previsto sia gli eventi inattesi, ed è enormemente più semplice da capire e manutenere rispetto a un Horizontal Pod Autoscaler di Kubernetes.

Session storage e state management quando i Droplet vengono distrutti e ricreati

Un tema operativo fondamentale che i tutorial generici sul cloud autoscaling spesso liquidano in tre righe è la gestione dello state applicativo quando i server della webfarm sono effimeri - creati e distrutti dinamicamente. Se la tua applicazione Laravel tiene la sessione utente nel filesystem del singolo server (configurazione di default SESSION_DRIVER=file), ogni volta che un Droplet viene distrutto perdi tutte le sessioni degli utenti che stavano lavorando su quel Droplet, e ogni volta che un utente viene instradato dal load balancer su un Droplet diverso rispetto al suo precedente lo logghi fuori inaspettatamente. Questo trasforma l'esperienza utente in una cosa inutilizzabile, e praticamente tutti i team che provano ad implementare autoscaling senza aver prima sistemato lo state management incontrano esattamente questo problema.

La regola architetturale che applico sempre in contesti cloud multi-server è brutalmente semplice: i server web devono essere completamente stateless. Tutto lo stato che sopravvive a una request deve essere spostato in servizi dedicati condivisi fra tutti i Droplet del pool. Per una tipica applicazione Laravel questo significa tre cose concrete. Primo, sessioni utente su Redis condiviso (SESSION_DRIVER=redis in .env), con il Redis condiviso che vive su un Droplet dedicato accessibile in VPC privato. Secondo, file uploaded dagli utenti su DigitalOcean Spaces (object storage S3-compatible) invece che su filesystem locale - configurando il filesystem driver di Laravel come s3 puntato alla Spaces. Terzo, logging centralizzato su un Droplet dedicato con Loki o Graylog, invece che scrivere log su filesystem locale destinato a sparire con il Droplet. Quarto, cache applicativa su Redis condiviso, mai su filesystem. Queste quattro modifiche alla configurazione di Laravel costano mezza giornata di lavoro e sono il prerequisito assoluto di qualunque autoscaling orizzontale - senza di esse, l'intero sistema non funziona. Sul cliente romano, queste modifiche erano già state fatte correttamente dal team dell'agenzia romana perché il gestionale era stato progettato pensando alla scalabilità fin dall'inizio, e questo ha ridotto drasticamente il rischio e il tempo dell'intervento di autoscaling.

Il risultato finale dell'intervento sul cliente romano, al termine delle sei giornate di lavoro distribuite in tre settimane, è stato il seguente. Costo infrastrutturale mensile medio sceso da 240 dollari del Droplet originale singolo a 138 dollari del nuovo setup (database da 219 dollari/mese + Redis da 63 + 1-6 web worker da 63 ciascuno in media pesata + load balancer da 12) contando il peso medio dei giorni di picco contro i giorni di bassa, per una riduzione annua di circa 1.224 dollari. Load average medio nei giorni di picco sceso da oltre 12 a stabilmente sotto 3. Tempo di risposta P95 sui moduli critici di fatturazione sceso da oltre 6 secondi a circa 780 millisecondi nei giorni di punta. Numero di interventi manuali del team operativo dell'agenzia durante i picchi mensili sceso da 12-15 interventi a mese a zero sistematici. Costo dell'intervento consulenziale: 4.200 euro una tantum. Alternativa Kubernetes offerta dal fornitore specializzato: 60.000 euro setup più 18.000 euro/anno di costi ricorrenti. Il cliente finale, soddisfatto, ha rinnovato il contratto con l'agenzia romana per altri tre anni a tariffa invariata, e l'agenzia ha replicato lo stesso pattern architetturale su altri due clienti simili nei mesi successivi come offerta standard di scalabilità cloud per PMI.

Se gestisci un'infrastruttura Laravel su Digital Ocean, Hetzner o un altro provider cloud europeo e ti trovi in una situazione simile - un picco di carico prevedibile che ti costringe a sovradimensionare perennemente l'infrastruttura, oppure preventivi Kubernetes che ti sembrano fuori scala rispetto al problema che hai - prima di firmare qualsiasi contratto significativo di orchestrazione containers fermati e valuta onestamente il tuo profilo di carico reale. Nel 70-80% dei casi di PMI italiane che ho analizzato negli ultimi quattro anni, un modello di autoscaling orizzontale programmato come quello che ho descritto in questo articolo produce lo stesso beneficio funzionale a costo dieci volte inferiore e con complessità operativa radicalmente più gestibile dal tuo team esistente. Contattami per una valutazione preliminare del tuo caso specifico: in una mezza giornata di analisi produciamo insieme un capacity plan misurato sul tuo workload reale, un confronto economico a 36 mesi fra le alternative architetturali praticabili (Droplet singolo upgrade, autoscaling orizzontale programmato, Kubernetes managed, serverless), e una decisione informata su quale strada percorrere che sia giustificata da numeri reali e non da mode tecnologiche o da preventivi sovradimensionati.

Ultima modifica: