Oi, Habr!
Lembramos que lançamos outro extremamente interessante e útil sobre os padrões do Kubernetes. Tudo começou com "" Brendan Burns, e aliás, temos trabalho nesse segmento . Hoje convidamos você a ler um artigo do blog MinIO que descreve brevemente as tendências e especificidades dos padrões de armazenamento de dados no Kubernetes.
O Kubernetes mudou fundamentalmente os padrões tradicionais de desenvolvimento e implantação de aplicativos. Agora uma equipe pode desenvolver, testar e implantar um aplicativo em questão de dias, em vários ambientes, todos dentro de clusters Kubernetes. Esse trabalho com gerações anteriores de tecnologia geralmente demorava semanas, senão meses.
Essa aceleração é possível graças à abstração fornecida pelo Kubernetes – isto é, porque o próprio Kubernetes interage com os detalhes de baixo nível das máquinas físicas ou virtuais, permitindo aos usuários declarar o processador desejado, a quantidade desejada de memória e o número de contêineres. instâncias, entre outros parâmetros. Com uma enorme comunidade apoiando o Kubernetes e sua adoção em constante expansão, o Kubernetes é líder entre todas as plataformas de orquestração de contêineres por uma ampla margem.
À medida que o uso do Kubernetes aumenta, aumenta também a confusão sobre seus padrões de armazenamento..
Com todos competindo por um pedaço do bolo do Kubernetes (ou seja, armazenamento de dados), quando se trata de armazenamento de dados, o sinal é afogado em muito ruído.
Kubernetes incorpora um modelo moderno para desenvolvimento, implantação e gerenciamento de aplicativos. Este modelo moderno separa o armazenamento de dados da computação. Para entender completamente o desapego no contexto do Kubernetes, você também precisa entender o que são aplicativos com e sem estado e como o armazenamento de dados se encaixa nisso. É aqui que a abordagem REST API usada pelo S3 tem vantagens claras sobre a abordagem POSIX/CSI de outras soluções.
Neste artigo, falaremos sobre padrões de armazenamento de dados no Kubernetes e abordaremos especificamente o debate entre aplicativos com e sem estado para entender melhor qual é a diferença entre eles e por que ela é importante. O restante do texto examinará os aplicativos e seus padrões de armazenamento de dados à luz das práticas recomendadas para trabalhar com contêineres e Kubernetes.
Contêineres sem Estado
Os contêineres são por natureza leves e efêmeros. Eles podem ser facilmente interrompidos, removidos ou implantados em outro nó – tudo em segundos. Em um grande sistema de orquestração de contêineres, essas operações acontecem o tempo todo e os usuários nem percebem essas mudanças. No entanto, as movimentações só são possíveis se o contêiner não tiver nenhuma dependência do nó no qual está localizado. Diz-se que tais recipientes funcionam apátrida.
Contêineres com estado
Se um contêiner armazenar dados em dispositivos conectados localmente (ou em um dispositivo de bloco), o armazenamento de dados no qual ele reside deverá ser movido para um novo nó, junto com o próprio contêiner, no caso de falha. Isso é importante porque, caso contrário, o aplicativo em execução no contêiner não poderá funcionar corretamente, pois precisa acessar os dados armazenados na mídia local. Diz-se que tais recipientes funcionam com estado.
Do ponto de vista puramente técnico, os contêineres com estado também podem ser movidos para outros nós. Isso normalmente é conseguido usando sistemas de arquivos distribuídos ou bloqueando o armazenamento de rede anexado a todos os nós que executam contêineres. Dessa forma, os contêineres acessam volumes para armazenamento persistente de dados e as informações são armazenadas em discos localizados em toda a rede. Vou chamar esse método de “abordagem de contêiner com estado", e no restante do artigo vou chamá-lo assim por uma questão de uniformidade.

Em uma abordagem típica de contêiner com estado, todos os pods de aplicativos são anexados a um único sistema de arquivos distribuído – um tipo de armazenamento compartilhado onde residem todos os dados do aplicativo. Embora algumas variações sejam possíveis, esta é uma abordagem de alto nível.
Agora vamos ver por que a abordagem de contêiner com estado é um antipadrão em um mundo centrado na nuvem.
Design de aplicativo nativo da nuvem
Tradicionalmente, os aplicativos usavam bancos de dados para armazenamento estruturado de informações e discos locais ou sistemas de arquivos distribuídos onde todos os dados não estruturados ou mesmo semiestruturados eram despejados. À medida que os volumes de dados não estruturados cresciam, os desenvolvedores perceberam que o POSIX era muito tagarela, tinha uma sobrecarga significativa e, em última análise, prejudicava o desempenho do aplicativo ao migrar para escalas realmente grandes.
Isto contribuiu principalmente para o surgimento de um novo padrão de armazenamento de dados, ou seja, o armazenamento baseado em nuvem, funcionando principalmente com base na API REST e liberando a aplicação da onerosa manutenção de um armazenamento local de dados. Nesse caso, o aplicativo entra efetivamente no modo sem estado (já que o estado está em armazenamento remoto). Os aplicativos modernos são criados do zero com esse fator em mente. Via de regra, qualquer aplicação moderna que processe dados de um tipo ou de outro (logs, metadados, blobs, etc.) é construída de acordo com um paradigma orientado à nuvem, onde o estado é transferido para um sistema de software especialmente dedicado ao seu armazenamento.
A abordagem do contêiner com estado força todo esse paradigma de volta exatamente ao ponto onde começou!
Ao usar interfaces POSIX para armazenar dados, os aplicativos operam como se tivessem estado e, por causa disso, eles se afastam dos princípios mais importantes do design centrado na nuvem, ou seja, a capacidade de variar o tamanho dos threads de trabalho do aplicativo dependendo da entrada. input.load, mover para um novo nó assim que o nó atual falhar e assim por diante.
Olhando mais de perto esta situação, descobrimos que ao escolher um armazenamento de dados, nos deparamos repetidamente com o dilema POSIX vs. REST API, MAS com o agravamento adicional dos problemas POSIX devido à natureza distribuída dos ambientes Kubernetes. Em particular,
- POSIX é tagarela: a semântica POSIX exige que cada operação seja associada a metadados e descritores de arquivo que ajudam a manter o estado da operação. Isso resulta em custos significativos que não têm valor real. As APIs de armazenamento de objetos, especialmente a API S3, eliminam esses requisitos, permitindo que o aplicativo seja acionado e “esqueça” a chamada. A resposta do sistema de armazenamento indica se a ação foi bem-sucedida ou não. Se falhar, o aplicativo poderá tentar novamente.
- Restrições de rede: Em um sistema distribuído, está implícito que pode haver muitos aplicativos tentando gravar dados na mesma mídia anexada. Portanto, não apenas os aplicativos competirão entre si pela largura de banda de transferência de dados (para enviar dados para a mídia), mas o próprio sistema de armazenamento competirá por essa largura de banda, enviando dados através de discos físicos. Devido à loquacidade do POSIX, o número de chamadas de rede aumenta várias vezes. Por outro lado, a API S3 fornece uma distinção clara entre chamadas de rede entre aquelas que se originam do cliente para o servidor e aquelas que ocorrem dentro do servidor.
- segurança: O modelo de segurança POSIX foi projetado para participação humana ativa: os administradores configuram níveis de acesso específicos para cada usuário ou grupo. Este paradigma é difícil de adaptar a um mundo centrado na nuvem. As aplicações modernas dependem de modelos de segurança baseados em API, onde os direitos de acesso são definidos como um conjunto de políticas, contas de serviço, credenciais temporárias, etc.
- Gerenciabilidade: contêineres com estado apresentam alguma sobrecarga de gerenciamento. Estamos falando de sincronizar o acesso paralelo aos dados, garantindo a consistência dos dados, tudo isso requer uma consideração cuidadosa sobre quais padrões de acesso aos dados usar. Software adicional deve ser instalado, monitorado e configurado, sem mencionar o esforço adicional de desenvolvimento.
Interface de armazenamento de dados de contêiner
Embora a Container Storage Interface (CSI) tenha sido uma grande ajuda na proliferação da camada de volume do Kubernetes, terceirizando-a parcialmente para fornecedores de armazenamento terceirizados, ela também contribuiu inadvertidamente para a crença de que a abordagem de contêiner com estado é o método recomendado para armazenando dados no Kubernetes.
O CSI foi desenvolvido como um padrão para fornecer sistemas arbitrários de armazenamento de blocos e arquivos para aplicativos legados em execução no Kubernetes. E, como este artigo mostrou, a única situação em que uma abordagem de contêiner com estado (e CSI em sua forma atual) faz sentido é quando a aplicação em si é um sistema legado no qual não é possível adicionar suporte para a API de armazenamento de objetos. .
É importante entender que utilizando o CSI em sua forma atual, ou seja, montando volumes ao trabalhar com aplicações modernas, encontraremos aproximadamente os mesmos problemas que surgiram em sistemas onde o armazenamento de dados é organizado no estilo POSIX.
Uma abordagem melhor
Nesse caso, é importante compreender que a maioria dos aplicativos não são inerentemente projetados especificamente para trabalho com ou sem estado. Esse comportamento depende da arquitetura geral do sistema e das escolhas específicas de design feitas. Vamos falar um pouco sobre aplicações com estado.
Em princípio, todos os dados do aplicativo podem ser divididos em vários tipos amplos:
- Dados de registro
- Dados de carimbo de data/hora
- Dados de transação
- Metadados
- Imagens de contêiner
- Dados de blob (objeto binário grande)
Todos esses tipos de dados são muito bem suportados em plataformas de armazenamento modernas e existem diversas plataformas nativas da nuvem adaptadas para fornecer dados em cada um desses formatos específicos. Por exemplo, dados de transações e metadados podem residir em um banco de dados moderno nativo da nuvem, como CockroachDB, YugaByte, etc. Imagens de contêiner ou dados de blob podem ser armazenados em um registro docker baseado em MinIO. Os dados de carimbo de data/hora podem ser armazenados em um banco de dados de série temporal, como InfluxDB, etc. Não entraremos em detalhes sobre cada tipo de dados e suas aplicações aqui, mas a ideia geral é evitar o armazenamento persistente de dados que depende da montagem em disco local.

Além disso, muitas vezes é eficaz fornecer uma camada de cache temporária que sirva como armazenamento temporário de arquivos para aplicativos, mas os aplicativos não devem depender dessa camada como fonte da verdade.
Armazenamento de aplicativos com estado
Embora na maioria dos casos seja útil manter os aplicativos sem estado, os aplicativos projetados para armazenar dados - como bancos de dados, armazenamentos de objetos, armazenamentos de valores-chave - devem ter estado. Vejamos por que esses aplicativos são implantados no Kubernetes. Tomemos o MinIO como exemplo, mas princípios semelhantes se aplicam a qualquer outro grande sistema de armazenamento nativo da nuvem.
Os aplicativos nativos da nuvem são projetados para aproveitar ao máximo a flexibilidade inerente aos contêineres. Isso significa que eles não fazem suposições sobre o ambiente em que serão implantados. Por exemplo, o MinIO usa um mecanismo interno de codificação de eliminação para fornecer ao sistema resiliência suficiente para permanecer operacional mesmo se metade dos discos falhar. MinIO também gerencia a integridade e a segurança dos dados usando hashing e criptografia proprietários no lado do servidor.
Para essas aplicações centradas na nuvem, os volumes persistentes locais (PVs) são mais convenientes como armazenamento de backup. O PV local oferece a capacidade de armazenar dados brutos, enquanto os aplicativos executados nesses PVs coletam informações de forma independente para dimensionar os dados e gerenciar os crescentes requisitos de dados.
Esta abordagem é muito mais simples e significativamente mais escalável do que os PVs baseados em CSI, que introduzem as suas próprias camadas de gestão de dados e redundância no sistema; a questão é que essas camadas geralmente entram em conflito com aplicativos projetados para ter estado.
Um forte movimento no sentido de dissociar os dados dos cálculos
Neste artigo, falamos sobre como os aplicativos são reorientados para funcionar sem salvar o estado, ou, em outras palavras, o armazenamento de dados é desacoplado da computação neles. Concluindo, vejamos alguns exemplos reais dessa tendência.
, uma importante plataforma de análise de dados, tem sido tradicionalmente stateful e implantada em HDFS. No entanto, à medida que o Spark avança para um mundo centrado na nuvem, a plataforma é cada vez mais usada sem estado usando `s3a`. O Spark usa s3a para transferir estado para outros sistemas, enquanto os próprios contêineres do Spark operam totalmente sem estado. Outros grandes players empresariais na área de análise de big data, em particular, , , Eles também estão passando a trabalhar com a separação do armazenamento de dados e dos cálculos neles.
Padrões semelhantes também podem ser vistos em outras grandes plataformas analíticas, incluindo Presto, Tensorflow to R, Jupyter. Ao descarregar o estado para sistemas remotos de armazenamento em nuvem, fica muito mais fácil gerenciar e dimensionar seu aplicativo. Além disso, facilita a portabilidade do aplicativo para diversos ambientes.
Fonte: habr.com
