Con la crescita dei cluster Kubernetes verso decine di migliaia di nodi, i controller che osservano risorse ad alta cardinalità come i Pod si scontrano con un limite di scalabilità ben preciso. Kubernetes v1.36 introduce in alpha una soluzione elegante: il Server-Side Sharded List and Watch (KEP-5866), che sposta il filtraggio upstream nell’API server, riducendo drasticamente il traffico verso ogni replica di un controller.
Il problema: scaling client-side
Alcuni controller, come kube-state-metrics, supportano già lo sharding orizzontale: ogni replica è assegnata a una porzione dello spazio delle chiavi e scarta gli oggetti che non le appartengono. Questo approccio funziona dal punto di vista della correttezza, ma non risolve il problema di scala:
- N repliche × stream completo: ogni replica deserializza ed elabora ogni evento, poi scarta ciò che non le compete
- La banda di rete scala con le repliche, non con la dimensione dello shard
- CPU sprecata: il costo di deserializzazione è sostenuto per la frazione scartata
In sintesi, scalare orizzontalmente il controller moltiplica il costo per replica invece di ridurlo.
La soluzione: sharding lato server
Il Server-Side Sharded List and Watch risolve il problema spostando il filtraggio nell’API server. Ogni replica comunica all’API server quale intervallo di hash è di sua competenza, e l’API server invia solo gli eventi corrispondenti.
La feature aggiunge un campo shardSelector a ListOptions. I client specificano un intervallo di hash tramite la funzione shardRange():
shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')
L’API server calcola un hash FNV-1a a 64 bit deterministico del campo specificato e restituisce solo gli oggetti il cui hash ricade nell’intervallo [start, end). Questo vale sia per le risposte di list che per i flussi di eventi watch.
La funzione di hash produce lo stesso risultato su tutte le istanze dell’API server, quindi la feature è sicura anche con più repliche dell’API server.
I percorsi di campo attualmente supportati sono object.metadata.uid e object.metadata.namespace.
Utilizzo pratico nei controller
I controller tipicamente usano informer per fare list e watch sulle risorse. Per fare lo sharding del carico, ogni replica inietta il shardSelector nelle ListOptions usate dai suoi informer tramite WithTweakListOptions:
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/informers"
)
shardSelector := "shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')"
factory := informers.NewSharedInformerFactoryWithOptions(
client,
resyncPeriod,
informers.WithTweakListOptions(func(opts *metav1.ListOptions) {
opts.ShardSelector = shardSelector
}),
)
Per un deployment con 2 repliche, i selettori dividono lo spazio degli hash a metà:
// Replica 0: metà inferiore dello spazio di hash
"shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')"
// Replica 1: metà superiore dello spazio di hash
"shardRange(object.metadata.uid, '0x8000000000000000', '0x10000000000000000')"
Una singola replica può anche coprire range non contigui usando ||:
"shardRange(object.metadata.uid, '0x0000000000000000', '0x4000000000000000') || " +
"shardRange(object.metadata.uid, '0x8000000000000000', '0xc000000000000000')"
Verificare che il server supporti il selettore
Quando l’API server onora un shard selector, la risposta list include un campo shardInfo nel metadata della risposta che riporta il selettore applicato:
{
"kind": "PodList",
"apiVersion": "v1",
"metadata": {
"resourceVersion": "10245",
"shardInfo": {
"selector": "shardRange(object.metadata.uid, '0x0000000000000000', '0x8000000000000000')"
}
},
"items": [ ... ]
}
Se shardInfo è assente, il server non ha applicato il shard selector e il client ha ricevuto la collezione completa non filtrata. In questo caso, il client deve essere pronto a gestire il risultato completo — ad esempio applicando un filtraggio client-side per scartare gli oggetti fuori dal proprio shard.
Come abilitarla e stato della feature
Questa feature è in alpha in Kubernetes v1.36 e richiede di abilitare il feature gate ShardedListAndWatch sull’API server:
--feature-gates=ShardedListAndWatch=true
Non è ancora consigliata per ambienti di produzione, ma rappresenta un’opportunità preziosa per i team che gestiscono cluster di grandi dimensioni di iniziare i test e fornire feedback al SIG API Machinery.
Implicazioni architetturali
L’impatto di questa feature va oltre la semplice riduzione del traffico di rete. Cambia il modello mentale con cui si progettano controller scalabili:
- Efficienza reale: il costo per replica diminuisce proporzionalmente al numero di shard, invece di aumentare
- Scalabilità lineare: aggiungere repliche riduce effettivamente il carico su ciascuna
- Semplicità implementativa: lo sharding è dichiarativo — si specifica l’intervallo, l’API server fa il resto
- Compatibilità hash deterministica: FNV-1a garantisce distribuzione uniforme e risultati coerenti su più API server
Conclusioni
Il Server-Side Sharded List and Watch è una di quelle feature che risolvono un problema reale per chi opera cluster Kubernetes di grandi dimensioni. Spostare il filtraggio nell’API server è la scelta architetturalmente corretta: elimina il lavoro inutile alla radice invece di cercare di ottimizzarlo lato client.
Per i controller author e gli operatori di cluster grandi, vale la pena iniziare a sperimentare con questa feature sin da ora, contribuendo feedback al team di SIG API Machinery tramite il canale #sig-api-machinery su Kubernetes Slack.
Fonte originale: Kubernetes v1.36: Server-Side Sharded List and Watch — Kubernetes Blog, Jeffrey Ying