Empacotando aplicativos principais do ASP.NET usando o Docker

Os aplicativos ASP.NET Core são verdadeiramente multiplataforma e podem ser executados em nixes e, consequentemente, no Docker. Vamos ver como eles podem ser empacotados para serem implantados no Linux e usados ​​em conjunto com o Nginx. Detalhes sob o corte!



Nota: continuamos a série de publicações de versões completas de artigos da revista Hacker. Ortografia e pontuação do autor salvas.


Sobre o Docker


Quase todo mundo já ouviu falar sobre arquitetura de microsserviços. O conceito de dividir o aplicativo em partes não significa dizer que é novo. Mas, o novo é o velho esquecido e reciclado.


Se você tentar falar sobre arquitetura em poucas palavras, o aplicativo Web será dividido em partes unitárias separadas - serviços. Os serviços não interagem diretamente entre si e não possuem bancos de dados comuns. Isso é feito para poder alterar cada serviço sem consequências para os outros. Os serviços são empacotados em contêineres. Entre os contêineres, Docker domina a bola.


Para descrever o que o Docker é frequentemente simplificado, use o termo "máquina virtual". Definitivamente, há uma semelhança, mas é errado dizer isso. A maneira mais fácil de entender essa diferença é observando as seguintes imagens da documentação oficial da janela de encaixe:




Os contêineres usam o núcleo do sistema operacional atual e o dividem entre si. Enquanto as máquinas virtuais que usam o hipervisor usam recursos de hardware.
Imagem do Docker é um objeto somente leitura que essencialmente armazena um modelo para a construção de um contêiner. Um contêiner é um ambiente no qual o código é executado. As imagens são armazenadas em repositórios. Por exemplo, o repositório oficial do Docker Hub permite armazenar apenas uma imagem em particular. No entanto, é gratuito, portanto, mesmo para isso, você precisa agradecer a eles.


INFO


O Docker não é o único representante da conteinerização. Além disso, existem outras tecnologias. Por exemplo:


rkt (pronuncia-se 'rocket') por CoreOS


LXD (pronunciado 'lexdi') pelo Ubuntu


Windows Containers - você nunca vai adivinhar ninguém.


Agora que nos familiarizamos com a teoria, passemos à prática.


Não faz sentido desmontar a instalação do docker, porque ele pode ser instalado em muitos sistemas operacionais. Apenas indicarei que você pode baixá-lo para sua plataforma na Docker Store . Se você estiver instalando o Docker no Windows, a virtualização deverá estar ativada no BIOS e no SO. Você pode ler sobre como habilitá-lo em 10-ke no seguinte artigo: Instalando o Hyper-V no Windows10


Criando um projeto ativado para janela de encaixe


O Docker é, obviamente, um produto Linux, mas se necessário, você pode usá-lo no desenvolvimento para Mac ou Windows. Ao criar um projeto no Visual Studio, para adicionar suporte ao docker, marque a caixa de seleção Ativar suporte ao docker.


O suporte do Docker pode ser adicionado a um projeto existente. É adicionado ao projeto da mesma maneira que vários novos componentes são adicionados. Menu de contexto Add - Docker Support.


Se a janela de encaixe estiver instalada e em execução na sua máquina, o console será aberto automaticamente e o comando será executado


docker pull microsoft/aspnetcore:2.0 

que inicia o processo de download da imagem. Esta imagem é realmente um espaço em branco com base no qual sua imagem será criada. O ASP.NET Core 2.1 usa uma imagem diferente - microsoft / dotnet: sdk


Os seguintes arquivos serão criados automaticamente no diretório com a solução para você:
.dockerignore (excluindo arquivos e diretórios da imagem do docker), docker-compose.yml (usando esse arquivo, você pode configurar a execução de vários serviços), docker-compose.override.yml (configuração auxiliar docker-compose), docker-compose.dcproj ( arquivo de projeto para o Visual Studio).


Um arquivo Dockerfile será criado no diretório do projeto. Na verdade, com a ajuda deste arquivo, criamos nossa imagem. Por padrão (no caso de o projeto ser chamado DockerServiceDemo), pode ser algo como isto:


 FROM microsoft/aspnetcore:2.0 AS base WORKDIR /app EXPOSE 80 FROM microsoft/aspnetcore-build:2.0 AS build WORKDIR /src COPY DockerServiceDemo/DockerServiceDemo.csproj DockerServiceDemo/ RUN dotnet restore DockerServiceDemo/DockerServiceDemo.csproj COPY . . WORKDIR /src/DockerServiceDemo RUN dotnet build DockerServiceDemo.csproj -c Release -o /app FROM build AS publish RUN dotnet publish DockerServiceDemo.csproj -c Release -o /app FROM base AS final WORKDIR /app COPY --from=publish /app . ENTRYPOINT ["dotnet", "DockerServiceDemo.dll"] 

A configuração inicial do .NET Core 2.0 não permitirá que você crie imediatamente a imagem usando o comando docker build. Ele está configurado para iniciar o arquivo docker-compose a partir de um diretório um nível acima. Para que a construção prossiga com sucesso, o Dockerfile pode ser trazido para uma aparência semelhante:


 FROM microsoft/aspnetcore:2.0 AS base WORKDIR /app EXPOSE 80 FROM microsoft/aspnetcore-build:2.0 AS build WORKDIR /src COPY DockerServiceDemo.csproj DockerServiceDemo.csproj RUN dotnet restore DockerServiceDemo.csproj COPY . . WORKDIR /src RUN dotnet build DockerServiceDemo.csproj -c Release -o /app FROM build AS publish RUN dotnet publish DockerServiceDemo.csproj -c Release -o /app FROM base AS final WORKDIR /app COPY --from=publish /app . ENTRYPOINT ["dotnet", "DockerServiceDemo.dll"] 

Tudo o que fiz foi remover o diretório DockerServiceDemo extra.


Se você usar o Código do Visual Studio, precisará gerar os arquivos manualmente. Embora o VS Code tenha funcionalidade auxiliar na forma de uma extensão do Docker , adicionarei um link ao manual sobre como trabalhar com o docker do VS Code - Trabalhando com o Docker . Sim, o artigo está em inglês, mas é com fotos


Docker de três acordes


Para o trabalho diário com a janela de encaixe, apenas alguns comandos são suficientes para lembrar.


A equipe mais importante é, obviamente, a construção de uma imagem. Para fazer isso, você precisa usar o bash / CMD / PowerShell para ir para o diretório em que o Dockerfile está localizado e executar o comando:


 docker build -t your_image_name . 

Aqui, após a opção -t, o nome da sua imagem é definido. Atenção - no final do comando, um espaço após o espaço Este ponto significa que o diretório atual está sendo usado. Uma imagem pode ser marcada com uma tag (número ou nome). Para fazer isso, coloque dois pontos após o nome e especifique uma tag. Se a tag não for especificada, por padrão, ela será definida com o nome mais recente. Para enviar uma imagem para o repositório, é necessário que o nome da imagem inclua o nome do repositório. Algo assim:


 docker build -t docker_account_name/image_name:your_tag . 

Aqui your_docker_account_name é o nome da sua conta do hub do docker.


Se você criou a imagem apenas com um nome local que não inclui o repositório, poderá marcar a imagem com um nome diferente após a construção usando o seguinte comando:


 docker tag image_name docker_account_name/image_name:your_tag 

Para enviar alterações ao hub, agora você precisa executar o seguinte comando:


 docker push docker_account_name/image_name:your_tag 

Antes disso, você precisa fazer login na sua conta do Docker. No Windows, isso é feito a partir da interface do usuário do aplicativo, mas no * nix, isso é feito pelo comando:


 docker login 

De fato, três equipes não são suficientes. Você também deve poder verificar a operação do contêiner. O comando com o qual você pode iniciar o contêiner é semelhante a este:


 docker run -it -p 5000:80 image_name 

A opção -it criará um pseudo-TTY e seu contêiner responderá às solicitações. Depois de executar o comando, o serviço estará disponível em http: // localhost: 5000 /


-p 5000: 80 associa a porta 5000 do contêiner à porta 80 do host.


Além disso, existem esses comandos:


 docker ps –a 

Mostre uma lista de contêineres. Como a opção -a foi adicionada, todos os contêineres serão exibidos, não apenas aqueles que estão em execução no momento.


 docker rm container_name 

Este comando removerá o contêiner denominado container_name. rm - abreviação de remover


 docker logs container_name 

Exibir logs de contêiner


 docker rmi image_name 

Exclui uma imagem chamada image_name


Ativando um Contêiner Através de um Servidor Proxy Reverso


O fato é que os aplicativos .NET Core usam o servidor da web Kestrel. Este servidor não é recomendado para produção. Porque Existem várias explicações.
Se houver vários aplicativos que compartilhem IP e porta, o Kestrel não poderá distribuir o tráfego. Além disso, o servidor proxy reverso fornece uma camada adicional de segurança, simplifica o balanceamento de carga e as configurações SSL e também se integra melhor à infraestrutura existente. Para a maioria dos desenvolvedores, o motivo mais importante para a necessidade de proxies reversos é a segurança adicional.


Primeiro, restaure a configuração original do Dockerfile. Depois disso, lidaremos com o arquivo docker-compose.yml e tentaremos executar nosso serviço sozinho. O formato do arquivo yml é lido como "yaml" e é uma abreviação de "Yet Another Markup Language" ou "YAML Ain't Markup Language". Outra linguagem de marcação ou nenhuma linguagem de marcação. De alguma forma, nem tudo está certo.


Meu arquivo docker-compose padrão é parecido com este:


 version: '3.4' services: dockerservicedemo: image: ${DOCKER_REGISTRY}dockerservicedemo build: context: . dockerfile: DockerServiceDemo/Dockerfile 

O arquivo docker-compose.override.yml adiciona várias configurações à configuração:
versão: '3.4'


 services: dockerservicedemo: environment: - ASPNETCORE_ENVIRONMENT=Development ports: - "80" 

Podemos criar a solução criada usando o docker-compose build, chamando o comando docker-compose up, lançaremos nosso contêiner. Tudo funciona? Então vá para o próximo passo. Crie o arquivo nginx.info. A configuração será aproximadamente da seguinte maneira:


 worker_processes 4; events { worker_connections 1024; } http { sendfile on; upstream app_servers { server dockerservicedemo:80; } server { listen 80; location / { proxy_pass http://app_servers; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection keep-alive; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } } 

Aqui, indicamos que o nginx escutará na porta 80 (escute 80;). E as solicitações recebidas serão redirecionadas para a 80ª porta do host no contêiner dockerservicedemo. Além disso, dizemos ao nginx quais cabeçalhos transmitir.


Podemos usar http no nginx e acessar o site por https. Quando uma solicitação https passa por um proxy http, muitas informações de https não são transmitidas para http. Além disso, ao usar um proxy, o endereço IP externo é perdido. Para que essas informações sejam transmitidas nos cabeçalhos, você precisa alterar o código do nosso projeto ASP.NET e adicionar o seguinte código ao início do método Configure do arquivo Startup.cs:


  app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto }); 

A maioria dos servidores proxy usa os cabeçalhos X-Forwarded-For e X-Forwarded-Proto. São esses cabeçalhos que estão especificados agora na configuração nginx.


Agora inclua a imagem nginx e o arquivo nginx.conf na configuração doker-compose. Cuidado nos espaços YAML é importante:


 version: '3.4' services: dockerservicedemo: image: ${DOCKER_REGISTRY}dockerservicedemo build: context: . dockerfile: DockerServiceDemo/Dockerfile ports: - 5000:80 proxy: image: nginx:latest volumes: - ./DockerServiceDemo/nginx.conf:/etc/nginx/nginx.conf ports: - 80:80 

Aqui, adicionamos proxies à nossa configuração como uma imagem nginx. Anexamos a esta imagem um arquivo de configurações externas. Nós meio que o montamos no sistema de arquivos do contêiner usando um mecanismo chamado volume. Se você adicionar no final: ro, o objeto será montado como somente leitura.


O proxy escuta a 80a porta externa da máquina na qual o contêiner está em execução e envia uma solicitação para a 80a porta interna do contêiner.


Ao executar o comando doker-compose up, preencheremos, ou seja, extrairemos a imagem nginx do repositório e iniciaremos nosso contêiner junto com o contêiner proxy. Agora em http: // localhost: 80 / ele estará acessível via nginx. Na porta 5000, o aplicativo "gira" também no Kestrel.


Podemos verificar se a solicitação para o aplicativo da Web passa pelo proxy reverso. Abra as ferramentas de desenvolvedor no navegador Chrome e vá para a guia Rede. Clique no host local aqui e selecione a guia Cabeçalhos.



Lançamos o contêiner por meio de proxies e HTTPS


O ASP.NET Core 2.1 trouxe melhorias no suporte a HTTPS.
Digamos que o seguinte middleware permita redirecionar de uma conexão não segura para uma segura:


 app.UseHttpsRedirection(); 

E o próximo permite que você use o HSTS (Strict Transport Security Protocol) HTTP.


 app.UseHsts(); 

O HSTS é um recurso do protocolo HTTP / 2, cuja especificação foi lançada em 2015. Essa funcionalidade é suportada por navegadores modernos e informa que o site usa apenas https. Portanto, a proteção contra um ataque de downgrade ocorre durante o qual o invasor pode tirar proveito da situação usando a transição para um protocolo http inseguro. Por exemplo, faça o downgrade do TLS ou até substitua um certificado.


Normalmente, esse tipo de ataque é usado em conjunto com ataques man-in-the-middle. Você deve saber e lembrar que o HSTS não o salva de uma situação em que um usuário visita o site usando o protocolo http e depois o redireciona para https. Existe a chamada lista de pré - carregamento do Chrome , que contém links para sites compatíveis com https. Outros navegadores (Firefox, Opera, Safari, Edge) também suportam listas de sites https criados com base na lista do Chrome. Mas todas essas listas estão longe de todos os sites.


Na primeira vez em que você executa qualquer aplicativo Core no Windows, você receberá uma mensagem informando que um certificado de desenvolvedor foi criado e instalado. Ao clicar no botão e instalar o certificado, você o tornará confiável. Na linha de comando no macOS, você pode adicionar confiança ao certificado usando o comando:
dotnet dev-certs https –trust


Se o utilitário dev-certs não estiver instalado, você poderá instalá-lo com o comando:


 dotnet tool install --global dotnet-dev-certs 

Como adicionar um certificado como confiável no Linux depende da distribuição.
Para fins de teste, usamos o certificado do desenvolvedor. As ações com um certificado assinado pela CA são semelhantes. Se desejar, você pode usar certificados LetsEncrypt gratuitos


Você pode exportar o certificado de desenvolvedor para um arquivo usando o comando


 dotnet dev-certs https -ep ___.pfx 

O arquivo deve ser copiado para o diretório% APPDATA% / ASP.NET / Https / no Windows ou para /root/.aspnet/https/ no macOS / Linux.


Para que o contêiner escolha o caminho do certificado e sua senha, crie segredos do usuário com o seguinte conteúdo:


 { "Kestrel":{ "Certificates":{ "Default":{ "Path": "/root/.aspnet/https/__.pfx", "Password": "___" } } } } 

Este arquivo armazena dados não criptografados e, portanto, é usado apenas durante o desenvolvimento. Um arquivo é criado no Visual Studio chamando o menu de contexto no ícone do projeto ou usando o utilitário segredos do usuário no Linux.


No Windows, o arquivo será salvo no diretório% APPDATA% \ Microsoft \ UserSecrets \ <user_secrets_id> \ secrets.json e no macOS e Linux será salvo no ~ / .microsoft / usersecrets / <user_secrets_id> /secrets.json


Para salvar as configurações de produção, algumas distribuições do Linux podem usar systemd. As configurações são salvas no atributo Serviço. Por exemplo, assim:


 [Service] Environment="Kestrel _ Certificates _ Default _Path=/root/.aspnet/https/__.pfx" Environment="Kestrel _ Certificates _ Default _Password=___" 

A seguir, fornecerei e analisarei imediatamente a versão funcional da configuração do docker para o proxy e o contêiner via https.


Arquivo de composição do Docker:


 version: '3.4' services: dockerservicedemo21: image: ${DOCKER_REGISTRY}dockerservicedemo build: context: . dockerfile: DockerServiceDemo/Dockerfile  override: version: '3.4' services: dockerservicedemo: environment: - ASPNETCORE_ENVIRONMENT=Development - ASPNETCORE_URLS=https://+:44392;http://+:80 - ASPNETCORE_HTTPS_PORT=44392 ports: - "59404:80" - "44392:44392" volumes: - ${APPDATA}/ASP.NET/Https:/root/.aspnet/https:ro - ${APPDATA}/Microsoft/UserSecrets:/root/.microsoft/usersecrets:ro proxy: image: nginx:latest volumes: - ./DockerServiceDemo/nginx.conf:/etc/nginx/nginx.conf - ./DockerServiceDemo/cert.crt:/etc/nginx/cert.crt - ./DockerServiceDemo/cert.rsa:/etc/nginx/cert.rsa ports: - "5001:44392" 

Agora vou descrever momentos incompreensíveis. ASPNETCORE_URLS nos permite não indicar no código do aplicativo usando app.UseUrl a porta em que o aplicativo está ouvindo.


ASPNETCORE_HTTPS_PORT faz um redirecionamento semelhante ao que o código a seguir faria:
services.AddHttpsRedirection (opções => opções.HttpsPort = 44392)


Ou seja, o tráfego de solicitações http será redirecionado para uma porta específica de solicitações https.
Usando portas, é indicado que a solicitação da porta externa 59404 será redirecionada para o 80º contêiner e da 44392ª porta externa para a 44392ª. Teoricamente, uma vez que configuramos um servidor proxy reverso, podemos remover portas com esses redirecionamentos.
Usando volumes, um diretório com um certificado pfx e um aplicativo UserSecrets são montados com uma senha e um link para o certificado.


A seção proxy indica que as solicitações da 5001ª porta externa serão redirecionadas para a 44392ª porta nginx. Além disso, é montado um arquivo de configuração nginx, além de um certificado e uma chave de certificado.


Para que eles criem um único certificado pfx (que já possuímos) para criar arquivos crt e rsa, você pode usar o OpenSSL. Primeiro você precisa extrair o certificado:


 openssl pkcs12 -in ./_.pfx -clcerts -nokeys -out domain.crt 

E então a chave privada:


 openssl pkcs12 -in ./_.pfx -nocerts -nodes -out domain.rsa 

A configuração do nginx é a seguinte:


 worker_processes 4; events { worker_connections 1024; } http { sendfile on; upstream app_servers { server dockerservicedemo:44392; } server { listen 44392 ssl; ssl_certificate /etc/nginx/cert.crt; ssl_certificate_key /etc/nginx/cert.rsa; location / { proxy_pass https://app_servers; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection keep-alive; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } } 

O servidor proxy está escutando na porta 44392. Essa porta recebe solicitações da 5001ª porta do host. Em seguida, o proxy redireciona solicitações para a porta 44392 do contêiner dockerdemoservice.


Depois de entender esses exemplos, você terá uma boa base para trabalhar com docker, microsserviços e nginx.


Lembramos que esta é a versão completa de um artigo da revista Hacker . Seu autor é Alexey Sommer .

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


All Articles