.NET-Anwendungen in Docker-Containern betreiben

Docker und .NET – ein Beispiel
Kommentare

Nachdem wir im Artikel „Docker – was bringt’s für Entwickler?“ (Seite 14) die Grundlagen von Docker betrachtet haben, gehen wir jetzt zur Anwendung über. Unser Ziel ist der Aufbau einer Testumgebung mit Docker, in der wir eine ASP.NET-vNext-Anwendung betreiben können. Anhand dieses Beispiels lernen Sie die wichtigsten Grundelemente von Docker kennen.

Installieren von Docker: Wie schon im vorangegangenen Artikel (Seite 14) erwähnt, funktioniert Docker im Moment nur unter Linux. Auf die Version für Windows-Server müssen wir noch etwas warten. Als .NET-Entwickler arbeiten Sie mit großer Wahrscheinlichkeit mit Windows. Insofern haben Sie drei Möglichkeiten, um Docker auszuprobieren:

  • Installation von Linux (für erste Schritte in Sachen Docker empfehle ich Ubuntu) in einer virtuellen Maschine mit Hyper-V mit anschließender Installation von Docker,
  • Verwenden von boot2docker,
  • Verwenden einer virtuellen Maschine der Microsoft Azure-Cloud.

Wie Sie bei den ersten beiden Varianten vorgehen sollten, können Sie in den oben angegebenen Quellenlinks im Detail nachlesen. Am einfachsten und schnellsten kommt man zu einer Docker-Testumgebung aber über den dritten Weg, über Microsoft Azure.

Ubuntu und Docker in Microsoft Azure

Microsoft Azure unterstützt Linux schon länger. Relativ neu ist jedoch, dass es eine fertige VM Extension gibt, die Docker in Ihrer Linux VM installiert und konfiguriert (Abb. 1). Die Docker Extension verlangt zwingend den Zugriff über https. Aus diesem Grund müssen Sie beim Hinzufügen der Extension die entsprechenden Zertifikate und Schlüssel angeben. Eine genaue Anleitung, wie diese erstellt werden, finden Sie hier.

Abb. 1: Docker Extension in einer Linux VM im Azure-Portal installieren

Wenn Sie möglichst rasch eine neue VM mit Docker zum Probieren möchten, empfehle ich, nicht erst Ubuntu und danach in einem zweiten Schritt Docker zu installieren, sondern von Anfang an eine Ubuntu VM mit vorinstalliertem Docker anzulegen. Im Azure-Portal finden Sie dafür seit Neuestem eine eigene VM-Vorlage (Abb. 2), die sich speziell für ein erstes Kennenlernen gut eignet. Für Scripting stellt Azure das Shell-Kommando azure vm docker create zur Verfügung. Es ist Teil des Azure Cross-Platform Interface und kann daher unter Windows, Mac OS X und Linux ausgeführt werden. Eine Schritt-für-Schritt-Anleitung stellt Microsoft hier bereit. Ein Vorteil dieses Ansatzes ist, dass Sie nicht unbedingt selbst die Zertifikate für https anlegen müssen. Wenn Sie keine angeben, erstellt Azure diese selbst und speichert sie im Verzeichnis ~/.docker ab.

Abb. 2: Ubuntu-VM-Vorlage im Azure-Portal

Testumgebung mit Docker und Visual Studio 2015

Für unser Beispiel mit Docker und .NET verwenden wir drei virtuelle Maschinen:

  • Docker Host (Ubuntu),
  • Docker Client (Ubuntu), um den Fernzugriff auf einen Docker Host zu demonstrieren; für den Fall, dass Sie mit eigenen Visual-Studio-Anwendungen experimentieren möchten, installieren wir auf diesem Server auch Samba für File Sharing mit Windows,
  • Visual Studio 2015 (Windows).

Sie könnten natürlich alle drei Umgebungen manuell mithilfe des Azure-Portals anlegen und konfigurieren. Ich möchte Ihnen jedoch hier zeigen, wie man eine solche Infrastruktur automatisiert mithilfe eines Bash-Skripts (createDemoEnv.sh) anlegt. Es verwendet das Azure Cross-Platform Interface (Installationsanleitung siehe hier) und ist daher plattformunabhängig. Getestet und entwickelt wurde es unter Ubuntu. Sie finden eine auf das Wesentliche gekürzte Version des Skriptcodes zum Anlegen der VMs mit Docker und Visual Studio 2015 in Listing 1. Den kompletten Code können Sie von GitHub herunterladen und bei sich ausführen. Dort finden Sie auch eine Schritt-für-Schritt-Anleitung, wie Sie die für das Skript notwendigen Systemvoraussetzungen schaffen. Zusätzlich zu createDemoEnv.sh finden Sie im erwähnten GitHub Repository auch zwei Hilfsskripte (openDockerHostShell.sh und openLinuxClientShell.sh) zum Öffnen einer Remote Shell mit SSH am Docker Host und Client.

Bitte achten Sie auf die Kommentare im Quellcode der kompletten Version des Skripts, die im Detail erklären, was das Skript in Ihrer Azure Subscription anlegen wird. Falls Sie das Skript in Ihrer Subscription ausführen möchten, ändern Sie bitte die Variable prefix am Beginn auf einen für Sie individuellen Wert (z. B. von dockersample auf dockerihrname). Wenn in diesem Artikel Hostnamen referenziert werden, müssen Sie jeweils dockersample durch Ihren Wert ersetzen (z. B. dockerihrnamehost statt dockersamplehost).

#!/bin/bash
[...]
prefix="dockersample"
vnetname="${prefix}net"
affinitygroupname="${prefix}ag"
region="North Europe"
storagename="${prefix}vmstorage"
linuxclientname="${prefix}linuxclient"
dockerhostname="${prefix}host"
vsname="${prefix}vs"
username="dockersample"
password="P@ssw0rd!"
machinesize="Basic_A1"
sshkey="sshkey"
ubuntuimage="b39f27a8b8c64d52b05eac6a62ebad85__Ubuntu-14_04-[...]"
vsimage="03f55de797f546a1b29d1b8d66be687a__Visual-Studio-2015-[...]"
[...]
# Define Azure affinity group
azure account affinity-group create --location "$region" --label "$prefix" "$affinitygroupname"

# Create storage account for VMs
azure storage account create --affinity-group "$affinitygroupname" --label "$prefix" 
--disable-geoReplication "$storagename"

# Define Azure network
azure network vnet create --affinity-group "$affinitygroupname" "$vnetname"

# Generate SSH key file
if [ ! -f ${sshkey}.key ]; then
  openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout "$sshkey".key -out "$sshkey".pem 
    -config createdockerkeys.config
  chmod 600 ${sshkey}.key
fi

# Create docker Linux client (also used to create other VMs)
azure vm create --ssh 22 --virtual-network-name "$vnetname" 
--vm-size "$machinesize" $linuxclientname --affinity-group "$affinitygroupname" 
--ssh-cert ${sshkey}.pem --no-ssh-password "$ubuntuimage" $username 

# Create docker host (note that this will create docker certs, too)
azure vm docker create --ssh 22 --virtual-network-name "$vnetname" 
--vm-size "$machinesize" $dockerhostname --affinity-group "$affinitygroupname" 
--ssh-cert ${sshkey}.pem --no-ssh-password "$ubuntuimage" $username 

# Create VS2015 trial VM (Windows)
# (Just for demo purposes, not part of the core sample. Uncomment
#  if you want to play with VS2015 together with Docker)
# azure vm create --rdp --virtual-network-name "$vnetname" 
# --vm-size "$machinesize" $vsname --affinity-group "$affinitygroupname" 
# "$vsimage" $username $password
[...]

Docker Hello World

Lassen Sie uns als Erstes manuell einen Ubuntu Container mit einer Bash Shell anlegen. Dazu verbinden Sie sich mit dem erwähnten Hilfsskript openLinuxClientShell.sh zum Docker Client. Führen Sie dort das Kommando docker –tls run -h tcp://dockersamplehost.cloudapp.net:4243 -i -t ubuntu /bin/bash aus (Abb. 3). Dieses Kommando erstellt über das Docker-REST-API (https, daher –tls) am Docker Host dockersamplehost.cloudapp.net (4243 ist der Port, den Azure für Docker verwendet) eine interaktive (-i) Bash Shell (/bin/bash) mithilfe des Base Image ubuntu und erstellt eine virtuelle TTY-Schnittstelle (-t). Falls Sie das Kommando direkt am Docker Host ausführen wollen, können Sie die -h-Option weglassen. Beim ersten Starten des Kommandos werden Sie kurz warten müssen, da das Base Image erst vom Docker Hub geladen werden muss (Abb. 3). Starten Sie den Container aber weitere Male, entfällt die Wartezeit und Sie können virtuelle Ubuntu-Container wie von Docker versprochen binnen Sekunden starten.

Abb. 3: Starten einer Bash Shell in einem Docker-Container

Wenn Sie über ein zweites Terminal einsteigen ohne zuvor die Bash Shell im Container mit exit zu verlassen, werden Sie bei Abfrage aller laufenden Container mit docker ps Ihre Shell entdecken (Abb. 4).

Abb. 4: Laufende Container mit „docker ps“ abfragen

Das Eingeben des Docker-Hostnamens bei jedem Kommando wird schnell lästig. Sie können daher eine Umgebungsvariable mit dem Kommando export DOCKER_HOST=tcp://dockersamplehost.cloudapp.net:4243 setzen und brauchen danach den Hostnamen nicht mehr anzugeben.

Im weiteren Verlauf dieses Beispiels werden wir Container nicht mehr manuell erstellen sondern dafür Dockerfiles verwenden. Falls Sie jedoch neugierig geworden sind und wissen möchten, welche Möglichkeiten Sie über die Kommandozeile noch haben, um Docker-Container zu verwalten, finden Sie alle Details hier. Dieses Wissen ist selbst dann wichtig, wenn Sie wie in Folge gezeigt immer mit Dockerfiles arbeiten möchten. Der Grund ist Debugging. In der Praxis müssen Sie öfter einen Container mit einer interaktiven Shell für ein Image öffnen, das nicht so funktioniert, wie Sie sich das vorstellen.

Ein Webserver mit statischem Inhalt

Als nächsten Schritt wollen wir einen Webserver mit statischem Inhalt (z. B. eine, mit Visual Studio erstellte Single Page App) in einem Docker-Container betreiben. Hier ist noch kein .NET im Spiel. Wir verwenden dafür den unter Linux beliebten Webserver nginx. Theoretisch könnten wir wie oben gezeigt einen leeren Docker-Container erstellen und den Webserver per Hand einrichten. Das ist aber nicht der empfohlene Weg. Stattdessen erstellen wir eine wiederverwendbare Anleitung für Docker, wie der Container zu erstellen ist. Das Werkzeug dafür sind Dockerfiles. Eine komplette Beschreibung aller Möglichkeiten von Dockerfiles würde den Rahmen dieses Artikels bei Weitem sprengen. Interessierte Leser finden eine gute Übersicht in der Docker-Dokumentation. Ich möchte Ihnen an dieser Stelle ein Grundverständnis vermitteln und konzentriere mich daher auf wenige, ausgewählte Funktionen.

Zum Anlegen eines Containers mit nginx brauchen wir nur wenige Docker-Kommandos wie Ausführen (RUN), Freigeben von Ports und Kopieren von Dateien (COPY). Damit könnten wir nginx ausgehend von einem leeren Ubuntu-Image installieren und konfigurieren. Es geht allerdings wesentlich einfacher.

Wie schon erwähnt, gibt es mit dem Docker Hub eine Plattform zum Austausch von Docker-Images. Da ein nginx-Webserver eine häufige Anforderung ist, steht bereits ein fertiges Image bzw. Dockerfile dafür zur Verfügung. Darauf können wir aufbauen. In diesem Zusammenhang ein Tipp zum Einlernen in Docker: Lesern, die an mehr Details interessiert sind, empfehle ich, einen Blick auf hierauf inklusive dem zugehörigen Dockerfile, das dort verlinkt ist, zu werfen. Man lernt viel über Dockerfiles, indem man die fertigen Dockerfiles populärer Images liest.

# Version 0.0.1
FROM nginx
MAINTAINER Rainer Stropek "rainer@timecockpit.com"
ENV REFRESHED_AT 2014-01-02
RUN apt-get -qq update
COPY *.html /usr/share/nginx/html/

Listing 2 zeigt unser Dockerfile. Es baut auf dem nginx-Image aus dem Docker Hub auf. Das wichtigste Kommando ist das COPY-Kommando am Ende. Es kopiert alle .html-Dateien in den nginx-Ordner. In diesem Zusammenhang ist wichtig zu verstehen, dass Docker vor dem Erstellen des Images alle Dateien aus dem aktuellen Context (Verzeichnis, in dem das gleich folgende Kommando docker build ausgeführt wird, plus alle Unterverzeichnisse) vom Docker Client zum Docker Daemon überträgt. Wir müssen uns also nicht selbst darum kümmern, unseren Webseiteninhalt auf den Docker Host zu übertragen. Ein Tipp dazu am Rande: Docker unterstützt ähnlich wie Git eine .dockerignore-Datei, die verwendet werden kann, um Dateien aus der Übertragung im Rahmen des Contexts auszuschließen (Details siehe hier).

Eine Alternative zu COPY ist die Verwendung eines Volume. Damit könnten verschiedene Docker-Container auf gemeinsame Dateien zugreifen, die in ihren jeweiligen Verzeichnisbaum gemountet werden. Eine typische Anwendung wäre ein Build-Server, der das Build-Ergebnis in ein Volume schreibt und der Webserver, der den Webseiteninhalt von dort liest. Näheres zu Docker Volumes findet man hier. In unserem einfachen Beispiel kommen wir mit COPY aus.

Abbildung 5 zeigt, wie man Schritt für Schritt aus dem Dockerfile in Listing 2 ein Docker-Image und danach einen Docker-Container erstellt. Der letzte Schritt zeigt, dass der Webserver auch tatsächlich funktioniert.

Abb. 5: Statischen Webserver in einem Docker-Container betreiben

Wenn Sie das Beispiel nachvollziehen, werden Sie merken, dass das Erstellen des Containers einige Zeit in Anspruch nimmt. Der Grund ist, dass das Docker-Base-Image für nginx beim ersten Ausführen heruntergeladen werden muss. Außerdem dauert auch das Abarbeiten des Dockerfiles seine Zeit. Hier kommt uns aber der Docker-Cache zu Hilfe. Abbildung 6 zeigt, wie das Aktualisieren unseres Docker-Images abläuft. Beachten Sie die Hinweise Using cache. Docker speichert den Zwischenstand des Image nach jeder Anweisung des Dockerfiles. Dieser Cache wird verwendet, bis Docker auf ein Kommando stößt, das sich geändert hat (in unserem Fall das COPY). Durch den Cache beschleunigt sich der Prozess der Imageerstellung gewaltig. Natürlich haben Sie die Kontrolle über den Cache und können ihn bei Bedarf auch abschalten. Nähere Informationen dazu finden Sie hier.

Abb. 6: Nutzung des Docker-Cache bei Aktualisierung des Image

Jetzt haben wir ein funktionierendes Docker-Image und könnten es auf Docker Hub in einem öffentlichen oder privaten Repository veröffentlichen (docker push). Von dort könnten es andere Entwickler holen, um es z. B. in einer Produktivumgebung zu veröffentlichen. Dabei wäre es kein Problem, mehrere Container basierend auf unserem Image gleichzeitig zu starten, um z. B. einen Cluster aufzubauen.

Alternativ zur manuellen Veröffentlichung im Docker Hub könnten wir mit Docker auch einen automatisierten Build für unser Image erstellen, der das Dockerfile z. B. von GitHub holt und automatisch baut, wenn sich am GitHub Repository etwas ändert. Auf die manuelle oder automatische Veröffentlichung von Images auf Docker Hub gehe ich aber in diesem Artikel nicht näher ein. Interessierte Leser finden Details hier.

.NET mit Docker

Nun kommen wir zum letzten und für .NET-Entwickler wahrscheinlich spannendsten Szenario: ASP.NET nNext MVC in Docker unter Linux. Falls Sie mit der plattformunabhängigen, neuen ASP.NET-Version vNext noch nicht vertraut sind, empfehle ich einen Blick hierauf.

Als Erstes brauchen wir ein Basisimage, in dem wir die neue .NET-Laufzeitumgebung (KRE, K Runtime Environment) installiert ist. Die Voraussetzung dafür ist Mono. Dank Docker Hub brauchen wir uns weder um die Installation von Mono noch um die der KRE zu kümmern. Unser Image (Listing 3, steht hier zum Download zur Verfügung) kann auf einem von Microsoft zur Verfügung gestellten Image aufsetzen, das ASP.NET vNext schon fix und fertig installiert hat (microsoft/aspnet). Auch hier gilt: Ein Blick auf die Dockerfiles von mono und microsoft/aspnet hilft, um tieferes Verständnis für Docker aufzubauen.

Ausgestattet mit einem Base-Image für ASP.NET können wir uns ein Image zum Testen einer ASP.NET-MVC-Anwendung erstellen. In diesem Artikel konzentriere ich mich auf Docker und werde daher nicht auf die Grundlagen von Programmierung mit ASP.NET eingehen. Stattdessen holen wir uns in diesem Beispiel die offiziellen ASP.NET Samples, die Microsoft hier auf GitHub zur Verfügung stellt. In der Praxis würden Sie Ihre eigene Anwendung z. B. aus Git holen oder über ein Docker Volume von einem Build-Server in einem anderen Docker-Container erhalten.

Das Dockerfile, das unsere ASP.NET-Testumgebung definiert, finden Sie in Listing 3 (steht hier zum Download zur Verfügung). Achten Sie darauf, dass es auf dem Microsoft ASP.NET Base Image basiert (FROM microsoft/aspnet). Durch das ENTRYPOINT-Kommando am Ende des Dockerfiles wird festgelegt, dass beim Starten eines Containers jedes Mal das Skript refreshAndRunSample.sh (Listing 4) ausgeführt werden soll. Es holt den aktuellen Stand des Quellcodes aus GitHub, stellt eventuell fehlende Pakete wieder her und startet den Kestrel-Webserver. Die Erstellung des Image aus dem Dockerfile erfolgt mit dem Kommando docker –tls build -t myapp .

# Image for testing ASP.NET vNext samples
FROM microsoft/aspnet
MAINTAINER Rainer Stropek "rainer@timecockpit.com"
ENV REFRESHED_AT 2015-01-02

ENV SOURCE_DIR /app/src

# Create directory that will receive sourcecode
RUN mkdir -p $SOURCE_DIR
WORKDIR $SOURCE_DIR

# Copy the script that will run whenever we start a new container
# from this image
COPY refreshAndRunSample.sh $SOURCE_DIR/
RUN chmod a+x $SOURCE_DIR/refreshAndRunSample.sh

# Get the initial version of the ASP.NET vNext sample and restore
# packages. In practice you could e.g. get your application's code
# from a git repository.
RUN apt-get -qqy install git
RUN git init 
 && git pull https://github.com/aspnet/Home.git 
 && cd  samples/HelloMvc/ 
 && kpm restore

ENTRYPOINT ["/app/src/refreshAndRunSample.sh"]
#!/bin/bash

# Get latest version from GitHub
git pull https://github.com/aspnet/Home.git

# Restore packages
cd samples/HelloMvc/
kpm restore

# Start kestrel web server
k kestrel

Jetzt können wir den Docker-Container mit docker –tls run -d -t -p 80:5004 myapp starten. Abbildung 7 Fehler: Referenz nicht gefunden zeigt im Fenster links oben, wie es gemacht wird. Achten Sie darauf, dass wir in diesem Szenario immer noch vom Docker Client aus arbeiten und es keine Notwendigkeit gibt, dass wir uns direkt auf den Docker Host verbinden müssten.

Das zweite Fenster zeigt, wie man mit den docker-Kommandos ps und logs die laufenden Container ermitteln und die zugehörigen Logs abfragen kann. Wenn am Ende des Logs der Hinweis Started steht, haben wir es geschafft. Die in Abbildung 7 Fehler: Referenz nicht gefunden im Browser zu sehende MVC-Anwendung läuft unter Linux in unserem Docker-Container.

Abb. 7: ASP.NET unter Linux in einem Docker-Container

Zusammenfassung

Docker ermöglicht viele faszinierende neue Szenarien für Anwendungsvirtualisierung, die bisher mit Hyper-V und Co. nicht denkbar waren. Leider gibt es Docker im Moment noch nicht für Windows. Dank der Plattformunabhängigkeit von ASP.NET können wir .NET-Entwickler Docker jedoch schon jetzt nutzen, sofern der Betrieb unserer Anwendungen unter Linux in Frage kommt.

Zum Abschluss jedoch ein Wort der Warnung: Wenn Sie sich schon jetzt mit ASP.NET und Docker auseinandersetzen wollen, seien Sie sich bewusst, dass Sie es mit Betasoftware zu tun haben. Ein produktiver Betrieb von geschäftskritischen Anwendungen ist zum jetzigen Zeitpunkt auf keinen Fall empfehlenswert. Der aktuelle Entwicklungsstand sollte aber bereits reichen, um die zukünftige Bedeutung von Docker für Ihre Projekte ausloten zu können. Ich hoffe, dieser Artikel hilft Ihnen beim Einstieg und hat Ihre Begeisterung für .NET mit Docker entfacht.



Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -