View a markdown version of this page

Monitoramento do plano de controle - Amazon EKS

As traduções são geradas por tradução automática. Em caso de conflito entre o conteúdo da tradução e da versão original em inglês, a versão em inglês prevalecerá.

Monitoramento do plano de controle

Servidor da API

Ao analisar nosso servidor de API, é importante lembrar que uma de suas funções é limitar as solicitações de entrada para evitar sobrecarregar o plano de controle. O que pode parecer um gargalo no nível do servidor de API pode, na verdade, estar protegendo-o de problemas mais sérios. Precisamos considerar os prós e os contras de aumentar o volume de solicitações que circulam pelo sistema. Para determinar se os valores do servidor de API devem ser aumentados, aqui está uma pequena amostra do que precisamos considerar:

  1. Qual é a latência das solicitações que circulam pelo sistema?

  2. Essa latência é do próprio servidor da API ou algo “downstream” como etcd?

  3. A profundidade da fila do servidor de API é um fator nessa latência?

  4. As filas da API Priority and Fairness (APF) estão configuradas corretamente para os padrões de chamada da API que queremos?

Onde está o problema?

Para começar, podemos usar a métrica de latência da API para nos dar uma ideia de quanto tempo o servidor da API está levando para atender às solicitações. Vamos usar o mapa de calor ProMQL e Grafana abaixo para exibir esses dados.

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

Para uma descrição detalhada sobre como monitorar o servidor da API com o painel da API usado neste artigo, consulte o blog a seguir

Mapa de calor da duração da solicitação de API

Essas solicitações estão todas abaixo da marca de um segundo, o que é uma boa indicação de que o plano de controle está tratando as solicitações em tempo hábil. Mas e se esse não fosse o caso?

O formato que estamos usando na duração da solicitação de API acima é um mapa de calor. O que é bom sobre o formato do mapa de calor é que ele nos diz o valor do tempo limite da API por padrão (60 segundos). No entanto, o que realmente precisamos saber é em que limite esse valor deve ser motivo de preocupação antes de atingirmos o limite de tempo limite. Para uma orientação aproximada de quais são os limites aceitáveis, podemos usar o SLO upstream do Kubernetes, que pode ser encontrado aqui https://github.com/kubernetes/community/blob/master/sig-scalability/slos/slos.md#steady-state-slisslos

nota

Percebeu a função máxima nesta declaração? Ao usar métricas que agregam vários servidores (por padrão, dois servidores de API no EKS), é importante não calcular a média desses servidores juntos.

Padrões de tráfego assimétricos

E se um servidor de API [pod] estivesse levemente carregado e o outro estivesse muito carregado? Se calculássemos a média desses dois números juntos, poderíamos interpretar mal o que estava acontecendo. Por exemplo, aqui temos três servidores de API, mas toda a carga está em um desses servidores de API. Como regra, qualquer coisa que tenha vários servidores, como servidores etcd e API, deve ser interrompida ao investir em problemas de escala e desempenho.

Total de solicitações a bordo

Com a mudança para Prioridade e Equidade da API, o número total de solicitações no sistema é apenas um fator para verificar se o servidor da API está com excesso de inscrições. Como o sistema agora funciona com uma série de filas, devemos verificar se alguma dessas filas está cheia e se o tráfego dessa fila está diminuindo.

Vamos analisar essas filas com a seguinte consulta:

max without(instance)(apiserver_flowcontrol_nominal_limit_seats{})
nota

Para obter mais informações sobre como a API A&F funciona, consulte o seguinte guia de melhores práticas

Aqui, vemos os sete grupos de prioridades diferentes que vêm por padrão no cluster.

Concorrência compartilhada

Em seguida, queremos ver qual porcentagem desse grupo prioritário está sendo usada, para que possamos entender se um determinado nível de prioridade está sendo saturado. Limitar as solicitações no nível de carga de trabalho baixa pode ser desejável, mas reduções no nível de eleição de um líder não seriam.

O sistema API Priority and Fairness (APF) tem várias opções complexas, algumas dessas opções podem ter consequências inesperadas. Um problema comum que vemos no campo é aumentar a profundidade da fila a ponto de começar a adicionar latência desnecessária. Podemos monitorar esse problema usando a apiserver_flowcontrol_current_inqueue_request métrica. Podemos verificar se há quedas usando apiserver_flowcontrol_rejected_requests_total o. Essas métricas serão um valor diferente de zero se algum intervalo exceder sua simultaneidade.

Solicitações em uso

Aumentar a profundidade da fila pode tornar o servidor de API uma fonte significativa de latência e deve ser feito com cuidado. Recomendamos que você seja criterioso com o número de filas criadas. Por exemplo, o número de compartilhamentos em um sistema EKS é 600. Se criarmos muitas filas, isso poderá reduzir os compartilhamentos em filas importantes que precisam da taxa de transferência, como a fila de eleição de líderes ou a fila do sistema. Criar muitas filas extras pode dificultar o dimensionamento correto dessas filas.

Para focar em uma mudança simples e impactante que você pode fazer no APF, simplesmente pegamos ações de baldes subutilizados e aumentamos o tamanho dos baldes que estão em seu uso máximo. Ao redistribuir de forma inteligente as ações entre esses grupos, você pode diminuir a probabilidade de quedas.

Para obter mais informações, visite as configurações de Prioridade e Equidade da API no Guia de Melhores Práticas do EKS.

API versus latência etcd

Como podemos usar o metrics/logs do servidor da API para determinar se há um problema com o servidor da API, ou um problema no servidor upstream/downstream da API, ou uma combinação dos dois. Para entender isso melhor, vamos ver como o API Server e o etcd podem estar relacionados e como pode ser fácil solucionar problemas no sistema errado.

No gráfico abaixo, vemos a latência do servidor de API, mas também vemos que grande parte dessa latência está correlacionada ao servidor etcd devido às barras no gráfico que mostram a maior parte da latência no nível etcd. Se houver 15 segundos de latência etcd ao mesmo tempo em que houver 20 segundos de latência do servidor de API, a maior parte da latência estará, na verdade, no nível etcd.

Ao analisar todo o fluxo, vemos que é aconselhável não se concentrar apenas no servidor de API, mas também procurar sinais que indiquem que o etcd está sob pressão (ou seja, aumento lento dos contadores de aplicação). Ser capaz de se mover rapidamente para a área problemática correta com apenas uma olhada é o que torna um painel poderoso.

nota

O painel na seção pode ser encontrado em https://github.com/RiskyAdventure/Troubleshooting-Dashboards/blob/main/api-troubleshooter.json

Coação ETC

Problemas no plano de controle versus problemas do lado do cliente

Neste gráfico, estamos procurando as chamadas de API que levaram mais tempo para serem concluídas nesse período. Nesse caso, vemos que um recurso personalizado (CRD) está chamando uma função APPLY que é a chamada mais latente durante o período de 05:40.

Solicitações mais lentas

Com esses dados, podemos usar uma consulta Ad-Hoc ProMQL ou CloudWatch Insights para extrair solicitações LIST do registro de auditoria durante esse período para ver qual aplicativo pode ser esse.

Encontrando a fonte com CloudWatch

As métricas são melhor usadas para encontrar a área do problema que queremos examinar e restringir o período de tempo e os parâmetros de pesquisa do problema. Quando tivermos esses dados, queremos fazer a transição para os registros para obter horários e erros mais detalhados. Para fazer isso, transformaremos nossos registros em métricas usando o CloudWatch Logs Insights.

Por exemplo, para investigar o problema acima, usaremos a seguinte consulta do CloudWatch Logs Insights para extrair o userAgent e o requestURI para que possamos identificar qual aplicativo está causando essa latência.

nota

Uma contagem apropriada precisa ser usada para evitar o List/Resync comportamento normal em um relógio.

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

Usando essa consulta, encontramos dois agentes diferentes executando um grande número de operações de lista de alta latência. Splunk e CloudWatch agente. Com os dados, podemos tomar a decisão de remover, atualizar ou substituir esse controlador por outro projeto.

Resultados de consultas
nota

Para obter mais detalhes sobre esse assunto, consulte o seguinte blog

Agendador

Como as instâncias do plano de controle do EKS são executadas em uma conta separada da AWS, não poderemos coletar esses componentes em busca de métricas (o servidor da API é a exceção). No entanto, como temos acesso aos registros de auditoria desses componentes, podemos transformá-los em métricas para ver se algum dos subsistemas está causando um gargalo de escalabilidade. Vamos usar o CloudWatch Logs Insights para ver quantos pods não programados estão na fila do agendador.

Pods não programados no registro do agendador

Se tivéssemos acesso para coletar as métricas do agendador diretamente em um Kubernetes autogerenciado (como o Kops), usaríamos o seguinte ProMQL para entender o backlog do agendador.

max without(instance)(scheduler_pending_pods)

Como não temos acesso à métrica acima no EKS, usaremos a consulta CloudWatch Logs Insights abaixo para ver a lista de pendências verificando quantos pods não puderam ser desprogramados durante um determinado período. Em seguida, poderíamos nos aprofundar nas mensagens no horário de pico para entender a natureza do gargalo. Por exemplo, os nós não estão girando rápido o suficiente ou o limitador de taxa no próprio agendador.

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

Aqui, vemos os erros do programador dizendo que o pod não foi implantado porque o PVC de armazenamento não estava disponível.

CloudWatch Consulta de registros
nota

O registro de auditoria deve ser ativado no plano de controle para ativar essa função. Também é uma prática recomendada limitar a retenção de registros para não aumentar desnecessariamente os custos ao longo do tempo. Um exemplo para ativar todas as funções de registro usando a ferramenta EKSCTL abaixo.

cloudWatch: clusterLogging: enableTypes: ["*"] logRetentionInDays: 10

Gerenciador de controladores Kube

O Kube Controller Manager, como todos os outros controladores, tem limites de quantas operações ele pode fazer ao mesmo tempo. Vamos analisar quais são alguns desses sinalizadores examinando uma configuração KOPS na qual podemos definir esses parâmetros.

kubeControllerManager: concurrentEndpointSyncs: 5 concurrentReplicasetSyncs: 5 concurrentNamespaceSyncs: 10 concurrentServiceaccountTokenSyncs: 5 concurrentServiceSyncs: 5 concurrentResourceQuotaSyncs: 5 concurrentGcSyncs: 20 kubeAPIBurst: 30 kubeAPIQPS: "20"

Esses controladores têm filas que se enchem durante períodos de alta rotatividade em um cluster. Nesse caso, vemos que o controlador do conjunto replicaset tem uma grande lista de pendências em sua fila.

Queues (Filas)

Temos duas maneiras diferentes de lidar com essa situação. Se executássemos o autogerenciado, poderíamos simplesmente aumentar as goroutines simultâneas; no entanto, isso teria um impacto no etcd ao processar mais dados no KCM. A outra opção seria reduzir o número de objetos replicaset usados .spec.revisionHistoryLimit na implantação para reduzir o número de objetos replicaset que podemos reverter, reduzindo assim a pressão sobre esse controlador.

spec: revisionHistoryLimit: 2

Outros recursos do Kubernetes podem ser ajustados ou desativados para reduzir a pressão em sistemas com alta taxa de rotatividade. Por exemplo, se o aplicativo em nossos pods não precisar se comunicar diretamente com a API k8s, desativar o segredo projetado nesses pods diminuiria a carga. ServiceaccountTokenSyncs Essa é a maneira mais desejável de resolver esses problemas, se possível.

kind: Pod spec: automountServiceAccountToken: false

Em sistemas em que não conseguimos acessar as métricas, podemos analisar novamente os registros para detectar contenção. Se quiséssemos ver o número de solicitações sendo processadas por controlador ou em um nível agregado, usaríamos a seguinte consulta do CloudWatch Logs Insights.

Volume total processado pelo 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

A principal lição aqui é, ao analisar problemas de escalabilidade, analisar cada etapa do caminho (API, agendador, KCM, etc.) antes de passar para a fase detalhada de solução de problemas. Freqüentemente, na produção, você descobrirá que são necessários ajustes em mais de uma parte do Kubernetes para permitir que o sistema funcione com o melhor desempenho. É fácil solucionar inadvertidamente o que é apenas um sintoma (como o tempo limite do nódulo) de um gargalo de garrafa muito maior.

ETCD

O etcd usa um arquivo mapeado na memória para armazenar pares de valores-chave de forma eficiente. Há um mecanismo de proteção para definir o tamanho desse espaço de memória disponível, geralmente definido nos limites de 2, 4 e 8 GB. Menos objetos no banco de dados significa menos limpeza que o etcd precisa fazer quando os objetos são atualizados e as versões mais antigas precisam ser limpas. Esse processo de limpeza de versões antigas de um objeto é chamado de compactação. Depois de várias operações de compactação, há um processo subsequente que recupera o espaço útil chamado desfragmentação, que ocorre acima de um determinado limite ou em um cronograma fixo de tempo.

Há alguns itens relacionados ao usuário que podemos fazer para limitar o número de objetos no Kubernetes e, assim, reduzir o impacto do processo de compactação e desfragmentação. Por exemplo, Helm se mantém em altarevisionHistoryLimit. Isso faz com que objetos mais antigos, como ReplicaSets no sistema, possam fazer reversões. Ao definir os limites do histórico para 2, podemos reduzir o número de objetos (como ReplicaSets) de dez para dois, o que, por sua vez, colocaria menos carga no sistema.

apiVersion: apps/v1 kind: Deployment spec: revisionHistoryLimit: 2

Do ponto de vista do monitoramento, se os picos de latência do sistema ocorrerem em um padrão definido separado por horas, verificar se esse processo de desfragmentação é a origem pode ser útil. Podemos ver isso usando CloudWatch Logs.

Se você quiser ver os start/end horários de desfragmentação, use a seguinte consulta:

fields @timestamp, @message
| filter @logStream like /etcd-manager/
| filter @message like /defraging|defraged/
| sort @timestamp asc
Desfragmentar consulta