
Na RIT 2019, nosso colega Alexander Korotkov fez sobre automação do desenvolvimento na CIAN: para simplificar a vida e o trabalho, usamos nossa própria plataforma Integro. Ela rastreia o ciclo de vida das tarefas, aliviando os desenvolvedores de operações rotineiras e reduzindo significativamente a quantidade de bugs em produção. Neste post, complementamos a apresentação de Alexander e contamos como passamos de simples scripts para a integração de produtos open source por meio de nossa própria plataforma e o que uma equipe separada de automação faz por nós.
Nível zero
«Não existe nível zero, eu não conheço isso»
Mestre Shifu do filme «Kung Fu Panda»
A automação na CIAN começou 14 anos após a fundação da empresa. Na época, a equipe de desenvolvimento contava com 35 pessoas. Dificil acreditar, né? Claro, de alguma forma a automação existia, mas a direção separada para integração contínua e entrega de código começou a se formar precisamente em 2015.
Naquele momento, tínhamos um enorme monólito em Python, C# e PHP, implantado em servidores Linux/Windows. Para implantar esse monstro, tínhamos um conjunto de scripts que executávamos manualmente. Também havia a construção do monólito, causando dor e sofrimento devido a conflitos de mesclagem, correção de defeitos e recompilação 'com um conjunto diferente de tarefas no build'. Simplificadamente, o processo parecia assim:

Isso não nos satisfazia, e queríamos construir um processo de construção e implantação repetível, automatizado e gerenciado. Para isso, precisávamos de um sistema CI/CD e estávamos escolhendo entre a versão gratuita do Teamcity e o Jenkins gratuito, pois já trabalhávamos com ambos e ambos nos davam suporte em termos de funcionalidades. Escolhemos o Teamcity por ser um produto mais recente. Naquela época, ainda não usávamos uma arquitetura de microsserviços e não esperávamos um grande número de tarefas e projetos.
Chegamos à ideia de um sistema próprio
A implementação do Teamcity eliminou apenas parte do trabalho manual: ainda era necessário criar Pull Requests, avançar tarefas pelos status no Jira e escolher tarefas para o lançamento. A sistema Teamcity não conseguia mais lidar com isso. Era necessário escolher um caminho de automação adicional. Consideramos opções de trabalhar com scripts no Teamcity ou mudar para sistemas de automação de terceiros. Mas, no final, decidimos que precisávamos da máxima flexibilidade, que só uma solução interna poderia oferecer. Assim surgiu a primeira versão do sistema interno de automação chamado Integro.
Teamcity foca na automação ao nível de iniciar processos de construção e implantação, enquanto o Integro se concentrou na automação de alto nível dos processos de desenvolvimento. Era necessário integrar o trabalho com tarefas no Jira e o processamento do código fonte relacionado no Bitbucket. Nesta fase, o Integro começou a desenvolver seus próprios fluxos de trabalho para lidar com tarefas de diferentes tipos.
Devido ao aumento da automação nos processos de negócios, o número de projetos e execuções no Teamcity cresceu. Assim, surgiu um novo problema: uma instância gratuita do Teamcity não era mais suficiente (3 agentes e 100 projetos), adicionamos mais uma instância (mais 3 agentes e 100 projetos) e depois mais uma. No final, conseguimos um sistema com vários clusters, o que tornou a gestão complicada:

Quando surgiu a questão de uma quarta instância, percebemos que não poderíamos continuar assim, pois os custos totais para manter 4 instâncias já estavam insustentáveis. Surgiu a dúvida de comprar o Teamcity pago ou optar pelo Jenkins gratuito. Fizemos cálculos sobre as instâncias e planos de automação e decidimos que iríamos trabalhar com Jenkins. Após algumas semanas, migramos para o Jenkins e aliviamos parte da dor de cabeça relacionada à manutenção de várias instâncias do Teamcity. Isso nos permitiu focar no desenvolvimento do Integro e na adaptação do Jenkins para nossas necessidades.
Com o aumento 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), surgiu um forte desejo de eliminar ao máximo os lançamentos manuais e entregar esse trabalho aos robôs. Além disso, dentro da empresa, começamos a migração para microserviços, que exigiam lançamentos frequentes, e feitos separadamente. Assim, gradualmente chegamos aos lançamentos automáticos de nossos microserviços (o monolito ainda é lançado manualmente devido à complexidade do processo). Mas, como costuma acontecer, surgiu uma nova dificuldade.
Automatizamos os testes

Devido à automação dos lançamentos, os processos de desenvolvimento foram acelerados, parcialmente em função da omissão de algumas etapas de teste. Isso resultou em uma perda temporária de qualidade. Pode soar banal, mas junto com a aceleração dos lançamentos, tínhamos que mudar também a metodologia de desenvolvimento do produto. Era necessário pensar na automação dos testes, no reforço da responsabilidade pessoal (aqui falamos sobre "aceitar a ideia na mente", e não sobre multas financeiras) do desenvolvedor pelo código produzido e pelos bugs nele, além de uma decisão sobre a liberação/não liberação das tarefas por meio do deploy automático.
Ao resolver problemas de qualidade, chegamos a duas decisões importantes: começamos a realizar testes canários e implementamos monitoramento automático de erros em segundo plano com resposta automática quando os limites são excedidos. A primeira decisão permitiu detectar erros óbvios antes que o código chegasse à produção, enquanto a segunda reduziu o tempo de resposta a problemas na produção. Erros, claro, acontecem, mas gastamos a maior parte do tempo e esforço não na correção, mas na minimização.
Equipe de automação
Atualmente, temos uma equipe de 130 desenvolvedores, e continuamos . A equipe de integração e entrega contínua de código (daqui em diante — equipe Deploy e Integração ou DI) é composta por 7 pessoas e trabalha em 2 frentes: desenvolvimento da plataforma de automação Integro e DevOps.
DevOps é responsável pelos ambientes Dev/Beta do site CIAN, ambientes Integro, ajuda os desenvolvedores a resolver problemas e desenvolve novas abordagens para escalonamento de ambientes. A área de desenvolvimento Integro lida tanto com a Integro em si quanto com serviços relacionados, como plugins para Jenkins, Jira, Confluence, além de desenvolver utilitários e aplicativos auxiliares para equipes de desenvolvedores.
A equipe DI trabalha em conjunto com a equipe de Plataforma, que lida com o desenvolvimento da arquitetura, bibliotecas e abordagens de desenvolvimento dentro da empresa. Além disso, qualquer desenvolvedor dentro da CIAN pode contribuir para a automação, por exemplo, criando microautomação para atender às necessidades da equipe ou compartilhando uma ideia legal de como melhorar ainda mais a automação.
Camadas de automação na CIAN

Todos os sistemas envolvidos na automação podem ser divididos em várias camadas:
- Sistemas externos (Jira, Bitbucket, etc.). Eles são utilizados pelas equipes de desenvolvimento.
- Plataforma Integro. Na maioria das vezes, os desenvolvedores não interagem diretamente com ela, mas é ela que suporta a operação de toda a automação.
- Serviços de entrega, orquestração e descoberta (como Jenkins, Consul, Nomad). Com eles, implantamos o código nos servidores e garantimos a operação conjunta dos serviços.
- Nível físico (servidores, SO, software relacionado). É neste nível que nosso código opera. Isso pode ser em um servidor físico ou virtual (LXC, KVM, Docker).
Com base neste conceito, dividimos as zonas de responsabilidade dentro da equipe DI. Os dois primeiros níveis estão sob a responsabilidade da área de desenvolvimento Integro, enquanto os dois últimos níveis ficam sob a responsabilidade do DevOps. Essa divisão permite focar nas tarefas e não interfere na interação, uma vez que estamos próximos uns dos outros e constantemente trocamos conhecimentos e experiências.
Integro
Vamos focar na Integro e começar pela pilha tecnológica:
- CentOs 7
- Docker + Nomad + Consul + Vault
- Java 11 (o antigo monólito Integro permanecerá em Java 8)
- Spring Boot 2.X + Spring Cloud Config
- PostgreSql 11
- RabbitMQ
- Apache Ignite
- Camunda (embutido)
- Grafana + Graphite + Prometheus + Jaeger + ELK
- Web UI: React (CSR) + MobX
- SSO: Keycloak
Adotamos o princípio do desenvolvimento de microsserviços, embora tenhamos um legado na forma do monolito da versão anterior do Integro. Cada microsserviço roda em seu próprio contêiner Docker, e os serviços se comunicam entre si por meio de solicitações HTTP e mensagens RabbitMQ. Os microsserviços se encontram através do Consul e fazem a ele uma solicitação, passando pela autenticação via SSO (Keycloak, OAuth 2/OpenID Connect).

Como um exemplo real, consideremos a interação com o Jenkins, que consiste nos seguintes passos:
- O microsserviço de gerenciamento de workflow (daqui em diante, o microsserviço Flow) deseja iniciar uma construção no Jenkins. Para isso, ele encontra através do Consul o IP:PORT do microsserviço de integração com o Jenkins (daqui em diante, o microsserviço Jenkins) e envia para ele uma solicitação assíncrona para iniciar a construção no Jenkins.
- O microsserviço Jenkins, após receber a solicitação, gera e devolve um Job ID, que poderá ser usado posteriormente para identificar o resultado da operação. Juntamente com isso, ele inicia a construção no Jenkins através da chamada da API REST.
- O Jenkins executa a construção e, após a conclusão, envia um webhook com os resultados da execução para o microsserviço Jenkins.
- O microsserviço Jenkins, ao receber o webhook, forma uma mensagem de conclusão do processamento da solicitação e anexa a ela os resultados da execução. A mensagem formada é enviada para a fila RabbitMQ.
- Através do RabbitMQ, a mensagem publicada chega ao microsserviço Flow, que fica ciente do resultado do processamento de sua tarefa, comparando o Job ID da solicitação com o Job ID da mensagem recebida.
Atualmente, temos cerca de 30 microsserviços, que podem ser organizados em vários grupos:
- Gerenciamento de configurações.
- Informação e interação com usuários (mensageiros, email).
- Trabalho com código-fonte.
- Integração com ferramentas de deployment (jenkins, nomad, consul, etc.).
- Monitoramento (lançamentos, erros, etc.).
- Web-utilitários (UI para gerenciamento de ambientes de teste, coleta de estatísticas, etc.).
- Integração com rastreadores de tarefas e sistemas semelhantes.
- Gerenciamento de workflow para diferentes tarefas.
Workflow da tarefa
O Integro automatiza ações relacionadas ao ciclo de vida da tarefa. Em termos simplificados, entendemos por ciclo de vida da tarefa o workflow da tarefa no Jira. Em nossos processos de desenvolvimento, existem várias variações de workflow dependendo do projeto, do tipo de tarefa e das opções selecionadas na tarefa específica.
Vamos considerar o workflow que usamos com mais frequência:

No diagrama, a engrenagem indica que a transição é invocada automaticamente pelo Integro, enquanto a figura da pessoa significa que a transição é acionada manualmente pelo usuário. Vamos considerar alguns caminhos pelos quais a tarefa pode passar neste fluxo de trabalho.
Testes totalmente manuais em DEV+BETA sem testes canários (geralmente assim lançamos o monólito):

Podem existir outras combinações de transição. Às vezes, o caminho que a tarefa seguirá pode ser escolhido através de opções no Jira.
Movimento da tarefa
Vamos considerar as etapas principais que são realizadas ao mover uma tarefa pelo fluxo de trabalho "Teste em DEV + testes canários":
1. O desenvolvedor ou PM cria a tarefa.
2. O desenvolvedor começa a trabalhar na tarefa. Após a conclusão, ele a coloca no status IN REVIEW.
3. O Jira envia um Webhook para o microserviço Jira (responsável pela integração com o Jira).
4. O microserviço Jira envia uma solicitação para o serviço Flow (responsável pelos fluxos de trabalho internos onde o trabalho é realizado) para iniciar o fluxo de trabalho.
5. Dentro do serviço Flow:
- Revisores são designados para a tarefa (microserviço Users, que sabe tudo sobre os usuários + microserviço Jira).
- Através do microserviço Source (que conhece os repositórios e branches, mas não trabalha com o código em si), é feita a busca pelos repositórios que possuem a branch da nossa tarefa (por simplificação, o nome da branch coincide com o número da tarefa no Jira). Normalmente, a tarefa tem apenas uma branch em um único repositório, o que simplifica a gestão da fila para o deployment e reduz a complexidade entre os repositórios.
- Para cada branch encontrada, é executada a seguinte sequência de ações:
i) O merge da branch master (microserviço Git para trabalho com o código).
ii) A branch é bloqueada para mudanças pelo desenvolvedor (microserviço Bitbucket).
iii) É criado um Pull Request para esta branch (microserviço Bitbucket).
iv) Uma mensagem sobre o novo Pull Request é enviada para os chats dos desenvolvedores (microserviço Notify para trabalho com notificações).
v) A construção, teste e deployment da tarefa em DEV são iniciados (microserviço Jenkins para trabalho com Jenkins).
vi) Se todos os passos anteriores foram concluídos com sucesso, o Integro coloca sua Aprovação no Pull Request (microserviço Bitbucket). - O Integro aguarda a Aprovação no Pull Request dos revisores designados.
- Assim que todas as Aprovações necessárias forem recebidas (incluindo os testes automatizados que passaram com sucesso), o Integro move a tarefa para o status Test on Dev (microserviço Jira).
6. Os testadores realizam testes na tarefa. Se não houver problemas, a tarefa é transferida para o status Pronta Para Construção.
7. O Integro 'vê' que a tarefa está pronta para o lançamento e inicia seu deploy em modo canário (microserviço Jenkins). A prontidão para o lançamento é determinada por um conjunto de regras. Por exemplo, a tarefa está no status correto, não há bloqueios em outras tarefas, atualmente não há implantações ativas desse microserviço, etc.
8. A tarefa é transferida para o status Canário (microserviço Jira).
9. Jenkins inicia o deploy da tarefa em modo canário através do Nomad (geralmente 1-3 instâncias) e notifica sobre o lançamento o serviço de monitoramento de lançamentos (microserviço DeployWatch).
10. O microserviço DeployWatch coleta o fundo de erros e reage a ele se necessário. Se o fundo de erros exceder um determinado limite (o limite é calculado automaticamente), os desenvolvedores são notificados através do microserviço Notify. Se em 5 minutos o desenvolvedor não reagir (clicar em Reverter ou Manter), uma reversão automática das instâncias canárias é iniciada. Se o fundo não for excedido, o desenvolvedor deve iniciar manualmente o deploy da tarefa em Produção (clicando no botão na interface). Se, dentro de 60 minutos, o desenvolvedor não iniciar o deploy em Produção, as instâncias canárias também serão retiradas por motivos de segurança.
11. Após iniciar o deploy em Produção:
- A tarefa é transferida para o status Produção (microserviço Jira).
- O microserviço Jenkins inicia o processo de deploy e notifica sobre o lançamento o microserviço DeployWatch.
- O microserviço DeployWatch verifica se todos os contêineres em Produção foram atualizados (houve casos em que nem todos foram atualizados).
- Uma notificação sobre os resultados do deploy em Produção é enviada através do microserviço Notify.
12. Os desenvolvedores terão 30 minutos para iniciar a reversão da tarefa em Produção em caso de comportamento incorreto do microserviço. Após esse tempo, a tarefa será automaticamente mesclada no master (microserviço Git).
13. Após um merge bem-sucedido no master, o status da tarefa será alterado para Fechado (microserviço Jira).
O diagrama não se propõe a uma detalhamento completo (na realidade há ainda mais etapas), mas permite avaliar o grau de integração nos processos. Não consideramos este diagrama ideal e estamos aprimorando os processos de suporte automático a lançamentos e deploy.
O que vem a seguir
Temos grandes planos para o desenvolvimento da automação, como por exemplo, eliminar operações manuais durante os lançamentos do monolito, melhorar a monitoração durante o deployment automático e aprimorar a interação com os desenvolvedores.
Mas por enquanto, vamos parar aqui. Abordamos muitos tópicos na revisão da automação de forma superficial, alguns não foram mencionados de todo, então estaremos felizes em responder suas perguntas. Aguardamos sugestões sobre o que detalhar, escrevam nos comentários.
Fonte: habr.com
