Le traduzioni sono generate tramite traduzione automatica. In caso di conflitto tra il contenuto di una traduzione e la versione originale in Inglese, quest'ultima prevarrà.
Monitoraggio del piano di controllo
Server API
Quando esaminiamo il nostro server API, è importante ricordare che una delle sue funzioni è limitare le richieste in entrata per evitare di sovraccaricare il piano di controllo. Ciò che può sembrare un collo di bottiglia a livello di server API potrebbe in realtà essere la protezione da problemi più gravi. Dobbiamo tenere conto dei pro e dei contro dell'aumento del volume di richieste che transitano attraverso il sistema. Per stabilire se i valori del server API debbano essere aumentati, ecco un piccolo esempio delle cose a cui dobbiamo prestare attenzione:
-
Qual è la latenza delle richieste che attraversano il sistema?
-
Quella latenza è il server API stesso o qualcosa di «downstream» come etcd?
-
La profondità della coda del server API è un fattore di questa latenza?
-
Le code API Priority and Fairness (APF) sono configurate correttamente per i modelli di chiamata API desiderati?
Dov'è il problema?
Per iniziare, possiamo utilizzare la metrica della latenza delle API per avere un'idea del tempo impiegato dal server API per soddisfare le richieste. Utilizziamo la mappa termica ProMQL e Grafana riportata di seguito per visualizzare questi dati.
max(increase(apiserver_request_duration_seconds_bucket{subresource!="status",subresource!="token",subresource!="scale",subresource!="/healthz",subresource!="binding",subresource!="proxy",verb!="WATCH"}[$__rate_interval])) by (le)
Nota
Per un articolo approfondito su come monitorare il server API con la dashboard API utilizzata in questo articolo, consulta il seguente blog https://aws.amazon.com/blogs/containers/troubleshooting-amazon-eks-api-servers-with-prometheus/
Queste richieste sono tutte inferiori a un secondo, il che è una buona indicazione del fatto che il piano di controllo sta gestendo le richieste in modo tempestivo. Ma se non fosse così?
Il formato che stiamo utilizzando nella precedente API Request Duration è una mappa di calore. La cosa bella del formato heatmap è che ci indica il valore di timeout per l'API per impostazione predefinita (60 sec). Tuttavia, ciò che dobbiamo veramente sapere è a quale soglia questo valore dovrebbe essere preoccupante prima di raggiungere la soglia di timeout. Per avere una linea guida approssimativa sulle soglie accettabili, possiamo utilizzare l'upstream di Kubernetes SLO, disponibile qui https://github.com/kubernetes/community/blob/master/sig-scalability/slos/slos.md#steady-state-slisslos
Nota
Notate la funzione max in questa dichiarazione? Quando si utilizzano metriche che aggregano più server (per impostazione predefinita due server API su EKS) è importante non calcolare la media di questi server insieme.
Schemi di traffico asimmetrici
Cosa succederebbe se un server API [pod] fosse leggermente caricato e l'altro pesantemente caricato? Se facessimo la media di questi due numeri insieme potremmo interpretare erroneamente ciò che stava accadendo. Ad esempio, qui abbiamo tre server API ma tutto il carico è su uno di questi server API. Di norma, tutto ciò che ha più server, come etcd e server API, dovrebbe essere suddiviso in caso di problemi di scalabilità e prestazioni.
Con il passaggio a API Priority and Fairness, il numero totale di richieste sul sistema è solo uno dei fattori da verificare per verificare se il numero di iscrizioni al server API è eccessivo. Poiché ora il sistema funziona su una serie di code, dobbiamo verificare se alcune di queste code sono piene e se il traffico relativo a quella coda viene interrotto.
Diamo un'occhiata a queste code con la seguente query:
max without(instance)(apiserver_flowcontrol_nominal_limit_seats{})
Nota
Per ulteriori informazioni su come funziona l'API A&F, consulta la seguente guida alle best practice https://docs.aws.amazon.com/eks/latest/best-practices/scale-control-plane.html#_api_priority_and_fairness
Qui vengono visualizzati i sette diversi gruppi di priorità presenti di default nel cluster
Successivamente vogliamo vedere quale percentuale di quel gruppo di priorità viene utilizzata, in modo da poter capire se un determinato livello di priorità è saturo. Potrebbe essere auspicabile limitare le richieste a un livello di carico di lavoro basso, mentre un calo del livello di elezione di un leader non lo sarebbe.
Il sistema API Priority and Fairness (APF) presenta una serie di opzioni complesse, alcune delle quali possono avere conseguenze indesiderate. Un problema comune riscontrato sul campo è l'aumento della profondità della coda al punto che inizia ad aggiungere latenza non necessaria. Possiamo monitorare questo problema utilizzando la metrica. apiserver_flowcontrol_current_inqueue_request Possiamo verificare la presenza di cadute utilizzando ilapiserver_flowcontrol_rejected_requests_total. Queste metriche avranno un valore diverso da zero se un bucket supera la sua concorrenza.
L'aumento della profondità della coda può rendere l'API Server una fonte significativa di latenza e deve essere eseguito con cautela. Ti consigliamo di essere prudente con il numero di code create. Ad esempio, il numero di condivisioni su un sistema EKS è 600. Se creiamo troppe code, ciò può ridurre le condivisioni in code importanti che richiedono una maggiore produttività, come la coda per l'elezione dei leader o la coda di sistema. La creazione di troppe code aggiuntive può rendere più difficile il corretto dimensionamento di queste code.
Per concentrarci su una semplice modifica di grande impatto che puoi apportare in APF, prendiamo semplicemente le condivisioni dai bucket sottoutilizzati e aumentiamo le dimensioni dei bucket che sono al massimo utilizzo. Ridistribuendo in modo intelligente le azioni tra questi bucket, puoi ridurre la probabilità di cali.
Per ulteriori informazioni, consulta le impostazioni di priorità ed equità delle API nella Guida alle best practice EKS.
Latenza tra API e etcd
Come possiamo utilizzare il metrics/logs server API per determinare se c'è un problema con il server API, o un problema che riguarda il server API o una combinazione upstream/downstream di entrambi. Per capirlo meglio, diamo un'occhiata a come API Server ed etcd possono essere correlati e a quanto può essere facile risolvere i problemi del sistema sbagliato.
Nel grafico seguente vediamo la latenza del server API, ma vediamo anche che gran parte di questa latenza è correlata al server etcd a causa delle barre nel grafico che mostrano la maggior parte della latenza a livello etcd. Se ci sono 15 secondi di latenza etcd contemporaneamente ci sono 20 secondi di latenza del server API, allora la maggior parte della latenza è effettivamente a livello etcd.
Osservando l'intero flusso, vediamo che è consigliabile non concentrarsi solo sull'API Server, ma anche cercare segnali che indichino che etcd è in difficoltà (ad esempio, il numero di applicazioni lente aumenta). Essere in grado di passare rapidamente all'area problematica giusta con una sola occhiata è ciò che rende potente una dashboard.
Nota
La dashboard nella sezione è disponibile all'indirizzo https://github.com/RiskyAdventure/Troubleshooting-Dashboards/blob/main/api-troubleshooter.json
Problemi relativi al piano di controllo e al cliente
In questo grafico stiamo cercando le chiamate API che hanno richiesto più tempo per essere completate in quel periodo. In questo caso vediamo che una risorsa personalizzata (CRD) sta chiamando una funzione APPLY che è la chiamata più latente durante l'intervallo di tempo delle 05:40.
Grazie a questi dati, possiamo utilizzare una query Ad-Hoc ProMQL o CloudWatch Insights per estrarre le richieste LIST dal registro di controllo durante quel periodo di tempo per vedere di quale applicazione si tratta.
Trovare la fonte con CloudWatch
Le metriche vengono utilizzate al meglio per trovare l'area problematica che vogliamo esaminare e restringere sia il periodo di tempo che i parametri di ricerca del problema. Una volta ottenuti questi dati, vogliamo passare ai log per visualizzare tempi ed errori più dettagliati. Per fare ciò trasformeremo i nostri log in metriche utilizzando CloudWatch Logs Insights.
Ad esempio, per esaminare il problema precedente, utilizzeremo la seguente query CloudWatch Logs Insights per estrarre UserAgent e RequestURI in modo da poter individuare l'applicazione che causa questa latenza.
Nota
È necessario utilizzare un Count appropriato per non rilevare il normale List/Resync comportamento di un orologio.
fields @timestamp, @message | filter @logStream like "kube-apiserver-audit" | filter ispresent(requestURI) | filter verb = "list" | parse requestReceivedTimestamp /\d+-\d+-(?<StartDay>\d+)T(?<StartHour>\d+):(?<StartMinute>\d+):(?<StartSec>\d+).(?<StartMsec>\d+)Z/ | parse stageTimestamp /\d+-\d+-(?<EndDay>\d+)T(?<EndHour>\d+):(?<EndMinute>\d+):(?<EndSec>\d+).(?<EndMsec>\d+)Z/ | fields (StartHour * 3600 + StartMinute * 60 + StartSec + StartMsec / 1000000) as StartTime, (EndHour * 3600 + EndMinute * 60 + EndSec + EndMsec / 1000000) as EndTime, (EndTime - StartTime) as DeltaTime | stats avg(DeltaTime) as AverageDeltaTime, count(*) as CountTime by requestURI, userAgent | filter CountTime >=50 | sort AverageDeltaTime desc
Utilizzando questa query abbiamo trovato due agenti diversi che eseguono un gran numero di operazioni di elenco ad alta latenza. Splunk e agent. CloudWatch Grazie ai dati, possiamo decidere di rimuovere, aggiornare o sostituire questo controller con un altro progetto.
Nota
Per maggiori dettagli su questo argomento, consulta il seguente blog
Scheduler
Poiché le istanze del piano di controllo EKS vengono eseguite in un account AWS separato, non saremo in grado di analizzare tali componenti per le metriche (il server API è l'eccezione). Tuttavia, poiché abbiamo accesso ai log di controllo per questi componenti, possiamo trasformarli in metriche per vedere se qualcuno dei sottosistemi sta causando un collo di bottiglia nella scalabilità. Usiamo CloudWatch Logs Insights per vedere quanti pod non pianificati sono presenti nella coda dello scheduler.
Pod non programmati nel registro dello scheduler
Se potessimo accedere all'analisi delle metriche dello scheduler direttamente su un Kubernetes autogestito (come Kops), utilizzeremmo il seguente ProMQL per comprendere il backlog dello scheduler.
max without(instance)(scheduler_pending_pods)
Poiché non abbiamo accesso alla metrica precedente in EKS, utilizzeremo la seguente query CloudWatch Logs Insights per visualizzare il backlog controllando quanti pod non sono stati in grado di annullare la pianificazione durante un determinato periodo di tempo. Quindi potremmo approfondire ulteriormente i messaggi nei momenti di punta per comprendere la natura del collo di bottiglia. Ad esempio, i nodi non si avviano abbastanza velocemente o il limitatore di velocità nello scheduler stesso.
fields timestamp, pod, err, @message
| filter @logStream like "scheduler"
| filter @message like "Unable to schedule pod"
| parse @message /^.(?<date>\d{4})\s+(?<timestamp>\d+:\d+:\d+\.\d+)\s+\S*\s+\S+\]\s\"(.*?)\"\s+pod=(?<pod>\"(.*?)\")\s+err=(?<err>\"(.*?)\")/
| count(*) as count by pod, err
| sort count desc
Qui vediamo gli errori dello scheduler che indicano che il pod non è stato implementato perché il PVC di archiviazione non era disponibile.
Nota
La registrazione degli audit deve essere attivata sul piano di controllo per abilitare questa funzione. È inoltre consigliabile limitare la conservazione dei registri per non aumentare inutilmente i costi nel tempo. Un esempio per attivare tutte le funzioni di registrazione utilizzando lo strumento EKSCTL di seguito.
cloudWatch: clusterLogging: enableTypes: ["*"] logRetentionInDays: 10
Kube Controller Manager
Kube Controller Manager, come tutti gli altri controller, ha dei limiti al numero di operazioni che può eseguire contemporaneamente. Esaminiamo quali sono alcuni di questi flag esaminando una configurazione KOPS in cui possiamo impostare questi parametri.
kubeControllerManager: concurrentEndpointSyncs: 5 concurrentReplicasetSyncs: 5 concurrentNamespaceSyncs: 10 concurrentServiceaccountTokenSyncs: 5 concurrentServiceSyncs: 5 concurrentResourceQuotaSyncs: 5 concurrentGcSyncs: 20 kubeAPIBurst: 30 kubeAPIQPS: "20"
Questi controller hanno code che si riempiono durante i periodi di elevato tasso di abbandono di un cluster. In questo caso vediamo che il controller del set replicaset ha un ingente backlog nella sua coda.
Abbiamo due modi diversi per affrontare una situazione del genere. Se l'esecuzione fosse gestita autonomamente, potremmo semplicemente aumentare le goroutine simultanee, ma ciò avrebbe un impatto su etcd elaborando più dati nel KCM. L'altra opzione sarebbe quella di ridurre il numero di oggetti replicaset utilizzati nella distribuzione per ridurre il numero di oggetti replicaset di cui è possibile eseguire il rollback, riducendo così la pressione .spec.revisionHistoryLimit su questo controller.
spec: revisionHistoryLimit: 2
Altre funzionalità di Kubernetes possono essere ottimizzate o disattivate per ridurre la pressione nei sistemi ad alto tasso di abbandono. Ad esempio, se l'applicazione nei nostri pod non ha bisogno di comunicare direttamente con l'API k8s, disattivare il segreto proiettato in quei pod ridurrebbe il carico di lavoro. ServiceaccountTokenSyncs Questo è il modo più desiderabile per risolvere tali problemi, se possibile.
kind: Pod spec: automountServiceAccountToken: false
Nei sistemi in cui non possiamo accedere alle metriche, possiamo esaminare nuovamente i log per rilevare eventuali conflitti. Se volessimo vedere il numero di richieste elaborate per controller o a livello aggregato, utilizzeremmo la seguente CloudWatch Logs Insights Query.
Volume totale elaborato dal KCM
# Query to count API qps coming from kube-controller-manager, split by controller type. # If you're seeing values close to 20/sec for any particular controller, it's most likely seeing client-side API throttling. fields @timestamp, @logStream, @message | filter @logStream like /kube-apiserver-audit/ | filter userAgent like /kube-controller-manager/ # Exclude lease-related calls (not counted under kcm qps) | filter requestURI not like "apis/coordination.k8s.io/v1/namespaces/kube-system/leases/kube-controller-manager" # Exclude API discovery calls (not counted under kcm qps) | filter requestURI not like "?timeout=32s" # Exclude watch calls (not counted under kcm qps) | filter verb != "watch" # If you want to get counts of API calls coming from a specific controller, uncomment the appropriate line below: # | filter user.username like "system:serviceaccount:kube-system:job-controller" # | filter user.username like "system:serviceaccount:kube-system:cronjob-controller" # | filter user.username like "system:serviceaccount:kube-system:deployment-controller" # | filter user.username like "system:serviceaccount:kube-system:replicaset-controller" # | filter user.username like "system:serviceaccount:kube-system:horizontal-pod-autoscaler" # | filter user.username like "system:serviceaccount:kube-system:persistent-volume-binder" # | filter user.username like "system:serviceaccount:kube-system:endpointslice-controller" # | filter user.username like "system:serviceaccount:kube-system:endpoint-controller" # | filter user.username like "system:serviceaccount:kube-system:generic-garbage-controller" | stats count(*) as count by user.username | sort count desc
Quando si esaminano i problemi di scalabilità, è fondamentale esaminare ogni fase del percorso (API, scheduler, KCM, ecc.) prima di passare alla fase dettagliata di risoluzione dei problemi. Spesso in produzione si scopre che sono necessarie modifiche a più di una parte di Kubernetes per consentire al sistema di funzionare al massimo delle prestazioni. È facile risolvere inavvertitamente quello che è solo un sintomo (come un timeout del nodo) di un collo di bottiglia molto più grande.
ECCETERA
etcd utilizza un file mappato in memoria per memorizzare le coppie chiave-valore in modo efficiente. Esiste un meccanismo di protezione per impostare la dimensione di questo spazio di memoria disponibile impostato comunemente sui limiti di 2, 4 e 8 GB. Meno oggetti nel database significa meno pulizia, etcd deve fare quando gli oggetti vengono aggiornati e le versioni precedenti devono essere ripulite. Questo processo di pulizia delle vecchie versioni di un oggetto viene definito compattazione. Dopo una serie di operazioni di compattazione, c'è un processo successivo che recupera lo spazio utilizzabile chiamato deframmentazione che avviene al di sopra di una certa soglia o in base a un programma di tempo fisso.
Ci sono un paio di elementi relativi agli utenti che possiamo fare per limitare il numero di oggetti in Kubernetes e quindi ridurre l'impatto del processo di compattazione e deframmentazione. Ad esempio, Helm mantiene un livello elevato. revisionHistoryLimit Ciò consente agli oggetti più vecchi, ad esempio ReplicaSets presenti nel sistema, di eseguire il rollback. Impostando i limiti della cronologia a 2 possiamo ridurre il numero di oggetti (like ReplicaSets) da dieci a due, il che a sua volta comporterebbe un carico minore sul sistema.
apiVersion: apps/v1 kind: Deployment spec: revisionHistoryLimit: 2
Dal punto di vista del monitoraggio, se si verificano picchi di latenza del sistema secondo uno schema prestabilito separato da ore, può essere utile verificare se questo processo di deframmentazione è la fonte. Possiamo vederlo usando Logs. CloudWatch
Se vuoi vedere i start/end tempi di deframmentazione usa la seguente query:
fields @timestamp, @message | filter @logStream like /etcd-manager/ | filter @message like /defraging|defraged/ | sort @timestamp asc