A sua organização já passou pelo seguinte cenário?
Um cientista terminou de desenvolver uma ferramenta ou pipeline analítico, mas ninguém no time sabe ao certo como colocá-lo em produção de um jeito que simplifique a implantação automatizada, faça os recursos de computação que sustentam o pipeline escalarem (para cima e para baixo) com uso otimizado em custo, garanta resiliência a falhas inesperadas de hardware/software e, ao mesmo tempo, viabilize a implantação fácil de atualizações e a descontinuação de versões.
Com tantas perguntas críticas de engenharia ainda sem resposta, como o seu time científico pode sair da mentalidade de testes e desenvolvimento em um ambiente acadêmico de experimentação para projetar ferramentas científicas realmente confiáveis e em escala de produção, sem disparar as horas de trabalho necessárias para montar e manter esse sistema?
Você gostaria de rodar esses workloads no GCP ou na AWS para aproveitar a escala global, a altíssima confiabilidade de infraestrutura e o custo-benefício que essas plataformas oferecem em comparação com clusters de computação on-prem?
Talvez também queira entender os princípios fundamentais de DevOps por trás da execução desse tipo de workload na nuvem?
Vou usar uma demonstração detalhada para mostrar como aproveitar um exemplo funcional, tanto no Google Cloud Platform (GCP) quanto na Amazon Web Services (AWS), que ilustra a execução de um workload real de computação científica (bioinformática) aplicando vários princípios modernos de DevOps. Sem mais delongas, mãos à obra.
De forma geral, o código de exemplo:
- Mostra como empacotar ferramentas comuns de bioinformática (FastQC e BWA) em imagens de container e enviá-las ao repositório de imagens de cada nuvem
- Provisiona (e permite o desmonte rápido) de toda a infraestrutura de nuvem necessária para executar workloads via Terraform, uma ferramenta de Infraestrutura como Código
- Implanta um pipeline de workflow comum baseado nessas imagens e na infraestrutura de nuvem provisionada no serviço totalmente gerenciado de Kubernetes de cada nuvem, um sistema de orquestração e gestão de execução de containers. O Argo será usado para orquestrar um workflow / pipeline de tarefas em um cluster Kubernetes
Usando Docker, Terraform, Kubernetes e Argo, você vai aprender a:
- Subir, atualizar e desmontar a infraestrutura necessária para rodar seus workloads com comandos simples
- Executar workloads analíticos de ponta a ponta orientados por DAG, com novas tentativas automáticas em etapas individuais caso ocorram erros inesperados ou falhas de infraestrutura
- Centralizar o logging dos workloads e o monitoramento de métricas em ferramentas nativas da nuvem para exploração de logs e métricas
- Rodar workloads em uma infraestrutura que escala automaticamente para cima (até a capacidade de escala global) e para baixo (com poucos ou nenhum recurso de computação) conforme a necessidade, para que você pague apenas pelos recursos de CPU/RAM/armazenamento exigidos para concluir suas tarefas. Já era a época de deixar servidores ociosos rodando e inflar a conta sem necessidade
- Implantar novas versões de software de forma transparente e ir descontinuando o uso de versões antigas
O código que demonstra esses princípios está aqui, e a Parte 2 mostra como utilizá-lo. Se você quiser pular a visão geral dos princípios de DevOps e ir direto ao código, pode parar a leitura desta Parte 1.
Mas, se quiser um tour guiado pelas tecnologias por trás desses objetivos de DevOps, fique comigo. Recomendo muito preparar uma xícara de café forte. Você vai precisar para encarar este artigo detalhado.
A seguir, um curso intensivo sobre DevOps, containers, Kubernetes, Terraform e por que usá-los é fundamental no uso real de software, em bioinformática ou em qualquer outra área.
Containers: viabilizando a implantação escalável de ferramentas
O problema central que este post e o seu código resolvem é o de DevOps, ou Development Operations. Dockerização, Kubernetes e Terraform atuam juntos com o mesmo objetivo: simplificar a implantação confiável de programas em escala. Entender os fundamentos do DevOps moderno passa por uma compreensão básica de containers. Vamos começar por aí.
A maioria dos bioinformatas conhece bem este problema: você quer usar um programa open-source, provavelmente criado no meio acadêmico, com dependências desatualizadas. A instalação vira um pesadelo de gerenciamento de pacotes, porque os requisitos de versão para Python, Perl e outras dependências entram em conflito com versões mais novas instaladas localmente.
Você provavelmente já contornou isso criando ambientes virtuais com o Anaconda ou com o comando venv do Python. Mas, infelizmente, essa abordagem tem falhas. Em algum momento, esse jeito de trabalhar fica insustentável e mais difícil de manter do que vale a pena. Ambientes virtuais até funcionam em desenvolvimento e testes, mas não se sustentam em escala.
Já vi muitas empresas, pequenas e grandes, descobrirem isso da pior maneira. Insistiram por tempo demais antes de jogar a toalha.
Entra em cena a containerização
Um container é um processo de software isolado que reúne todo o código e as dependências necessárias para executar a aplicação rapidamente, com desempenho próximo ao bare-metal, em qualquer ambiente de computação.
Uma imagem de container/Docker é a definição de como um container deve ser construído e executado. Containers são processos que executam as instruções empacotadas dentro das imagens.
Uma imagem de container é criada com um simples Dockerfile que define:
- O SO base no qual a imagem se baseia (isso não impede que um container baseado nela seja executado em outros SOs hospedeiros)
- Os pacotes a serem instalados
- Os comandos de shell a serem executados quando um container baseado na imagem é executado
Esse Dockerfile é usado pelo docker build para criar uma imagem imutável.
Como prover redundância e escalabilidade horizontal para o serviço empacotado na imagem?
Você consegue isso implantando instâncias executáveis de uma imagem em vários containers rodando em vários hosts, distribuídos por vários data centers distintos, e então fazendo o load balancing entre esses containers. Dada a escala global dos provedores de nuvem, o potencial de escalabilidade horizontal e resiliência a falhas com containers é, na prática, ilimitado.
Você poderia, por exemplo, criar uma imagem de container que empacota uma ferramenta como o FastQC com todas as suas dependências. Containers iniciados a partir dessa imagem do FastQC podem ser implantados em praticamente qualquer ambiente de computação, em qualquer escala, independentemente do hardware, software ou SO da máquina hospedeira. Uma verdadeira mão na roda.
A execução desse container é parecida com a do FastQC rodando isoladamente. É simples, por exemplo, passar arquivos de entrada e receber arquivos de saída de um container que esteja executando o FastQC.
Cada provedor de nuvem tem seu próprio container registry para enviar (push) e baixar (pull) imagens. Funciona de modo parecido com o push e pull de branches em repositórios git. Por exemplo, você roda docker pull <image_name> para baixar uma imagem do FastQC hospedada no repositório de containers da sua nuvem. Isso permite baixar a imagem em uma variedade de recursos de computação no seu ambiente de nuvem, deixando o FastQC pronto para rodar imediatamente com docker run <image_name>, sem precisar recorrer a métodos arcanos para gerenciar a instalação de dependências.
A semelhança com repositórios git não para por aí. Containers também aceitam tags como latest ou v1.0.1, para que você acompanhe qual imagem corresponde a qual versão específica do código. docker run <image_name>:<tag_name> permite baixar e executar um container baseado em uma versão específica da imagem.
Containers vs. máquinas virtuais (VMs)
Se você acha que containers parecem muito com máquinas virtuais — que também empacotam software e o tornam executável em uma ampla variedade de ambientes de computação —, talvez esteja se perguntando quais são as diferenças entre os dois e por que os containers são preferidos para DevOps escalável.
- Tanto containers quanto VMs podem ser executados em um único host, mas os containers fazem isso sem o forte impacto de desempenho que as VMs têm por rodarem o próprio sistema operacional, além do software empacotado. Já os containers compartilham o SO do host, o que os torna leves; tipicamente você verá uma degradação de desempenho de ~0,5%.
Com uma máquina virtual rodando em um hypervisor moderno, você terá sorte se observar apenas 1% a 3% de degradação de desempenho. Em escalas maiores, essas diferenças têm um impacto significativo e tangível no custo.
- Containers são bem mais rápidos de construir e implantar. Eles viabilizam um processo de desenvolvimento Ágil que não existe ao trabalhar com VMs, que podem levar minutos ou horas para serem construídas.
- Containers são imutáveis e definidos por um único arquivo, o que torna a integração contínua de software mais confiável. Por exemplo: você pode reverter para uma versão anterior conhecida e funcional do software quando precisar, com a tranquilidade de saber que ela não pode ter sido alterada desde a última implantação.
- Containers incentivam a prática consagrada de arquitetura de software de favorecer microsserviços distribuídos e fracamente acoplados em vez de aplicações monolíticas. Essa prática agiliza a implantação quando uma correção ou novo recurso precisa sair, e impede que um único problema em uma aplicação monolítica derrube todos os outros aspectos da aplicação que não precisariam estar fortemente vinculados.
O ideal é que cada programa implantado em escala seja escrito em sua própria imagem de container, permitindo que a quantidade de containers implantados desse programa escale para cima e para baixo de forma independente, conforme a demanda de recursos.
- Containers facilitam o logging e o monitoramento de métricas em nível de SO e de aplicação, já que compartilham os recursos do SO do host e foram projetados pensando em logging detalhado e captura de métricas.
Gestão de execução de containers
Suponha que você tenha containerizado um programa como o FastQC e o tenha marcado com tags adequadas como latest e v0.11.9. O que fazer com isso para não só viabilizar, mas simplificar enormemente a execução tolerante a falhas e escalável do FastQC?
Entra em cena o Kubernetes
O Kubernetes (frequentemente abreviado como K8s) é uma plataforma open-source criada pelo Google em 2014 como agendador de jobs e sistema de gerenciamento de cluster open-source para facilitar a adoção, pela comunidade, do gerenciamento automatizado de workloads containerizados. Ele ganhou ampla popularidade pela relativa facilidade com que viabiliza operações em escala global.
O Kubernetes é hoje o segundo repositório mais popular do GitHub em número de autores e issues, atrás apenas do kernel do Linux.
A documentação do K8s traz uma explicação excelente, embora longa, sobre por que o Kubernetes é tão útil. Resumindo o que ele tem de melhor, o K8s permite:
- Bin packing automático: você fornece ao Kubernetes um cluster de nós (servidores em nuvem) que ele pode usar para executar tarefas containerizadas. Você informa quanto de CPU, memória e armazenamento cada container precisa, e o Kubernetes encaixa os containers nos nós para otimizar o uso dos recursos.
- Auto-recuperação: o Kubernetes reinicia containers que falham (por erros de software ou falhas de hardware), substitui containers, encerra containers que não respondem ao health check definido por você e só os anuncia aos clientes quando estão prontos para atender.
- Service discovery e load balancing: o Kubernetes pode expor um container usando o nome DNS ou o próprio endereço IP. Se a carga de tráfego for alta, o Kubernetes faz o load balancing e distribui o tráfego de rede entre várias instâncias daquele container, mantendo a implantação estável em escala.
- Rollouts e rollbacks automatizados: você descreve o estado desejado para os containers implantados usando o Kubernetes, e ele leva o estado real ao estado desejado em um ritmo controlado. Por exemplo, dá para automatizar o Kubernetes para criar novos containers para a sua implantação (ex.: containers do seu software com a tag 'v2'), remover os containers existentes (ex.: com a tag 'v1') e migrar todos os recursos de computação deles para o novo container. Essa migração pode ser feita de uma vez só ou personalizada — por exemplo, à taxa de 5% dos containers existentes sendo substituídos a cada cinco minutos. Se a taxa de erros aumentar durante o rollout do novo container, reverter a nova implantação é tão simples quanto rodar um único comando.
Kubernetes totalmente gerenciado
Você vai gostar de saber que o Kubernetes é oferecido como serviço totalmente gerenciado pelos principais provedores de nuvem hoje. Entre eles:
- GKE (Google Kubernetes Engine) no Google Cloud
- EKS (Elastic Kubernetes Service) na Amazon Web Services
Essas ofertas simplificam a instalação do Kubernetes e o provisionamento de hardware, abstraindo a criação do cluster Kubernetes. Com elas, você foca em lançar workloads escaláveis em um cluster K8s que leva apenas alguns minutos e poucos cliques no console para ser criado.
Tanto o GKE quanto o EKS contam com uma máquina de plano de controle do cluster serverless. Ela atua como uma espécie de nó master, junto a várias máquinas de trabalho chamadas nós. Você submete os containers ao plano de controle, que então os agenda para serem executados nos nós. Quando rodam nos nós, essas tarefas são chamadas de pods.
Recapitulando: pods (normalmente um único container) rodam em nós, e o aumento ou a redução da quantidade de pods é controlado pela máquina do plano de controle. As ofertas de K8s totalmente gerenciado também automatizam a configuração do auto-scaling de nós (no GKE ele é nativo; no EKS, templates de EC2 Auto Scaling são criados dentro da AWS). No fim, você consegue auto-scaling tanto de pods quanto de nós com esforço mínimo no GKE e no EKS.
Ao configurar um cluster K8s, você também deve definir node groups opcionais, ou seja, a família de recursos de hardware a ser usada no cluster. Node groups são comumente especificados ao submeter workloads de computação científica.
Por exemplo, você poderia criar um node group 'high cpu' que usa hardware de famílias otimizadas para CPU, como a família c2 no GCP ou a família c5 na AWS, e atribuir workloads intensivos em CPU a esse node group. Você poderia ter um node group 'GPU' separado que usa a família a2-highgpu no GCP ou a família p3 na AWS, ao qual seriam submetidos workloads que utilizam GPU. Operando assim, tipos distintos de máquinas escalam para cima e para baixo de forma independente, atrelados aos workloads que precisam rodar em cada tipo.
No entanto, se você usar o modo Autopilot do GKE em vez do modo Standard, não precisa especificar node groups para o cluster. O provisionamento dos recursos de hardware fica abstraído, conforme o GKE Autopilot lidera o setor de DevOps levando o ecossistema K8s mais para uma abordagem serverless de computação em escala global. Com o GKE Autopilot, você simplesmente submete o seu job K8s, no qual estão definidos os requisitos de CPU/RAM/armazenamento do container, e o GKE escala os recursos de computação para cima e para baixo nos bastidores, conforme necessário, para executar essa tarefa de container com base nas suas especificações. O ECS Fargate é o equivalente da AWS ao GKE Autopilot.
Infraestrutura reproduzível e automatizada
Conectando a containerização à execução robusta de containers no Kubernetes está o Terraform, uma ferramenta open-source de infraestrutura como código que permite definir, com texto YAML fácil de ler, o estado desejado da sua infraestrutura de nuvem. Com um único comando, você cria, atualiza ou desmonta a sua infraestrutura de nuvem.
Enquanto containers tornam as versões de software imutáveis, fáceis de reproduzir e de escalar, o Terraform torna a infraestrutura em nuvem na qual esses containers rodam fácil de replicar, atualizar ou excluir, de forma totalmente versionável.
Ao mover seu software para containers, seu sistema de gerenciamento de workloads para o Kubernetes e codificar sua infraestrutura de nuvem com o Terraform, você cria as bases para uma empresa capaz de escalar do modo stealth até operações globais e de se recuperar rapidamente de causas comuns de falha, como releases novos com bugs e problemas de hardware, com pouquíssimas mudanças centrais no seu ecossistema de DevOps.
Colocando o novo conhecimento em prática
Obrigado pelo seu tempo — espero, sinceramente, que este artigo tenha contribuído para a sua compreensão de DevOps. Se quiser começar a aplicar na prática o que vimos aqui, acompanhando uma base de código de demonstração funcional, siga para a última parte desta série de duas partes.
Obrigado pela leitura! Para se manter por dentro, siga-nos no DoiT Engineering Blog , no canal da DoiT no LinkedIn e no canal da DoiT no Twitter . Para conhecer oportunidades de carreira, acesse https://careers.doit-intl.com .