Niniejszym wpisem chciałbym rozpocząć cykl opisujący migrację małego projektu webowego napisanego dawno temu w języku Python do .NET Core 2.0. Trudności na samym początku napotkałem wiele. Wynikają one z tego, że aplikacja powstała dawno temu, a .NET Core nie znam.

Króciutko o projekcie. Aplikację stworzyłem dawno temu. Wykorzystuje ona framework Flask, działa na bazie PostgreSQL i produkcyjnie działa na serwerze z Linuksem. Od dawna chciałem poznać .NET Core, więc zamiast odkopywać swoją znajomość języka Python, postanowiłem przepisać aplikację na ASP.NET Core 2.0. Aplikację chciałbym rozwijać w Visual Studio Code na Windows 10, a produkcyjnie aplikacja ma działać na Linux. Założenie jest takie, że aplikacja po migracji ma mieć dokładnie taki sam zakres funkcjonalności.

Na samym początku projektu lubię dopracować deployment, bo zostawianie tego na koniec nie kończy się nigdy dobrze. Aplikacja nie ma dużego ruchu, więc najtańszy plan za 5 dolarów za miesiąc w DigitalOcean powinien wystarczyć. W sieci jest sporo artykułów nt. instalacji .NET Core na serwerach tej firmy, ale niestety nie są aktualne a sama instalacja sprawiła mi trochę kłopotu. Wpis ten powstał dlatego, żeby być może komuś pomóc zaoszczędzić trochę czasu.

Utworzenie serwera

Proces założenia konta w DigitalOcean pominę, bo nic zaskakującego w nim nie ma. Jeśli je mamy, trzeba stworzyć Droplet. Kiedyś serwis miał prekonfigurowany serwer z .NET Core. Obecnie trzeba samemu trochę się namęczyć. Wybiorę czysty serwer z Ubuntu 16.04.

Stworzenie dropleta

Po chwili na maila przyjdą namiary na nowy serwer. Z konsoli:

ssh root@adres.ip.serwera

Zmieniamy hasło i już jesteśmy zalogowani na serwerze. Zainstalujmy tam sdk:

curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.gpg
sudo mv microsoft.gpg /etc/apt/trusted.gpg.d/microsoft.gpg
sudo sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-ubuntu-xenial-prod xenial main" > /etc/apt/sources.list.d/dotnetdev.list'
sudo apt-get update
sudo apt-get install dotnet-sdk-2.0.3

Stworzenie przykładowej aplikacji

Na maszynie developerskiej przygotujmy sobie aplikację. Z natury jestem leniwy, więc na początek umieszczę na serwerze aplikację wygenerowaną z template. Z linii komend polecenie:

dotnet new mvc -o NazwaProjektu

I mamy wygenerowany projekt. Zróbmy jeszcze małą modyfikację. W klasie Startup w metodzie Configure na samym końcu dodajmy:

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

Teraz go zbudujmy, żeby mieć co umieścić na serwerze. W tym celu znów w konsoli trzeba napisać:

cd NazwaProjektu
dotnet publish -c Release

Po chwili w katalogu NazwaProjektu\bin\Release\netcoreapp2.0\publish mamy aplikację, którą wrzucimy na serwer.

Konfiguracja serwera

Dalsze kroki dość szczegółowo opisane są tutaj, więc nie będę ich tłumaczył. Najpierw należy zainstalować i uruchomić serwer nginx:

sudo apt-get install nginx
sudo service nginx start

Po tym zabiegu w przeglądarce pod adresem http://adres.ip.naszego.dropleta powinna pojawić się strona startowa serwera nginx.

Serwer trzeba przygotować do serwowania naszej aplikacji. Zawartość pliku /etc/nginx/sites-available/default zamieniamy na następującą:

server {
    listen 80;
    server_tokens off;
    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_cache_bypass $http_upgrade;
    }
}

Żeby serwer załapał zmiany, należy go zrestartować:

sudo nginx -s reload

Teraz serwer wszelkie żądania na porcie 80 będzie przekierowywał na 5000. Wrzućmy naszą aplikację do katalogu /var/aspnetcore/naszaaplikacja i dodajmy monitoring. W tym celu stwórzmy plik /etc/systemd/system/kestrel-naszaaplikacja.service o treści:

[Unit]
Description=Opis naszej aplikacji

[Service]
WorkingDirectory=/var/aspnetcore/naszaaplikacja
ExecStart=/usr/bin/dotnet /var/aspnetcore/naszaaplikacja/naszaaplikacja.dll
Restart=always
RestartSec=10  # Restart service after 10 seconds if dotnet service crashes
SyslogIdentifier=dotnet-naszaaplikacja
User=www-data
Environment=ASPNETCORE_ENVIRONMENT=Production
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false

[Install]
WantedBy=multi-user.target

Następnie należy uruchomić stworzoną krok wcześniej usługę:

systemctl enable kestrel-naszaaplikacja.service
systemctl start kestrel-naszaaplikacja.service

Po tym kroku pod adresem http://adres.ip.naszego.dropleta powinna pojawić się nasza aplikacja.

HTTPS z Let’s Encrypt

Wypadałoby zabezpieczyć stronę certyfikatem. Cały proces jest opisany tutaj, więc będę się streszczał. Zainstalujmy certbota:

sudo add-apt-repository ppa:certbot/certbot
sudo apt-get update
sudo apt-get install python-certbot-nginx

Dodajmy domenę do serwera. W tym celu należy w pliku /etc/nginx/sites-available/default dodać:

server_name example.com www.example.com;

a następnie przeładować serwer:

sudo systemctl reload nginx

Otwórzmy ruch HTTPS przez firewall. W tym celu w konsoli należy uruchomić następujący zestaw poleceń:

sudo ufw enable
sudo ufw allow 'OpenSSH'
sudo ufw allow 'Nginx Full'
sudo ufw delete allow 'Nginx HTTP'

Teraz wystarczy pobrać certyfikat:

sudo certbot --nginx -d example.com -d www.example.com

Pod adresem https://example.com powinniśmy zobaczyć naszą stronę.