
Ciao! Voglio spiegare in modo semplice la meccanica della comparsa dello steal all'interno delle macchine virtuali e alcuni artefatti non immediatamente evidenti che siamo riusciti a scoprire durante la nostra ricerca, in cui sono dovuto immergermi come CTO della piattaforma cloud. . La piattaforma funziona su KVM.
Il tempo di CPU steal è il periodo in cui la macchina virtuale non riceve risorse della CPU necessarie per la sua esecuzione. Questo tempo è considerato solo nei sistemi operativi guest negli ambienti di virtualizzazione. Le ragioni per cui queste risorse allocate sembrano scomparire sono, come nella vita reale, piuttosto oscure. Ma abbiamo deciso di indagare, e abbiamo persino condotto una serie di esperimenti. Non è che ora sappiamo tutto sullo steal, ma abbiamo alcune informazioni interessanti da condividere.
1. Cos'è lo steal
Quindi, lo steal è una metrica che indica la carenza di tempo di CPU per i processi all'interno della macchina virtuale. Come descritto , steal — è il tempo durante il quale l'hypervisor esegue altri processi sul sistema operativo host, mentre ha messo il processo della macchina virtuale in coda per l'esecuzione. In altre parole, il tempo di steal è considerato come la differenza tra il momento in cui il processo è pronto per essere eseguito e il momento in cui gli viene assegnato del tempo di CPU.
La metrica di steal viene ricevuta dalla macchina virtuale dall'hypervisor. Tuttavia, l'hypervisor non specifica quali altri processi sta eseguendo, semplicemente «mentre sono occupato, non posso dedicarti tempo». Nel KVM, il supporto per il conteggio dello steal è stato aggiunto nei . Ci sono due punti chiave qui:
- La macchina virtuale apprende dello steal dall'hypervisor. Cioè, dal punto di vista delle perdite, per i processi sulla stessa virtual machine si tratta di una misura indiretta, che può essere soggetta a varie distorsioni.
- L'hypervisor non condivide con la macchina virtuale le informazioni su cosa stia facendo di altro — l'importante è che non le dedichi tempo. A causa di ciò, la stessa macchina virtuale non può rilevare le distorsioni nel valore dello steal, che potrebbero essere valutate in base alla natura dei processi concorrenti.
2. Cosa influisce sullo steal
2.1. Calcolo dello steal
In sostanza, il termine "steal" viene considerato quasi allo stesso modo del normale utilizzo della CPU. Non ci sono molte informazioni su come venga calcolato l'utilizzo. Probabilmente perché la maggior parte delle persone considera questa questione ovvia. Ma ci sono anche degli insidiosi dettagli. Per approfondire questo processo, puoi leggere : scoprirai molte sfumature nel calcolo dell'utilizzo e le situazioni in cui questo conteggio potrebbe essere errato per i seguenti motivi:
- Surriscaldamento della CPU, durante il quale si saltano dei cicli.
- Attivazione/disattivazione del turbo boost, che comporta una modifica della frequenza di clock della CPU.
- Variazione della durata del quantum di tempo, che avviene quando si utilizzano tecnologie di risparmio energetico della CPU, come SpeedStep.
- Il problema del calcolo della media: una stima dell'utilizzo dell'80% in un minuto può nascondere un picco temporaneo del 100%.
- Il blocco ciclico (spin lock) porta a una situazione in cui il processore è utilizzato, ma il processo dell'utente non mostra progressi nell'esecuzione. Di conseguenza, l'utilizzo calcolato del processore da parte del processo sarà del cento percento, anche se fisicamente il tempo di utilizzo del processore non sarà consumato dal processo.
Non ho trovato articoli che descrivano un conteggio simile per il steal (se ne conoscete, condivideteli nei commenti). Ma, a giudicare dal codice sorgente, il meccanismo di calcolo è lo stesso di quello per l'utilizzo. Viene semplicemente aggiunto un altro contatore nel kernel, specificamente per il processo KVM (il processo della macchina virtuale), che calcola la durata della permanenza del processo KVM in attesa del tempo di processore. Il contatore raccoglie informazioni sul processore dalle sue specifiche e verifica se tutti i suoi tick sono stati utilizzati dal processo virtuale. Se tutti lo sono, consideriamo che il processore è stato impegnato solo con il processo della macchina virtuale. In caso contrario, informiamo che il processore è stato impegnato in qualcos'altro, è apparso il steal.
Il processo di conteggio dello steal è soggetto agli stessi problemi del normale conteggio dell'utilizzo. Non si può dire che questi problemi si verifichino frequentemente, ma sono sicuramente scoraggianti.
2.2. Tipi di virtualizzazione su KVM
In generale, ci sono tre tipi di virtualizzazione, tutti supportati da KVM. Il tipo di virtualizzazione può influenzare il meccanismo di generazione dello steal.
Traduzione. In questo caso, il funzionamento del sistema operativo della macchina virtuale con i dispositivi fisici dell'iperdispositore avviene più o meno in questo modo:
- Il sistema operativo guest invia un comando al suo dispositivo guest.
- Il driver del dispositivo guest riceve il comando, elabora una richiesta per il BIOS del dispositivo e la invia all'iperdispositore.
- Il processo dell'iperdispositore traduce il comando in un comando per il dispositivo fisico, rendendolo, tra l'altro, più sicuro.
- Il driver del dispositivo fisico riceve il comando modificato e lo invia al dispositivo fisico stesso.
- I risultati dell'esecuzione dei comandi tornano indietro lungo lo stesso percorso.
Il vantaggio della virtualizzazione è che consente di emulare qualsiasi dispositivo senza necessitare di una preparazione specifica del kernel del sistema operativo. Tuttavia, questo comporta un sacrificio, soprattutto in termini di prestazioni.
Virtualizzazione hardware. In questo caso, il dispositivo comprende a livello hardware i comandi provenienti dal sistema operativo. Questo è il metodo più veloce e efficace. Sfortunatamente, però, non è supportato da tutti i dispositivi fisici, hypervisor e sistemi operativi guest. Al momento, i principali dispositivi che supportano la virtualizzazione hardware sono le CPU.
Paravirtualizzazione. La modalità di virtualizzazione più comune su KVM e, in generale, la più diffusa per i sistemi operativi guest. La sua caratteristica principale è che l'interazione con alcuni sottosistemi dell'ipervisor (ad esempio, con lo stack di rete o disco) o l'allocazione delle pagine di memoria avviene tramite le API dell'ipervisor, senza la traduzione di comandi a basso livello. Lo svantaggio di questo metodo di virtualizzazione è la necessità di modificare il kernel del sistema operativo guest affinché possa interagire con l'ipervisor attraverso queste API. Tuttavia, solitamente ciò si risolve installando driver speciali sul sistema operativo guest. In KVM, questa API è chiamata .
Con la paravirtualizzazione, rispetto alla traslazione, il percorso verso il dispositivo fisico si riduce significativamente grazie all'invio diretto dei comandi dalla macchina virtuale al processo dell'ipervisor sull'host. Questo consente di accelerare l'esecuzione di tutte le istruzioni all'interno della macchina virtuale. In KVM, ciò è gestito dall'API virtio, che funziona solo per determinati dispositivi, come l'adattatore di rete o il disco. È per questo che all'interno delle macchine virtuali vengono installati i driver virtio.
L'ombra di tale accelerazione è che non tutti i processi eseguiti all'interno della macchina virtuale rimangono al suo interno. Questo crea alcuni effetti collaterali che possono portare alla comparsa di steal. Consiglio di iniziare un'analisi approfondita di questo tema con .
2.3. Scheduling 'equo'
La macchina virtuale sull'ipervisor è, di fatto, un processo normale, che è soggetto alle regole di scheduling (distribuzione delle risorse tra i processi) del kernel Linux, quindi esaminiamola più in dettaglio.
In Linux si utilizza il cosiddetto CFS, Completely Fair Scheduler, che è diventato il gestore predefinito a partire dal kernel 2.6.23. Per comprendere questo algoritmo, è possibile leggere il Linux Kernel Architecture o i suoi sorgenti. L'essenza del CFS consiste nella distribuzione del tempo di CPU tra i processi in base alla durata della loro esecuzione. Maggiore è il tempo di CPU richiesto da un processo, minore sarà il tempo che riceverà. Questo garantisce un'esecuzione "equilibrata" di tutti i processi, in modo che un singolo processo non monopolizzi continuamente tutte le CPU, e gli altri processi possano anche essere eseguiti.
Talvolta questa paradigma porta a interessanti artefatti. I veterani di Linux ricorderanno sicuramente il congelamento di un normale editor di testo sul desktop durante l'avvio di applicazioni pesanti come un compilatore. Ciò accadeva perché i compiti leggeri delle applicazioni desktop competevano con quelli che consumavano attivamente risorse, come il compilatore. Il CFS considera questo ingiusto, quindi interrompe periodicamente l'editor di testo e permette alla CPU di elaborare i compiti del compilatore. Questo è stato risolto grazie a un meccanismo , ma ci sono ancora molte altre caratteristiche della distribuzione del tempo di CPU tra le attività. In effetti, non si tratta di come tutto sia problematico in CFS, ma è un tentativo di attirare l'attenzione sul fatto che una distribuzione "equa" del tempo di CPU non è una questione banale.
Un altro punto importante nel pianificatore è la preemption. Questo è necessario per espellere un processo affamato dalla CPU e dare l'opportunità di lavorare ad altri. Il processo di espulsione è chiamato context switching, lo switching del contesto della CPU. Durante questo processo, tutto il contesto del task viene salvato: lo stato dello stack, i registri e altro, dopodiché il processo viene messo in attesa e un altro prende il suo posto. Questa è un'operazione costosa per il sistema operativo e viene utilizzata raramente, ma in sostanza non c'è niente di male. Un frequente switching del contesto può indicare un problema nel sistema operativo, ma di solito avviene in modo continuo e non indica nulla di particolare.
Una storia così lunga è necessaria per spiegare un fatto: più risorse della CPU un processo cerca di utilizzare in uno scheduler Linux onesto, più velocemente verrà fermato, affinché anche altri processi possano lavorare. Se ciò sia corretto o meno è una questione complessa che varia in base ai diversi carichi. In Windows, fino a poco tempo fa, lo scheduler era orientato alla gestione prioritaria delle applicazioni desktop, il che poteva generare blocchi nei processi in background. In Sun Solaris c'erano cinque classi diverse di scheduler. Con l'introduzione della virtualizzazione è stato aggiunto un sesto, , poiché i cinque precedenti non funzionavano adeguatamente con la virtualizzazione di Solaris Zones. Consiglio di iniziare un approfondimento su questo argomento con libri come o .
2.4. Come monitorare il steal?
Monitorare il steal all'interno di una macchina virtuale, come qualsiasi altra metrica della CPU, è semplice: si può utilizzare qualsiasi strumento di raccolta metriche della CPU. L'importante è che la virtual machine sia su Linux. Per qualche motivo, Windows non fornisce queste informazioni ai suoi utenti. 🙁

Output del comando top: dettaglio del carico sulla CPU, nell'ultima colonna a destra — steal
La difficoltà sorge quando si cerca di ottenere queste informazioni dal hypervisor. Si può provare a stimare lo steal sulla machine host, ad esempio, attraverso il parametro Load Average (LA) — il valore medio dei processi in attesa nella coda di esecuzione. Il metodo di calcolo di questo parametro non è semplice, ma in generale, se il LA normalizzato rispetto al numero di thread della CPU è superiore a 1, ciò indica che il server Linux è sovraccarico.
Cosa stanno aspettando tutti questi processi? La risposta ovvia è — la CPU. Ma la risposta non è del tutto corretta, poiché a volte la CPU è libera, ma il LA è alle stelle. Ricordate, . Allo stesso modo, anche un disco e altri dispositivi di input/output possono comportarsi in questo modo. Ma in realtà, i processi possono attendere il completamento di qualsiasi tipo di blocco, sia fisico legato al dispositivo di input/output, sia logico, come i mutex. Questo include anche i blocchi a livello hardware (come le risposte dal disco) o logico (i cosiddetti primitivi di blocco, che comprendono molte entità come mutex adattivi, spin, semafori, variabili di condizione, rw locks, ipc locks…).
Un'altra caratteristica della LA è che viene considerata come una media nel sistema operativo. Ad esempio, se 100 processi competono per un file, allora LA=50. Un valore così elevato potrebbe far sembrare che il sistema operativo stia male. Tuttavia, per un codice mal scritto, questo può essere uno stato normale, in cui solo quel codice presenta problemi, mentre gli altri processi nel sistema operativo non ne risentono.
A causa di questa mediazione (che richiede almeno un minuto), cercare di definire qualcosa utilizzando il parametro LA non è l'impresa più gratificante, con risultati piuttosto incerti in casi specifici. Se provate a fare chiarezza, scoprirete che negli articoli di Wikipedia e in altre risorse disponibili sono descritti solo i casi più semplici, senza una spiegazione approfondita del processo. Rimando tutti gli interessati, ancora una volta, — nei collegamenti successivi. Chi non ha voglia di leggere in inglese — .
3. Effetti speciali
Ora ci concentreremo sui principali casi di apparizione di steal, con cui abbiamo a che fare. Vi dirò come derivano da quanto detto sopra e come si relazionano ai parametri del hypervisor.
Riutilizzazione. La cosa più semplice e comune: l'hypervisor è sovraccarico. In effetti, ci sono molte macchine virtuali in esecuzione, un alto consumo di CPU e una notevole competizione, con un utilizzo superiore a 1 in base ai thread della CPU. All'interno di tutte le macchine virtuali, tutto rallenta. Lo steal, trasmesso dall'hypervisor, aumenta anche; è necessario ridistribuire il carico o spegnere qualcuno. In generale, è tutto logico e chiaro.
Paravirtualizzazione contro istanze singole. Sull'hypervisor c'è una sola macchina virtuale, che consuma una piccola parte di esso, ma impone un grande carico in termini di input/output, ad esempio sul disco. E da qualche parte in essa appare un piccolo steal, fino al 10% (come mostrano alcuni esperimenti condotti).
Un caso interessante. Steal appare proprio a causa dei blocchi a livello dei driver paravirtualizzati. All'interno della virtual machine viene generata un'interruzione, che viene gestita dal driver e inviata al hypervisor. A causa della gestione dell'interruzione nel hypervisor, per la virtual machine questo sembra come una richiesta inviata, è pronta per essere eseguita e attende la CPU, ma non le viene data del tempo processore. La virtual machine pensa che questo tempo sia stato rubato.
Questo avviene nel momento in cui il buffer viene inviato, entra nello spazio kernel del hypervisor e iniziamo ad attenderlo. Sebbene, dal punto di vista della virtual machine, dovrebbe tornare subito. Di conseguenza, secondo l'algoritmo di calcolo di steal, questo tempo è considerato rubato. Probabilmente, in questa situazione potrebbero esserci anche altri meccanismi (ad esempio, l'elaborazione di alcune sys calls), ma non dovrebbero differire molto.
Scheduler contro virtual machines ad alto carico. Quando una macchina virtuale soffre di steal più di altre, ciò è legato proprio al pianificatore. Più intensamente un processo carica la CPU, più velocemente il pianificatore lo espelle per permettere anche agli altri di lavorare. Se una macchina virtuale consuma poco, non noterà quasi il steal: il suo processo ha onestamente atteso e ha bisogno di più tempo. Se una macchina virtuale produce un carico massimo su tutti i suoi core, verrà espulsa più spesso dalla CPU e si cercherà di non darle troppo tempo.
Ancora peggio è quando i processi all'interno della macchina virtuale cercano di ottenere più CPU, perché non riescono a elaborare i dati. Allora il sistema operativo sul hypervisor, grazie a un'ottimizzazione onesta, assegnerà sempre meno tempo di CPU. Questo processo avviene in modo esponenziale, e il steal schizza alle stelle, anche se le altre macchine virtuali possono quasi non notarlo. E più core ci sono, peggio sarà per la macchina colpita. In sintesi, soffrono di più le macchine virtuali ad alto carico con numerosi core.
Basso LA, ma c'è steal. Se il LA è circa 0,7 (cioè, l'hypervisor sembra non essere sovraccarico), ma all'interno di singole macchine virtuali si osserva lo steal:
- La variante di paravirtualizzazione già descritta sopra. La macchina virtuale può ricevere metriche che indicano lo steal, anche se l'hypervisor è a posto. Dai risultati dei nostri esperimenti, questa forma di steal non supera il 10% e non dovrebbe avere un impatto significativo sulle prestazioni delle applicazioni all'interno della macchina virtuale.
- Il parametro LA è calcolato in modo errato. Più precisamente, in ogni momento specifico viene calcolato correttamente, ma l'averlo mediato su un minuto risulta sottovalutato. Ad esempio, se una macchina virtuale consuma tutti i suoi processori per esattamente mezzo minuto su un terzo dell'hypervisor, il LA dell'hypervisor per un minuto sarà 0,15; quattro di queste macchine virtuali che lavorano simultaneamente daranno 0,6. E il fatto che per mezzo minuto ciascuna di esse ha avuto uno steal estremo del 25% non può essere recuperato.
- Ancora una volta, a causa dello scheduler che ha deciso che qualcuno stava consumando troppe risorse, e che quel qualcuno deve aspettare. Nel frattempo, cambio il contesto, gestisco le interruzioni e mi occupo di altre importanti attività di sistema. Alla fine, alcune macchine virtuali non riscontrano problemi, mentre altre subiscono una grave degradazione delle prestazioni.
4. Altri disturbi
Ci sono anche un milione di altre ragioni per le distorsioni nella giusta assegnazione del tempo di CPU in una macchina virtuale. Ad esempio, il hyper-threading e il NUMA complicano ulteriormente i calcoli. Questi confondono completamente la scelta del core per l'esecuzione del processo, poiché lo scheduler utilizza coefficienti — pesi che rendono il conteggio ancora più complicato durante il cambio di contesto.
Possono verificarsi distorsioni a causa di tecnologie come il turbo boost o, al contrario, della modalità di risparmio energetico, che possono alterare artificialmente la frequenza o anche il quantum di tempo su un server durante il calcolo della sua utilizzazione. L'attivazione del turbo boost riduce le prestazioni di un thread di CPU a causa dell'aumento delle prestazioni di un altro. In quel momento, le informazioni sulla frequenza attuale del processore non vengono trasferite alla macchina virtuale, che crede che il suo tempo venga rubato (ad esempio, se chiedeva 2 GHz ma ne riceve solo la metà).
In generale, ci possono essere molte cause di distorsione. In un sistema specifico, potresti scoprire qualcos'altro. È meglio iniziare con i libri ai quali ho fornito i link sopra e raccogliere statistiche dall'iper-vizore utilizzando strumenti come perf, sysdig, systemtap, ce ne sono .
5. Conclusioni
- Un certo livello di steal può verificarsi a causa della paravirtualizzazione ed è considerato normale. Si legge online che questo valore può raggiungere il 5-10%. Dipende dalle applicazioni all'interno della virtual machine e dal carico che esse impongono sulle proprie risorse fisiche. È importante prestare attenzione a come si comportano le applicazioni all'interno delle virtual machine.
- Il rapporto di carico sul hypervisor e lo steal all'interno della virtual machine non sono sempre chiaramente correlati; entrambe le stime dello steal possono risultare errate in situazioni specifiche con carichi diversi.
- Il scheduler tende a penalizzare i processi che effettuano molte richieste. Cerca di dare meno risorse a chi richiede di più. Grandi virtual machine sono problematiche.
- Un piccolo steal può essere nella norma anche senza paravirtualizzazione (considerando il carico all'interno della virtual machine, le caratteristiche del carico dei vicini, la distribuzione del carico sui thread e altri fattori).
- Se desideri capire il phenomenon dello steal in un sistema specifico, devi esplorare diverse opzioni, raccogliere metriche, analizzarle attentamente e pianificare come distribuire uniformemente il carico. Ci possono essere deviazioni da qualsiasi caso che devono essere confermate sperimentalmente o osservate nel debugger del kernel.
Fonte: habr.com
