À medida que os aplicativos evoluem, também evoluem seus esquemas de banco de dados. A prática de automatizar a implantação de alterações no esquema do banco de dados evoluiu de mãos dadas com os princípios modernos do DevOps para o que é conhecido como migrações de banco de dados.
Como parte desta evolução, centenas de “ferramentas de migração” foram criadas para ajudar os desenvolvedores a gerenciar suas migrações de banco de dados. Essas ferramentas variam de ORM e ferramentas específicas de linguagem, como Alambique para Python até ferramentas independentes de linguagem, como Rota de voo e Quibás.
Migrações no Kubernetes: o estado atual
Quando o Kubernetes surgiu e as equipes começaram a conteinerizar seus aplicativos, a reação instintiva foi agrupar ferramentas de migração legadas em um contêiner e executá-las como parte do processo de implantação do aplicativo.
Como muitas vezes acontece quando tentamos projetar ferramentas antigas em uma nova plataforma, o resultado é um conjunto de deficiências que precisam ser corrigidas. Vamos agora revisar e discutir algumas dessas práticas comuns.
Executando migrações no aplicativo
A abordagem mais simples para executar migrações é simplesmente invocá-las durante a inicialização do aplicativo. Isso não exige o uso de nenhum recurso especial do Kubernetes. Tudo o que precisamos fazer é garantir que nossa ferramenta de migração, arquivos de migração e credenciais de banco de dados estejam disponíveis dentro do contêiner do aplicativo. Então, só precisamos alterar nossa lógica de inicialização para primeiro tentar executar as migrações e, se for bem-sucedido, iniciar nosso aplicativo.
Isso é considerado um antipadrão por vários motivos. Primeiro, do ponto de vista da segurança, é melhor reduzir a superfície de ataque do seu ambiente de tempo de execução para não incluir nada que não seja estritamente necessário durante o tempo de execução. Com esse padrão, tanto a ferramenta de migração quanto as credenciais de banco de dados elevadas necessárias para executar instruções DDL permanecem no ambiente de tempo de execução para serem exploradas por um invasor.
Em segundo lugar, supondo que seu aplicativo execute várias réplicas por motivos de redundância e disponibilidade, a execução de migrações como parte da inicialização do aplicativo força o carregamento sequencial das réplicas, em vez de em paralelo. É muito perigoso tentar aplicar as mesmas alterações no banco de dados de vários locais ao mesmo tempo, e é por isso que praticamente todas as ferramentas adquirem (ou exigem que o usuário tome cuidado) alguma técnica de bloqueio ou sincronização. Na prática, isso significa que um novo pod não pode ser iniciado até que tenha excluído mutuamente todos os outros da inicialização.
Se você tiver apenas algumas réplicas, poderá não sentir a diferença, mas considere o que aconteceria se você tivesse centenas delas que agora precisam competir umas contra as outras (com as novas tentativas, recuos, etc. necessários) para inicializar.
Executando migrações como um contêiner de inicialização
Uma ligeira melhoria nesta técnica é usar contêineres init.O Kubernetes possibilita definir um “contêiner de inicialização”, que é um contêiner executado antes do contêiner principal em um PodSpec. Usando essa abordagem, as equipes podem trazer ferramentas independentes (como Liquibase ou FlyWay) e executá-las antes da inicialização do aplicativo.
Além disso, as próprias migrações (arquivos SQL) para as revisões de esquema devem de alguma forma ser disponibilizadas para este contêiner, seja construindo uma imagem personalizada ou montando-as a partir de alguma fonte externa.
Essa abordagem é melhor do que executar migrações no aplicativo porque remove a ferramenta de migração e as credenciais do ambiente de tempo de execução, mas sofre dos mesmos problemas de sincronização que demonstramos com as migrações no aplicativo.
Além disso, considere o que acontece quando as migrações falham. As migrações podem falhar por vários motivos, desde SQL inválido até violações de restrições e conectividade de rede instável. Quando suas migrações são acopladas ao tempo de execução do seu aplicativo, qualquer falha na etapa de migração deixará você com uma quantidade enorme de pods em um estado de looping de travamento, o que pode significar disponibilidade reduzida ou até mesmo tempo de inatividade de seus aplicativos.
Executando migrações como trabalhos do Kubernetes
Kubernetes permite executar programas usando a API “Jobs”. Semelhante ao uso de contêineres init, as equipes usariam um trabalho que agrupasse a ferramenta de migração e de alguma forma montasse os arquivos de migração para serem executados antes da inicialização do aplicativo.
A vantagem dessa abordagem é que, ao usar Jobs, é possível garantir que as migrações sejam executadas como uma etapa discreta antes que os novos pods do aplicativo comecem a ser implementados. As equipes geralmente usam ganchos de pré-atualização do Helm ou ganchos de pré-sincronização do ArgoCD para implementar essa técnica.
Quando combinados, o resultado são migrações que são executadas apenas uma vez, evitando a confusa “corrida para migrar” que é exibida com contêineres init e são isoladas do ambiente de tempo de execução, reduzindo a superfície de ataque dos aplicativos, conforme discutido acima.
Princípios e migrações do GitOps
“Podemos agrupar soluções de gerenciamento de esquema existentes em contêineres e executá-las no Kubernetes como Jobs. Mas isso é BOBO. Não é assim que trabalhamos no Kubernetes.”-Viktor Farcic, kit de ferramentas DevOps
Resumindo, executar migrações como Jobs usando ganchos ArgoCD ou Helm é uma tarefa árdua. OK solução. Mas examinados através das lentes dos princípios modernos do GitOps, mais problemas são revelados.
GitOps é uma metodologia de desenvolvimento e implantação de software que usa Git como repositório central para configuração de código e infraestrutura, permitindo implantações automatizadas e auditáveis.
Nesse contexto, vamos considerar como as técnicas de migração que descrevemos são mapeadas para dois princípios GitOps comumente aceitos:
Fonte: https://opengitops.dev/
Declarativo — Praticamente todas as ferramentas de migração que estão em uso na indústria hoje usam um imperativo, abordagem versionada. O estado desejado do banco de dados nunca é descrito, mas pode ser deduzido da aplicação de todos os scripts de migração em sequência. Isso significa que essas ferramentas não podem lidar com quaisquer alterações imprevistas ou manuais no ambiente de destino da maneira que o GitOps deveria ser capaz de lidar.
Reconciliado Continuamente — Kubernetes Jobs tem uma maneira muito simplista de lidar com falhas: novas tentativas de força bruta. Se uma migração falhar, o Job Pod irá travar e o Kubernetes tentará executá-lo novamente (com alguma estratégia de recuo). Isto pode funcionar, mas na maioria das vezes as ferramentas de migração não são concebidas para lidar com falhas parciais e tentar novamente se torna um esforço completo.
O padrão do operador
Se executar migrações como Jobs é uma estratégia mal equipada para satisfazer os princípios do GitOps, qual é a peça que falta?
Kubernetes é uma excelente solução para gerenciar recursos sem estado. No entanto, para muitos recursos com estado, como bancos de dados, conciliar o estado desejado de um banco de dados com seu estado real pode ser uma tarefa complexa que requer conhecimento de domínio específico. Operadores Kubernetes foram introduzidos no ecossistema Kubernetes para ajudar os usuários a gerenciar recursos complexos com estado, codificando esse conhecimento de domínio em um controlador Kubernetes.
Em alto nível, os Operadores trabalham introduzindo novos CRDs (definições de recursos personalizados) que estendem a API Kubernetes para descrever novos tipos de recursos e fornecendo um controlador – um software especializado que é executado no cluster e é responsável por gerenciar esses recursos em de forma declarativa usando um loop de reconciliação.
E se pudéssemos usar um operador Kubernetes adequado para gerenciar o esquema de banco de dados de nossos aplicativos?
O Operador Atlas
O Operador Atlas Kubernetes é um controlador Kubernetes que usa Atlas para gerenciar o esquema do seu banco de dados. O Operador Atlas Kubernetes permite definir o esquema desejado e aplicá-lo ao seu banco de dados usando a API Kubernetes.
O Operador Atlas suporta um fluxo totalmente declarativo, no qual o estado desejado do banco de dados é definido pelo usuário e o operador é responsável por reconciliar o estado desejado com o estado real do banco de dados (planejando e executando instruções CREATE, ALTER e DROP ).
Além disso, um estilo mais clássico traduzido O fluxo de trabalho também é suportado no qual o versão desejada do banco de dados é fornecido ao operador e atua para reconciliar o estado atual e real do banco de dados para satisfazer isso.
Usar um operador Kubernetes para gerenciar nosso banco de dados tem muitas vantagens:
Isso torna o gerenciamento de esquema um processo declarativo. — que satisfaz os princípios do GitOps, mas o mais importante é que é muito mais simples para o usuário final — eles só precisam definir o que eles querem e conseguem pensar menos sobre o como.
É continuamente reconciliado. — A robustez dos jobs é limitada a mecanismos de nova tentativa muito básicos, como mostramos, mas os operadores que possuem um ciclo de reconciliação de longa duração têm muito mais meios e oportunidades para progredir em direção ao estado desejado de nossa aplicação.
É semanticamente mais rico. — Os empregos são uma forma muito opaca de gerir recursos. Deles especificação lida principalmente com como eles correm em vez do recurso que representam e da sua exposição status também não contém informações significativas sobre este recurso. Os CRDs, por outro lado, podem ser gerenciados e manipulados usando ferramentas padrão do Kubernetes e seu status pode ser consumido de forma programática que pode ser usada para construir fluxos de trabalho de ordem superior.
Conclusão
Neste artigo, mostramos algumas das práticas existentes para gerenciar esquemas de banco de dados em aplicativos Kubernetes e discutimos suas deficiências. Por fim, demonstramos como o padrão do operador pode ser usado para satisfazer os princípios do GitOps e levar adiante o gerenciamento de banco de dados.
YOUTUBE.COM/THENEWSTACK
A tecnologia avança rápido, não perca um episódio. Inscreva-se em nosso canal no YouTube para transmitir todos os nossos podcasts, entrevistas, demonstrações e muito mais.
SE INSCREVER
Rotem Tamir, cofundador e CTO da Ariga. Co-criador e mantenedor do Atlas, uma ferramenta de código aberto que permite gerenciar o esquema do seu banco de dados como código. Co-mantenedor do Ent, a estrutura de entidade apoiada pela Linux Foundation para Go. Ex-líder da equipe de infra-estrutura da ironSource, ex-data…
Este site utiliza cookies para melhorar sua experiência de navegação. Ao continuar, você concorda com o uso de cookies. Para mais informações, consulte nossa Política de Privacidade.
Funcional
Sempre ativo
O armazenamento ou acesso técnico é estritamente necessário para a finalidade legítima de permitir a utilização de um serviço específico explicitamente solicitado pelo assinante ou utilizador, ou com a finalidade exclusiva de efetuar a transmissão de uma comunicação através de uma rede de comunicações eletrónicas.
Preferências
O armazenamento ou acesso técnico é necessário para o propósito legítimo de armazenar preferências que não são solicitadas pelo assinante ou usuário.
Estatísticas
O armazenamento ou acesso técnico que é usado exclusivamente para fins estatísticos.O armazenamento técnico ou acesso que é usado exclusivamente para fins estatísticos anônimos. Sem uma intimação, conformidade voluntária por parte de seu provedor de serviços de Internet ou registros adicionais de terceiros, as informações armazenadas ou recuperadas apenas para esse fim geralmente não podem ser usadas para identificá-lo.
Marketing
O armazenamento ou acesso técnico é necessário para criar perfis de usuário para enviar publicidade ou para rastrear o usuário em um site ou em vários sites para fins de marketing semelhantes.