Marx J. Moura

18 de agosto de 2019

Hospedar ASP.NET Core no Linux com Nginx

.NET Core + Linux + Nginx

Este é um guia passo a passo de como configurar o Nginx no Ubuntu Server para executar uma única ou múltiplas ASP.NET Core APIs em um domínio (sem subdomínio).

Esse post é bem extenso então vamos dividí-lo em etapas para alcançar isso:

  1. Instalar o Nginx
  2. Configurar o firewall
  3. Configurar o Nginx
  4. Criar um server block para o domínio
  5. Configurar a ASP.NET Core API
    1. Configurar a ASP.NET Core API como um serviço
    2. Configurar uma única ASP.NET Core API no domínio
    3. Configurar múltiplas ASP.NET Core APIs em um domínio

1. Instalar o Nginx

Primeiro precisamos do Nginx instalado no servidor. Nós podemos usar o apt-get para instalar.

sudo apt-get install nginx

Depois da instalação o servidor já deve está executando. Para checar o status do Nginx execute o seguinte comando:

sudo systemctl status nginx

2. Configurar o firewall

Após instalar o Nginx pode ser preciso abrir as portas HTTP e HTTPS no firewall. Se você está usando o Amazon EC2 (assim como eu) apenas use o console da AWS (AWS Management Console) para fazer isso e você estará pronto para ir para o próximo tópico.

Vamos listar as configurações de aplicação que o ufw conhece:

sudo ufw app list

Existem três perfis disponíveis para o Nginx abrir as portas 80 (HTTP) e 443 (HTTPS):

Vamos executar o seguinte comando para habilitar o perfil Nginx Full.

sudo ufw allow 'Nginx Full'

Agora vamos executar o seguinte comando para verificar a alteração:

sudo ufw status

3. Configurar o Nginx

A configuração a seguir é a que eu estou usando e deve ser o suficiente para iniciar. Caso você esteja interessado no detalhes, a documentação do Nginx é um bom lugar para referência e também devdocs.io/nginx.

Crie o arquivo de configuração /etc/nginx/proxy.conf com o conteúdo:

proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
large_client_header_buffers 4 16k;

Modifique o arquivo /etc/nginx/nginx.conf e substitua com o seguinte conteúdo:

user www-data;
worker_processes auto;
pid /run/nginx.pid;

include /etc/nginx/modules-enabled/*.config;

events {
  worker_connections 1024;
}

http {
  sendfile on;
  tcp_nopush on;
  tcp_nodelay on;
  types_hash_max_size 2048;
  keepalive_timeout 30;
  server_tokens off;

  include /etc/nginx/mime.types;
  include /etc/nginx/conf.d/*.config;
  include /etc/nginx/sites-enabled/*;
  include /etc/nginx/proxy.config;

  default_type application/octet-stream;

  log_format custom '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent" '
                    '"$http_x_forwarded_for" $request_id ';

  access_log /var/log/nginx/access.log custom;
  error_log  /var/log/nginx/error.log;
}

Edite o server block padrão para tratar server_name desconhecido retornando o status code 444 (conexão fechada sem resposta — connection closed without response). Modifique o server block padrão do Nginx /etc/nginx/sites-available/default e substitua com o seguinte conteúdo:

server {
  listen 80 default_server;
  listen [::]:80 default_server;

  return 444;
}

Teste a configuração do Nginx para verificar se a sintaxe está correta:

sudo nginx -t

Se não houver erros, reinicie o Nginx.

sudo systemctl restart nginx

4. Crie um server block para o domínio

Primeiro vamos criar uma pasta para o domínio (ex.: example.com) dentro do diretório /var/www usando a flag -p para criar os diretórios pais se necessário:

sudo mkdir -p /var/www/example.com

Em seguida, para evitar qualquer problema de permissão, torne o usuário do Nginx www-data proprietário do diretório que acabamos de criar:

sudo chown -R www-data: /var/www/example.com

Permita o proprietário e grupos para escrever no diretório:

sudo chmod -R 775 /var/www/example.com

Adicione outro usuário para o grupo www-data caso precise (o usuário ubuntu da Amazon EC2, por exemplo):

sudo usermod -a -G www-data ubuntu

Agora vamos criar o server block (um arquivo de configuração) /etc/nginx/sites-available/example.com com o seguinte conteúdo:

server {
  listen 80;
  listen [::]:80;

  server_name example.com *.example.com;

  location / {
    return 200 "OK";
  }
}

Para habilitar o novo server block é necessário criar um link simbólico partindo do arquivo no diretório sites-available para o diretório sites-enabled, o qual é lido pelo Nginx quando é iniciado:

sudo ln -sfn /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/

Então teste a sintaxe da configuração e reinicie o Nginx:

sudo nginx -t
sudo systemctl restart nginx

Agora você deve obter o texto "OK" quando acessar http://example.com no seu navegador.

5. Configurar a ASP.NET Core API

Basicamente nós precisamos configurar a ASP.NET Core API como um serviço e configurar o Nginx como um proxy reverso. A documentação da Microsoft me ajudou muito.

Durante esse processo os seguintes arquivos serão criados ou editados:

5.1. Configurar a ASP.NET Core API como um serviço

Nginx não é configurado para gerenciar processos do Kestrel. Por isso, vamos utilizar o systemd para criar um arquivo de configuração do serviço para comandar (iniciar, parar, reiniciar) e monitorar a API.

Crie um arquivo de configuraração para o serviço:

sudo vim /etc/systemd/system/my-example-api.service

A seguir está um exemplo de arquivo de configuração do serviço para My Example API (se necessário, altere para atender o seu caso de uso):

[Unit]
Description=My Example API

[Service]
WorkingDirectory=/var/www/example-api/current
ExecStart=/usr/bin/dotnet /var/www/example-api/current/ExampleAPI.dll
Restart=always
RestartSec=10
KillSignal=SIGINT
SyslogIdentifier=my-example-api
User=www-data
Environment=ASPNETCORE_ENVIRONMENT=Production
Environment=ASPNETCORE_URLS=http://localhost:5000
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false
EnvironmentFile=/path/to/file.conf

[Install]
WantedBy=multi-user.target

Inicie o serviço e verifique que está executando.

sudo systemctl start my-example-api.service
sudo systemctl status my-example-api.service

Uma vez que a API que está sendo executada no Kestrel é gerenciada pelo systemd, todos os eventos e processos são logados para o journal (um componente do systemd). Para visualizar os registros do serviço do API my-example-api.service, use o seguinte comando:

sudo journalctl -fu my-example-api.service

Para filtrar por período e reduzir o número de registros retornados:

sudo journalctl -fu my-example-api.service --since "2019-01-01" --until "2019-01-20 06:00"

5.2. Configuar uma única ASPI.NET Core API no domínio

É necessário configurar o Nginx como um proxy reverso para encaminhar requisições feitas de http://example.com para a ASP.NET Core API executandp no Kestrel em http://localhost:5000. Modifique o server block /etc/nginx/sites-available/example.com e substitua o conteúdo com o seguinte:

server {
  listen 80;
  listen [::]:80;

  server_name example.com *.example.com;

  location / {
    proxy_pass http://localhost:5000;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection keep-alive;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_cache_bypass $http_upgrade;
    fastcgi_buffers 16 16k;
    fastcgi_buffer_size 32k;
  }
}

Então teste a sintaxe da configuração e reinicie o Nginx:

sudo nginx -t
sudo systemctl restart nginx

A API estará disponível em http://example.com.

5.3. Configurar múltiplas ASP.NET Core APIs em um domínio

Nós iremos configurar o Nginx como um proxy reverso para encaminhar as requisições de várias ASP.NET Core APIs usando a diretiva rewrite. Modifique /etc/nginx/sites-available/example.com e substitua o conteúdo com o seguinte:

server {
  listen 80;
  listen [::]:80;

  server_name example.com *.example.com;

  location /myapi1 {
    rewrite /myapi1/?(.*) /$1 break;
    proxy_pass http://localhost:5000;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection keep-alive;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_cache_bypass $http_upgrade;
    fastcgi_buffers 16 16k;
    fastcgi_buffer_size 32k;
  }

  location /myapi2 {
    rewrite /myapi2/?(.*) /$1 break;
    proxy_pass http://localhost:5010;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection keep-alive;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_cache_bypass $http_upgrade;
    fastcgi_buffers 16 16k;
    fastcgi_buffer_size 32k;
  }
}

Você precisará ter um arquivo de configuração para cada API para executar ambas as APIs como um serviço. Apenas repita o passo já descrito neste post prestando atenção nos caminhos e na porta usada.

Então teste a sintaxe da configuração e reinicie o Nginx:

sudo nginx -t
sudo systemctl restart nginx

Agora ambas as APIs myapi1 e myapi2 estarão disponíveis no mesmo servidor em http://example.com/myapi1 e http://example.com/myapi2 respectivamente.

marxjmoura

Eu sou Marx J. Moura. Neste blog escrevo sobre arquitetura de microsserviços e desenvolvimento de aplicações web.