Nossa experiência de desenvolvimento de um driver CSI no Kubernetes para Yandex.Cloud

Nossa experiência de desenvolvimento de um driver CSI no Kubernetes para Yandex.Cloud

Estamos felizes em anunciar que a empresa "Flant" está expandindo sua contribuição para ferramentas de Open Source para Kubernetes, lançando a versão alfa do driver CSI (Container Storage Interface) para Yandex.Cloud.

Mas antes de entrarmos nos detalhes da implementação, vamos responder à pergunta, por que isso é necessário, quando o Yandex já possui o serviço Managed Service for Kubernetes..

Introdução

Por que isso?

Dentro da nossa empresa, desde o início da operação do Kubernetes em produção (ou seja, há vários anos), desenvolvemos uma ferramenta própria (deckhouse), que, por sinal, também planejamos tornar disponível em breve como um projeto Open Source. Com ela, configuramos e ajustamos uniformemente todos os nossos clusters, e atualmente já são mais de 100, em diferentes configurações de hardware e em todos os serviços de nuvem disponíveis.

Os clusters que utilizam o deckhouse possuem todos os componentes necessários para funcionar: balanceadores, monitoramento com gráficos e métricas amigáveis, alertas, autenticação de usuários através de provedores externos para acesso a todos os dashboards, e assim por diante. Um cluster "potente" como esse não faz sentido ser colocado em uma solução gerenciada, pois muitas vezes isso é impossível, ou levará à necessidade de desativar metade dos componentes.

NB: Esta é a nossa experiência, e ela é bastante específica. De forma alguma afirmamos que todos devem lidar com a configuração de clusters Kubernetes em vez de utilizar soluções prontas. Vale ressaltar que não temos experiência real no uso do Kubernetes da parte do Yandex e não faremos uma avaliação deste serviço neste artigo.

O que é isso e para quem?

Assim, já falamos sobre a abordagem moderna para armazenamento em Kubernetes: como funciona o CSI e como a comunidade chegou a essa abordagem.

Atualmente, muitos grandes provedores de serviços em nuvem desenvolveram drivers para utilizar seus "discos de nuvem" como Persistent Volume no Kubernetes. Se o provedor não tiver tal driver, mas todas as funções necessárias forem oferecidas através de API, nada impede a implementação de um driver por conta própria. Assim, foi como conseguimos com o Yandex.Cloud.

Tomamos como base para o desenvolvimento o driver CSI para a nuvem DigitalOcean e algumas ideias do driver para GCP,já que a interação com as APIs dessas nuvens (Google e Yandex) possui várias semelhanças. Em particular, as APIs de ambos GCPe Yandex retorna um objeto Operação para monitorar o status de operações longas (por exemplo, a criação de um novo disco). Para interagir com a API do Yandex.Cloud, utiliza-se Yandex.Cloud Go SDK.

O resultado do trabalho realizado está publicado no GitHub e pode ser útil para aqueles que, por algum motivo, usam sua própria instalação do Kubernetes em máquinas virtuais do Yandex.Cloud (mas não um cluster gerenciado pronto) e gostariam de usar (encomendar) discos através do CSI.

Implementação

Principais funcionalidades

Atualmente, o driver suporta as seguintes funções:

  • Encomenda de discos em todas as zonas do cluster de acordo com a topologia dos nós disponíveis no cluster;
  • Remoção de discos encomendados anteriormente;
  • Redimensionamento offline para discos (Yandex.Cloud não suporta aumentando discos que estão montados na máquina virtual). Sobre como foi necessário melhorar o driver para realizar o redimensionamento da forma mais indolor possível, veja abaixo.

Futuramente, está previsto implementar o suporte à criação e remoção de snapshots de discos.

A principal dificuldade e sua superação

A ausência na API do Yandex.Cloud da possibilidade de aumentar discos em tempo real é uma limitação que complica a operação de redimensionamento para PV (Volume Persistente): neste caso, é necessário que o pod da aplicação que utiliza o disco esteja parado, o que pode causar a inatividade da aplicação.

De acordo com a especificação CSI, se o controlador CSI informa que só consegue fazer resize de discos «offline» (VolumeExpansion.OFFLINE), então o processo de aumento do disco deve ocorrer da seguinte forma:

Se o plugin tem apenas VolumeExpansion.OFFLINE capacidade de expansão e o volume está atualmente publicado ou disponível em um nó, então ControllerExpandVolume DEVE ser chamado SOMENTE após uma das seguintes condições:

  • O plugin tem capacidade de controlador PUBLISH_UNPUBLISH_VOLUME e ControllerUnpublishVolume foi invocado com sucesso.

OU

  • O plugin NÃO possui capacidade de controlador, o plugin tem PUBLISH_UNPUBLISH_VOLUME capacidade de nó STAGE_UNSTAGE_VOLUME e NodeUnstageVolume foi concluído com sucesso.

OU

  • O plugin NÃO possui capacidade de controlador, o plugin tem PUBLISH_UNPUBLISH_VOLUME capacidade, nem nó STAGE_UNSTAGE_VOLUME e NodeUnpublishVolume foi concluído com sucesso.

Isso essencialmente significa que é necessário desconectar o disco da máquina virtual antes de aumentá-lo.

No entanto, infelizmente, implementação as especificações do CSI através dos sidecars não atendem a esses requisitos:

  • No container sidecar csi-attacher, que deve ser responsável por garantir o espaço adequado entre as montagens, esse funcionalidade não foi implementada no redimensionamento offline. A discussão sobre isso foi iniciada aqui.
  • O que é, afinal, um contêiner sidecar nesse contexto? O próprio plugin CSI não interage com a API do Kubernetes, apenas reage a chamadas gRPC que são enviadas a ele pelos contêineres sidecar. Os últimos são desenvolvidos são da comunidade Kubernetes.

No nosso caso (plugin CSI), a operação de aumento de disco parece da seguinte maneira:

  1. Recebemos uma chamada gRPC ControllerExpandVolume;
  2. Tentamos aumentar o disco na API, mas recebemos um erro de operação não permitida, pois o disco está montado;
  3. Salvamos o identificador do disco em um mapa que contém discos para os quais é necessária a operação de aumento. Daqui em diante, para abreviar, chamaremos esse mapa de volumeResizeRequired;
  4. Removemos manualmente o pod que está utilizando o disco. O Kubernetes o reiniciará. Para que o disco não seja montado (ControllerPublishVolume) antes da conclusão da operação de aumento durante a tentativa de montagem, verificamos se este disco ainda está em volumeResizeRequired e retornamos um erro;
  5. O driver CSI tenta repetir a operação de resize. Se a operação for bem-sucedida, removemos o disco de volumeResizeRequired;
  6. Como o identificador do disco não está presente em volumeResizeRequired, ControllerPublishVolume a operação é bem-sucedida, o disco é montado e o pod é iniciado.

Tudo parece bastante simples, mas como sempre há armadilhas. O aumento dos discos é feito por external-resizer, que em caso de erro ao realizar a operação usa uma fila com aumento exponencial do tempo de tempo limite até 1000 segundos:

func DefaultControllerRateLimiter() RateLimiter {
  return NewMaxOfRateLimiter(
  NewItemExponentialFailureRateLimiter(5*time.Millisecond, 1000*time.Second),
  	// 10 qps, 100 bucket size.  Isso é apenas para velocidade de repetição e é apenas o fator geral (não por item)
  &BucketRateLimiter{Limiter: rate.NewLimiter(rate.Limit(10), 100)},
  )
}

Isso pode ocasionalmente fazer com que a operação de aumento de disco se prolongue por mais de 15 minutos, resultando assim na indisponibilidade do pod correspondente.

A única opção que nos permitiu reduzir o tempo potencial de inatividade de forma relativamente fácil foi usar nossa própria versão do external-resizer com um limite máximo de tempo limite de 5 segundos:

workqueue.NewItemExponentialFailureRateLimiter(5*time.Millisecond, 5*time.Second)

Não achamos necessário iniciar uma discussão urgente e corrigir o external-resizer, pois o resize offline de discos é um atavismo que em breve desaparecerá de todos os provedores de nuvem.

Como começar a usar?

O driver é suportado nas versões do Kubernetes 1.15 e superiores. Para que o driver funcione, os seguintes requisitos devem ser atendidos:

  • A bandeira --allow-privileged definido como verdadeiro para o API server e kubelet;
  • Ativado --feature-gates=VolumeSnapshotDataSource=true,KubeletPluginsWatcher=true,CSINodeInfo=true,CSIDriverRegistry=true para o API server e kubelet;
  • A propagação de montagem (propagação de montagem) deve ser habilitada no cluster. Ao usar o Docker, o daemon deve ser configurado para permitir montagens compartilhadas.

Todos os passos necessários para a instalação estão descritos no README. A instalação envolve a criação de objetos no Kubernetes a partir de manifestos.

Para o funcionamento do driver, você precisará do seguinte:

  • especificar no manifesto o identificador do diretório (folder-id) do Yandex.Cloud (veja a documentação);
  • . Para interagir com a API do Yandex.Cloud no driver CSI, é utilizado uma conta de serviço. No manifesto Secret, é necessário fornecer chaves autorizadas da conta de serviço. Na documentação está descrito, como criar uma conta de serviço e obter as chaves.

Em geral — tente, e ficaremos felizes com seu feedback e novos problemas, caso encontre alguma dificuldade!

Suporte adicional

Por fim, gostaríamos de destacar que este driver CSI foi desenvolvido não por um grande desejo de nos divertir escrevendo aplicativos em Go, mas por uma necessidade urgente dentro da empresa. Consideramos que manter nossa própria implementação não é viável, portanto, se o Yandex demonstrar interesse e decidir continuar o suporte ao driver, ficaremos felizes em transferir o repositório para eles.

Além disso, provavelmente o Yandex tem sua própria implementação do driver CSI no cluster Kubernetes gerenciado, que pode ser lançado como Open Source. Essa opção de desenvolvimento também nos parece favorável — a comunidade poderá usar um driver testado do provedor de serviços, em vez de uma empresa externa.

P.S.

Confira também em nosso blog:

Fonte: habr.com

Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS 🔥 Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS | ProHoster