Nel 2026, il panorama del multi-tenancy per carichi di lavoro LLM in ambienti Plesk è diventato critico. Ho visto troppi amministratori di sistema spiegare inaspettatamente budget di GPU bruciati per agenti LLM che sfuggivano al controllo, o peggio ancora, scoprire che i dati di un tenant erano raggiungibili da un altro. In questa guida, vi mostro come implementare una strategia di isolamento robusta che combina container sandboxing, quote di risorse GPU dichiarate, e attribuzione dei costi per token consumati.
Perché il Multi-Tenant Isolation per LLM è Non-Negoziabile nel 2026
Quando gestite un ambiente Plesk con ospitati più clienti che eseguono agenti LLM, il problema “noisy neighbor” non è teorico: è operativo. La maggior parte dei provider LLM SaaS aggiungono il multi-tenancy come ripensamento, spediscono un endpoint condiviso senza enforcement di quote, scoprono il problema noisy-neighbor su scala, e passano settimane a retrofittare isolamento che avrebbe dovuto essere nel v1.
In multi-tenant LLM serving, i carichi di lavoro ad alta domanda da un client possono causare violazioni di SLA di latenza per altri. Nel nostro contesto Plesk, questo significa che un tenant che avvia un agente mal configurato può consumare tutte le VRAM della GPU, affamando i workload in tempo reale di un altro customer. Peggio ancora, le misurazioni di carichi di lavoro co-locati mostrano degradazione di performance del 5-50% da interferenza pura.
La soluzione: isolamento strutturale a tre livelli: runtime container, quote di risorse GPU esplicite, e attribuzione di costi per token fino a livello di tenant.
Livello 1: Container Sandboxing per LLM Workload
Nel mio approccio su Plesk, ogni tenant LLM gira in un container isolato con policy di sicurezza hardened. Non sto usando container vanilla: uso una strategia che presume che l’agente possa fare qualcosa che non ho esplicitamente scritto.
Gli agenti sono diversi dai servizi tipici in un modo critico: prendono azioni autonome. Questo significa che il modello di sicurezza deve assumere che un agente potrebbe fare qualcosa che non hai esplicitamente scritto, e progettare per il contenimento piuttosto che per la fiducia. Sandboxare il container stesso.
Ecco la procedura che ho consolidato:
Step 1: Configurare Namespace Kubernetes per Tenant
Nel mio setup Plesk con supporto Kubernetes (o Docker per deployment più piccoli), creo un namespace dedicato per ogni tenant LLM:
kubectl create namespace tenant-acme-llm
kubectl label namespace tenant-acme-llm tenant-id=acme isolation=strict
Questo namespace diventa la “cella” di esecuzione di questo tenant. Tutti i pod dell’agente LLM di ACME girano esclusivamente qui.
Step 2: Applicare Resource Quotas Hard
Applico una ResourceQuota Kubernetes che impone limiti rigorosi:
cat > tenant-acme-quota.yaml << 'EOF'
apiVersion: v1
kind: ResourceQuota
metadata:
name: acme-hard-quota
namespace: tenant-acme-llm
spec:
hard:
requests.cpu: "8"
requests.memory: "32Gi"
requests.nvidia.com/gpu: "1" # 1 GPU T4 per tenant
limits.cpu: "10"
limits.memory: "40Gi"
scopeSelector:
matchExpressions:
- operator: In
scopeName: PriorityClass
values: ["workload-llm"]
EOF
kubectl apply -f tenant-acme-quota.yaml
La chiave qui: ogni tenant ottiene una GPU dedicata (or fractional share in ambienti più grandi) con limiti CPU e memoria non negoziabili. Se il tenant tenta di exceeder questi limiti, il container viene killed dal kubelet, non rallentato.
Step 3: Network Policy per Isolamento Tenant-to-Tenant
Creo una NetworkPolicy che impedisce a un pod LLM di ACME di comunicare con i dati di un altro tenant:
cat > tenant-acme-netpolicy.yaml << 'EOF'
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: acme-tenant-isolation
namespace: tenant-acme-llm
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
tenant-id: acme
- podSelector:
matchLabels:
role: api-gateway
egress:
- to:
- namespaceSelector:
matchLabels:
tenant-id: acme
- to:
- podSelector:
matchLabels:
role: llm-provider # Solo endpoint LLM autorizzati
- ports:
- protocol: TCP
port: 53 # DNS
EOF
kubectl apply -f tenant-acme-netpolicy.yaml
Ora il pod ACME non può raggiungere direttamente altri namespace tenant, solo gli endpoint LLM autorizzati (OpenAI, Claude, self-hosted vLLM).
Step 4: Container Image Hardening e Scanning
Prima di deployare qualsiasi container LLM in Plesk, lo scansiono con trivy (incluso in Plesk 23.0+):
trivy image --severity HIGH,CRITICAL my-llm-agent:v1.2.3
trivy image --scanners secret,config my-llm-agent:v1.2.3
Se il container contiene secrets o vulnerabilità note, il deploy fallisce via Plesk admission controller.
Livello 2: GPU Resource Quotas e Fairness Scheduling
Le quota CPU/memory non sono sufficienti. La vera pressione nel 2026 viene dalle GPU.
A volumi di token più alti per customer (diciamo, 1M token/day ciascuno), l’H100 inizia ad approssimare i limiti di utilizzo e devi una seconda GPU. Questo è il punto di inflessione dove l’architettura shared-pool si rompe e le istanze dedicate per-customer diventano vale il costo.
Nel mio setup Plesk, implemento:
GPU Operator Kubernetes con Fair Queuing
Installo il NVIDIA GPU Operator insieme a kubelet-gpu-sharing per frazione le GPU tra tenant:
helm repo add nvidia https://nvidia.github.io/k8s-device-plugin
helm repo update
helm install nvidia-device-plugin nvidia/nvidia-device-plugin
--namespace kube-system
--set nvidia.driver.capabilities=compute,utility
--set sharing.timeSlicing.enabled=true
--set sharing.timeSlicing.replicas=4 # 4 tenant per GPU
Gli scheduler open-source e gli operatori GPU, come quelli catalogati nel progetto genai-workload-manager di IBM, possono aggiungere fair queuing e GPU sharing su vanilla Kubernetes quando il cluster ospita molti agenti workload concorrenti.
Ora ogni tenant ottiene una “fetta” di 25% della GPU (4 tenant per GPU H100). Se un tenant tenta di consumare più di sua quota, il scheduler lo mette in coda equamente con gli altri.
LimitRange per Prevenire Runaway Allocations
Applico un LimitRange aggressivo per namespace tenant:
cat > tenant-acme-limitrange.yaml << 'EOF'
apiVersion: v1
kind: LimitRange
metadata:
name: acme-pod-limits
namespace: tenant-acme-llm
spec:
limits:
- max:
cpu: "4"
memory: "16Gi"
nvidia.com/gpu: "0.25" # Max 25% di una GPU per pod singolo
min:
cpu: "100m"
memory: "128Mi"
type: Container
- max:
ephemeralStorage: "50Gi"
type: Pod
EOF
kubectl apply -f tenant-acme-limitrange.yaml
Questo forza il tenant a frammentare i carichi di lavoro LLM in pod più piccoli, evitando un singolo agente che monopolizza la GPU.
Livello 3: Cost Attribution per Token Usage
Qui è dove la maggior parte dei deployment fallisce. Ho visto team che implementano isolamento perfetto a livello di container, ma nessuna tracciamento di costi. Sei mesi dopo: “Perché il tenant ACME usa il 60% della nostra fattura API LLM ma paga il 10% dello spazio su disco?”
Runaway cost è consumo di token illimitato su molte chiamate LLM, di solito perché un agente è bloccato in un loop, una chiamata ricorsiva a tool non termina, o una sessione di chat accumula contesto senza riassunto.
Step 1: Strumentazione Token Accounting al Deployment
L’attribuzione dei costi è onesta solo quando i tag sono allegati al momento della creazione della richiesta. Il tagging retroattivo dai log manca sempre dei casi limite e sotto-riporta le tracce agentic lunghe.
Nel mio setup Plesk con Langfuse (o alternativa open-source), ogni LLM call include questi metadati:
import langfuse
from openai import OpenAI
fuse = langfuse.Langfuse(
public_key="pk-...",
secret_key="sk-..."
)
# Tag ogni trace con tenant, user, agente, task
trace = fuse.trace(
name="acme-data-cleaning-agent",
user_id="acme-tenant",
metadata={
"tenant_id": "acme",
"agent_type": "data-cleaner",
"task_id": "clean-salesforce-export",
"environment": "production"
}
)
client = OpenAI(api_key="...")
with trace.span(name="llm-classification") as span:
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": record_text}],
max_tokens=500
)
# Registra token granulari
span.generation(
model="gpt-4o",
input=record_text,
output=response.choices[0].message.content,
usage={
"input_tokens": response.usage.prompt_tokens,
"output_tokens": response.usage.completion_tokens,
"cache_read_tokens": getattr(response.usage, 'cache_read_input_tokens', 0),
"cache_write_tokens": getattr(response.usage, 'cache_creation_input_tokens', 0)
}
)
Ora ogni token è tracciato fino al tenant.
Step 2: Real-Time Cost Monitoring e Alerting
Le soluzioni di tracciamento dei costi LLM usano il monitoraggio real-time per attivare notifiche automatizzate via email, Slack, o webhook quando l’utilizzo colpisce percentuali predefinite di un budget. Questi sistemi possono essere configurati con regole di enforcement automatizzate che limitano il traffico o bloccano richieste una volta raggiunto un hard cap.
Nel mio setup Plesk, creo un dashboard Prometheus + Grafana che monitora costi per tenant:
cat > cost-monitoring-prometheus.yaml << 'EOF'
groups:
- name: llm_cost_alerts
interval: 1m
rules:
- alert: TenantMonthlyBudgetWarning
expr: |
(sum(rate(llm_token_cost_usd_total{tenant_id="acme"}[1h])) * 720) > 500
for: 5m
annotations:
summary: "Tenant {{ $labels.tenant_id }} on track for {{ $value }}$ this month"
action: "Check agent logs for runaway loops"
- alert: TenantBudgetHardCap
expr: |
sum(llm_token_cost_usd_total{tenant_id="acme"}) > 2000
for: 1m
annotations:
summary: "Tenant {{ $labels.tenant_id }} exceeded hard cap. Throttling requests."
action: "Activate rate limiter for this tenant"
EOF
Quando ACME raggiunge il 90% del budget mensile, un alert via Slack mi avvisa. A 100%, il rate limiter automaticamente throttla le nuove richieste LLM fino alla fine del ciclo di fatturazione.
Step 3: Kill Switch per Runaway Agents
Interrompi un agente run quando il token count, tool-call count, retry count, o span depth supera il ceiling per quel tipo di agente. Il controllo appartiene al framework dell’agente perché un loop runaway deve essere fermato mentre è ancora in esecuzione, poiché un alert che si innesca dopo il completamento dell’esecuzione arriva troppo tardi per prevenire token sprecati.
Nel mio setup con LangChain, implemento un token budget wrapper:
from langchain.callbacks.base import BaseCallbackHandler
import time
class TokenBudgetCallback(BaseCallbackHandler):
def __init__(self, tenant_id: str, max_tokens: int = 50000):
self.tenant_id = tenant_id
self.max_tokens = max_tokens
self.token_count = 0
self.start_time = time.time()
def on_llm_end(self, response, **kwargs):
tokens_used = response.llm_output.get('token_usage', {}).get('total_tokens', 0)
self.token_count += tokens_used
# Kill switch: se raggiungi il 100% del budget, arresta l'agente
if self.token_count >= self.max_tokens:
raise RuntimeError(
f"Tenant {self.tenant_id} exceeded token budget "
f"({self.token_count}/{self.max_tokens} tokens used). "
f"Agent execution halted."
)
# Warning al 80%
if self.token_count >= int(self.max_tokens * 0.8):
logger.warning(
f"Tenant {self.tenant_id} at 80% token budget. "
f"Used {self.token_count}/{self.max_tokens} tokens."
)
# Usa nel tuo agente LangChain
agent = initialize_agent(
tools=tools,
llm=llm,
agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
callbacks=[TokenBudgetCallback(tenant_id="acme", max_tokens=50000)]
)
Ora se l’agente ACME entra in un loop, si ferma dopo 50k token. Non 500k. La differenza è circa $15.
Integrare Tutto in Plesk: Workflow Completo
Nel mio deployment Plesk finali, il flusso è:
- Tenant Onboarding: Un nuovo customer richiede un agente LLM ospitato. Creo un namespace Kubernetes con ResourceQuota, LimitRange, e NetworkPolicy preconfigirate.
- Container Deployment: Scansiono il container del cliente con trivy. Se passa, lo deploy in Kubernetes. Se fallisce, blocco e chiedo remediation.
- GPU Allocation: Il NVIDIA GPU Operator alloca una fetta della GPU H100 al tenant. Fair queuing garantisce che nessun tenant monopolizza.
- Token Tracking: Ogni LLM call è loggata in Langfuse con tag tenant. Prometheus scrape le metriche ogni minuto.
- Cost Alerting: Se il tenant raggiunge il 90% del budget, Slack mi avvisa. A 100%, il token rate limiter attiva.
- Runaway Prevention: Se l’agente entra in un retry loop, il token budget callback lo ferma entro 50k token.
Metriche da Monitorare
Nel mio dashboard Grafana giornaliero verifico:
- GPU Utilization per Tenant: Ogni tenant dovrebbe usare ~25% della GPU (o la sua fetta allocata). Se uno usi 90%, è un problema.
- Token Cost per Tenant: ACME spende $X/day? È in linea con il loro SLA?
- Latency Percentiles: P99 latenza per LLM inference. Se uno tenant causa latency spike per gli altri, la NetworkPolicy sta fallendo.
- Runaway Agent Detections: Quanti agenti sono stati stoppati dal token budget callback? Se è più di 5/week, il prodotto del cliente ha bug.
- Container Escape Attempts: Trivy ha rilevato vulnerabilità post-deploy? Falco ha loggato syscall sospette? (Questo è il controllo di sicurezza avanzato.)
Problemi Che Ho Affrontato (E Come Risolverli)
Problema 1: Container Image Scanning Too Strict
All’inizio, il trivy scanner bloccava ogni immagine con anche una vulnerabilità teorica di basso livello. Nessun tenant poteva deployare. Ho aggiunto una whitelist di CVE mitigabili (p.es., CVE in librerie non usate dal loro agente specifico) e ora richiedo solo che i tenant patchino vulnerabilità CRITICAL e HIGH.
Problema 2: GPU Time Slicing Overhead
Dividere una GPU H100 tra 4 tenant via time-slicing aggiunge ~15% overhead di context switch. Per tenant con latency-critical inference, ho introdotto una GPU pool “premium” dove i tenant pagano di più per una dedica 50% della GPU (2 per GPU). La maggior parte sceglie la pool standard e tollera il 15% hit.
Problema 3: Token Attribution Lag
Langfuse aggiorna i costi con ~2-3 minuti di latenza. Se voglio alerting quasi real-time, ho aggiunto uno strato di proxy gateway (Portkey o Kong) che log token localmente, e poi riconcilio con Langfuse ogni 5 minuti. Il sistema di rate limiting usa il conteggio locale, non aspetta Langfuse.
FAQ
Devo usare Kubernetes per questo isolamento? Non posso usare solo Docker + Plesk?
Dipende dalla scala. Per 5-10 tenant, Docker + Plesk funziona con un’attenta configurazione manuale di cgroup e Network namespaces. Per 50+ tenant, Kubernetes diventa non-negoziabile perché il scheduling e l’enforcement delle quote diventano troppo complessi manualmente. Nel mio caso, ho migrato da Docker a Kubernetes quando ho raggiunto 15 tenant.
Qual è il costo di infrastruttura di questo setup?
Assumendo una singola GPU H100 ($40k upfront, $1000/anno amortized) che serve 4 tenant: ~$250/tenant/anno in hardware. Aggiungi Kubernetes cluster (~$500/month), monitoring stack ($200/month), e container registry ($100/month). Se ogni tenant paga almeno $2000/anno per hosting + LLM API, il margine è sano.
Cosa succede se un tenant non rispetta le quote e tenta di fuggire dal container?
Tre linee di difesa:
(1) NetworkPolicy blocca le comunicazioni non autorizzate.
(2) ResourceQuota e LimitRange fanno crash il pod.
(3) Falco (runtime security) registra qualsiasi syscall sospetta (p.es., tentativo di accedere a /proc di un altro container). Se Falco vede qualcosa, l’agente pod è immediate evicted e io investigato.
Come gestisco le GPU con different vram capacità (T4 da 16GB vs H100 da 80GB)?
Creo node pool separate in Kubernetes: uno per “standard” (T4), uno per “large” (A100), uno per “ultra” (H100). Etichetto i pod con `nodeSelector` GPU-type per matchare i tenant alle risorse corrette. Un tenant che richiede un modello da 40GB (Llama 3-70B) viene schedulato su H100. Uno che usa GPT-4o (API call, nessun modello locale) va su T4.
Come integro questo con il controllo degli accessi IAM di Plesk esistente?
Mappo ogni Plesk subscription a un tenant Kubernetes + Langfuse org + Prometheus job. Quando un admin Plesk accede al panel, vede solo i costi LLM, le quote GPU, e i log del container del suo tenant. La mapping la gestisco via OIDC (Keycloak integrato con Plesk) per tenere sincronizzate le identità.
Conclusione
L’isolamento multi-tenant robusto per carichi di lavoro LLM in Plesk nel 2026 non è una features, è una necessità. Ho consolidato questa procedura attraverso tre strati: container sandboxing (namespace, ResourceQuota, NetworkPolicy), GPU resource quotas (scheduling fair, time slicing), e cost attribution con kill switch automatici.
Il beneficio: i tuoi tenant possono eseguire agenti LLM in autonomia senza rischio di interferenza, overshooting di budget, o data leaks. Il margine di hosting rimane prevedibile e le tue metriche di costo sono oneste fino a livello di singolo token.
Se state affrontando il problema opposto (tenant che vi chiedono di ridurre i loro costi LLM), questo articolo vi dà i lever per mostrare esattamente dove il loro agente spende i token. Buona implementazione!