IT Praxis

.NET Core 5 Anwendung unter Linux (Ubuntu) – Teil 2

Im ersten Teil dieser Reihe haben wir gezeigt, wie die .NET Core 5 Runtime und der nginx Webserver unter Linux Ubuntu 21.04 installiert werden. In diesem Teil geht es um die Vorbereitung und das Deployment der Anwendung, die später auf diesem Server laufen soll.

Das Anwendungssystem besteht aus folgenden Komponenten:

  1. Webserver: nginx (liefert die statischen Seiten der SPA aus und dient als Reverseproxy zum Kestrel-Ersatz, um die HTTP-Anforderungen zu beenden und diese an die .NET Core Anwendung weiterzuleiten)
  2. App: Angular Single Page Application (SPA)
  3. API: .NET 5 Web API (als Service)
  4. Datenbank: MS SQL Server 2019 (als Docker Container in diesem Beispiel)

Die Beziehungen zwischen den Komponenten sind in der folgenden Abbildung dargestellt:

Systemüberblick Angular Client und .NET Core 5 Web API

Die Vorbereitung und das Deployment der einzelnen Komponenten wird in den folgenden Abschnitten beschrieben.


Verzeichnisstruktur auf dem Linux-Host

Auf dem Linux-Host wird folgende Verzeichnisstruktur erstellt, unsere Anwendung heißt kproject.

root@dotnetapps:/var/www/kproject# ls -la
total 42
drwxr-xr-x 4 root root  4 Sep  7 17:10 .
drwxr-xr-x 4 root root  4 Sep  7 10:16 ..
drwxr-xr-x 4 root root 68 Sep  7 16:20 app
drwxr-xr-x 3 root root 15 Sep  7 16:09 client

Die Angular SPA (client) und die .NET Core API (app) erhalten dabei jeweils separate Verzeichnisse.


Vorbereitung nginx zum Ausliefern der Angular SPA

Für unsere Anwendung erstellen wir eine eigene Konfigurationsdatei für nginx

nano /etc/nginx/sites-available/kproject

mit folgendem Inhalt:

server {
  listen 80 default_server;
  listen [::]:80 default_server;
  root /var/www/kproject/client;
  index index.html;
  
  location / {
    try_files $uri $uri/ /index.html =404;
  }
}

Damit diese Konfigurationsdatei auch von nginx verarbeitet werden kann, verlinken wir die Datei unter die aktiven Seiten:

ln -s /etc/nginx/sites-available/kproject /etc/nginx/sites-enabled/kproject

Anschließend nginx neu starten, um die Konfiguration zu laden:

systemctl restart nginx

Wenn hierbei keine Fehlermeldung auftaucht, wurde die Konfiguration erfolgreich gelesen. Anschließend kann der Status des nginx Webservers geprüft werden und sollte wie im Beispiel aussehen:

root@dotnetapps:/# systemctl status nginx.service 
* nginx.service - A high performance web server and a reverse proxy server
     Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
     Active: active (running) since Tue 2021-09-07 16:09:14 UTC; 2h 49min ago
       Docs: man:nginx(8)
    Process: 1090 ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_process on; (code=exited, status=0/SUCCESS)
    Process: 1092 ExecStart=/usr/sbin/nginx -g daemon on; master_process on; (code=exited, status=0/SUCCESS)
   Main PID: 1093 (nginx)
      Tasks: 2 (limit: 38066)
     Memory: 3.3M
        CPU: 64ms
     CGroup: /system.slice/nginx.service
             |-1093 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
             `-1094 nginx: worker process

Sep 07 16:09:14 dotnetapps systemd[1]: Starting A high performance web server and a reverse proxy server...
Sep 07 16:09:14 dotnetapps systemd[1]: Started A high performance web server and a reverse proxy server.

Vorbereitung Angular SPA (statische Inhalte)

In diesem Beispiel wird eine separate Anwendung (GUI) verwendet, die mit Angular als Single Page Application (SPA) erstellt wurde. Auf die eigentliche Erstellung der Angular SPA bzw. einer beliebigen Webanwendung mit statischen Inhalten wird an dieser Stelle nicht weiter eingegangen.

Beachten Sie grundsätzlich die Konfiguration für die „Produktionsumgebung“ innerhalb Ihrer Anwendung. Bei der Angular-Anwendung sind dies zum Beispiel in der environment.prod.ts hinterlegte Urls für die API-Aufrufe.

export const environment = {
  production: true,  
  BaseUrl: 'http://192.168.0.100/',
  ApiUrl: "http://192.168.0.100/api/"
};

Die IP-Adresse ist in diesem Fall die IP-Adresse des Linux-Hosts.

Erstellen Sie den Build der Angular App mit

ng build --prod

Sie erhalten dann im Ordner dist eine Ausgabe, ähnlich dem folgenden Bild.

Angular Production Build

Deployment der Angular SPA auf den Linux-Host

Anschließend werden die Dateien in das Verzeichnis client auf dem Linux-Host kopiert. Dies kann zum Beispiel mittels scp erfolgen:

scp -r * root@192.168.0.100:/var/www/kproject/client

Wenn Sie jetzt im Browser http://192.168.0.100 aufrufen, sollten Sie die Webanwendung sehen.

Angular SPA unter nginx

Diese funktioniert natürlich noch nicht vollständig, da die .NET Core Web API noch nicht auf dem Linux-Host bereitgestellt wurde. Um die .NET Core Web API kümmern wir uns jetzt.


Vorbereitung der .NET Core 5 Web API

Je nach Komplexität Ihrer .NET Core Web API und der darin genutzten Schnittstellen sind unterschiedliche Konfigurationen für die „Produktionsumgebung“ notwendig. Nachfolgend einige typisch notwendige Konfigurationen.

Anpassung Datenbank-Verbindungsparameter

Typischerweise wird auf einer Testdatenbank (lokale MS SQL Server, in Verbindung mit Visual Studio o.ä.) entwickelt, für die Produktion wird eine „Produktionsdatenbank“ verwendet. Die entsprechenden Parameter sind anzupassen, beispielsweise in der appsettings.json.

{
 "DefaultConnection": "Server=tcp:192.168.0.150,1433;Initial Catalog=KProject;Persist Security Info=False;User ID=sa;Password=SUPERSECRET;MultipleActiveResultSets=False;Encrypt=False;TrustServerCertificate=True;Connection Timeout=30;"
  },

Anpassung der Startup.cs

Abhängig von Ihrem persönlichen Szenario kann es sein, dass einige Services in der Startup.cs angepasst werden müssen. Dies betrifft vor allem den Einsatz bzw. nicht-Einsatz von HTTPS und die Regel für Cross Origin Requests (CORS).

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{	
	if (env.IsDevelopment())
	{
		app.UseSwagger();
		app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "API v1"));
	}
	//app.UseHttpsRedirection();
	app.UseRouting();
	app.UseCors(x => x.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin());
	//app.UseCors(x => x.AllowAnyHeader().AllowAnyMethod().WithOrigins(
	//"http://localhost:3000",
	// "https://localhost:4200"
	// ));
	app.UseAuthentication();
	app.UseAuthorization();
	app.UseEndpoints(endpoints =>
	{
		endpoints.MapControllers();
	});
}

Veröffentlichung der .NET Core 5 Web API (Publishing)

In diesem Beispiel führen wir die Veröffentlichung direkt aus Visual Studio 2019 heraus durch. Wählen Sie „Veröffentlichen“ und erstellen Sie ein Profil für die Veröffentlichung in einem Verzeichnis mit folgenden Einstellungen:

Einstellungen zur Veröffentlichung der .NET Core 5 Web API

Hinweis: Wählen Sie ein Verzeichnis außerhalb des Projektverzeichnisses (zu viele Ebenen), um später leichter darauf zugreifen zu können.

Das Veröffentlichungsprofil sollte anschließend wie folgt aussehen:

Veröffentlichen der .NET Core 5 Web API

Deployment der .NET Core 5 Web API auf den Linux Host

Die Dateien aus dem Publish-Verzeichnis, hier J:\kproject werden in das Verzeichnis app auf dem Linux-Host kopiert. Dies wird wieder über scp erledigt:

scp -r * root@192.168.0.100:/var/www/kproject/app

Ob die bisherigen Bemühungen erfolgreich waren, kann auf dem Linux Host einfach überprüft werden. Dazu wird die .NET Core Web API testweise gestartet:

root@dotnetapps:/var/www/kproject/app# dotnet API.dll
2021-09-07 20:20:35.0962||DEBUG|API.Program|Starting API... |url: |action: 
2021-09-07 20:20:35.1275||DEBUG|API.Program|BaseDirectory: /var/www/kproject/app/ |url: |action: 
2021-09-07 20:20:35.3284||DEBUG|API.Program|Starting Migrations... |url: |action: 
2021-09-07 20:20:36.2800||DEBUG|API.Program|Starting Seedings... |url: |action: 
2021-09-07 20:20:36.2800||DEBUG|API.Program|Seeding Default User And Roles... |url: |action: 
2021-09-07 20:20:36.2949||DEBUG|Persistence.SeedData.DBInitializer|Creating Default Roles and Users... |url: |action: 
2021-09-07 20:20:36.4562||DEBUG|Persistence.SeedData.DBInitializer|Creating Default Roles and Users... Finished! |url: |action: 
2021-09-07 20:20:36.4562||DEBUG|API.Program|Seeding Projects... |url: |action: 
2021-09-07 20:20:37.3163||DEBUG|Persistence.SeedData.DBInitializer|Seeding Projects... |url: |action: 
2021-09-07 20:20:37.3216||DEBUG|API.Program|Seeding TeamUsers... |url: |action: 
2021-09-07 20:20:37.3239||DEBUG|Persistence.SeedData.DBInitializer|Seeding TeamUser... |url: |action: 
2021-09-07 20:20:37.3268||DEBUG|API.Program|Seeding Budgets... |url: |action: 
2021-09-07 20:20:37.3268||DEBUG|Persistence.SeedData.DBInitializer|Seeding BUdgets... |url: |action: 
2021-09-07 20:20:37.3319||DEBUG|API.Program|Starting Host... |url: |action: 
2021-09-07 20:20:37.4257||INFO|Microsoft.Hosting.Lifetime|Now listening on: http://localhost:5000 |url: |action: 
2021-09-07 20:20:37.4310||INFO|Microsoft.Hosting.Lifetime|Application started. Press Ctrl+C to shut down. |url: |action: 
2021-09-07 20:20:37.4310||INFO|Microsoft.Hosting.Lifetime|Hosting environment: Production |url: |action: 
2021-09-07 20:20:37.4310||INFO|Microsoft.Hosting.Lifetime|Content root path: /var/www/kproject/app |url: |action: 

Wenn der Start erfolgreich war, dann mittels STRG-C die Ausführung abbrechen, wir können weiter fortfahren.

Zeigen sich beim Start Fehler, dann müssen diese leider individuell analysiert und gefunden werden 😉


Einrichtung der .NET Core 5 Web API als Dienst auf dem Linux Host

Damit die API auch nach einem Neustart des Rechners dauerhaft zur Verfügung steht, muss diese als Service / Dienst auf dem Linux Host eingerichtet werden.

Dazu erstellen wir eine entsprechende Service-Datei

nano /etc/systemd/system/kproject.service

mit folgendem Inhalt:

[Unit]
Description= kproject webapp
[Service]
WorkingDirectory=/var/www/kproject/app
ExecStart=/usr/bin/dotnet /var/www/kproject/app/API.dll
Restart=always
# Restart service after 10 seconds if the dotnet service crashes:
RestartSec=10
SyslogIdentifier=kproject
Environment=ASPNETCORE_ENVIRONMENT=Production

[Install]
WantedBy=multi-user.target

Die Datei speichern und unseren neuen Dienst aktivieren:

systemctl enable kproject.service

Anschließend den Service starten:

systemctl start kproject.service

Ob der Dienst erfolgreich gestartet wurde, prüfen wir mit:

root@dotnetapps:/# systemctl status kproject.service 
* kproject.service - kproject webapp
     Loaded: loaded (/etc/systemd/system/kproject.service; enabled; vendor preset: enabled)
     Active: active (running) since Tue 2021-09-07 20:31:07 UTC; 1s ago
   Main PID: 2712 (dotnet)
      Tasks: 12 (limit: 38066)
     Memory: 41.8M
        CPU: 1.419s
     CGroup: /system.slice/kproject.service
             `-2712 /usr/bin/dotnet /var/www/kproject/app/API.dll

Sep 07 20:31:07 dotnetapps systemd[1]: Started kproject webapp.
Sep 07 20:31:07 dotnetapps kproject[2712]: 2021-09-07 20:31:07.8714||DEBUG|API.Program|Starting API... |url: |action:
Sep 07 20:31:07 dotnetapps kproject[2712]: 2021-09-07 20:31:07.8993||DEBUG|API.Program|BaseDirectory: /var/www/kproject/app/ |url: |action:
Sep 07 20:31:08 dotnetapps kproject[2712]: 2021-09-07 20:31:08.1133||DEBUG|API.Program|Starting Migrations... |url: |action:
Sep 07 20:31:09 dotnetapps kproject[2712]: 2021-09-07 20:31:09.0323||DEBUG|API.Program|Starting Seedings... |url: |action:
Sep 07 20:31:09 dotnetapps kproject[2712]: 2021-09-07 20:31:09.0323||DEBUG|API.Program|Seeding Default User And Roles... |url: |action:
Sep 07 20:31:09 dotnetapps kproject[2712]: 2021-09-07 20:31:09.0484||DEBUG|Persistence.SeedData.DBInitializer|Creating Default Roles and Users... |url: |action:
Sep 07 20:31:09 dotnetapps kproject[2712]: 2021-09-07 20:31:09.2105||DEBUG|Persistence.SeedData.DBInitializer|Creating Default Roles and Users... Finished! |url: |action:
Sep 07 20:31:09 dotnetapps kproject[2712]: 2021-09-07 20:31:09.2105||DEBUG|API.Program|Seeding Projects... |url: |action:

Die Ausgabe sollte analog zum testweisen manuellen Start oben aussehen.


nginx und .NET Core 5 Web API verbinden

Eine abschließende Konfiguration von nginx ist noch durchzuführen, damit der Reverseproxy die Anfragen an die API verarbeitet.

Dazu wird die oben erstellte Konfigurationsdatei ergänzt und sieht final wie folgt aus:

server {
  listen 80 default_server;
  listen [::]:80 default_server;
  root /var/www/kproject/client;
  index index.html;
  
  location / {
    try_files $uri $uri/ /index.html =404;
  }

  location /api/ {
    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 $http_host;
    proxy_cache_bypass $http_upgrade;
  }
}

Jetzt noch einmal den nginx neu starten mit

systemctl restart nginx.service

Und voilá, unsere Angular SPA mit .NET Core 5 Web API läuft unter Linux Ubuntu!

Angular App mit .NET Core 5 Web API unter Linux Ubuntu

Fazit

Der Artikel ist ganz schön lang geworden, aber die Arbeit die dahinter steckt, war es wert. Komplexe .NET Core Anwendungen unter Linux zum Laufen zu bringen ist einfach, wenn man weiß, wie es geht – aber so ist das immer im Leben.

Dieses Szenario hat aus meiner Sicht viele Vorteile, vor allem den Kostenaspekt, wenn es darum geht auf einem Heimserver oder einem kleinen Linux-Server bei einem Hoster

  • einen Prototypen zu zeigen,
  • einem Dritten Zugang zu einem Entwicklungsstand zu geben, um Feedback zu erhalten und
  • selbst entwickelt Anwendungen auch außerhalb von Azure & Co in kleinem Rahmen betreiben zu können.

Oder auch einfach nur um zu zeigen, wie man die .NET Core Technologie von Microsoft unter Linux betreiben kann. Denn das Microsoft-Development Biotop aus .NET (Core), C#, Visual Studio oder Visual Code, MS SQL Server, etc. ist aus Entwicklersicht eine wahre Freude.


Ausblick

Teil 1 und Teil 2 dieser Reihe hatten die Lauffähigkeit bzw. ein proof of concept zum Ziel => check!

Aber selbstverständlich sind auch dabei weitere Optimierungen möglich, beispielsweise

  • Nutzung von HTTPS im nginx und damit auch Zugriff auf die Web API per HTTPS
  • Verwendung des nginx Proxy Managers für einen gesicherten Zugriff von außen auf das System im Heimnetz
  • Optimierung des Deployment-Prozesses, aktuell dauert ein komplett manuelles Erstellen und Deployment der Angular SPA und der .NET Core 5 Web API ca. 3 Minuten

Je nach Interesse an dieser Reihe wird es vermutlich dazu einen Teil 3 geben.

Also schön kommentieren, Fragen stellen, Tips geben und natürlich diesen Artikel teilen 🙂

Ein Gedanke zu „.NET Core 5 Anwendung unter Linux (Ubuntu) – Teil 2

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert