.NET Core no Linux, DevOps no comando

Nós desenvolvemos DevOps da melhor maneira que pudemos. Éramos 8 pessoas, e o Vasya era o melhor em Windows. De repente, o Vasya saiu, e eu fiquei encarregado de lançar um novo projeto que fornece desenvolvimento em Windows. Quando joguei no escritório toda a pilha de desenvolvimento em Windows, percebi que a situação era dolorosa...

Assim começa a história Alexandra Sinchinova para DevOpsConf. Quando o especialista líder em Windows deixou a empresa, Alexander se questionou sobre o que fazer agora. Mudar para Linux, claro! Alexander contará como conseguiu criar um precedente e transferir parte do desenvolvimento em Windows para Linux, usando como exemplo um projeto realizado para 100.000 usuários finais.

.NET Core no Linux, DevOps no comando

Como entregar um projeto em RPM de forma fácil e descontraída, usando TFS, Puppet e Linux .NET Core? Como manter o versionamento do banco de dados do projeto se os desenvolvedores ouviram falar pela primeira vez sobre Postgres e Flyway, e o prazo é depois de amanhã? Como integrar com Docker? Como motivar os desenvolvedores .NET a abandonarem o Windows e os smoothies em favor do Puppet e do Linux? Como resolver conflitos ideológicos se atender ao Windows em produção não é viável nem desejado? Faremos isso, assim como sobre Web Deploy, testes, CI, as práticas de uso do TFS em projetos existentes e, claro, sobre pernas quebradas e soluções funcionais, na transcrição da palestra de Alexander.

Por que no hosting comum a administração do PrestaShop é lenta, e no seu não é?

Então, o Vasya saiu, a tarefa ficou comigo, e os desenvolvedores esperam ansiosamente. Quando percebi que não conseguiria trazer o Vasya de volta, comecei a trabalhar. Primeiro, avaliei a porcentagem de VMs Windows em nosso parque. A contagem era desfavorável para o Windows.

.NET Core no Linux, DevOps no comando

Como estamos desenvolvendo ativamente o DevOps, percebi que era preciso mudar a abordagem de implementação do novo aplicativo. A solução era clara: transferir tudo para Linux, se possível. O Google me ajudou – naquele momento, o .NET já havia sido portado para Linux, e percebi que essa era a solução!

Por que .NET Core em conjunto com Linux?

Havia várias razões para isso. Entre "pagar" e "não pagar", a maioria escolherá a segunda opção – assim como eu. A licença do MSDB custa cerca de 1.000 $, e a manutenção de um parque de máquinas virtuais Windows gera custos na casa das centenas de dólares. Para uma grande empresa, são altos gastos. Portanto, a economia — é a primeira razão. Não é a mais importante, mas é uma das mais significativas.

As máquinas virtuais Windows consomem mais recursos do que suas irmãs Linux – elas são pesadas. Considerando a escala de uma grande empresa, escolhemos o Linux.

O sistema integra-se facilmente ao CI existente. Nós nos consideramos DevOps progressivos, utilizamos Bamboo, Jenkins e GitLab CI, portanto, a maior parte do nosso trabalho gira em torno do Linux.

A última razão é a conveniência do suporte. Precisávamos reduzir a barreira de entrada para os 'suportadores' - pessoas que entendem a parte técnica, garantem a continuidade e mantêm os serviços de um nível secundário. Eles já estavam familiarizados com o stack Linux, então era muito mais fácil para eles entenderem o novo produto, manter e dar suporte, do que gastar recursos adicionais para se familiarizarem com uma funcionalidade semelhante de software para a plataforma Windows.

Requisitos

Primeiro e mais importante - a facilidade da nova solução para os desenvolvedores. Nem todos estavam prontos para a mudança, especialmente após a menção do Linux. Os desenvolvedores querem o Visual Studio que amam, TFS com testes automatizados e smoothies. Como a entrega acontece em produção - não lhes importa. Portanto, decidimos não mudar o processo familiar e deixar tudo inalterado para o desenvolvimento no Windows.

O novo projeto precisa ser integrado ao CI existente. As trilhas já estavam estabelecidas e todo o trabalho precisava ser realizado considerando os parâmetros do sistema de gerenciamento de configuração, os padrões de entrega aceitos e os sistemas de monitoramento.

A simplicidade no suporte e operação, como uma condição para a mínima barreira de entrada para todos os novos participantes de diferentes departamentos e da equipe de suporte.

O prazo era ontem.

A equipe de desenvolvimento do Win

Com o que a equipe do Windows estava trabalhando então?

.NET Core no Linux, DevOps no comando

Agora posso afirmar com confiança que IdentityServer4 é uma ótima alternativa gratuita ao ADFS com funcionalidades semelhantes, ou que Entity Framework Core é um paraíso para o desenvolvedor, onde ele não precisa se preocupar em escrever scripts SQL, mas pode descrever consultas ao banco de dados em termos de POO. Mas naquela época, na discussão do plano de ação, eu via aquele stack como uma escrita cuneiforme suméria, reconhecendo apenas PostgreSQL e Git.

Naquele momento, estávamos utilizando ativamente Puppet como sistema de gerenciamento de configuração. Na maioria dos nossos projetos, aplicávamos GitLab CI, Elastic, equilibrando serviços de alta carga com HAProxy, monitorando tudo com Zabbix, uma combinação de Grafana e Prometheus, Jaeger, e tudo isso funcionava em HP c ESXi para VMware. Todo mundo conhece - um clássico.

.NET Core no Linux, DevOps no comando

Vamos analisar e tentar entender o que aconteceu antes de começarmos todas essas intervenções.

O que aconteceu

O TFS é um sistema bastante poderoso, que não apenas entrega código do desenvolvedor à máquina de produção final, mas também possui um conjunto para uma integração muito flexível com diversos serviços – para garantir CI em um nível multiplataforma.

.NET Core no Linux, DevOps no comando
Antes, eram apenas caixas contínuas. O TFS usava vários agentes de Build, onde muitos projetos eram compilados. Em cada agente, havia de 3 a 4 workers, para paralelizar tarefas e otimizar o processo. Em seguida, de acordo com os planos de lançamento, o TFS entregava o Build recém-preparado em um servidor Windows de aplicativos.

A que queríamos chegar

Para entrega e desenvolvimento, usamos o TFS, e executamos o aplicativo em um servidor de aplicação Linux, com alguma mágica entre eles. Esse Caixa Mágica é o cerne do trabalho futuro. Antes de desmontá-lo em partes, vou fazer uma pausa e dizer algumas palavras sobre o aplicativo.

Projeto

O aplicativo oferece funcionalidades para operar cartões pré-pagos.

.NET Core no Linux, DevOps no comando

Cliente

Existiam dois tipos de usuários. Primeiro acessava o sistema, autenticando-se com um certificado SSL SHA-2. O segundo tinha acesso com login e senha.

HAProxy

Depois, a solicitação do cliente chegava ao HAProxy, que resolvia as seguintes tarefas:

  • autenticação inicial;
  • terminação SSL;
  • ajuste de requisições HTTP;
  • transmissão de requisições.

A verificação do certificado do cliente seguia por uma cadeia. Nós somos a autoridade e podemos nos dar ao luxo, pois emitimos certificados aos clientes do serviço.

Preste atenção ao terceiro ponto, voltaremos a ele mais tarde.

Backend

O backend foi planejado para ser feito em Linux. O backend interage com o BD, carrega a lista necessária de privilégios e, então, dependendo dos privilégios do usuário autenticado, fornece acesso para assinar documentos financeiros e enviá-los para execução, ou gerar algum relatório.

Economia com HAProxy

Além dos dois contextos que cada um dos clientes percorria, havia também o contexto de identidade. IdentityServer4 é o que permite a autenticação, um análogo gratuito e poderoso para ADFS — Active Directory Federation Services.

A solicitação de identidade era processada em várias etapas. A primeira etapa – leve chegava ao backend., que trocava dados com este servidor e verificava a existência de um token para o cliente. Se não fosse encontrado, a solicitação retornava ao contexto de onde veio, mas já com um redirecionamento, e com o redirecionamento ia para identity.

O segundo passo - a solicitação chegava à página de autorização no IdentityServer, onde o cliente estava registrado e, no banco de dados do IdentityServer, o tão aguardado token aparecia.

O terceiro passo - o cliente era redirecionado de volta ao contexto de onde veio.

.NET Core no Linux, DevOps no comando

O IdentityServer4 tem uma particularidade: a resposta à solicitação de volta é retornada via HTTP. Por mais que tentássemos configurar o servidor e buscássemos na documentação, cada vez recebíamos a solicitação original do cliente com uma URL que vinha via HTTPS, e o IdentityServer retornava o mesmo contexto, mas com HTTP. Fomos pegos de surpresa! E passamos todo esse processo através do contexto identity no HAProxy, tendo que modificar o protocolo HTTP para HTTPS nos headers.

Qual é a melhoria e onde economizamos?

Economizamos dinheiro usando uma solução gratuita para a autorização de grupo de usuários, recursos, pois não colocamos o IdentityServer4 como um nó separado em um segmento distinto, mas o utilizamos juntamente com o backend no mesmo servidor onde o backend da aplicação está em execução.

Como deve funcionar

Então, como prometi - Magic Box. Já entendemos que estamos avançando com garantia em direção ao Linux. Vamos formular as tarefas específicas que requeriam solução.

.NET Core no Linux, DevOps no comando

Manifestos Puppet. Para entregar e gerenciar a configuração do serviço e da aplicação, foi necessário escrever boas receitas. Um rolo com um lápis ilustra claramente como isso foi feito de forma rápida e eficiente.

Método de entrega. O padrão é RPM. Todos entendem que no Linux isso é fundamental, mas o próprio projeto, após a compilação, consistia em um conjunto de arquivos DLL executáveis. Eram cerca de 150, o projeto é bastante pesado. A única solução harmoniosa foi empacotar este binário em RPM e então desplegar a aplicação a partir dele.

Versionamento. Nós precisávamos lançar com bastante frequência e foi necessário decidir como formar o nome do pacote. Essa é uma questão de integração com o TFS. O agente de Build estava em Linux. Quando o TFS envia uma tarefa para o processador - worker - no agente de Build, ele também passa um conjunto de variáveis que entram no ambiente do processo do processador. Nesse conjunto de variáveis de ambiente, o nome do Build, nome da versão e outras variáveis são transmitidas. Detalhes sobre isso podem ser encontrados na seção 'construção do pacote RPM'.

Configuração do TFS resumia-se à configuração do Pipeline. Antes, coletávamos todos os projetos do Windows em agentes Windows, mas agora temos um agente Linux - o agente de Build - que precisa ser incluído no grupo de construção, enriquecido com alguns artefatos, especificar que tipo de projetos serão construídos nesse agente de Build, e modificar de alguma forma o Pipeline.

IdentityServer. ADFS não é o nosso caminho, nós apoiamos o Open Source.

Vamos passar pelos componentes.

Caixa Mágica

Consiste em quatro partes.

.NET Core no Linux, DevOps no comando

Agente de Build Linux. Linux, porque estamos construindo para ele - é lógico. Essa parte foi realizada em três etapas.

  • Configurar workers e não apenas um, pois esperava-se um trabalho distribuído no projeto.
  • Instalar .NET Core 1.x. Por que especificamente 1.x, quando já está disponível 2.0 no repositório padrão? Porque, quando começamos o desenvolvimento, a versão estável era 1.09, e decidiu-se que o projeto seria feito com ela.
  • Git 2.x.

Repositório RPM. Os pacotes RPM precisavam ser armazenados em algum lugar. Supunha-se que usaríamos o mesmo repositório RPM corporativo, que está disponível para todos os hosts Linux. E assim fizemos. No servidor do repositório, foi configurado webhook que baixava do local especificado o pacote RPM necessário. A versão do pacote era informada ao webhook pelo agente de Build.

GitLab. Atenção! O GitLab aqui é utilizado não pelos desenvolvedores, mas pelo departamento de operações para controle de versões do aplicativo, versões dos pacotes, controle do estado de todas as máquinas Linux e nele é armazenada a receita - todos os manifests do Puppet.

Puppet resolve todas as questões controversas e entrega exatamente a configuração que desejamos, do GitLab.

Vamos nos aprofundar. Como ocorre a entrega do DLL no RPM?

Entrega do DLL no RPM

Suponha que temos uma estrela do rock do desenvolvimento em .NET. Ele usa o Visual Studio e cria um ramo de release. Depois disso, ele o carrega no Git, e o Git aqui é uma entidade do TFS, ou seja, é o repositório do aplicativo com o qual o desenvolvedor trabalha.

.NET Core no Linux, DevOps no comando

Depois disso, o TFS percebe que um novo commit foi enviado. Qual aplicativo? Nas configurações do TFS, existe uma etiqueta que indica quais recursos cada agente de construção possui. Neste caso, ele vê que estamos compilando um projeto .NET Core e seleciona um agente de construção Linux do pool.

O agente de construção recebe os códigos-fonte, baixa os necessários dependencies do repositório .NET, npm, etc., e após a compilação do próprio aplicativo e o empacotamento subsequente, envia o pacote RPM para o repositório RPM.

Por outro lado, o seguinte acontece. O engenheiro da equipe de operações está lidando diretamente com a implementação do projeto: ele altera as versões dos pacotes no Hiera no repositório onde a receita do aplicativo está armazenada, após o que o Puppet dispara Yum, busca o novo pacote do repositório e a nova versão do aplicativo está pronta para uso.

.NET Core no Linux, DevOps no comando

Em teoria, tudo é simples, mas o que acontece internamente no agente de construção?

Empacotamento DLL RPM

Os códigos-fonte do projeto foram recebidos e a tarefa de compilação do TFS. O agente de construção inicia a compilação do próprio projeto a partir dos códigos-fonte.O projeto compilado está disponível na forma de vários arquivos DLL, que são empacotados em um arquivo zip para reduzir a carga no sistema de arquivos.

O arquivo ZIP é enviado para o diretório de construção do pacote RPM. Em seguida, um script Bash inicializa variáveis de ambiente, encontra a versão da construção, a versão do projeto, o caminho para o diretório de construção e inicia o RPM-build. Após a conclusão da construção, o pacote é publicado no repositório local, que está localizado no agente de construção.

Depois, do agente de construção para o servidor em repositórios RPM é enviado um pedido JSON especificando o nome da versão e da construção. O Webhook, do qual falei anteriormente, baixa esse pacote do repositório local no agente de construção e torna uma nova construção disponível para instalação.

.NET Core no Linux, DevOps no comando

Por que essa lógica de entrega do pacote ao repositório RPM? Por que não enviar o pacote compilado diretamente para o repositório? A questão é que esta é uma condição para garantir a segurança. Esse cenário limita a possibilidade de upload não autorizado de pacotes RPM por pessoas não autorizadas para o servidor, que está acessível a todas as máquinas Linux.

Versionamento do BD

Na reunião de desenvolvimento, ficou claro que a equipe preferia o MS SQL, mas na maioria dos projetos não-Windows já estávamos usando PostgreSQL. Como já decidimos abrir mão de tudo que é pago, começamos a usar PostgreSQL aqui também.

.NET Core no Linux, DevOps no comando

Nesta parte, quero falar sobre como realizamos o versionamento do banco de dados e como escolhemos entre Flyway e Entity Framework Core. Vamos analisar os prós e contras deles.

Desvantagens

O Flyway funciona apenas em uma direção, nós não podemos reverter alterações — esse é um ponto negativo significativo. Comparar com o Entity Framework Core pode ser feito por outros critérios — do ponto de vista da conveniência para o desenvolvedor. Você se lembra que isso foi o que priorizamos, e o critério principal foi não alterar nada para o desenvolvimento no Windows.

Para o Flyway, nós precisávamos de algum wrapper, para que a equipe não precisasse escrever consultas SQL. É muito mais familiar para eles trabalhar em termos de OOP. Escrevemos instruções para trabalhar com os objetos do banco de dados, uma consulta SQL foi formada e executada. A nova versão do banco de dados está pronta, testada — tudo bem, tudo funcionando.

O Entity Framework Core tem um ponto fraco — sob alta carga, ele gera consultas SQL não otimizadas, e a queda no banco de dados pode ser significativa. No entanto, como nosso serviço não é de alta carga, não medimos a carga em centenas de RPS, aceitamos esses riscos e delegamos o problema para o nosso futuro.

Prós

Entity Framework Core funciona pronto para uso e é conveniente para desenvolvimento, enquanto o Flyway integra-se facilmente ao CI existente. Mas estamos tornando tudo mais conveniente para os desenvolvedores :)

O processo de aplicação

O Puppet verifica as atualizações de versão dos pacotes, entre os quais está o responsável pela migração. Primeiro, instala o pacote que contém os scripts de migração e a funcionalidade relacionada ao banco de dados. Após isso, o aplicativo que trabalha com o banco de dados é reiniciado. Depois vem a instalação dos componentes restantes. A ordem de instalação dos pacotes e o início dos aplicativos estão descritos no manifesto do Puppet.

Os aplicativos utilizam dados sensíveis, como tokens, senhas para o banco de dados, tudo isso é puxado na configuração do Puppet master, onde são armazenados de forma criptografada.

Problemas no TFS

Depois que confirmamos que tudo realmente funcionava, decidi investigar como estavam as compilações no TFS de forma geral para o departamento de desenvolvimento em Windows para outros projetos — se estávamos compilando/lançando rapidamente ou não, e encontrei problemas significativos com a velocidade.

Um dos principais projetos está levando de 12 a 15 minutos para compilar — isso é muito, não podemos viver assim. Uma análise rápida mostrou uma queda extremamente ruim em I/O, e isso em arrays.

Analisando por componentes, identifiquei três focos. O primeiro — «Kaspersky antivirus», que escaneia o código-fonte em todos os agentes de Build do Windows. O segundo — Windows Indexer. Ele não foi desativado e tudo foi indexado em tempo real nos agentes de Build durante o processo de deployment.

O terceiro — Npm install. Descobrimos que na maioria dos Pipelines nós usávamos exatamente esse script. O que há de errado com isso? O procedimento Npm install é executado durante a formação da árvore de dependências em package-lock.json, onde as versões dos pacotes que serão usadas para a construção do projeto são registradas. O problema é que o Npm install puxa a versão mais recente dos pacotes da internet a cada vez, o que consome um tempo considerável em um projeto grande.

Os desenvolvedores às vezes experimentam em suas máquinas locais para verificar o funcionamento de uma parte específica ou do projeto como um todo. Às vezes, tudo funcionava perfeitamente localmente, mas ao construir e implantar — nada funcionava. Começamos a investigar qual era o problema — ah, diferentes versões de pacotes e suas dependências.

Solução

  • Códigos-fonte em exceções AV.
  • Desativação da indexação.
  • Mudança para npm ci.

As vantagens do npm ci são que nós construímos a árvore de dependências uma única vez, e obtemos a chance de fornecer ao desenvolvedor uma lista atualizada de pacotes, com a qual ele pode experimentar à vontade localmente. Isso economiza tempo dos desenvolvedores que escrevem código.

Configuração

Agora, um pouco sobre a configuração do repositório. Historicamente, usamos Nexus para gerenciar repositórios, incluindo Internal REPO. Nesse repositório interno, todos os componentes que usamos para finalidades internas são fornecidos, como monitoramentos feitos em casa.

.NET Core no Linux, DevOps no comando

Nós também usamos NuGet, pois ele faz um cache melhor em comparação com outros gerenciadores de pacotes.

Resultado

Após otimizarmos os agentes de Build, o tempo médio de construção foi reduzido de 12 minutos para 7.

Se contarmos todas as máquinas que poderíamos ter usado para Windows, mas que migramos para Linux neste projeto, economizamos cerca de $10.000. E isso só em licenças, e se levarmos em conta os custos de manutenção — é mais.

Planos

Para o próximo trimestre, planejamos trabalhar na otimização da entrega de código.

Mudança para a pré-build da imagem Docker. O TFS é uma ferramenta incrível com muitos plugins que permitem integrar ao Pipeline, incluindo a construção por um trigger, por exemplo, de uma imagem Docker. Este trigger que queremos implementar é exatamente esse package-lock.json. Se a composição dos componentes utilizados para construir o projeto mudar de alguma forma, um novo Docker image será criado. Este, posteriormente, é usado para a implantação de um contêiner com o aplicativo construído. No momento, isso não acontece, mas planejamos migrar para uma arquitetura de microserviços no Kubernetes, que está se desenvolvendo ativamente em nossa empresa e há muito suporta soluções de produção.

Resumo

Eu incentivo todos a abandonarem o Windows, mas não porque eu não saiba como prepará-lo. A razão é que a maior parte das soluções Opensource é pilha Linux. Você economizará recursos.Na minha opinião, o futuro está em soluções Open Source no Linux com uma comunidade forte.

Perfil do palestrante Alexandre Sinchino no GitHub.

DevOps Conf é uma conferência sobre integração de processos de desenvolvimento, teste e operações para profissionais por profissionais. É por isso que o projeto sobre o qual Alexandre falou foi implementado e está em funcionamento, e no dia da sua apresentação, duas liberações bem-sucedidas foram realizadas. No DevOps Conf em RIT++ nos dias 27 e 28 de maio haverá ainda mais casos como esses apresentados por praticantes. Você ainda pode embarcar no último trem e submeter uma proposta ou com calma reservar um ingresso. Nos encontramos em Skolkovo!

Fonte: habr.com

Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS 🔥 Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS | ProHoster