Dos scripts à nossa própria plataforma: como automatizamos o desenvolvimento na CIAN

Dos scripts à nossa própria plataforma: como automatizamos o desenvolvimento na CIAN

Na RIT 2019, nosso colega Alexander Korotkov fez reportar sobre automação de desenvolvimento na CIAN: para simplificar a vida e o trabalho, utilizamos nossa própria plataforma Integro. Ele rastreia o ciclo de vida das tarefas, libera os desenvolvedores de operações rotineiras e reduz significativamente o número de bugs na produção. Nesta postagem, complementaremos o relatório de Alexander e contaremos como passamos de scripts simples para combinar produtos de código aberto por meio de nossa própria plataforma e o que nossa equipe de automação separada faz.
 

Nível zero

“Não existe nível zero, não conheço tal coisa”
Mestre Shifu do filme "Kung Fu Panda"

A automação na CIAN começou 14 anos após a fundação da empresa. Naquela época havia 35 pessoas na equipe de desenvolvimento. Difícil de acreditar, certo? É claro que a automação existia de alguma forma, mas uma direção separada para integração contínua e entrega de código começou a tomar forma em 2015. 

Naquela época, tínhamos um enorme monólito de Python, C# e PHP, implantado em servidores Linux/Windows. Para implantar esse monstro, tínhamos um conjunto de scripts que executamos manualmente. Houve também a montagem do monólito, que trouxe dor e sofrimento devido a conflitos na fusão de filiais, correção de defeitos e reconstrução “com um conjunto diferente de tarefas na construção”. Um processo simplificado ficou assim:

Dos scripts à nossa própria plataforma: como automatizamos o desenvolvimento na CIAN

Não ficamos satisfeitos com isso e queríamos construir um processo de construção e implantação repetível, automatizado e gerenciável. Para isso precisávamos de um sistema CI/CD, e escolhemos entre a versão gratuita do Teamcity e a versão gratuita do Jenkins, pois trabalhamos com eles e ambos nos convinham no conjunto de funções. Escolhemos o Teamcity como um produto mais recente. Naquela época, ainda não havíamos utilizado a arquitetura de microsserviços e não esperávamos um grande número de tarefas e projetos.

Chegamos à ideia do nosso próprio sistema

A implementação do Teamcity retirou apenas parte do trabalho manual: o que resta é a criação de Pull Requests, promoção de Issues por status no Jira e seleção de Issues para liberação. O sistema Teamcity não conseguia mais lidar com isso. Foi necessário escolher o caminho da maior automação. Consideramos opções para trabalhar com scripts no Teamcity ou mudar para sistemas de automação de terceiros. Mas no final decidimos que precisávamos de máxima flexibilidade, que só a nossa própria solução pode proporcionar. Foi assim que surgiu a primeira versão do sistema de automação interna denominado Integro.

Teamcity lida com automação no nível de lançamento dos processos de construção e implantação, enquanto Integro se concentra na automação de nível superior dos processos de desenvolvimento. Foi necessário combinar o trabalho com problemas no Jira com o processamento do código-fonte associado no Bitbucket. Nesta fase, o Integro passou a ter fluxos de trabalho próprios para trabalhar com tarefas de diversos tipos. 

Devido ao aumento da automação nos processos de negócios, o número de projetos e execuções no Teamcity aumentou. Então surgiu um novo problema: uma instância gratuita do Teamcity não foi suficiente (3 agentes e 100 projetos), adicionamos outra instância (mais 3 agentes e 100 projetos), depois outra. Como resultado, acabamos com um sistema de vários clusters, difícil de gerenciar:

Dos scripts à nossa própria plataforma: como automatizamos o desenvolvimento na CIAN

Quando surgiu a questão da 4ª instância, percebemos que não poderíamos continuar a viver assim, porque os custos totais de suporte a 4 instâncias já não estavam dentro de quaisquer limites. Surgiu a questão sobre a compra do Teamcity pago ou a escolha do Jenkins gratuito. Fizemos cálculos sobre instâncias e planos de automação e decidimos que viveríamos no Jenkins. Depois de algumas semanas, mudamos para Jenkins e eliminamos algumas das dores de cabeça associadas à manutenção de várias instâncias do Teamcity. Portanto, pudemos nos concentrar no desenvolvimento do Integro e na personalização do Jenkins para nós mesmos.

Com o crescimento da automação básica (na forma de criação automática de Pull Requests, coleta e publicação de cobertura de código e outras verificações), há um forte desejo de abandonar ao máximo os lançamentos manuais e entregar esse trabalho aos robôs. Além disso, a empresa começou a migrar para microsserviços dentro da empresa, o que exigia lançamentos frequentes e separados uns dos outros. Foi assim que gradativamente chegamos aos lançamentos automáticos de nossos microsserviços (atualmente estamos liberando o monólito manualmente devido à complexidade do processo). Mas, como costuma acontecer, surgiu uma nova complexidade. 

Automatizamos testes

Dos scripts à nossa própria plataforma: como automatizamos o desenvolvimento na CIAN

Devido à automação dos lançamentos, os processos de desenvolvimento foram acelerados, em parte devido à omissão de algumas etapas de testes. E isso levou a uma perda temporária de qualidade. Parece trivial, mas junto com a aceleração dos lançamentos foi necessária uma mudança na metodologia de desenvolvimento de produtos. Foi preciso pensar na automatização dos testes, incutindo responsabilidade pessoal (aqui estamos falando em “aceitar a ideia na cabeça”, não em multas monetárias) do desenvolvedor pelo código liberado e bugs nele, bem como a decisão de liberar/não liberar uma tarefa por meio de implantação automática. 

Eliminando problemas de qualidade, tomamos duas decisões importantes: começamos a realizar testes canário e introduzimos o monitoramento automático do histórico de erros com resposta automática ao seu excesso. A primeira solução permitiu encontrar erros óbvios antes que o código fosse totalmente lançado em produção, a segunda reduziu o tempo de resposta a problemas em produção. É claro que erros acontecem, mas gastamos a maior parte do nosso tempo e esforço não em corrigi-los, mas em minimizá-los. 

Equipe de Automação

Atualmente temos uma equipe de 130 desenvolvedores e continuamos crescer. A equipe de integração contínua e entrega de código (doravante denominada equipe de Deploy and Integration ou DI) é composta por 7 pessoas e atua em 2 direções: desenvolvimento da plataforma de automação Integro e DevOps. 

DevOps é responsável pelo ambiente Dev/Beta do site CIAN, o ambiente Integro, ajuda os desenvolvedores a resolver problemas e desenvolve novas abordagens para escalar ambientes. A direção de desenvolvimento do Integro lida tanto com o próprio Integro quanto com serviços relacionados, por exemplo, plugins para Jenkins, Jira, Confluence, e também desenvolve utilitários auxiliares e aplicativos para equipes de desenvolvimento. 

A equipe de DI trabalha em colaboração com a equipe da Plataforma, que desenvolve internamente a arquitetura, as bibliotecas e as abordagens de desenvolvimento. Ao mesmo tempo, qualquer desenvolvedor da CIAN pode contribuir com a automação, por exemplo, fazer microautomação para atender às necessidades da equipe ou compartilhar uma ideia bacana de como tornar a automação ainda melhor.

Bolo de camadas de automação na CIAN

Dos scripts à nossa própria plataforma: como automatizamos o desenvolvimento na CIAN

Todos os sistemas envolvidos na automação podem ser divididos em várias camadas:

  1. Sistemas externos (Jira, Bitbucket, etc.). As equipes de desenvolvimento trabalham com eles.
  2. Plataforma Integro. Na maioria das vezes, os desenvolvedores não trabalham diretamente com isso, mas é o que mantém toda a automação funcionando.
  3. Serviços de entrega, orquestração e descoberta (por exemplo, Jeknins, Consul, Nomad). Com a ajuda deles, implantamos código em servidores e garantimos que os serviços funcionem entre si.
  4. Camada física (servidores, sistema operacional, software relacionado). Nosso código opera neste nível. Pode ser um servidor físico ou virtual (LXC, KVM, Docker).

Com base neste conceito, dividimos áreas de responsabilidade dentro da equipe de DI. Os dois primeiros níveis estão na área de responsabilidade da direção de desenvolvimento do Integro, e os dois últimos níveis já estão na área de responsabilidade do DevOps. Essa separação permite focar nas tarefas e não atrapalha a interação, já que estamos próximos e trocamos conhecimentos e experiências constantemente.

integro

Vamos nos concentrar no Integro e começar com a pilha de tecnologia:

  • CentOS 7
  • Docker + Nomad + Cônsul + Vault
  • Java 11 (o antigo monólito Integro permanecerá no Java 8)
  • Spring Boot 2.X + Configuração Spring Cloud
  • PostgreSql 11
  • RabbitMQ 
  • Apache IgniteName
  • Camunda (incorporado)
  • Grafana + Grafite + Prometheus + Jaeger + ELK
  • UI da Web: React (CSR) + MobX
  • SSO: Keycloak

Aderimos ao princípio do desenvolvimento de microsserviços, embora tenhamos um legado na forma de um monólito de uma versão inicial do Integro. Cada microsserviço é executado em seu próprio contêiner Docker e os serviços se comunicam por meio de solicitações HTTP e mensagens RabbitMQ. Os microsserviços se encontram através do Consul e fazem uma solicitação a ele, passando a autorização através de SSO (Keycloak, OAuth 2/OpenID Connect).

Dos scripts à nossa própria plataforma: como automatizamos o desenvolvimento na CIAN

Como exemplo da vida real, considere interagir com Jenkins, que consiste nas seguintes etapas:

  1. O microsserviço de gerenciamento de fluxo de trabalho (doravante denominado microsserviço Flow) deseja executar uma compilação no Jenkins. Para fazer isso, ele usa o Consul para encontrar o IP:PORT do microsserviço para integração com Jenkins (doravante denominado microsserviço Jenkins) e envia uma solicitação assíncrona para iniciar a construção no Jenkins.
  2. Após receber uma solicitação, o microsserviço Jenkins gera e responde com um Job ID, que pode então ser usado para identificar o resultado do trabalho. Ao mesmo tempo, aciona a construção no Jenkins por meio de uma chamada de API REST.
  3. Jenkins realiza a construção e, após a conclusão, envia um webhook com os resultados da execução para o microsserviço Jenkins.
  4. O microsserviço Jenkins, ao receber o webhook, gera uma mensagem sobre a conclusão do processamento da solicitação e anexa a ele os resultados da execução. A mensagem gerada é enviada para a fila RabbitMQ.
  5. Por meio do RabbitMQ, a mensagem publicada chega ao microsserviço Flow, que aprende o resultado do processamento de sua tarefa combinando o ID do Job da solicitação e a mensagem recebida.

Agora temos cerca de 30 microsserviços, que podem ser divididos em vários grupos:

  1. Gerenciamento de configurações.
  2. Informação e interação com usuários (mensageiros, correio).
  3. Trabalhando com código fonte.
  4. Integração com ferramentas de implantação (jenkins, nomad, cônsul, etc.).
  5. Monitoramento (lançamentos, erros, etc.).
  6. Utilitários da Web (UI para gerenciar ambientes de teste, coletar estatísticas, etc.).
  7. Integração com rastreadores de tarefas e sistemas similares.
  8. Gerenciamento de fluxo de trabalho para diferentes tarefas.

Tarefas de fluxo de trabalho

O Integro automatiza atividades relacionadas ao ciclo de vida da tarefa. Em termos simplificados, o ciclo de vida de uma tarefa será entendido como o fluxo de trabalho de uma tarefa no Jira. Nossos processos de desenvolvimento possuem diversas variações de fluxo de trabalho dependendo do projeto, do tipo de tarefa e das opções selecionadas em uma determinada tarefa. 

Vejamos o fluxo de trabalho que usamos com mais frequência:

Dos scripts à nossa própria plataforma: como automatizamos o desenvolvimento na CIAN

No diagrama, a engrenagem indica que a transição é chamada automaticamente pelo Integro, enquanto a figura humana indica que a transição é chamada manualmente por uma pessoa. Vejamos vários caminhos que uma tarefa pode seguir neste fluxo de trabalho.

Testes totalmente manuais em DEV+BETA sem testes canário (geralmente é assim que lançamos um monólito):

Dos scripts à nossa própria plataforma: como automatizamos o desenvolvimento na CIAN

Pode haver outras combinações de transição. Às vezes, o caminho que um problema seguirá pode ser selecionado por meio de opções no Jira.

Movimento de tarefas

Vejamos as principais etapas que são executadas quando uma tarefa passa pelo fluxo de trabalho “Teste DEV + Testes Canário”:

1. O desenvolvedor ou PM cria a tarefa.

2. O desenvolvedor leva a tarefa para o trabalho. Após a conclusão, ele muda para o status EM REVISÃO.

3. Jira envia um Webhook para o microsserviço Jira (responsável pela integração com Jira).

4. O microsserviço Jira envia uma solicitação ao serviço Flow (responsável pelos fluxos de trabalho internos nos quais o trabalho é executado) para iniciar o fluxo de trabalho.

5. Dentro do serviço Flow:

  • Os revisores são atribuídos à tarefa (microsserviço de usuários que sabe tudo sobre os usuários + microsserviço Jira).
  • Através do microsserviço Source (ele conhece repositórios e ramificações, mas não funciona com o código em si), é feita uma busca por repositórios que contenham uma ramificação do nosso problema (para simplificar a busca, o nome da ramificação coincide com o problema número em Jira). Na maioria das vezes, uma tarefa possui apenas uma ramificação em um repositório; isso simplifica o gerenciamento da fila de implantação e reduz a conectividade entre repositórios.
  • Para cada ramificação encontrada, a seguinte sequência de ações é executada:

    i) Atualização do branch master (microsserviço Git para trabalhar com código).
    ii) A filial está bloqueada para alterações pelo desenvolvedor (microsserviço Bitbucket).
    iii) Uma solicitação pull é criada para esta ramificação (microsserviço Bitbucket).
    iv) Uma mensagem sobre um novo Pull Request é enviada para chats de desenvolvedores (microsserviço Notify para trabalhar com notificações).
    v) As tarefas de construção, teste e implantação são iniciadas no DEV (microsserviço Jenkins para trabalhar com Jenkins).
    vi) Se todas as etapas anteriores forem concluídas com sucesso, o Integro coloca seu Approve no Pull Request (microsserviço Bitbucket).

  • Integro aguarda aprovação em solicitação pull de revisores designados.
  • Assim que todas as aprovações necessárias forem recebidas (incluindo testes automatizados aprovados positivamente), o Integro transfere a tarefa para o status Test on Dev (microsserviço Jira).

6. Os testadores testam a tarefa. Se não houver problemas, a tarefa será transferida para o status Ready For Build.

7. O Integro “vê” que a tarefa está pronta para liberação e inicia sua implantação no modo canário (microsserviço Jenkins). A prontidão para liberação é determinada por um conjunto de regras. Por exemplo, a tarefa está no status obrigatório, não há bloqueios em outras tarefas, atualmente não há uploads ativos deste microsserviço, etc.

8. A tarefa é transferida para o status Canary (microsserviço Jira).

9. Jenkins inicia uma tarefa de implantação por meio do Nomad no modo canário (geralmente de 1 a 3 instâncias) e notifica o serviço de monitoramento de liberação (microsserviço DeployWatch) sobre a implantação.

10. O microsserviço DeployWatch coleta o histórico do erro e reage a ele, se necessário. Se o erro de fundo for excedido (a norma de fundo é calculada automaticamente), os desenvolvedores serão notificados por meio do microsserviço Notify. Se após 5 minutos o desenvolvedor não responder (clicar em Reverter ou Permanecer), uma reversão automática das instâncias canário será iniciada. Se o plano de fundo não for excedido, o desenvolvedor deverá iniciar manualmente a implantação da tarefa na produção (clicando em um botão na UI). Se dentro de 60 minutos o desenvolvedor não tiver iniciado a implantação em produção, as instâncias canário também serão revertidas por motivos de segurança.

11. Após iniciar a implantação em produção:

  • A tarefa é transferida para o status Produção (microsserviço Jira).
  • O microsserviço Jenkins inicia o processo de implantação e notifica o microsserviço DeployWatch sobre a implantação.
  • O microsserviço DeployWatch verifica se todos os contêineres em Produção foram atualizados (houve casos em que nem todos foram atualizados).
  • Através do microsserviço Notify, uma notificação sobre os resultados da implantação é enviada para Produção.

12. Os desenvolvedores terão 30 minutos para começar a reverter uma tarefa da produção se for detectado um comportamento incorreto do microsserviço. Após esse tempo, a tarefa será automaticamente mesclada no master (microsserviço Git).

13. Após uma mesclagem bem-sucedida no mestre, o status da tarefa será alterado para Fechado (microsserviço Jira).

O diagrama não pretende ser totalmente detalhado (na realidade existem ainda mais etapas), mas permite avaliar o grau de integração nos processos. Não consideramos este esquema ideal e estamos melhorando os processos de liberação automática e suporte à implantação.

Qual é o próximo

Temos grandes planos para o desenvolvimento da automação, por exemplo, eliminando operações manuais durante lançamentos monolíticos, melhorando o monitoramento durante a implantação automática e melhorando a interação com os desenvolvedores.

Mas vamos parar por aqui por enquanto. Abordamos muitos tópicos na revisão de automação superficialmente, alguns nem foram abordados, por isso teremos prazer em responder a perguntas. Estamos aguardando sugestões sobre o que abordar detalhadamente, escreva nos comentários.

Fonte: habr.com

Adicionar um comentário