15.11.20227 min

Michał Giza Administrator systemów / DevOps

Uruchamianie aplikacji ASP.NET na Linuksie

Sprawdź, w jaki sposób z frameworka dość jednoznacznie kojarzonego z Windowsem, można skorzystać także na systemach linuksowych.

Uruchamianie aplikacji ASP.NET na Linuksie

Całe środowisko frameworka .NET zwykle kojarzy nam się z systemami Windows i serwerem IIS. To zdecydowanie najlepszy sposób na hostowanie projektów pisanych w tej technologii. Istnieje jednak możliwość wykorzystania Linuxa do uruchomienia tych aplikacji. Niestety nie zawsze będzie to w pełni możliwe, ponieważ mimo wszystko jest to zupełnie inny system.

Przedstawię trzy podejścia. Pierwsze z nich to użycie Mono, czyli implementacji .NET w środowisku Linux. O ile nie ma problemów z obsługą prostych aplikacji, to już migracja rozbudowanych projektów działających do tej pory na Windows może być skomplikowana. Mono nie obsługuje wszystkich funkcjonalności dostępnych w przypadku użycia Windows. Z kolei zaletą może być stosunkowo prosta integracja przykładowo z serwerem WWW.

Druga opcja to zwykłe „uruchomienie” aplikacji poprzez dotnet run, a następnie utworzenie reverse proxy. Zaletą tego podejścia jest wygoda, szczególnie jeśli udostępniamy naszą aplikację poprzez NuGet. Ciężko sobie wyobrazić, aby to polecenie było uruchomione przez cały czas po prostu z terminala, również użycie wirtualnego terminala screen nie będzie szczególnie profesjonalne. Dlatego powinno się utworzyć działającą w tle usługę, co nie jest trudne. W obu podejściach do obsługi ruchu wykorzystamy NGINX, a jako system operacyjny Ubuntu 20.04.

Trzecia możliwość to zastosowanie Docker. Tworzymy kontener z działającą aplikacją, co pozwoli na skorzystanie z zalet konteneryzacji.

Spora część projektów .NET używa SQL Server. Ten system baz danych z sukcesem można uruchomić na Linuxie, chociaż nie wszystkie dystrybucje są wspierane. Rozwiązaniem w takich przypadkach może być użycie Docker. Na temat używania bazy danych w formie kontenera w środowiskach produkcyjnych zdania są podzielone, natomiast w celu lokalnego uruchomienia aplikacji może to mieć sens. Dodam też, że w repozytorium Microsoft pakiet mssql-server dostępny jest najwyżej dla Ubuntu 20.04, więc jeśli używamy Ubuntu 22.04, to pozostaje nam Docker.

Niezależnie od wybranej metody pewnie przyda się nam działające polecenie dotnet. Należy wiedzieć, że dla wersji 20.04 i starszych musimy dodać repozytorium Microsoft (lub użyć snap zamiast apt), w przypadku Ubuntu 22.04 pakiet znajduje się w domyślnym systemowym repozytorium (czyli wystarczy wykonać sudo apt install dotnet-sdk-6.0).

Instalacja dla Ubuntu 20.04 wygląda następująco:

wget https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb
sudo dpkg -i packages-microsoft-prod.deb
sudo apt update
sudo apt install dotnet-sdk-6.0


Mono

W pierwszej kolejności instalujemy podstawowe pakiety wchodzące w skład środowiska. Instrukcje dostępne są w tym miejscu. Najlepiej zainstalować po prostu mono-complete w celu uniknięcia ewentualnych problemów związanych z brakami pakietów. Zamierzamy użyć NGINX, więc musimy dodatkowo zainstalować mono-fastcgi-server4.

Przykładowa minimalna konfiguracja NGINX ogranicza się do kilku linii:

server {
    listen 80;
    listen 443 ssl;
    ssl_certificate /etc/nginx/ssl/aspnet-app.crt;
    ssl_certificate_key /etc/nginx/ssl/aspnet-app.key;
    if ($scheme != "https") { rewrite ^ https://$host$uri permanent; }

    server_name aspnet-app;
    root /home/users/cms/wwwroot;
    index index.html index.htm index.aspx default.aspx;

    location / {
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }

    access_log /home/users/cms/logs/access.log;
    error_log /home/users/cms/logs/error.log error;
    location ~ ^/(favicon.ico|robots.txt)/ { access_log off; }
}


W głównym bloku location ustawiamy proxy dla lokalnego portu 9000. Polecenie uruchamiające serwer na tym porcie to fastcgi-mono-server4 /applications=aspnet-app:/:/home/users/cms/wwwroot/ /socket=tcp:127.0.0.1:9000. Powinniśmy „opakować” je w postaci usługi systemowej. Odpowiedni plik tworzymy w katalogu /etc/systemd/system i nadajemy uprawnienia 644. Zawartość może się ograniczyć do tej podstawowej postaci:

[Unit]
Description="ASP.NET"
After=network.target

[Service]
Type=simple
User=cms
Group=cms
ExecStart=/usr/bin/fastcgi-mono-server4 /applications=aspnet-app.local:/:/home/users/cms/wwwroot/ /socket=tcp:127.0.0.1:9000
PIDFile=/run/aspnet-cms.pid
Restart=always

[Install]
WantedBy=default.target


Serwer zostanie uruchomiony jako dedykowany użytkownik cms. Pewną „ochronę” przed przypadkowym wyłączeniem odpowiedniego procesu (ponieważ proces działa na prawach zwykłego użytkownika, wystarczy, że pobierze on jego PID i wykona kill) stanowi automatyczny restart usługi. Można dodać niewielkie opóźnienie, aby usługa nie próbowała się uruchomić natychmiast po zakończeniu (opcja RestartSec).

Na koniec wystarczy wykonać sudo systemctl daemon-reload i uruchomić usługę. W celu zapewnienia uruchomiania po restarcie systemu trzeba jeszcze aktywować usługę poleceniem sudo systemctl enable <usługa>. Teraz po wejściu na aspnet-app powinniśmy zobaczyć błąd 404, ponieważ katalog /home/users/cms/wwwroot jest pusty. Możemy umieścić w nim przykładowy plik Default.aspx wyświetlający treść w nagłówku h1:

<% @Page Language="C#" %>
<html>
<head>
    <title>ASP.NET</title>
    <meta charset="utf-8">
</head>
<body>
    <h1>Linux</h1>
</body>
</html>


Jeśli wszystko poprawnie skonfigurowaliśmy, to w przeglądarce zobaczymy „ambitny” efekt działania tego przykładu:


Jak wspomniałem, Mono nie do końca będzie idealnym zastosowaniem dla złożonych projektów. Jeśli koniecznie musimy wykorzystać Linuxa to, ogólnie rzecz biorąc, powinniśmy pisać aplikacje od podstaw zgodne z Mono, co nie zawsze może okazać się świetnym pomysłem. Polecam zapoznać się z opisem kompatybilności.

Natomiast jeśli do postawienia naszej aplikacji wystarczy uruchomienie dotnet run, to nie powinno być problemów z całkowitym przeniesieniem projektu na Linuxa.


Reverse proxy

To podejście korzysta po prostu z faktu, że dotnet run wystawia skompilowaną aplikację pod lokalnym portem (w tle działa Kestrel). Wystarczy więc ustawić odpowiednio reverse proxy i powinniśmy otrzymać w pełni działającą aplikację dostępną z zewnątrz na domyślnych portach HTTP(S), czyli 80 i 443.

Jako przykład zainstalujemy dość popularny system zarządzania treścią Umbraco. Skorzystamy także z bazy SQL Server. W Ubuntu 20.04 instalacja ogranicza się do następujących poleceń:

sudo add-apt-repository "$(wget -qO- https://packages.microsoft.com/config/ubuntu/20.04/mssql-server-2019.list)"
curl https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add -
sudo apt update
sudo apt install mssql-server


Nie możemy pominąć wstępnej konfiguracji. Służy do tego polecenie sudo /opt/mssql/bin/mssql-conf setup. Wskazujemy wersję bazy (wystarczy Express), akceptujemy warunki licencji, wybieramy język i ustawiamy hasło dla użytkownika SA. Osobnego użytkownika i bazę danych możemy utworzyć poprzez SQL Server Management Studio. Łączymy się w standardowy sposób z serwerem:


Dodajemy bazę i użytkownika z dostępem do niej.


Na serwerze korzystamy z NuGet do pobrania paczki z Umbraco.

dotnet new -i Umbraco.Templates
dotnet new umbraco --name aspnet-app

Modyfikujemy konfigurację NGINX zgodnie z opisem na stronie Microsoft. W naszym przypadku wystarczy zmienić jedynie sekcję location w ten sposób:

location / {
    proxy_pass http://127.0.0.1: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;
    proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header   X-Forwarded-Proto $scheme;
}


Edycji wymaga też utworzona przez nas usługa. Potrzebujemy zmiany wyłącznie w opcji ExecStart:

ExecStart=/usr/bin/dotnet run --urls=http://localhost:5000 --project /home/users/cms/aspnet-app


Po restarcie usługi w przeglądarce prawdopodobnie zobaczymy stronę instalacji Umbraco:

Wystarczy podać odpowiednie informacje i system będzie praktycznie natychmiast gotowy do dalszej pracy.


Docker

Bardzo dobrym rozwiązaniem będzie również wykorzystanie Docker. Przykładowo można przygotować osobne kontenery z aplikacją, SQL Server i NGINX. Jedynie kontener z serwerem WWW będzie wystawiał porty 80 i 443, pozostałe kontenery mogą działać całkowicie w sieci Docker. Dodatkowo tworząc dedykowaną sieć dla tej aplikacji, będzie można przypisać statyczne adresy IP kontenerom.

W katalogu z aplikacją (w naszym przykładzie to wciąż Umbraco) tworzymy Dockerfile:

FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /app
COPY . /app
RUN dotnet publish -c Release -o ./src
FROM mcr.microsoft.com/dotnet/aspnet:6.0
EXPOSE 5000
ENV ASPNETCORE_URLS http://+:5000
WORKDIR /app
COPY --from=build /app/src .
ENTRYPOINT ["dotnet", "aspnet-app.dll"]

To standardowy sposób, w którym jeden obraz służy wyłącznie do „budowy” aplikacji. Polecenie dotnet publish zapisze w takim wypadku cały potrzebny do uruchomienia kod w katalogu /app/src. Następnie ten katalog jest kopiowany do kolejnego obrazu, który po każdym starcie właściwego kontenera będzie wykonywał dotnet aspnet-app.dll z katalogu /app.

Po przygotowaniu obrazu uruchamiamy kontener z nim oraz drugi kontener z SQL Server, w którym tworzymy bazę i użytkownika dla projektu. Oba kontenery mogą działać całkowicie „lokalnie”. Ostatni krok to przygotowanie obrazu NGINX. Wystarczy skopiować podaną wyżej konfigurację (pamiętając o zmianie proxy_pass) i pliki certyfikatu SSL. Przykładowe uruchomienie:

docker run --net aspnet --ip 172.20.0.20 -d --name nginx -p 80:80 -p 443:443 nginx-aspnet


Lista kontenerów:

Lista kontenerów


Podsumowanie

Jak widać, również na Linuxie można z pewnym powodzeniem uruchomić i utrzymywać aplikacje korzystające z możliwości technologii .NET. Myślę, że jest to rozwiązanie korzystne dla specyficznych projektów i zastosowań. Obecnie wciąż polegałbym raczej na systemie Windows w przypadku konieczności hostowania tego typu aplikacji, natomiast wiedza o dostępnych w Linux sposobach obsługi .NET może być przydatna.

<p>Loading...</p>