Ottimizzazione del dimensionamento automatico del servizio Amazon ECS - Amazon Elastic Container Service

Ottimizzazione del dimensionamento automatico del servizio Amazon ECS

Un servizio Amazon ECS è una raccolta gestita di attività. Ogni servizio ha una definizione di attività associata, un numero di attività desiderato e una strategia di posizionamento opzionale.

Il dimensionamento automatico del servizio Amazon ECS funziona tramite il servizio Application Auto Scaling. Application Auto Scaling utilizza i parametri di CloudWatch come fonte per i parametri di scalabilità. Utilizza inoltre gli avvisi CloudWatch per impostare soglie su quando ampliare o disattivare il servizio.

Sei tu a fornire le soglie per la scalabilità. Puoi impostare un parametro di destinazione, denominato dimensionamento con monitoraggio della destinazione. Puoi anche specificare delle soglie, operazione denominata scalabilità a fasi.

Dopo aver configurato Application Auto Scaling, questo calcola continuamente il numero di attività desiderato appropriato per il servizio. Inoltre, notifica ad Amazon ECS quando il numero di attività desiderato deve cambiare, ridimensionandolo orizzontalmente o verticalmente.

Per utilizzare il service di dimensionamento automatico in modo efficace, devi scegliere un parametro di dimensionamento adeguato. Nelle sezioni seguenti viene descritto come scegliere un parametro.

Caratterizzazione dell'applicazione

Per scalare correttamente un'applicazione devi conoscere le condizioni in cui è necessario scalare l'applicazione e quando è necessario scalarla orizzontalmente.

In sostanza, devi aumentare orizzontalmente l'applicazione se prevedi che la domanda superi la capacità. Al contrario, puoi ridurre orizzontalmente l'applicazione per ridurre i costi quando le risorse superano la domanda.

Identificazione di un parametro di utilizzo

Per una scalabilità efficace devi identificare un parametro che indichi l'utilizzo o la saturazione. Tale parametro deve presentare le seguenti proprietà per essere utile per la scalabilità.

  • Il parametro deve essere correlato alla domanda. Quando si mantengono stabili le risorse ma la domanda cambia, deve cambiare anche il valore del parametro. Il parametro deve aumentare o diminuire quando la domanda aumenta o diminuisce.

  • Il valore del parametro deve essere ridotto orizzontalmente in proporzione alla capacità. Quando la domanda rimane costante, l'aggiunta di più risorse deve comportare una modifica proporzionale del valore del parametro. Pertanto, il raddoppio del numero di attività dovrebbe far diminuire il parametro del 50%.

Il modo migliore per identificare un parametro di utilizzo è eseguire test di carico in un ambiente di preproduzione come un ambiente di staging. Sono ampiamente disponibili soluzioni commerciali e open source per i test di carico. Queste soluzioni di solito possono generare carichi sintetici o simulare il traffico utente reale.

Per avviare il processo di test di carico devi prima creare delle dashboard per i parametri di utilizzo dell'applicazione. Tali parametri includono l'utilizzo della CPU, l'utilizzo della memoria, le operazioni di I/O, la profondità della coda di I/O e il throughput di rete. Puoi rilevare questi parametri con un servizio come CloudWatch Container Insights. Puoi inoltre rilevarli utilizzando Amazon Managed Service per Prometheus insieme a Grafana gestito da Amazon. Durante questo processo, assicurati di rilevare e monitorare i parametri relative ai tempi di risposta della tua applicazione o ai tassi di completamento del lavoro.

Quando esegui il test di caricamento, inizia con un tasso ridotto di inserimento di richieste o processi. Mantieni questa frequenza costante per diversi minuti per consentire all'applicazione di prepararsi. Quindi, aumenta lentamente la velocità e mantienila costante per alcuni minuti. Ripeti questo ciclo, aumentando la frequenza ogni volta fino a quando i tempi di risposta o completamento dell'applicazione non sono troppo lenti per soddisfare gli obiettivi di livello di servizio (SLO).

Durante il test di caricamento esamina ciascun parametro di utilizzo. I parametri che aumentano insieme al carico sono quelli più adatti come migliori parametri di utilizzo.

Successivamente, identifica la risorsa che raggiunge la saturazione. Allo stesso tempo, esamina anche i parametri di utilizzo per vedere quale si appiattisce per prima a un livello elevato. Oppure, esamina quale raggiunge per primo il picco massimo e poi blocca l'applicazione. Ad esempio, se l'utilizzo della CPU aumenta dallo 0% al 70-80% man mano che aggiungi carico e poi rimane a quel livello dopo aver aggiunto ancora più carico, allora si può tranquillamente affermare che la CPU è satura. A seconda dell'architettura della CPU, potrebbe non raggiungere mai il 100%. Ad esempio, supponiamo che l'utilizzo della memoria aumenti man mano che aggiungi carico e che l'applicazione si blocchi improvvisamente quando raggiunge il limite di memoria dell'attività o dell'istanza Amazon EC2. In questa situazione, è probabile che la memoria sia stata completamente consumata. L'applicazione potrebbe utilizzare più risorse. Pertanto, scegli il parametro che rappresenta la risorsa che si esaurisce per prima.

Infine, riprova a testare il carico dopo aver raddoppiato il numero di attività o di istanze Amazon EC2. Supponiamo che il parametro chiave aumenti o diminuisca della metà rispetto a prima. In tal caso, il parametro è proporzionale alla capacità. Si tratta di un buon parametro di utilizzo per il dimensionamento automatico.

Consideriamo ora questo scenario ipotetico. Supponiamo di testare un'applicazione e di scoprire che l'utilizzo della CPU alla fine raggiunge l'80% a 100 richieste al secondo. Quando aggiungi altro carico, l'utilizzo della CPU non aumenta più. Tuttavia, fa sì che l'applicazione risponda più lentamente. Poi esegui nuovamente il test di carico, raddoppiando il numero di attività ma mantenendo la frequenza al valore di picco precedente. Se noti che l'utilizzo medio della CPU scende a circa il 40%, l'utilizzo medio della CPU è un buon candidato come parametro di scalabilità. D'altra parte, se l'utilizzo della CPU rimane all'80% dopo l'aumento del numero di attività, l'utilizzo medio della CPU non è un buon parametro di scalabilità. In tal caso, sono necessarie ulteriori ricerche per trovare un parametro adeguato.

Modelli di applicazione e proprietà di dimensionamento comuni

Puoi eseguire software di ogni tipo su AWS. Molti carichi di lavoro sono creati internamente, mentre altri si basano su software open source comuni. Indipendentemente dalla loro origine, abbiamo osservato alcuni modelli di progettazione comuni per i servizi. Il modo in cui esegui una scalabilità efficace dipende in gran parte dal modello.

Il server efficiente collegato alla CPU

Il server efficiente legato alla CPU non utilizza quasi nessuna risorsa oltre alla CPU e al throughput di rete. Ogni richiesta può essere gestita dalla sola applicazione. Le richieste non dipendono da altri servizi come i database. L'applicazione è in grado di gestire centinaia di migliaia di richieste simultanee e può utilizzare in modo efficiente più CPU per farlo. Ogni richiesta è gestita da un thread dedicato con un basso sovraccarico di memoria, oppure esiste un ciclo di eventi asincrono che viene eseguito su ogni CPU che soddisfa le richieste. Ogni replica dell'applicazione è ugualmente in grado di gestire una richiesta. L'unica risorsa che potrebbe esaurirsi prima della CPU è la larghezza di banda della rete. Nei servizi legati alla CPU, l'utilizzo della memoria, anche al picco del throughput, è una frazione delle risorse disponibili.

Per questo tipo di applicazione puoi utilizzare il dimensionamento automatico basato sulla CPU. L'applicazione gode della massima flessibilità in termini di dimensionamento. Puoi scalarla verticalmente fornendogli istanze Amazon EC2 più grandi o vCPU Fargate. Puoi anche scalarla orizzontalmente aggiungendo più repliche. L'aggiunta di altre repliche o il raddoppio delle dimensioni dell'istanza dimezzano l'utilizzo medio della CPU rispetto alla capacità.

Se utilizzi la capacità di Amazon EC2 per questa applicazione, valuta la possibilità di collocarla su istanze ottimizzate per il calcolo come la famiglia c5 o c6g.

Il server efficiente legato alla memoria

Il server efficiente legato alla memoria alloca una quantità significativa di memoria per richiesta. In caso di massima concorrenza, ma non necessariamente throughput, la memoria si esaurisce prima che si esauriscano le risorse della CPU. La memoria associata a una richiesta viene liberata al termine della richiesta. Possono essere accettate richieste aggiuntive purché sia disponibile memoria.

Per questo tipo di applicazione puoi utilizzare il dimensionamento automatico basato sulla memoria. L'applicazione gode della massima flessibilità in termini di dimensionamento. Puoi scalarla verticalmente fornendogli risorse di memoria Amazon EC2 o Fargate più grandi. Puoi anche scalarla orizzontalmente aggiungendo più repliche. L'aggiunta di altre repliche o il raddoppio delle dimensioni dell'istanza possono dimezzare l'utilizzo medio della memoria rispetto alla capacità.

Se utilizzi la capacità di Amazon EC2 per questa applicazione, valuta la possibilità di collocarla su istanze ottimizzate per la memoria come la famiglia r5 o r6g.

Alcune applicazioni legate alla memoria non liberano la memoria associata a una richiesta al termine, quindi una riduzione della concorrenza non si traduce in una riduzione della memoria utilizzata. Per questo consigliamo di non utilizzare il dimensionamento basato sulla memoria.

Il server basato sul lavoro

Il server basato sul lavoro elabora una richiesta per ogni singolo thread di lavoro una dopo l'altra. I thread di lavoro possono essere thread leggeri, come i thread POSIX. Possono anche essere thread più pesanti, come i processi UNIX. Indipendentemente dal thread, c'è sempre una concorrenza massima che l'applicazione è in grado di supportare. Di solito il limite di concorrenza è impostato proporzionalmente alle risorse di memoria disponibili. Se viene raggiunto il limite di concorrenza, l'applicazione inserisce richieste aggiuntive in una coda di backlog. Se la coda di backlog si esaurisce, l'applicazione rifiuta immediatamente le richieste aggiuntive in entrata. Le applicazioni comuni che si adattano a questo modello includono il server web Apache e Gunicorn.

La concorrenza delle richieste è in genere il parametro migliore per scalare questa applicazione. Poiché esiste un limite di concorrenza per ogni replica, è importante aumentare orizzontalmente prima che venga raggiunto il limite medio.

Il modo migliore per ottenere i parametri relativi alla concorrenza delle richieste consiste nel farli segnalare dall'applicazione a CloudWatch. Ogni replica dell'applicazione può pubblicare il numero di richieste simultanee come parametro personalizzato ad alta frequenza. Consigliamo di impostare la frequenza su almeno una volta al minuto. Dopo aver raccolto diversi report, puoi utilizzare la concorrenza media come parametro di dimensionamento. Tale parametro si calcola prendendo la concorrenza totale e dividendola per il numero di repliche. Ad esempio, se la concorrenza totale è 1000 e il numero di repliche è 10, la concorrenza media è 100.

Se la tua applicazione è alla base di un Application Load Balancer, puoi anche utilizzare il parametro ActiveConnectionCount per il bilanciatore del carico come fattore nel parametro di dimensionamento. Per ottenere un valore medio devi dividere il parametro ActiveConnectionCount per il numero di repliche. Devi utilizzare il valore medio per il dimensionamento, anziché il valore di conteggio non elaborato.

Affinché questa progettazione funzioni al meglio, la deviazione standard della latenza di risposta deve essere minima a basse frequenze di richiesta. È bene che, durante i periodi di scarsa domanda, la maggior parte delle richieste riceva risposta in breve tempo e che non vi siano molte richieste che impiegano molto più tempo della media per rispondere. Il tempo di risposta medio dovrebbe essere vicino al tempo di risposta del 95° percentile. In caso contrario, potrebbero verificarsi sovraccarichi dalla coda. Ciò porta a errori. Consigliamo di fornire repliche aggiuntive laddove necessario per mitigare il rischio di overflow.

Il server di attesa

Il server di attesa esegue alcune elaborazioni per ogni richiesta, ma il funzionamento dipende molto da uno o più servizi a valle. Le applicazioni container fanno spesso un uso intensivo di servizi a valle come database e altri servizi API. La risposta di questi servizi può richiedere del tempo, in particolare in scenari ad alta capacità o ad alta concorrenza. Ciò accade perché tali applicazioni tendono a utilizzare poche risorse della CPU e a sfruttare la massima concorrenza in termini di memoria disponibile.

Il servizio di attesa è adatto sia nel modello di server legato alla memoria che nel modello di server basato sul lavoro, a seconda di come è progettata l'applicazione. Se la concorrenza dell'applicazione è limitata solo dalla memoria, bisognerebbe utilizzare l'utilizzo medio della memoria come parametro di dimensionamento. Se la concorrenza dell'applicazione si basa su un limite di lavoratori, bisognerebbe utilizzare la concorrenza media come parametro di dimensionamento.

Il server basato su Java

Se il server basato su Java è legato alla CPU e si adatta proporzionalmente alle risorse della CPU, potrebbe essere adatto all'efficiente modello di server associato alla CPU. In tal caso, l'utilizzo medio della CPU potrebbe essere adatto come parametro di dimensionamento. Tuttavia, molte applicazioni Java non sono legate alla CPU, il che le rende difficili da scalare.

Per prestazioni ottimali, consigliamo di allocare quanta più memoria possibile all'heap Java Virtual Machine (JVM). Le versioni recenti di JVM, tra cui l'aggiornamento 191 di Java 8 o le versioni successive, impostano automaticamente la dimensione dell'heap al massimo possibile per adattarla al container. Ciò significa che, in Java, l'utilizzo della memoria è raramente proporzionale all'utilizzo delle applicazioni. Con l'aumento della frequenza delle richieste e della concorrenza, l'utilizzo della memoria rimane costante. Per questo motivo, sconsigliamo di scalare i server basati su Java in base all'utilizzo della memoria. Al contrario, in genere consigliamo di ridimensionare l'utilizzo della CPU.

In alcuni casi, i server basati su Java vanno incontro all'esaurimento dell'heap prima di esaurire la CPU. Se l'applicazione è soggetta all'esaurimento dell'heap in caso di elevata simultaneità, il parametro di dimensionamento migliore è rappresentato dalle connessioni medie. Se la tua applicazione è soggetta all'esaurimento dell'heap a un throughput elevato, il parametro di dimensionamento migliore è la frequenza media delle richieste.

Server che utilizzano altri runtime raccolti tramite rimozione di oggetti inutili

Molte applicazioni server si basano su runtime che effettuano la rimozione di oggetti inutili, ad esempio .NET e Ruby. Queste applicazioni server potrebbero rientrare in uno dei modelli descritti in precedenza. Tuttavia, come nel caso di Java, sconsigliamo di scalare tali applicazioni in base alla memoria, poiché il loro utilizzo medio della memoria osservato spesso non è correlato al throughput o alla concorrenza.

Per tali applicazioni consigliamo di scalare l'utilizzo della CPU se l'applicazione è vincolata alla CPU. Altrimenti, consigliamo di scalare in base al throughput medio o alla concorrenza media, in base ai risultati dei test di carico.

Elaboratori di processi

Molti carichi di lavoro prevedono l'elaborazione asincrona dei processi. Questi includono applicazioni che non ricevono richieste in tempo reale, ma si iscrivono a una coda di lavoro per ricevere processi. Per questi tipi di applicazioni, il parametro di dimensionamento adatto è quasi sempre la profondità della coda. L'aumento della coda indica che il lavoro in sospeso supera la capacità di elaborazione, mentre una coda vuota indica che c'è più capacità del lavoro da fare.

I servizi di messaggistica AWS, come Amazon SQS e i flussi di dati Amazon Kinesis, forniscono parametri CloudWatch che possono essere utilizzati per il dimensionamento. Per Amazon SQS, ApproximateNumberOfMessagesVisible è il parametro migliore. Per Kinesis Data Streams, considera l'utilizzo del parametro MillisBehindLatest, pubblicato dalla Kinesis Client Library (KCL). È necessario calcolare la media di questo parametro tra tutti i consumatori prima di utilizzarlo per il dimensionamento.