O que sabemos sobre microsserviços

Olá! Meu nome é Vadim Madison, lidero o desenvolvimento da Avito System Platform. Já foi dito mais de uma vez como nós, na empresa, estamos mudando de uma arquitetura monolítica para uma de microsserviços. É hora de compartilhar como transformamos nossa infraestrutura para aproveitar ao máximo os microsserviços e evitar que nos percamos neles. Como o PaaS nos ajuda aqui, como simplificamos a implantação e reduzimos a criação de um microsserviço a um clique - continue lendo. Nem tudo o que escrevo abaixo está totalmente implementado no Avito, parte disso é como desenvolvemos nossa plataforma.

(E no final deste artigo falarei sobre a oportunidade de participar de um seminário de três dias do especialista em arquitetura de microsserviços Chris Richardson).

O que sabemos sobre microsserviços

Como chegamos aos microsserviços

Avito é um dos maiores sites de classificados do mundo, mais de 15 milhões de novos anúncios são publicados nele por dia. Nosso backend aceita mais de 20 mil solicitações por segundo. Atualmente temos várias centenas de microsserviços.

Estamos construindo uma arquitetura de microsserviços há vários anos. Como exatamente - nossos colegas em detalhes contado em nossa seção no RIT++ 2017. No CodeFest 2017 (veja. vídeo), Sergey Orlov e Mikhail Prokopchuk explicaram detalhadamente por que precisávamos da transição para microsserviços e qual o papel que o Kubernetes desempenhou aqui. Bem, agora estamos fazendo tudo para minimizar os custos de escalonamento inerentes a tal arquitetura.

Inicialmente, não criamos um ecossistema que nos ajudasse de forma abrangente a desenvolver e lançar microsserviços. Eles simplesmente coletaram soluções sensatas de código aberto, lançaram-nas em casa e convidaram o desenvolvedor para lidar com elas. Como resultado, ele foi a uma dezena de lugares (dashboards, serviços internos), após o que se fortaleceu em seu desejo de cortar o código da maneira antiga, em um monólito. A cor verde nos diagramas abaixo indica o que o desenvolvedor faz de uma forma ou de outra com as próprias mãos, e a cor amarela indica automação.

O que sabemos sobre microsserviços

Agora, no utilitário PaaS CLI, um novo serviço é criado com um comando e um novo banco de dados é adicionado com mais dois e implementado no Stage.

O que sabemos sobre microsserviços

Como superar a era da “fragmentação de microsserviços”

Com uma arquitetura monolítica, para manter a consistência das mudanças no produto, os desenvolvedores foram forçados a descobrir o que estava acontecendo com seus vizinhos. Ao trabalhar na nova arquitetura, os contextos de serviço não dependem mais uns dos outros.

Além disso, para que uma arquitetura de microsserviços seja eficaz, muitos processos precisam ser estabelecidos, a saber:

• exploração madeireira;
• rastreamento de solicitações (Jaeger);
• agregação de erros (Sentry);
• status, mensagens, eventos do Kubernetes (Event Stream Processing);
• limite de corrida/disjuntor (pode usar Hystrix);
• controle de conectividade de serviços (usamos Netramesh);
• monitoramento (Grafana);
• montagem (TeamCity);
• comunicação e notificação (Slack, email);
• rastreamento de tarefas; (Jira)
• preparação de documentação.

Para garantir que o sistema não perca sua integridade e permaneça eficaz à medida que aumenta, repensamos a organização dos microsserviços no Avito.

Como gerenciamos microsserviços

O seguinte ajuda a implementar uma “política partidária” unificada entre muitos microsserviços Avito:

  • dividir a infraestrutura em camadas;
  • Conceito de plataforma como serviço (PaaS);
  • monitorando tudo o que acontece com microsserviços.

As camadas de abstração de infraestrutura incluem três camadas. Vamos de cima para baixo.

A. Topo - malha de serviço. No começo tentamos o Istio, mas descobrimos que ele usa muitos recursos, o que é muito caro para nossos volumes. Portanto, o engenheiro sênior da equipe de arquitetura Alexander Lukyanchenko desenvolveu sua própria solução - Netramesh (disponível em código aberto), que atualmente usamos em produção e que consome várias vezes menos recursos que o Istio (mas não faz tudo o que o Istio pode se orgulhar).
B. Médio - Kubernetes. Implantamos e operamos microsserviços nele.
C. Fundo - metal descoberto. Não usamos nuvens ou coisas como OpenStack, mas confiamos inteiramente em bare metal.

Todas as camadas são combinadas por PaaS. E esta plataforma, por sua vez, é composta por três partes.

I. Geradores, controlado por meio de um utilitário CLI. É ela quem ajuda o desenvolvedor a criar um microsserviço da maneira certa e com o mínimo de esforço.

II. Colecionador consolidado com controle de todas as ferramentas através de um painel comum.

III. Armazenar. Conecta-se com agendadores que definem automaticamente gatilhos para ações significativas. Graças a esse sistema, nenhuma tarefa é perdida só porque alguém se esqueceu de configurar uma tarefa no Jira. Usamos uma ferramenta interna chamada Atlas para isso.

O que sabemos sobre microsserviços

A implementação dos microsserviços no Avito também é realizada de acordo com um esquema único, o que simplifica o controle sobre eles em cada etapa de desenvolvimento e lançamento.

Como funciona um pipeline de desenvolvimento de microsserviços padrão?

Em geral, a cadeia de criação de microsserviços é assim:

CLI-push → Integração Contínua → Bake → Deploy → Testes artificiais → Testes canário → Teste de compressão → Produção → Manutenção.

Vamos examinar exatamente nesta ordem.

CLI-push

• Criando um microsserviço.
Lutamos por muito tempo para ensinar cada desenvolvedor como fazer microsserviços. Isso incluiu escrever instruções detalhadas no Confluence. Mas os esquemas mudaram e foram complementados. O resultado é que um gargalo apareceu no início da jornada: demorava muito mais tempo para lançar microsserviços e ainda assim surgiam problemas durante sua criação.

No final, construímos um utilitário CLI simples que automatiza as etapas básicas ao criar um microsserviço. Na verdade, ele substitui o primeiro git push. Aqui está o que exatamente ela faz.

— Cria um serviço de acordo com um modelo — passo a passo, em modo “assistente”. Temos templates para as principais linguagens de programação no backend Avito: PHP, Golang e Python.

- Um comando por vez implanta um ambiente para desenvolvimento local em uma máquina específica - O Minikube é iniciado, os gráficos do Helm são gerados automaticamente e lançados em kubernetes locais.

— Conecta o banco de dados necessário. O desenvolvedor não precisa saber IP, login e senha para ter acesso ao banco de dados que necessita – seja localmente, em Stage ou em produção. Além disso, o banco de dados é implantado imediatamente em uma configuração tolerante a falhas e com balanceamento.

— Ele mesmo realiza a montagem ao vivo. Digamos que um desenvolvedor corrigiu algo em um microsserviço por meio de seu IDE. O utilitário vê alterações no sistema de arquivos e, com base nelas, reconstrói o aplicativo (para Golang) e reinicia. Para PHP, simplesmente encaminhamos o diretório dentro do cubo e o live-reload é obtido “automaticamente”.

— Gera autotestes. Na forma de espaços em branco, mas bastante adequados para uso.

• Implantação de microsserviços.

Implantar um microsserviço costumava ser uma tarefa árdua para nós. Foram necessários os seguintes:

I. Dockerfile.

II. Configuração.
III. Gráfico do Helm, que por si só é complicado e inclui:

— os próprios gráficos;
- modelos;
— valores específicos levando em consideração diferentes ambientes.

Eliminamos o trabalho de retrabalhar os manifestos do Kubernetes para que agora sejam gerados automaticamente. Mas o mais importante é que eles simplificaram a implantação ao limite. A partir de agora temos um Dockerfile, e o desenvolvedor escreve toda a configuração em um único arquivo app.toml curto.

O que sabemos sobre microsserviços

Sim, e no próprio app.toml não há nada para fazer por um minuto. Especificamos onde e quantas cópias do serviço serão geradas (no servidor de desenvolvimento, no teste, na produção) e indicamos suas dependências. Observe a linha size = "small" no bloco [engine]. Esse é o limite que será alocado ao serviço via Kubernetes.

Então, com base na configuração, todos os gráficos Helm necessários são gerados automaticamente e as conexões com os bancos de dados são criadas.

• Validação básica. Essas verificações também são automatizadas.
Precisa rastrear:
- existe um Dockerfile;
- existe app.toml;
— existe documentação disponível?
- a dependência está em ordem?
— se foram definidas regras de alerta.
Até o último ponto: o próprio proprietário do serviço determina quais métricas do produto monitorar.

• Preparação de documentação.
Ainda é uma área problemática. Parece ser o mais óbvio, mas ao mesmo tempo é também um registo “muitas vezes esquecido” e, portanto, um elo vulnerável da cadeia.
É necessário que exista documentação para cada microsserviço. Inclui os seguintes blocos.

I. Breve descrição do serviço. Literalmente algumas frases sobre o que faz e por que é necessário.

II. Link do diagrama de arquitetura. É importante que com uma rápida olhada seja fácil entender, por exemplo, se você está usando o Redis para armazenamento em cache ou como armazenamento de dados principal em modo persistente. No Avito, por enquanto, este é um link para o Confluence.

III. Livro de execução. Um breve guia sobre como iniciar o serviço e as complexidades de lidar com ele.

XNUMX. Perguntas frequentes, onde seria bom antecipar os problemas que os seus colegas poderão encontrar ao trabalhar com o serviço.

V. Descrição dos endpoints da API. Se de repente você não especificou os destinos, os colegas cujos microsserviços estão relacionados aos seus quase certamente pagarão por isso. Agora usamos o Swagger e nossa solução chamada brief para isso.

VI. Etiquetas. Ou marcadores que mostram a qual produto, funcionalidade ou divisão estrutural da empresa o serviço pertence. Eles ajudam você a entender rapidamente, por exemplo, se você está cortando funcionalidades que seus colegas implementaram para a mesma unidade de negócios há uma semana.

VII. Proprietário ou proprietários do serviço. Na maioria dos casos, eles — ou eles — podem ser determinados automaticamente usando PaaS, mas para garantir a segurança, exigimos que o desenvolvedor os especifique manualmente.

Finalmente, é uma boa prática revisar a documentação, semelhante à revisão de código.

Integração contínua

  • Preparando repositórios.
  • Criando um pipeline no TeamCity.
  • Definir direitos.
  • Procure proprietários de serviços. Existe um esquema híbrido aqui - marcação manual e automação mínima de PaaS. Um esquema totalmente automático falha quando os serviços são transferidos para suporte a outra equipe de desenvolvimento ou, por exemplo, se o desenvolvedor do serviço sai.
  • Registrando um serviço no Atlas (Veja acima). Com todos os seus proprietários e dependências.
  • Verificando migrações. Verificamos se algum deles é potencialmente perigoso. Por exemplo, em um deles aparece uma alter table ou outra coisa que pode quebrar a compatibilidade do esquema de dados entre diferentes versões do serviço. Então a migração não é realizada, mas sim colocada em uma assinatura – o PaaS deve sinalizar ao proprietário do serviço quando for seguro utilizá-lo.

assar

A próxima etapa é empacotar os serviços antes da implantação.

  • Construindo o aplicativo. De acordo com os clássicos - em uma imagem Docker.
  • Geração de gráficos Helm para o próprio serviço e recursos relacionados. Incluindo para bancos de dados e cache. Eles são criados automaticamente de acordo com a configuração app.toml que foi gerada no estágio CLI-push.
  • Criação de tickets para administradores abrirem portas (Quando solicitado).
  • Executando testes unitários e calculando a cobertura de código. Se a cobertura do código estiver abaixo do limite especificado, provavelmente o serviço não irá mais longe - para implantação. Se estiver próximo do aceitável, será atribuído ao serviço um coeficiente “pessimizante”: então, se não houver melhora no indicador ao longo do tempo, o desenvolvedor receberá uma notificação de que não há progresso em termos de testes ( e algo precisa ser feito sobre isso).
  • Contabilizando limitações de memória e CPU. Escrevemos principalmente microsserviços em Golang e os executamos em Kubernetes. Daí uma sutileza associada à peculiaridade da linguagem Golang: por padrão, ao iniciar, todos os núcleos da máquina são usados, se você não definir explicitamente a variável GOMAXPROCS, e quando vários desses serviços são iniciados na mesma máquina, eles começam competir por recursos, interferindo uns com os outros. Os gráficos abaixo mostram como o tempo de execução muda se você executar o aplicativo sem contenção e no modo corrida por recursos. (As fontes dos gráficos são aqui).

O que sabemos sobre microsserviços

Tempo de execução, menos é melhor. Máximo: 643ms, mínimo: 42ms. A foto é clicável.

O que sabemos sobre microsserviços

Hora da cirurgia, menos é melhor. Máximo: 14091 ns, mínimo: 151 ns. A foto é clicável.

Na fase de preparação da montagem, você pode definir esta variável explicitamente ou pode usar a biblioteca automaxprocs dos caras do Uber.

Implantar

• Verificando convenções. Antes de começar a entregar montagens de serviço nos ambientes pretendidos, é necessário verificar o seguinte:
- Terminais de API.
— Conformidade das respostas dos endpoints da API com o esquema.
— Formato de registro.
— Definir cabeçalhos para solicitações ao serviço (atualmente isso é feito pelo netramesh)
— Definir o token do proprietário ao enviar mensagens para o barramento de eventos. Isso é necessário para rastrear a conectividade dos serviços no barramento. Você pode enviar tanto dados idempotentes para o barramento, o que não aumenta a conectividade dos serviços (o que é bom), quanto dados de negócios que fortalecem a conectividade dos serviços (o que é muito ruim!). E no momento em que essa conectividade se torna um problema, entender quem escreve e lê o barramento ajuda a separar adequadamente os serviços.

Ainda não há muitas convenções em Avito, mas seu número está se expandindo. Quanto mais esses acordos estiverem disponíveis de uma forma que a equipe possa compreender e compreender, mais fácil será manter a consistência entre os microsserviços.

Testes sintéticos

• Teste em circuito fechado. Para isso, agora estamos usando código aberto Hoverfly.io. Primeiro, ele registra a carga real do serviço e depois - apenas em um circuito fechado - emula-a.

• Teste de Estresse. Tentamos trazer todos os serviços para um desempenho ideal. E todas as versões de cada serviço devem ser submetidas a testes de carga – desta forma podemos entender o desempenho atual do serviço e a diferença com versões anteriores do mesmo serviço. Se após uma atualização do serviço seu desempenho caiu uma vez e meia, este é um sinal claro para seus proprietários: você precisa se aprofundar no código e corrigir a situação.
Usamos os dados coletados, por exemplo, para implementar corretamente o auto scaling e, no final, entender de forma geral o quão escalável é o serviço.

Durante o teste de carga, verificamos se o consumo de recursos atende aos limites definidos. E nos concentramos principalmente nos extremos.

a) Observamos a carga total.
- Muito pequeno - provavelmente algo não funciona se a carga cair repentinamente várias vezes.
- Muito grande - é necessária otimização.

b) Observamos o ponto de corte de acordo com o RPS.
Aqui vemos a diferença entre a versão atual e a anterior e a quantidade total. Por exemplo, se um serviço produz 100 rps, então ou está mal escrito ou esta é a sua especificidade, mas em qualquer caso, este é um motivo para olhar o serviço com atenção.
Se, pelo contrário, houver muitos RPS, então talvez haja algum tipo de bug e alguns dos endpoints tenham parado de executar a carga útil e algum outro seja simplesmente acionado return true;

Testes canário

Depois de passarmos nos testes sintéticos, testamos o microsserviço em um pequeno número de usuários. Começamos com cuidado, com uma parcela ínfima do público-alvo do serviço – menos de 0,1%. Nesta etapa é muito importante que as métricas técnicas e de produto corretas sejam incluídas no monitoramento para que mostrem o problema no atendimento o mais rápido possível. O tempo mínimo para um teste canário é de 5 minutos, o principal é de 2 horas. Para serviços complexos, acertamos a hora manualmente.
Analisamos:
— métricas específicas da linguagem, em particular, trabalhadores php-fpm;
— erros no Sentry;
— status de resposta;
— tempo de resposta exato e médio;
- latência;
— exceções, processadas e não tratadas;
- métricas do produto.

Teste de compressão

O teste de compressão também é chamado de teste de “compressão”. O nome da técnica foi introduzido na Netflix. Sua essência é que primeiro preenchemos uma instância com tráfego real até o ponto de falha e assim definimos seu limite. Em seguida, adicionamos outra instância e carregamos este par - novamente ao máximo; vemos seu teto e delta com o primeiro “aperto”. E assim conectamos uma instância de cada vez e calculamos o padrão de mudanças.
Os dados de teste por meio de “compressão” também fluem para um banco de dados de métricas comum, onde enriquecemos os resultados de carga artificial com eles ou até mesmo substituímos “sintéticos” por eles.

Produção

• Dimensionamento. Quando implementamos um serviço para produção, monitoramos como ele é dimensionado. Em nossa experiência, monitorar apenas os indicadores da CPU é ineficaz. O escalonamento automático com benchmarking RPS em sua forma pura funciona, mas apenas para determinados serviços, como streaming online. Portanto, examinamos primeiro as métricas de produto específicas da aplicação.

Como resultado, ao dimensionar, analisamos:
- Indicadores de CPU e RAM,
— o número de solicitações na fila,
- tempo de resposta,
— previsão baseada em dados históricos acumulados.

Ao escalar um serviço, também é importante monitorar suas dependências para não escalarmos o primeiro serviço da cadeia e aqueles que ele acessa falharem sob carga. Para estabelecer uma carga aceitável para todo o conjunto de serviços, analisamos os dados históricos do serviço dependente “mais próximo” (com base em uma combinação de indicadores de CPU e RAM, juntamente com métricas específicas do aplicativo) e os comparamos com os dados históricos do serviço de inicialização, e assim por diante ao longo da “cadeia de dependências”, de cima para baixo.

Serviço

Depois que o microsserviço for colocado em operação, podemos anexar gatilhos a ele.

Aqui estão situações típicas em que ocorrem gatilhos.
— Detetadas migrações potencialmente perigosas.
— Atualizações de segurança foram lançadas.
— O serviço em si não é atualizado há muito tempo.
— A carga do serviço diminuiu visivelmente ou algumas das métricas do produto estão fora da faixa normal.
— O serviço não atende mais aos novos requisitos da plataforma.

Alguns dos gatilhos são responsáveis ​​​​pela estabilidade do funcionamento, alguns - em função da manutenção do sistema - por exemplo, algum serviço não é implantado há muito tempo e sua imagem base deixou de passar nas verificações de segurança.

Painel

Resumindo, o dashboard é o painel de controle de todo o nosso PaaS.

  • Um ponto único de informação sobre o serviço, com dados sobre a sua cobertura de testes, o número das suas imagens, o número de exemplares de produção, versões, etc.
  • Uma ferramenta para filtrar dados por serviços e rótulos (marcadores de pertencimento a unidades de negócios, funcionalidade do produto, etc.)
  • Uma ferramenta para integração com ferramentas de infraestrutura para rastreamento, registro e monitoramento.
  • Um único ponto de documentação de serviço.
  • Um único ponto de vista de todos os eventos nos serviços.

O que sabemos sobre microsserviços
O que sabemos sobre microsserviços
O que sabemos sobre microsserviços
O que sabemos sobre microsserviços

No total

Antes de introduzir o PaaS, um novo desenvolvedor poderia passar várias semanas entendendo todas as ferramentas necessárias para lançar um microsserviço em produção: Kubernetes, Helm, nossos recursos internos do TeamCity, configurando conexões com bancos de dados e caches de maneira tolerante a falhas, etc. leva algumas horas para ler o início rápido e criar o próprio serviço.

Dei uma reportagem sobre esse tema para o HighLoad++ 2018, você pode assistir vídeo и apresentação.

Faixa bônus para quem leu até o fim

Nós da Avito estamos organizando um treinamento interno de três dias para desenvolvedores de Chris Richardson, especialista em arquitetura de microsserviços. Gostaríamos de dar a oportunidade de participar a um dos leitores deste post. é O programa de treinamento foi publicado.

O treinamento acontecerá de 5 a 7 de agosto em Moscou. São dias úteis que estarão totalmente ocupados. O almoço e o treinamento serão em nosso escritório, e o participante selecionado pagará ele mesmo a viagem e hospedagem.

Você pode solicitar participação neste formulário do Google. De você - a resposta à pergunta por que você precisa participar do treinamento e informações sobre como entrar em contato com você. Responda em inglês, pois o próprio Chris escolherá o participante que participará do treinamento.
Anunciaremos o nome do participante do treinamento em atualização deste post e nas redes sociais Avito para desenvolvedores (AvitoTech em Фейсбуке, VKontakte, Chilro) até 19 de julho.

Fonte: habr.com

Adicionar um comentário