Cloud Intelligence™Cloud Intelligence™

Cloud Intelligence™

Come aumentare la disponibilità di AWS EKS con EC2 Spot

By Nir ForerApr 18, 20207 min read

Questa pagina è disponibile anche in English, Deutsch, Español, Français, 日本語 e Português.

In DoiT International puntiamo all'efficienza dei costi e all'eccellenza operativa. Per questo abbiamo accolto con entusiasmo l'aggiunta dell'expander "Priority" al Cluster Autoscaler di Kubernetes: una funzionalità che permette di definire regole di priorità per le decisioni di autoscaling dei node pool e, di conseguenza, di costruire un meccanismo di fallback dai gruppi di nodi Spot a quelli on-demand per i workloads stateless. Abbiamo pensato di proporle una guida rapida su come sfruttare questa funzionalità con cluster Kubernetes su AWS EKS.

Se conosce già l'Autoscaler e i diversi tipi di expander, può passare direttamente alla demo

Cluster Autoscaler

Che cos'è il Cluster Autoscaler?

"Il Cluster Autoscaler è un programma autonomo che adatta le dimensioni di un cluster Kubernetes alle esigenze del momento."

Questo programma autonomo gira insieme ai workloads nel cluster Kubernetes, dialoga con il metrics-server (l'aggregatore a livello di cluster dei dati di utilizzo delle risorse), calcola la capacità necessaria e richiama l'API del cloud provider per aggiungere o rimuovere capacità dal cluster Kubernetes. Il Cluster Autoscaler dispone di diverse logiche per scegliere quale tipo di capacità aggiungere al cluster: questi meccanismi prendono il nome di Expander.

Cosa sono gli Expander?

"Quando il Cluster Autoscaler rileva la necessità di scalare un cluster a causa di pod non schedulabili, aumenta il numero di nodi in alcuni node group. Con un solo node group la strategia è banale; quando i node group sono più di uno, occorre decidere quale espandere."

Attualmente il Cluster Autoscaler implementa cinque Expander diversi:

  • random - è l'Expander predefinito, da usare quando non c'è una particolare esigenza di scalare i node group in modo differenziato.
  • most-pods - seleziona il node group in grado di schedulare il maggior numero di pod durante lo scale-up. Utile quando si usa nodeSelector per assicurarsi che determinati pod finiscano su determinati nodi. Va notato che questo non porta l'Autoscaler a preferire nodi più grandi rispetto a quelli più piccoli, dato che può aggiungere più nodi piccoli contemporaneamente.
  • least-waste - seleziona il node group che, dopo lo scale-up, avrà la minor CPU inutilizzata (in caso di parità, la minor memoria inutilizzata). Utile quando si dispone di classi di nodi differenti, ad esempio nodi ad alta CPU o ad alta memoria, e si vuole espanderli solo in presenza di pod in attesa che richiedono molte di queste risorse.
  • price - seleziona il node group dal costo più basso le cui macchine, allo stesso tempo, siano coerenti con le dimensioni del cluster. Questo expander è descritto in maggior dettaglio qui. Al momento funziona solo per GKE.
  • priority - seleziona il node group con la priorità più alta assegnata dall'utente. La sua configurazione è descritta in maggior dettaglio qui.

In questa demo le mostreremo come sfruttare l'expander "Priority" affinché l'Autoscaler tenti prima di scalare un node group di istanze Spot e, se non vi riesce, ripieghi sullo scale-out di un node group on-demand.

Se questo è il comportamento predefinito dei cluster GKE, configurati con l'expander "price", non vale lo stesso per i cluster EKS. Per fortuna, con la versione 1.14 del Cluster Autoscaler è stato introdotto l'expander "Priority", grazie al quale possiamo definire l'ordine di priorità con cui i diversi node group vengono ampliati.

Demo

Per questa dimostrazione creeremo un cluster EKS con il fantastico tool eksctl. Si assicuri di utilizzare eksctl 0.16.0 o versione successiva, perché ricorreremo alla strategia di allocazione capacity-optimized per i nostri pool di istanze Spot, supportata appunto a partire da eksctl 0.16.0.

Il cluster sarà composto da due node group: il primo è il node group di istanze Spot, con capacità desiderata pari a un'istanza, minimo zero e massimo 10. Il secondo è il node group on-demand di fallback, con capacità desiderata pari a zero, minimo zero e massimo 10 istanze.

Diversificazione dei tipi di istanza e strategia Capacity-Optimized per Spot — la massima disponibilità

Il nostro workload necessita di circa 1,5 vCPU e 7 GB di RAM per funzionare in modo fluido (in teoria), perciò abbiamo scelto di diversificare i 2 node pool con tipi di istanze EC2 differenti che ci offrono 2 vCPU e 8 GB di RAM. È indispensabile che tutti i tipi di istanza all'interno di uno stesso pool abbiano le medesime specifiche di vCPU e RAM, altrimenti il Cluster Autoscaler non può funzionare correttamente.

Diversificare il pool con più tipi di istanza massimizza la disponibilità dei nodi, perché la mancanza di capacità per un tipo di istanza può essere compensata dalla disponibilità di un altro. È un approccio molto utile nei casi in cui manchi capacità sia Spot sia on-demand di un determinato tipo: rimarranno comunque disponibili altri tipi di istanza (con prezzi differenti).

L'uso della strategia di allocazione capacity-optimized per la nostra fleet Spot garantisce che i nodi vengano forniti dalla famiglia di istanze con la maggiore capacità disponibile e quindi con la più bassa probabilità di interruzione.

Cloni il repository della demo e si sposti nella relativa directory:

git clone https://github.com/doitintl/eks-spot-to-ondemand-fallback.git && cd eks-spot-to-ondemand-fallback

Crei il cluster:

eksctl create cluster -f cluster/cluster.yaml

Il comando avvierà la creazione di tre stack CloudFormation che configureranno l'infrastruttura del cluster (control plane EKS, due Auto Scaling Group, VPC, ecc.). Al termine, eksctl le segnalerà che il cluster è pronto.

[✔] EKS cluster "my-eks-cluster" in "us-east-1" region is ready

eksctl aggiornerà automaticamente il file Kubeconfig con le informazioni sul nuovo cluster, così potrà subito eseguire comandi kubectl sul cluster.

Esegua il deploy del Metrics-server:

kubectl apply -f metrics-server/

La ConfigMap dell'expander Priority dell'Autoscaler

È qui che impostiamo le priorità che l'Autoscaler dovrà seguire per scegliere quale node group scalare quando serve capacità aggiuntiva. Il nome della ConfigMap deve essere cluster-autoscaler-priority-expander e va creata nello stesso namespace in cui risiede l'Autoscaler (nel nostro caso abbiamo scelto kube-system come namespace per il Cluster Autoscaler).

Il campo priorities è un array di chiavi (priorità) e valori (regexp per selezionare il nome del node group). Abbiamo creato i node group inserendo le stringhe on-demand e spot nei loro nomi, in modo che il Cluster Autoscaler possa assegnare le priorità di conseguenza.

Esegua il deploy della ConfigMap dell'expander Priority:

kubectl apply -f 0-autoscaler/

Esegua il deploy dell'Autoscaler:

Per prima cosa deve verificare che il file di configurazione dell'Autoscaler corrisponda agli ASG creati da eksctl. Per farlo, sostituisca le righe 157 e 158 di 1-autoscaler/cluster-autoscaler.yaml con i nomi dei nuovi ASG creati nel suo account AWS. Può recuperarli dalla UI di EC2 oppure con questo comando AWS CLI.

aws --region=us-east-1 autoscaling describe-auto-scaling-groups | jq '.AutoScalingGroups[]? | "\(.Tags[]|select((.Key=="alpha.eksctl.io/cluster-name") and (.Value=="my-eks-cluster"))|.ResourceId )"'

Sostituisca la stringa SPOT (riga 157) con il nome dell'ASG delle istanze Spot e la stringa on-demand (riga 158) con quello dell'ASG delle istanze on-demand.

Successivamente, esegua il deploy dell'Autoscaler:

kubectl apply -f 1-autoscaler/

Esegua il deploy dell'app:

kubectl apply -f app/

Il nostro stack è ora composto da un nodo Spot che esegue una replica della nostra app.

Si noti che il node group di questo nodo è spot-ng

Se ora portiamo il deployment da una a due repliche, il nodo attuale non riuscirà a schedulare il nuovo pod (abbiamo impostato la richiesta di CPU del pod a 1200m, mentre il nodo dispone di 2 vCPU) e l'Autoscaler avvierà un altro nodo, auspicabilmente dal node group delle istanze Spot.

Effettui lo scale-up dell'app:

kubectl scale --replicas=2 deployment/php-apache

Come previsto, il nuovo pod risulta in stato pending perché non può essere schedulato sul nodo disponibile.

Dopo qualche secondo viene aggiunto un nuovo nodo.

Verifichiamo che provenga anch'esso dal node group delle istanze Spot.

Si noti che il node group di questo nodo è spot-ng

Il pod che era in pending è ora schedulato sul nuovo nodo.

Ora limitiamo a due la dimensione massima del node group Spot e scaliamo la nostra app, sperando che venga aggiunta una nuova istanza on-demand per gestire il carico.

Limitazione del node group Spot a un massimo di due istanze

Ora portiamo l'app a tre repliche:

kubectl scale --replicas=3 deployment/php-apache

L'Autoscaler avrà bisogno di un altro nodo per questo nuovo pod e, dato che il gruppo Spot ha ormai raggiunto la capacità massima (lo abbiamo limitato a due nodi), dovrà avviare un nuovo nodo dal gruppo on-demand.

Un nuovo pod è in attesa di un nuovo nodo.

Viene aggiunto un nuovo nodo.

Vediamo qual è il node group di questo nodo.

Si noti che il node group di questo nodo è on-demand-ng

Un fallback affidabile per i suoi nodi

Abbiamo visto come creare un cluster EKS con un node group di istanze Spot e uno di istanze on-demand, dando priorità all'espansione del node group Spot.

Pulizia

eksctl delete cluster -f cluster/cluster.yaml

Per semplicità, in questo esempio abbiamo scelto di tralasciare diversi aspetti, come la gestione delle interruzioni delle istanze Spot e l'HPA, che restano comunque molto importanti e da considerare nella progettazione di un ambiente di produzione.

Per qualsiasi domanda, non esiti a scrivere nei commenti!