Criando um aplicativo no .NET Core e Kubernetes: Nossa experiência

Olá pessoal!

Hoje falaremos sobre a experiência de um de nossos projetos de DevOps. Decidimos implementar um novo aplicativo para Linux usando o .Net Core em uma arquitetura de microsserviço.

Esperamos que o projeto esteja se desenvolvendo ativamente e que haja mais e mais usuários. Portanto, deve ser facilmente escalável, tanto em termos de funcionalidade quanto de desempenho.

Precisamos de um sistema tolerante a falhas - se um dos blocos de funcionalidade não funcionar, o restante deverá funcionar. Também queremos garantir a integração contínua, incluindo a implantação da solução nos servidores do cliente.

Portanto, usamos as seguintes tecnologias:

  • .Net Core para implementação de microsserviços. Nosso projeto usou a versão 2.0,
  • Kubernetes para orquestração de microsserviços,
  • Docker para criar imagens de microsserviço,
  • barramento de integração Rabbit MQ e Mass Transit,
  • Elasticsearch e Kibana para registro,
  • TFS para implementar o pipeline de CI / CD.

Este artigo compartilhará os detalhes de nossa solução.



Esta é uma transcrição do nosso discurso na reunião do .NET. Aqui está um link para o vídeo do discurso.

Nosso desafio comercial


Nosso cliente é uma empresa federal onde existem comerciantes - essas pessoas são responsáveis ​​por como as mercadorias são apresentadas nas lojas. E há supervisores - esses são os líderes dos comerciantes.

A empresa possui um processo de treinamento e avaliação do trabalho dos comerciantes pelos supervisores, que precisavam ser automatizados.



Veja como nossa solução funciona:

1. O supervisor elabora um questionário - esta é uma lista de verificação do que você precisa verificar no trabalho do comerciante.
2. Em seguida, o supervisor seleciona o funcionário cujo trabalho será verificado. A data do questionamento é atribuída.
3. Em seguida, a atividade é enviada para o dispositivo móvel do supervisor.
4. Em seguida, o questionário é preenchido e enviado ao portal.
5. O portal gera resultados e vários relatórios.

Os microsserviços nos ajudarão a resolver três problemas:


1. No futuro, queremos expandir facilmente a funcionalidade, pois existem muitos processos de negócios semelhantes na empresa.
2. Queremos que a solução seja tolerante a falhas. Se alguma parte deixar de funcionar, a solução poderá restaurar seu trabalho por conta própria, e a falha de uma parte não afetará muito a operação da solução como um todo.
3. A empresa para a qual estamos implementando a solução possui muitas filiais. Consequentemente, o número de usuários da solução está em constante crescimento. Portanto, eu queria que isso não afetasse o desempenho.

Como resultado, decidimos usar microsserviços neste projeto, o que exigiu várias decisões não triviais.

Quais tecnologias ajudaram a implementar esta solução:


O Docker simplifica a distribuição da distribuição da solução. A distribuição no nosso caso é um conjunto de imagens de microsserviço
• Como existem muitos microsserviços em nossa solução, precisamos gerenciá-los. Para isso, usamos o Kubernetes.
• Implementamos microsserviços usando o .Net Core.
• Para atualizar rapidamente a solução no cliente, precisamos implementar uma integração e entrega contínua e conveniente.

Aqui está todo o nosso conjunto de tecnologias:

• .Net Core que usamos para criar microsserviços,
• O microsserviço é empacotado em uma imagem do Docker,
• A integração contínua e a entrega contínua são implementadas usando o TFS,
• O front end é implementado em Angular,
• Para monitoramento e registro, usamos Elasticsearch e Kibana,
• RabbitMQ e MassTransit são usados ​​como barramento de integração.

Soluções .NET Core para Linux


Todos sabemos o que é o clássico .Net Framework. A principal desvantagem da plataforma é que ela não é multiplataforma. Portanto, não podemos executar soluções no .Net Framework para Linux no Docker.

Para fornecer a capacidade de usar C # no Docker, a Microsoft repensou o .Net Framework e criou o .Net Core. E para usar as mesmas bibliotecas, a Microsoft criou a especificação .Net Standard Library. Os assemblies da .Net Standart Library podem ser usados ​​no .Net Framework e no .Net Core.



Kubernetes - para orquestração de microsserviços


O Kubernetes é usado para gerenciar e agrupar contêineres do Docker. Aqui estão as principais vantagens do Kubernetes das quais aproveitamos:

- fornece a capacidade de configurar facilmente o ambiente dos microsserviços,
- simplifica a gestão ambiental (Dev, QA, Stage),
- Pronto para uso, replica os microsserviços e balança de carga nas réplicas.



Arquitetura da solução


No início do trabalho, nos perguntamos como dividir a funcionalidade em microsserviços. A divisão foi feita com base no princípio de uma única responsabilidade, apenas em um nível superior. Sua principal tarefa é fazer alterações em um serviço o menos possível, afetar outros microsserviços. Como resultado, no nosso caso, os microsserviços começaram a executar uma área separada de funcionalidade.

Como resultado, apresentamos serviços envolvidos no planejamento de questionários, um microsserviço para exibição de resultados, um microsserviço para trabalhar com um aplicativo móvel e outros microsserviços.



Opções para interagir com clientes externos


Microsoft em seu livro sobre microsserviços, “ Microservices .NET. .NET Container Application Architecture ”oferece três possíveis implementações de interação com microsserviços. Analisamos os três e escolhemos o mais adequado.

• serviço de gateway de API
A API do serviço Gateway é uma implementação de fachada para solicitações do usuário por outros serviços. O problema com a solução é que, se a fachada não funcionar, a solução inteira deixará de funcionar. Eles decidiram abandonar essa abordagem por tolerância a falhas.

• Gateway de API com Gerenciamento de API do Azure
A Microsoft fornece a capacidade de usar uma fachada de nuvem no Azure. Mas essa solução não se encaixava, porque estávamos implantando a solução não na nuvem, mas nos servidores do cliente.

• Comunicação direta entre cliente e microsserviço
Como resultado, temos a última opção - interação direta do usuário com microsserviços. Nós o escolhemos.



É mais uma tolerância a falhas. A desvantagem é que parte da funcionalidade terá que ser reproduzida em cada serviço separadamente. Por exemplo, era necessário configurar a autorização separadamente em cada microsserviço ao qual os usuários têm acesso.

Obviamente, surge a questão de como equilibraremos a carga e como a tolerância a falhas é implementada. Tudo é simples aqui - o Ingress Controller Kubernetes faz isso.



O nó 1, o nó 2 e o nó 3 são réplicas do mesmo microsserviço. Se uma das réplicas falhar, o balanceador de carga redirecionará automaticamente a carga para outros microsserviços.

Arquitetura física


Veja como organizamos nossa infraestrutura de soluções:

• Cada microsserviço possui seu próprio banco de dados (se ele, é claro, precisar dele), outros serviços não acessam o banco de dados de outro microsserviço.
• Os microsserviços se comunicam apenas através do barramento RabbitMQ + Mass Transit, bem como usando solicitações HTTP.
• Cada serviço tem sua própria responsabilidade claramente definida.
• Para o registro, usamos o Elasticsearch e o Kibana e a biblioteca para trabalhar com o Serilog .



O serviço de banco de dados foi implantado em uma máquina virtual separada e não no Kubernetes, porque o Microsoft DBMS não recomenda o uso do Docker nos ambientes do produto.

O serviço de log também foi implantado em uma máquina virtual separada por motivos de tolerância a falhas - se tivermos problemas com o Kubernetes, poderemos descobrir qual é o problema.

Implantação: como organizamos os ambientes de desenvolvimento e produto


Nossa infraestrutura possui três namespaces no Kubernetes. Todos os três ambientes acessam um serviço de banco de dados e um serviço de log. E, é claro, cada ambiente analisa seu próprio banco de dados.



Na infraestrutura do cliente, também temos dois ambientes - pré-produção e produção. Na produção, temos servidores de banco de dados separados para pré-venda e ambiente do produto. Para o registro, alocamos um servidor ELK em nossa infraestrutura e na infraestrutura do cliente.

Como implantar 5 ambientes com 10 microsserviços cada?


Em média, temos 10 serviços por projeto e três ambientes: QA, DEV, Stage, nos quais cerca de 30 microsserviços são implantados no total. E isso é apenas na infraestrutura de desenvolvimento! Adicione mais 2 ambientes à infraestrutura do cliente e obteremos 50 microsserviços.



É claro que esse número de serviços deve, de alguma forma, ser gerenciado. Kubernetes nos ajuda com isso.

Para implantar um microsserviço, você deve
• Expandir segredo,
• Implantar implantação,
• Expandir serviço.

Sobre o segredo, escreva abaixo.
A implantação é uma instrução para o Kubernetes, com base na qual ele lançará o contêiner Docker do nosso microsserviço. Aqui está o comando no qual a implantação é implantada:

kubectl apply -f .\(yaml deployment-) --namespace=DEV

 apiVersion: apps/v1beta1 kind: Deployment metadata: name: imtob-etr-it-dictionary-api spec: replicas: 1 template: metadata: labels: name: imtob-etr-it-dictionary-api spec: containers: - name: imtob-etr-it-dictionary-api image: nexus3.company.ru:18085/etr-it-dictionary-api:18289 resources: requests: memory: "256Mi" limits: memory: "512Mi" volumeMounts: - name: secrets mountPath: /app/secrets readOnly: true volumes: - name: secrets secret: secretName: secret-appsettings-dictionary 


Este arquivo descreve como a implantação é chamada (imtob-etr-it-dictionary-api), qual imagem ele precisa usar para execução, além de outras configurações. Na seção secreta, personalizaremos nosso ambiente.

Após a implantação, precisamos implantar o serviço, se necessário.

Os serviços são necessários quando é necessário o acesso externo ao microsserviço. Por exemplo, quando você deseja que um usuário ou outro microsserviço possa fazer uma solicitação Get para outro microsserviço.

kubectl apply -f .\imtob-etr-it-dictionary-api.yml --namespace=DEV

 apiVersion: v1 kind: Service metadata: name: imtob-etr-it-dictionary-api-services spec: ports: - name: http port: 80 targetPort: 80 protocol: TCP selector: name: imtob-etr-it-dictionary-api 


Geralmente a descrição do serviço é pequena. Nele, vemos o nome do serviço, como ele pode ser acessado e o número da porta.

Como resultado, para implantar o ambiente, precisamos

• um conjunto de arquivos com segredos para todos os microsserviços,
• um conjunto de arquivos com a implantação de todos os microsserviços,
• um conjunto de arquivos com os serviços de todos os microsserviços.

Armazenamos todos esses scripts no repositório git.

Para implantar a solução, obtivemos um conjunto de três tipos de scripts:

• pasta com segredos - são configurações para cada ambiente,
• pasta com implantação para todos os microsserviços,
• pasta com serviços para alguns microsserviços,

em cada - cerca de dez equipes, uma para cada microsserviço. Por conveniência, criamos uma página com scripts no Confluence, que nos ajuda a implantar rapidamente um novo ambiente.

Aqui está um script de implantação de implantação (existem conjuntos semelhantes para segredo e serviço):

Script de implantação
O kubectl aplica -f. \ imtob-etr-it-image-api.yml --namespace = DEV
O kubectl aplica -f. \ imtob-etr-it-mobile-api.yml --namespace = DEV
O kubectl aplica -f. \ imtob-etr-it-planning-api.yml --namespace = DEV
O kubectl aplica -f. \ imtob-etr-it-result-api.yml --namespace = DEV
O kubectl aplica -f. \ imtob-etr-it-web.yml --namespace = DEV
O kubectl aplica -f. \ imtob-etr-it-report-api.yml --namespace = DEV
O kubectl aplica -f. \ imtob-etr-it-template-construtor-api.yml --namespace = DEV
O kubectl aplica -f. \ imtob-etr-it-dictionary-api.yml --namespace = DEV
O kubectl aplica -f. \ imtob-etr-it-integration-api.yml --namespace = DEV
O kubectl aplica -f. \ imtob-etr-it-identity-api.yml --namespace = DEV


Implementação de CI / CD



Cada serviço está em sua própria pasta, além de termos uma pasta com componentes comuns.



Há também uma definição de compilação e definição de versão para cada microsserviço. Configuramos o lançamento da Definição de Compilação ao confirmar o serviço apropriado ou ao confirmar a pasta apropriada. Se o conteúdo da pasta com componentes comuns for atualizado, todos os microsserviços serão implantados.

Quais são as vantagens de uma organização Build?

1. A solução está em um repositório git,
2. Ao mudar em vários microsserviços, a montagem começa em paralelo com agentes de montagem livres,
3. Cada definição de compilação apresenta um script simples para compilar a imagem e enviá-la para o Nexus Registry.

Definição de compilação e definição de versão


Como implantar um agente VSTS, descrevemos anteriormente neste artigo .



Primeiro vem a definição de compilação. No comando TFS VSTS, o agente inicia a construção do Dockerfile. Como resultado, obtemos a imagem de um microsserviço. Esta imagem é salva localmente no ambiente em que o agente VSTS está sendo executado.

Após a compilação, o Push é iniciado, que envia a imagem que recebemos na etapa anterior para o Registro do Nexus. Agora pode ser usado externamente. O Nexus Registry é um tipo de Nuget, não apenas para bibliotecas, mas para imagens do Docker e muito mais.

Depois que a imagem estiver pronta e acessível do lado de fora, você precisará implantá-la. Para isso, temos a definição de versão. Tudo é simples aqui - executamos o comando set image:

kubectl set image deployment/imtob-etr-it-dictionary-api imtob-etr-it-dictionary-api=nexus3.company.ru:18085/etr-it-dictionary-api:$(Build.BuildId)

Depois disso, ele atualizará a imagem para o microsserviço desejado e iniciará um novo contêiner. Como resultado, nosso serviço foi atualizado.

Vamos agora comparar a compilação com e sem o Dockerfile.



Sem o Dockerfile, temos várias etapas, que têm muitas especificidades .Net. À direita, vemos uma imagem do Docker compilada. Tudo se tornou muito mais fácil.

Todo o processo de criação da imagem é descrito no Dockerfile. Este assembly pode ser depurado localmente.



Total: obtivemos um CI / CD simples e transparente



1. Separação de desenvolvimento e implantação. A montagem é descrita no Dockerfile e fica sobre os ombros do desenvolvedor.
2. Ao configurar o CI / CD, você não precisa conhecer os detalhes e os recursos da montagem - o trabalho é feito apenas com o Dockerfile.
3. Atualizamos apenas os microsserviços alterados.

Em seguida, você precisa configurar o RabbitMQ no K8S: escrevemos um artigo separado sobre isso.

Configuração do ambiente


De uma forma ou de outra, precisamos configurar microsserviços. A parte principal do ambiente está configurada no arquivo de configuração raiz Appsettings.json. Este arquivo contém configurações independentes do ambiente.

Essas configurações que dependem do ambiente são armazenadas na pasta secrets no arquivo appsettings.secret.json. Adotamos a abordagem descrita no artigo Gerenciando as configurações principais do aplicativo ASP.NET no Kubernetes .

 var configuration = new ConfigurationBuilder() .AddJsonFile($"appsettings.json", true) .AddJsonFile("secrets/appsettings.secrets.json", optional: true) .Build(); 


O arquivo appsettings.secrets.json contém as configurações para os índices do Elastic Search e a cadeia de conexão do banco de dados.
 { "Serilog": { "WriteTo": [ { "Name": "Elasticsearch", "Args": { "nodeUris": "http://192.168.150.114:9200", "indexFormat": "dev.etr.it.ifield.api.dictionary-{0:yyyy.MM.dd}", "templateName": "dev.etr.it.ifield.api.dictionary", "typeName": "dev.etr.it.ifield.api.dictionary.event" } } ] }, "ConnectionStrings": { "DictionaryDbContext": "Server=192.168.154.162;Database=DEV.ETR.IT.iField.Dictionary;User Id=it_user;Password=PASSWORD;" } } 


Adicionar arquivo de configuração ao Kubernetes


Para adicionar esse arquivo, você precisa implantá-lo no contêiner do Docker. Isso é feito no arquivo de implantação do Kubernetis. A implantação descreve em qual pasta o arquivo secreto c deve ser criado e com qual segredo é necessário associá-lo.

 apiVersion: apps/v1beta1 kind: Deployment metadata: name: imtob-etr-it-dictionary-api spec: replicas: 1 template: metadata: labels: name: imtob-etr-it-dictionary-api spec: containers: - name: imtob-etr-it-dictionary-api image: nexus3.company.ru:18085/etr-it-dictionary-api:18289 resources: requests: memory: "256Mi" limits: memory: "512Mi" volumeMounts: - name: secrets mountPath: /app/secrets readOnly: true volumes: - name: secrets secret: secretName: secret-appsettings-dictionary 


Você pode criar um segredo no Kubernetes usando o utilitário kubectl. Vemos aqui o nome do segredo e o caminho para o arquivo. Também indicamos o nome do ambiente para o qual criamos um segredo.

kubectl create secret generic secret-appsettings-dictionary
--from-file=./Dictionary/appsettings.secrets.json --namespace=DEMO


Conclusões


Contras da abordagem escolhida


1. Limiar alto de entrada. Se você estiver realizando esse projeto pela primeira vez, haverá muitas informações novas.
2. Microsserviços → projeto mais complexo. É necessário aplicar muitas soluções não óbvias devido ao fato de não termos uma solução monolítica, mas uma microsserviços.
3. Nem tudo está implementado para o Docker. Nem tudo pode ser executado na arquitetura de microsserviço. Por exemplo, enquanto o SSRS não estiver na janela de encaixe.

Prós de uma abordagem auto-testada


1. Infraestrutura como um código
A descrição da infraestrutura é armazenada no controle de origem. No momento da implantação, você não precisa adaptar o ambiente.
2. Dimensionamento no nível da funcionalidade e no nível de desempenho imediatamente.
3. Os microsserviços são bem isolados
Praticamente não há partes críticas, cuja falha leva à inoperabilidade do sistema como um todo.
4. Entrega rápida de alterações
Somente os microsserviços nos quais houve atualizações são atualizados. Se você não levar em consideração o tempo de coordenação e outras coisas relacionadas ao fator humano, a atualização de um microsserviço ocorrerá em 2 minutos ou menos.

Conclusões para nós


1. No .NET Core, você pode e deve implementar soluções industriais.
2. O K8S realmente facilitou a vida, simplificou a atualização de ambientes, facilita a configuração dos serviços.
3. O TFS pode ser usado para implementar o CI / CD para Linux.

Source: https://habr.com/ru/post/pt420665/


All Articles