Fullstack - ASP.NET Entwicklung mit DotNet Core und Vue.js - Teil 7 - Erstellen eines Docker Containers

Einleitung

Dieser Artikel ist eine Serie von Artikeln, die den Aufbau einer Fullstack Applikation unter DotNet Core und Vue.js beschreiben.

Bisher sind folgende Artikel erschienen:

  • Fullstack - ASP.NET Entwicklung mit DotNet Core und Vue.js - Erste Schritte Link
  • Fullstack - ASP.NET Entwicklung mit DotNet Core und Vue.js - Teil 2 Link
  • Fullstack - ASP.NET Entwicklung mit DotNet Core und Vue.js - Teil 3 - Einbinden PostgreSQL als Datenbank Link
  • Fullstack - ASP.NET Entwicklung mit DotNet Core und Vue.js - Teil 4 - Erweiterung um ein Repository Pattern und Service Layer Link
  • Fullstack - ASP.NET Entwicklung mit DotNet Core und Vue.js - Teil 5 - Erste Schritte mit Vue.js und Typescript Link

In diesen Artikel möchte ich "kurz" beschreiben, wie man unsere DotNet Core Applikation zu einen Docker Container macht und auf einen Server deployed. Anschließend ist die Anwendung als eigenständige Applikation live auf einen Server im Internet verfügbar.

In diesen Artikel werde ich nicht darauf eingehen, wie man einen Docker Server aufbaut. Hierzu habe ich bereits einen Artikel geschrieben, der dies recht detailiert für einen Dedicated Server von Hetzner beschreibt: http://vr-worlds.de/praxis-dokumentation-docker-bei-hetzner-aufsetzen/

Das Dockerfile

Damit wir unsere Applikation zu einen Docker Image machen können, benötigen wir "eigentlich" nur eine einzige neue Datei "Dockerfile" in unserem Projekt:

In die erste Zeile schreiben wir welches Images wir als Basis nehmen wollen. Hierfür gibt es von Microsoft mehrere im Angebot. Um ein möglichst kleines Image zu bekommen, verwenden wir hier das mit Alpine Linux als Basis.

Unter dem Docker Hub befindet sich folgende Images:
https://hub.docker.com/r/microsoft/dotnet/

Wir werden das Image "microsoft/dotnet:2.1-sdk-alpine" verwenden.

Auf unseren Docker Server können wir uns mit folgenden Befehl schon mal auf den Image umsehen:

docker run -it microsoft/dotnet:2.1-sdk-alpine sh  

Dotnet Core 2.1 ist bereits wie erwartet installiert:

Node und npm dagegen natürlich noch nicht:

Die Installation ist recht einfach mit dem "apk" Befehl:

Das wären die wichtigsten Punkte für unser Dockerfile:

FROM microsoft/dotnet:2.1-sdk-alpine

RUN apk add --no-cache nodejs  

Desweiteren wollen wir ein Arbeitsverzeichnis "/usr/src/app" und dort unseren Source Code hineinkopiert haben.

Anschließend mit "npm install" alles herunterladen an Javascript Bibliotheken und mit "dotnet restore" das gleiche für unsere Dotnet Applikation. Anschließend soll das alles compiliert werden. Den Port 5000 geben wir frei in unseren Docker und starten dann die Applikation.

Insgesamt sieht das "Dockerfile" dann vollständig wie folgt aus:

FROM microsoft/dotnet:2.1-sdk-alpine

# Install Nodejs
RUN apk add --no-cache nodejs nodejs-npm \  
    && mkdir /usr/src \
    && mkdir /usr/src/app

# Switch Workdir
WORKDIR /usr/src/app

# Copy everything local
COPY . .

# Install node packages and restore dotnet packages
RUN npm install \  
    && dotnet restore \
    && dotnet build

EXPOSE 5000

CMD ["dotnet", "run"]

Testen des builds

Wir schieben mit Git alle unseren Änderungen ink. dem Dockerfile auf GIT.

Anschließend laden wir uns dieses auf unseren Linux Build Server: (Am einfachsten kann man sich hier eine Virtuelle Maschine mit VirtualBox oder Hyper-V einrichten http://vr-worlds.de/praxis-dokumentation-basisaufbau-einer-testumgebung-in-centos-mit-virtualbox-fur-docker/)

[root@CentOS-73-64-minimal build]# git clone https://github.com/smoki99/DotNetVueBlog.git
Cloning into 'DotNetVueBlog'...  
remote: Counting objects: 140, done.  
remote: Compressing objects: 100% (103/103), done.  
remote: Total 140 (delta 41), reused 128 (delta 29), pack-reused 0  
Receiving objects: 100% (140/140), 61.35 KiB | 0 bytes/s, done.  
Resolving deltas: 100% (41/41), done.  
[root@CentOS-73-64-minimal build]# cd
demo1/             docker-alpine/     DotNetVueBlog/     powershell-alpine/ test/  
[root@CentOS-73-64-minimal build]# cd DotNetVueBlog/
[root@CentOS-73-64-minimal DotNetVueBlog]# ls Dockerfile
Dockerfile  

Anschließend können wir mit folgenden Befehl den Container bauen:

docker build -t dotnetvueblog:v1 .  

Ist alles korrekt gelaufen, ist der Container nach ein paar Minuten gebaut:

Wir sehen mit "docker images" nun unser neues Image mit "leider" 1.8 GB größe:

[root@CentOS-73-64-minimal DotNetVueBlog]# docker images | grep dotnet
dotnetvueblog                               v1                  28b02268bb05        3 minutes ago       1.8 GB  
docker.io/microsoft/dotnet                  2.1-sdk-alpine      d617f095944d        5 days ago          1.54 GB  

Für "docker run" benötigen wir eine postgresql Datenbank auf unseren Docker Server!

In meinen Beispiel habe ich bereits eine angelegt und auch eine entsprechende Datenbank "weatherdata" inkl. Usernamen und Passwort.

Diese Information benötigt unser Container um sich verbinden in Teil3 Link haben wir hierfür einen Environment Variable definiert.

Ein erster Versuch

[root@CentOS-73-64-minimal dockercompose]# docker run --name dotnetvueblog -e WheatherDB=Host="postgres;Database=weatherdata;Username=smoki;Password=XXXXXXXXXXXXX" --link postgres:postgres -d -p 8500:5000 dotnetvueblog:v1

Leider schlägt dieser fehl und man kann sich mit

docker logs dotnetvueblog  

Ansehen was das Problem ist:

Microsoft findet die libuv nicht. Ein Workaround wir hier angeboten:

https://github.com/aspnet/KestrelHttpServer/issues/2415

Entsprechend wird das Dockerfile erweitert:

FROM microsoft/dotnet:2.1-sdk-alpine

# Install Nodejs
RUN apk add --no-cache nodejs nodejs-npm libuv \  
    && ln -s /usr/lib/libuv.so.1 /usr/lib/libuv.so \
    && mkdir /usr/src \
    && mkdir /usr/src/app

# Switch Workdir
WORKDIR /usr/src/app

# Copy everything local
COPY . .

# Install node packages and restore dotnet packages
RUN npm install \  
    && dotnet restore \
    && dotnet build

EXPOSE 5000

CMD ["dotnet", "run"]  

Und anschließend bauen wir einen neuen Build:

Nun können wir den Container mit "docker rm" entfernen und "docker run" erneut starten:

Das Ergebnis ist schon sehr viel erfreulicher und auch die Tabellen in der Datenbank wurden angelegt:

Allerdings funktioniert das sich verbinden nicht. Weil "Krestel" aktuell nur auf "localhost" horcht:

Also zurück in den C# Code und die Methode "BuildWebHost" erweitern:

Erneut mit GIT einchecken und auf den Linux bauen lassen:

Anschließend kann der Container erneut gebaut werden und horcht nun auf alle IP-Adressen (0.0.0.0)

Tipp: Sollte es immer noch nicht gehen, kann es sein, dass die Linux Firewall den Port noch nicht offen hat.

Nun sollte es funktionieren:

Configurieren von NGINX Container

Ohne auf Details einzugehen, empfiehlt es sich NGINX zu konfiguieren, dass der Container von dort angesprochen wird, anstatt "Krestel" direkt nach außen freizugeben. NGINX hat eine höhere Performance und kann auch leicht mit Themen wie beispielsweise SSL erweitert werden.

Hierzu muss in NGINX die localhost.conf wie folgt erweitert werden:

server {  
  listen 0.0.0.0:80;
  server_name demo1.vr-worlds.de;
  access_log /var/log/nginx/demo1.log;

  location /.well-known/ {
     root /usr/share/nginx/html;
     allow all;
     default_type "text/plain";
  }

  location / {
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header HOST $http_host;
      proxy_set_header X-NginX-Proxy true;

      proxy_pass http://dotnetvueblog:5000;
      proxy_redirect off;
  }
client_max_body_size 40000M;  
}

Natürlich muss dem NGINX Container auch unser Container als (External-)Link bekannt gemacht werden. Und "demo1.vr-worlds.de" muss natürlich auch im DNS eingetragen sein.

Anschließend funktioniert der Zugriff:

Mit Daten befüllen über HEIDI

Damit der "Weather forcast" auch Werte anzeigen kann, muss man Zeilen mit HeidiSQL einfügen:

Den Container auf hub.docker.com hinzufügen

Um den Container direkt auf hob.docker.com bauen und verfügbar zu machen müssen nur noch wenige Schritte gemacht werden, da wir bereits unser Repository auf github.com haben.

Zunächst muss man seinen Github Account mit hub.docker.com verlinken:

Anschließend kann unter "Create" der Punkt "Create Automated Build" ausgewählt werden:

Dann können wir unser Repository wählen:

Und dann diesen Dialog ausfüllen und auf "Create" klicken:

Nach einiger Zeit steht anschließend der Container der Allgemeinheit zur Verfügung.

Zusammenfassung

Mit diesen Artikel habe ich die notwendigen Schritte und auch ein paar übliche Fallstricke gezeigt, damit aus einer DotNet Core Applikation inkl. JavaScript/Typescript Anteilen erfolgreich als Docker Container gebaut und deployed werden kann.

Der aktuelle Source-Code steht unter folgenden Link zur Verfügung: https://github.com/smoki99/DotNetVueBlog/releases/tag/v0.0.7