Quando ho iniziato a gestire workload LLM multi-tenant su Plesk quest’anno, ho capito subito che l’isolamento non è una questione di buone pratiche—è una questione di sopravvivenza operativa. Un singolo agente AI in runaway può consumare tutti i token di budget di un mese in poche ore, o più ancora, un agente compromesso può compromettere l’intero stack. In questa guida vi mostro come ho configurato sandboxing real, resource quotas e cost attribution su Plesk per proteggere il controllo plane e i tenant.
Perché Plesk Necessita di AI Sandboxing Oggi
La situazione è cambiata radicalmente. Più della metà degli agenti AI aziendali gira oggi senza supervisione di sicurezza o logging, mentre 1 su 8 delle violazioni AI riportate coinvolge sistemi agentici. Nel mio ambiente Plesk multi-tenant, questo significa che un agente mal configurato di un tenant può:
- Consumare memoria dell’host e causare OOM-kill dei servizi di altri tenant
- Effettuare richieste API illimitate a modelli LLM, spike di costi imprevisti
- Esfiltrare dati attraverso canali di rete non controllati (se non bloccati)
- Entrare in loop infiniti di ragionamento e autoreplica
Ho scoperto che un sandbox ben progettato consiste di quattro layer: isolamento del calcolo, boundary del filesystem, controlli di rete e limiti di storage. Plesk Control Plane gestisce bene il multi-tenancy a livello di account, ma per i workload AI serve una difesa aggiuntiva.
Architettura di Isolamento: Container vs MicroVM in Plesk
All’inizio, ho provato a eseguire agenti LLM direttamente in PHP/Node.js LiteSpeed senza isolamento. Non durato più di 72 ore: un agente entrato in loop con Claude 3 ha mandato 50 GB di log. Serve isolamento vero.
In Plesk, avete due opzioni:
Approccio 1: Container Docker via Plesk Extension (Standard)
I container standard condividono il kernel dell’host e non sono isolamento sufficiente per workload agentici che eseguono codice generato da LLM o chiamano tool esterni. Tuttavia, se combinate container Docker con cgroup limits ristretti, potete ottenere isolamento reasonevole per workload trusted. È quello che uso per il 70% dei tenant.
Vantaggi:
- Density alta su host singolo
- Integrazione nativa con Plesk (via Custom Containers o Plesk Extensions)
- Startup rapido (<500ms)
- Gestione facile di rete e storage
Approccio 2: Firecracker MicroVMs (Massimo Isolamento)
I tre approcci di isolamento principale sono microVM (Firecracker, Kata Containers), gVisor (kernel user-space), e container hardened. I microVM forniscono l’isolamento più forte con kernel dedicati per workload, gVisor offre intercettazione syscall senza VM complete, e container funzionano solo per codice trusted.
Per agenti che eseguono codice non-trusted o fanno tool calling su endpoint esterni, applicate limiti di risorsa per-sandbox: CPU, memoria, disk quota, larghezza di banda. Usate contesti di esecuzione ephemeral—demolite dopo il completamento di ogni task. Un’astrazione transazionale di SandboxClaim permette al orchestrator di agenti di richiedere un ambiente di esecuzione senza conoscere i dettagli del backend di isolamento.
Nel mio setup, uso Firecracker per il 30% di tenant che richiedono rigorosità massima. Costano più CPU, ma il rischio scende praticamente a zero.
Step 1: Configurare Resource Quotas su Plesk Control Plane
Cominciate dalla UI di Plesk. Non potete controllare ogni agente senza limiti a livello di tenant.
Limiti di Memoria e CPU via Plesk API/CLI
Accedo all’account Plesk di un tenant (es: tenant-ai-1) e setto limiti via RPC:
Configurazione Resource Quota (pseudocodice XML-RPC):
“`
plesk bin subscription update tenant-ai-1
-cpu-limit 2000m
-memory-limit 4096Mi
-ephemeral-storage-limit 20Gi
“`
Se usate Plesk con Docker extension, potete anche limitare a livello di container via docker-compose.yml:
“`yaml
services:
ai-agent:
image: python:3.11-slim
entrypoint: [“python”, “-m”, “agentic_framework”]
resources:
limits:
cpus: ‘2’
memory: 4G
reservations:
cpus: ‘1’
memory: 2G
ulimits:
nproc: 512
nofile: 4096
environment:
– OTEL_SDK_DISABLED=false
– TOKEN_BUDGET=100000
“`
Limiti CPU: prevenite l’esaurimento del calcolo impostando share CPU massimi e throttling dei processi runaway. Limiti memoria: bloccate memory bomb definendo limiti hard che terminano processi che superano l’allocazione. Quote disco: bloccate attacchi di storage limitando l’uso del filesystem e rate-limiting delle operazioni I/O. Larghezza di banda rete: prevenite esfiltrazioni dati con rate-limiting del traffico outbound e monitoraggio di pattern inusuali.
Step 2: Implementare Cost Attribution per Token Consumption
Senza tracking granulare dei token, i costi diventano invisibili. Ho configurato un sistema a tre livelli:
Livello 1: Tagging di Metadati all’Ingresso
Il tagging di metadati è il meccanismo che rende l’attribuzione funciona su scala. Ogni chiamata API LLM dovrebbe portare un set coerente di coppie chiave-valore identificando l’origine. Il tagging parziale è la ragione principale per cui i dati di attribuzione diventano inaffidabili. L’applicazione principale attacca i tag, ma microservizi interni o job in background no, creando gap che rendono i dati inutili per scopi di governance. Lo standard di tagging dovrebbe essere forzato a livello di infrastruttura attraverso il proxy layer, piuttosto che lasciato ai developer di implementare service per service.
Nel mio proxy LLM in Plesk (che intercepta tutte le richieste a OpenAI/Anthropic/Bedrock), aggiungo header standardizzati:
“`python
# llm_proxy.py – middleware che corre su Plesk Application Server
import httpx
from datetime import datetime
from uuid import uuid4
class LLMProxyMiddleware:
def __init__(self, app):
self.app = app
async def __call__(self, scope, receive, send):
if scope[‘type’] != ‘http’:
await self.app(scope, receive, send)
return
# Extract tenant/user context
tenant_id = scope[‘headers’].get(b’x-plesk-tenant’, b’unknown’).decode()
user_id = scope[‘headers’].get(b’x-plesk-user’, b’unknown’).decode()
feature_name = scope[‘headers’].get(b’x-feature’, b’generic-ai’).decode()
# Inject consistent tagging
request_id = str(uuid4())
trace_context = {
‘request_id’: request_id,
‘tenant_id’: tenant_id,
‘user_id’: user_id,
‘feature_name’: feature_name,
‘timestamp’: datetime.utcnow().isoformat(),
‘workload_type’: ‘agentic_ai’ # or ‘rag’, ‘fine_tuning’
}
# Store in context-local for logging
scope[‘ai_trace’] = trace_context
await self.app(scope, receive, send)
“`
Livello 2: Logging di Ogni Transazione LLM
Quattro Layer di Token Importano: Ogni richiesta consuma token di prompt, tool, memoria e risposta. Aggregare in un bucket singolo input/output nasconde dove lo spend va veramente e previene ottimizzazione mirata.
Ho implementato logging strutturato via OpenTelemetry:
“`python
import json
from opentelemetry import trace, metrics
from opentelemetry.exporter.jaeger.thrift import JaegerExporter
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
tracer = trace.get_tracer(__name__)
def log_llm_cost(tenant_id, model, prompt_tokens, completion_tokens, cache_read_tokens, cache_creation_tokens):
“””Log cost attribution con quattro dimensioni di token”””
span = tracer.start_as_current_span(f”llm.cost.attribution.{model}”)
span.set_attribute(“tenant_id”, tenant_id)
span.set_attribute(“model”, model)
span.set_attribute(“prompt_tokens”, prompt_tokens)
span.set_attribute(“completion_tokens”, completion_tokens)
span.set_attribute(“cache_read_tokens”, cache_read_tokens) # Billing ridotto
span.set_attribute(“cache_creation_tokens”, cache_creation_tokens)
# Calcolo costo netto (assumendo OpenAI Pricing)
cost = (
(prompt_tokens * 0.0005) + # $0.0005 per 1K input
(completion_tokens * 0.0015) + # $0.0015 per 1K output
(cache_read_tokens * 0.00009) + # 90% sconto su cache read
(cache_creation_tokens * 0.0005) # Stesso prezzo di input per cache write
) / 1000
span.set_attribute(“estimated_cost_usd”, cost)
span.end()
# Persistenza in backend di metriche
return {
“tenant_id”: tenant_id,
“model”: model,
“tokens_breakdown”: {
“prompt”: prompt_tokens,
“completion”: completion_tokens,
“cache_read”: cache_read_tokens,
“cache_creation”: cache_creation_tokens
},
“cost_usd”: cost
}
“`
Livello 3: Budget Enforcement Automatizzato
Un sistema di governance a quattro tier forza budget a livello di virtual key, team, customer, e configurazione di provider. Questo non è monitoraggio passivo; enforcement attivo di limiti di spend e reiezione di richieste che supererebbero budget configurati. Le virtual key servono come entità primaria di governance, abilitando permessi di accesso per-consumer, budget e rate limit.
Nel mio setup Plesk, ogni tenant riceve una “virtual key” con budget giornaliero/mensile:
“`python
class TokenBudgetGate:
def __init__(self, redis_client, tenant_id, daily_budget_usd=50, monthly_budget_usd=1000):
self.redis = redis_client
self.tenant_id = tenant_id
self.daily_budget = daily_budget_usd
self.monthly_budget = monthly_budget_usd
def check_and_debit(self, estimated_cost_usd):
“””Verifica budget prima di autorizzare chiamata LLM”””
today = datetime.utcnow().date().isoformat()
month = datetime.utcnow().strftime(‘%Y-%m’)
daily_key = f”tenant:{self.tenant_id}:spend:daily:{today}”
monthly_key = f”tenant:{self.tenant_id}:spend:monthly:{month}”
daily_spend = float(self.redis.get(daily_key) or 0)
monthly_spend = float(self.redis.get(monthly_key) or 0)
if daily_spend + estimated_cost_usd > self.daily_budget:
raise BudgetExceededError(
f”Daily budget ${self.daily_budget} exceeded. Current: ${daily_spend}, Requested: ${estimated_cost_usd}”
)
if monthly_spend + estimated_cost_usd > self.monthly_budget:
raise BudgetExceededError(
f”Monthly budget ${self.monthly_budget} exceeded. Current: ${monthly_spend}, Requested: ${estimated_cost_usd}”
)
# Debit from budget
self.redis.incrbyfloat(daily_key, estimated_cost_usd)
self.redis.incrbyfloat(monthly_key, estimated_cost_usd)
self.redis.expire(daily_key, 86400) # TTL 24 ore
return True
“`
Step 3: Prevenire Runaway AI Processes
Il scenario peggiore è un agente che entra in loop infinito di tool calling senza terminarsi mai. Ho implementato tre controlli in cascata:
Controllo 1: Timeout Globale e Depth Limit
Nei domini AI, il comportamento runaway si manifesta tipicamente come loop infiniti di ragionamento, uso non autorizzato di tool, o esaurimento di risorse. Prevenire comportamento runaway richiede una strategia di difesa multi-layer: limiti ristretti di iterazione e timeout, circuit breaker basati su risorsa, controllo granulare di accesso ai tool (RBAC), e monitoraggio comportamentale continuo. Il recovery necessita revoca di accesso immediata e capacità di intervento human-in-the-loop (HITL).
“`python
from tenacity import Retrying, stop_after_attempt, stop_after_delay
class SafeAgentExecutor:
def __init__(self, agent, max_iterations=10, timeout_seconds=300, max_tokens=50000):
self.agent = agent
self.max_iterations = max_iterations
self.timeout_seconds = timeout_seconds
self.max_tokens = max_tokens
self.iteration_count = 0
self.token_count = 0
def execute(self, input_prompt):
try:
for attempt in Retrying(
stop=stop_after_attempt(self.max_iterations),
stop_after_delay=self.timeout_seconds
):
with attempt:
self.iteration_count += 1
# Controlla se agente è entrato in loop
if self.iteration_count > self.max_iterations:
raise RuntimeError(
f”Max iterations {self.max_iterations} exceeded. Agent may be in infinite loop.”
)
# Esegui step agente
result = self.agent.agentic_step(input_prompt)
self.token_count += result.get(‘tokens_consumed’, 0)
# Controlla token budget
if self.token_count > self.max_tokens:
raise RuntimeError(
f”Max token budget {self.max_tokens} exceeded. Current: {self.token_count}”
)
return result
except TimeoutError:
# Timeout triggerato – termina agente
self.agent.kill(reason=”timeout_exceeded”)
raise
“`
Controllo 2: Network Egress Allowlist Rigoroso
Un agente sandboxato opera sotto un allowlist strettamente scoped. La guida pratica NVIDIA 2026 specifica controlli basati su HTTP proxy, IP e porta. In pratica, questo significa: definire esattamente quali API esterne l’agente è permesso di chiamare, forzare via un proxy di egress o network policy, e allertare su tutto altro traffico outbound.
Nel mio setup Plesk, uso iptables + custom proxy layer:
“`bash
# iptables rule: blocca tutto per default, consenti solo allowlist
iptables -I FORWARD 1 -i docker0 -o eth0 -j DROP # Default deny
# Allowlist specifico per tenant
iptables -I FORWARD 1 -i docker0 -o eth0 -d api.openai.com -j ACCEPT
iptables -I FORWARD 1 -i docker0 -o eth0 -d api.anthropic.com -j ACCEPT
iptables -I FORWARD 1 -i docker0 -o eth0 -d bedrock-runtime.us-east-1.amazonaws.com -j ACCEPT
# Block dataexfiltration su port 22, 25, 3306 (SSH, SMTP, MySQL)
iptables -I FORWARD 1 -i docker0 -o eth0 -p tcp –dport 22 -j DROP
iptables -I FORWARD 1 -i docker0 -o eth0 -p tcp –dport 25 -j DROP
iptables -I FORWARD 1 -i docker0 -o eth0 -p tcp –dport 3306 -j DROP
“`
Controllo 3: Circuit Breaker per Anomalie
I circuit breaker sono trigger automatizzati che fermano l’esecuzione quando soglie specifiche vengono superate. Implemento uno via Redis:
“`python
class AgentCircuitBreaker:
def __init__(self, redis_client, tenant_id, threshold_cost_usd=100):
self.redis = redis_client
self.tenant_id = tenant_id
self.threshold_cost = threshold_cost_usd
self.circuit_key = f”circuit_breaker:{self.tenant_id}”
def check_anomaly(self, current_spend_usd, time_window_minutes=5):
“””Rileva spike anomali di spending”””
# Recupera spend nei ultimi 5 minuti
recent_spend = float(self.redis.get(f”{self.circuit_key}:recent”) or 0)
# Se spike supera soglia, attiva circuit breaker
if recent_spend > self.threshold_cost:
self.trip(reason=f”Anomaly: ${recent_spend} spend in {time_window_minutes} minutes”)
return False
# Aggiorna counter
self.redis.incrbyfloat(f”{self.circuit_key}:recent”, current_spend_usd)
self.redis.expire(f”{self.circuit_key}:recent”, time_window_minutes * 60)
return True
def trip(self, reason):
“””Disabilita agente e notifica admin”””
self.redis.set(f”{self.circuit_key}:tripped”, “1”, ex=3600)
self.redis.set(f”{self.circuit_key}:reason”, reason)
# TODO: Invia alert Slack/Email a admin
print(f”[ALERT] Circuit breaker tripped for {self.tenant_id}: {reason}”)
“`
Step 4: Monitoraggio e Observability
Senza visibilità, non potete controllare nulla. Ho collegato tutto a un dashboard Grafana:
“`
Prometheus metrics esportati:
– llm_requests_total{tenant_id, model, status} # Contatore di richieste
– llm_token_consumption{tenant_id, token_type} # Prompt, completion, cache
– llm_cost_usd{tenant_id, model} # Costo effettivo per richiesta
– llm_agent_iterations{tenant_id} # Numero di step agente per esecuzione
– container_memory_limit_bytes{tenant_id} # Limite memoria
– container_memory_usage_bytes{tenant_id} # Uso memoria attuale
– budget_remaining_usd{tenant_id} # Budget rimanente
“`
FAQ
Come faccio a permettere un agente di fare tool calling senza rischi di esfiltrazioni di dati?
Il metodo più sicuro è deny-by-default egress filtering combinato con modello di rete zero-trust dove tutte le connessioni sono esplicitamente autorizzate piuttosto che implicitamente permesse. Definite un allowlist rigido di endpoint che l’agente può raggiungere (es: solo API ufficiali di LLM, non database interni), e implementate via iptables rules o proxy layer che intercetta ogni connessione.
Qual è la differenza tra container Docker e Firecracker per AI agent sandboxing?
I container Docker condividono il kernel dell’host e sono più efficienti per trusted workload. Firecracker e Kata Containers forniscono kernel isolato per VM-like isolation, ideale per codice non-trusted. Nel mio setup Plesk, uso container per il 70% (più efficient) e Firecracker per il 30% (max security) che fa tool calling esterno.
Come assicuro che un agente non entra in infinite loop di token consumption?
Implementate tre livelli di controllo: (1) timeout globale usando container resource limits per prevenire processi runaway, impostando limiti ristretti di CPU, memoria e uso disco per processi agenti AI, implementando timeout mechanism operations; (2) depth limit su iterazioni agente (max 10-15 step); (3) circuit breaker che monitora spend anomalo in finestre di tempo brevi (es: >$100 in 5 minuti).
Come calcolo il costo corretto se uso prompt caching?
Il caching di prompt cambia il costo di contesto ripetuto solo quando cache read e cache write sono loggati separatamente. Collezionare token cached e uncached in un conteggio di input singolo può sovrastimare lo spending su workload cache-heavy e offuscare se caching sta riducendo costi per prompt lunghi, documenti retrieval, o istruzioni di sistema ripetute. Loggare sempre i quattro token layer separatamente: prompt, completion, cache_read, cache_write.
Posso implementare questo su Plesk standard senza extensioni custom?
Parzialmente. Plesk standard fornisce basic resource quotas via UI, ma per sandboxing e cost attribution full-featured, dovete aggiungere un layer di application logic. Io uso Plesk Docker extension + custom Python proxy middleware. Se preferite una soluzione completamente managed, potete considerare provider specializzati come Northflank o Modal per AI sandboxing, integrando via Plesk API.
Conclusione
Implementare Plesk AI Agent Sandboxing e resource quotas non è triviale, ma è essenziale per multi-tenant LLM workload. Nel mio setup, ho combinato:
- Isolamento compute: Container Docker + Firecracker per max security
- Cost Attribution: Tagging metadati al ingresso, logging granulare di quattro token layer, budget enforcement automatico
- Runaway Prevention: Timeout, depth limit, circuit breaker anomaly detection
- Observability: Prometheus + Grafana per monitoring real-time
Quando avete questi controlli in place, un agente non può più causare danni catastrofici. Questo significa che potete scale AI workload su Plesk con confidenza, sapendo che il costo e il rischio sono sotto controllo. Questo è quello che ho imparato sulla strada difficile—spero che vi risparmi il tempo e i dollari.
Avete domande sul setup? Lasciate un commento qui sotto.