9 pontos por outsideris 2022-08-21 | 2 comentários | Compartilhar no WhatsApp

O Crossplane, criado pela Upbound, fornece um control plane de nuvem no Kubernetes. Por isso, ele tem centenas de CRDs (Custom Resource Definitions) para corresponder a recursos de nuvem como AWS, Azure e GCP. No Crossplane, eles são chamados de MRs (Managed Resources).

Mesmo usuários avançados de Kubernetes costumavam operar uma quantidade razoável de CRs na casa das dezenas, mas como no Crossplane é preciso usar centenas de MRs, eles começaram a investigar até onde vai o limite de quantos CRDs o Kubernetes consegue lidar.

Isso pode ser dividido em problemas do lado do cliente e do lado do servidor.

Problemas do lado do cliente

  • No cliente, o problema está no processo de discovery.
  • Clientes como o kubectl fazem discovery para descobrir quais APIs o servidor oferece, e para isso precisam percorrer todos os endpoints de API uma vez.
  • Os CRs são expostos como endpoints de API.
  • Para consultar um MR como https://example.org/apis/rds.aws.upbound.io/v1/instances/cool-db, é preciso encontrar os grupos de API suportados em https://example.org/apis/, depois encontrar as versões suportadas em https://example.org/apis/rds.aws.upbound.io e então encontrar os CRs suportados em https://example.org/apis/rds.aws.upbound.io/v1.
  • Os MRs do Crossplane para provedores de nuvem AWS, Azure e GCP somam cerca de 2.000 e estão divididos em 300 grupos e versões de API.
  • O cliente acaba enviando 300 requisições HTTP para fazer o discovery.
  • Nas condições de rede atuais isso não é um grande problema, mas os problemas encontrados foram rate limit e cache.

Rate limit do cliente

  • Havia um rate limit médio de 5 requisições por segundo (com burst de até 100), e o cache de discovery era invalidado a cada 10 minutos.
  • Isso pode ser resolvido aumentando o rate limit; ele continua em 5 requisições por segundo por padrão, mas agora pode ser elevado até 300.
  • Na época do kubectl v1.22 foi aberta uma issue pedindo aumento desse limite, e o cache de discovery também foi ajustado de 10 minutos para 5 horas, permitindo aproveitar o limite ampliado do cliente no Kubernetes v1.25.

Cache do cliente

  • Mesmo desativando o rate limit nos testes, consultar 300 grupos de API levava quase 20 segundos.
  • No início parecia um problema de rede, mas depois descobriram que era causado pela leitura dos arquivos de cache.
  • Isso foi corrigido no Kubernetes 1.25, ficando 25 vezes mais rápido no macOS e 2 vezes mais rápido no Linux.

Melhorias futuras no cliente

  • Faz sentido aplicar rate limit no cliente, mas na prática isso não protege adequadamente o servidor.
  • O API Priority and Fairness (AP&F), introduzido no Kubernetes 1.20, fornece filas e modelagem de tráfego no lado do servidor para proteger o servidor de API.
  • Um único endpoint HTTP agregado para discovery foi aprovado em um KEP e deve ser suportado em alpha na versão 1.26.

Problemas do lado do servidor

Cálculo do esquema OpenAPI

  • Depois de registrar centenas de CRDs, foi observado que as requisições à API ficavam lentas por quase uma hora.
  • Por meio de profiling, descobriram que esse problema vinha da lógica de cálculo do esquema OpenAPI v2.
  • Quando um CRD é adicionado ou atualizado, o controlador OpenAPI constrói a especificação swagger do CR, junta isso com os swaggers de todos os CRs em uma grande especificação única, serializa em JSON e a expõe em /openapi/v2.
  • Isso foi alterado para que /openapi/v2 fosse calculado sob demanda, apenas quando um endpoint relacionado a um CR real fosse solicitado.
  • Essa correção entrou na v1.24.0 e recebeu backport para 1.20.13, 1.21.7 e 1.22.4.

Cliente etcd

  • Esse foi um novo gargalo encontrado depois de resolver o problema do OpenAPI.
  • Foi descoberto que o servidor de API consumia 4 MiB de memória por CRD.
  • Isso é ainda mais problemático em Kubernetes gerenciados como GKE e EKS, porque CPU e memória do servidor de API são limitadas. Se forem necessários mais recursos, o servidor de API é expandido automaticamente, mas infelizmente a adição de CRDs não é um fator usado para decidir esse escalonamento. Por isso, ele não escala a menos que o servidor de API fique repetidamente sendo morto por OOM.
  • Em testes no GKE, AKE e EKS, o self-healing funcionava, mas o servidor de API ficava indisponível de 5 segundos até 1 hora. O cluster não parava completamente, mas toda a reconciliação era interrompida.
  • O profiling mostrou que a biblioteca de logging Zap consumia 20% da memória.
  • O servidor de API cria um cliente etcd para cada versão de CR, e cada cliente etcd cria um logger Zap.
  • Como resultado, além do aumento de memória por loggers duplicados, também eram criadas conexões TCP desnecessárias entre o servidor de API e o etcd.
  • Os maintainers concordaram que o certo seria usar um único cliente etcd para todos os endpoints de CR, mas como o lançamento do Kubernetes 1.25 estava próximo, era difícil corrigir tudo, então foi feita uma mudança menor para que todos os clientes etcd compartilhassem um único logger.
    -Isso deve ser incluído na 1.25 e receber backport para 1.22, 1.23 e 1.24. Isso deve reduzir o uso de memória em 20%.

Melhorias futuras no lado do servidor

  • Está previsto mudar de um cliente etcd criado por versão de CR para um cliente por transporte (um por cluster etcd).
  • Também estão colaborando com as equipes de engenharia do GKE, EKS e AKE para lidar com a instalação de muitos CRDs do Crossplane.

2 comentários

 
roxie 2022-08-21

Gratuidade -> invalidação

 
roxie 2022-08-21

Cliente -> Cliente