Legacy Asp.Net Webforms Portierung nach Azure

15 Oktober 2018
Alexander Baier
Im Folgenden wird die Portierung einer über 10 Jahre gewachsenen Web-Anwendung geschildert.
Hierbei gehen wir auf den Ist-Zustand der Anwendung ein und beschreiben die nötigen Anpassungen.
Erkenntnisse zu einigen Azure Services und ein Ausblick über mögliche weitere Schritte schließen den Bericht.

"Das Ziel dieser Übung war, die Anwendung mit möglichst wenig Modifikationen auf Azure App Services zu portieren!"


Die App Services wurden gewählt, um den administrativen Aufwand einer virtuellen Maschine deutlich zu verringern.

Im ersten Schritt haben wir die Abhängigkeiten der Anwendung identifiziert. Für die Persistenz kam bislang eine SQL Server Datenbank zum Einsatz. Des Weiteren wurden Dateien von bis zu 2GB Größe im Filesystem abgelegt. Für verschiedene periodische Aufgaben war ein Windows Service zuständig. Dieser hatte sowohl Zugriff auf die Datenbank, als auch auf die hochgeladenen Dateien.

Im zweiten Schritt wurde das Angebot auf Azure gesichtet, um zu entscheiden, welche Abhängigkeit zu welchem Service passt. Für die Datenbank haben wir auf Azure SQL Database zurückgegriffen. Wir haben für diesen Test die Datenbank per SQL-Skript mit generierten Test-Daten gefüllt. Dies lässt sich wie gewohnt mit dem SQL Server Management Studio erledigen. Wäre die Anforderung eine bestehende Produktionsdatenbank nach Azure zu migrieren, reicht dieser Schritt wahrscheinlich nicht aus. In dem Fall finden sich weitere Informationen zur Migration von Daten in einem Whitepaper von Microsoft "Choosing your database migration path to Azure".

Das Speichern von teilweise Gigabyte großen Dateien haben wir mit Azure Blob Storage gelöst. Der Blob Storage ermöglicht das Ablegen von großen "Dateien", welche anhand eines Identifiers referenziert werden können. Angebunden an die Anwendung wird der Speicher über eine Http REST Schnittstelle.

Die Web-Anwendung selbst und der ergänzende Windows-Service wurden in "Azure App Services" untergebracht. Dies ist eine vollständig verwaltete Plattform, die das Abstraktionslevel im Vergleich zum Hosten auf einer virtuellen Maschine noch mal erhöht. Die App Services sind auf das Hosten von Web-Anwendungen ausgelegt. Somit sind beispielsweise Sachen wie horizontale Skalierung inklusive Load Balancing, oder Deployments in Staging-Umgebungen im Handumdrehen konfiguriert. Ein Teil der App Services sind die WebJobs. Dies ermöglicht das Ausführen von Skripten oder Kommandozeilen-Programmen im Hintergrund der WebApp.

Um die oben identifizierten Dienste auf Azure auch tatsächlich nutzen zu können, musste die Anwendung an einigen Stellen angepasst werden. Hierzu wurden zunächst jegliche Dateisystemzugriffe entfernt und durch ein einfaches Storage-Interface ersetzt. Dieses Interface beschreibt einen Key-Value Storage, wobei die Keys den Namensrestriktionen von Azure-Resourcen-Name entsprechen müssen. Die Werte sind hier dann die bisher als Dateien gespeicherten Inhalte. In diesem Fall waren die bisher verwendeten Namen bereits valide, sodass die Portierung gut von der Hand ging. Um die Anwendung nun an Azure Blob Storage anzubinden, wurde das Interface mit Hilfe des Azure Storage SDKs implementiert. Auch dieser Schritt war nur ein dünner Wrapper um das bereits vorhandene SDK.


Die Anbindung der Datenbank war im Vergleich wesentlich einfacher, denn hier mussten lediglich die Verbindungsinformation in der Konfiguration ausgetauscht werden. Zuletzt galt es noch den Windows Service in eine einfache Konsolenanwendung umzuschreiben. Die alte Architektur hat hier ausschließlich von den Start- und Stop-Events der Windows Service API Gebrauch gemacht. Im Azure-Kontext ist diese Start- und Stopfunktionalität auf Dateisystemebene abgebildet. Das Pendant zum Stop-Event im Azure-Kontext ist eine bestimmte Datei, welche von der Azure App Services Laufzeitumgebung erstellt wird, sobald der WebJob beendet werden soll. Der Pfad dieser Datei wird über eine Umgebungsvariable bereitgestellt. In unserem Fall wurde ein Filewatcher eingesetzt, welcher die Stopfunktionalität unseres Services aufruft, sobald besagte Datei erscheint. Ein direktes Pendant zum Start Event gibt es nicht, hier wird lediglich die Main-Methode der Konsolenanwendung aufgerufen.

Mit diesen Änderungen an Bord konnte die Web-Anwendung dann das erste Mal auf Azure laufen.

Im nächsten Abschnitt werden wir ein paar Azure Dienste hervorheben, die wir im Laufe dieses Experiments ausprobiert haben.

Der erste Dienst nennt sich "Azure Monitor". Dieser bietet Funktionalität, um diverse Mess- und Logdaten zu sammeln, auszuwerten und zu visualisieren. Ein großer Vorteil hierbei ist, dass sich Daten aus verschiedenen Quellen leicht zusammen behandeln lassen. Dies ermöglicht zum Beispiel das einfache Korrelieren von Trace-/Logmessages mit der aktuellen Auslastung der Hardware. Oder eine umfassende Darstellung der Situation, in der sich die App befand, bevor eine Exception aufgetreten ist. Die meisten Azure-Dienste bringen von Haus aus eine Integration mit Azure Monitor mit. Sollen auch anwendungsspezifische Informationen gesammelt werden, so genügt es das Nugetpaket ApplicationInsights und dessen Abhängigkeiten zu installieren. Ohne weitere Mühe bekommt man damit bereits wertvolle Informationen, wie z.B. detaillierte Exception Logs, oder Antwortzeiten von Http-Requests.

Anhand eines Beispiels lässt sich der Mehrwert von Azure Monitor noch mal verdeutlichen. Nehmen wir an, in unserer Web-Anwendung tritt eine Exception auf. Ein fein einstellbares Alert-System kann nun die zuständige Person(en) benachrichtigen. Diese Person hat nun die Möglichkeit alle Logs/Traces zu betrachten, welche zu dem Http Request der Exception passen. Hierzu gehören im speziellen Datenbankaufrufe inklusive der SQL Anfrage, Logmessages aus der Applikation und Zugriffe auf Storage Accounts, wie Azure Blob Storage. Die Aufrufe von externen Systemen werden automatisch als "Dependency" klassifiziert und in einem Wasserfalldiagramm anhand ihrer Ausführungszeit dargestellt. Neben der Ausführungszeit ist auch angezeigt, ob besagter Aufruf erfolgreich oder fehlerhaft war.



Den Anwendungsentwicklern ist es auch möglich eigene Dependencies zu definieren, welche dann ebenfalls in dieser Ansicht auftauchen. Das Verschicken von E-Mails über einen SMTP-Server wäre hier ein passendes Beispiel. Hat man zuvor das Snapshot-Debugging Feature aktiviert, ist es möglich noch einen ganzen Schritt weiterzugehen. Ein Snapshot der Laufzeitumgebung zur Zeit der Exception steht dann, nämlich zur Verfügung. Dieser kann im Visual Studio geöffnet werden, um die Werte verschiedener Variablen zu inspizieren. Somit kann man sich ein gutes Bild von der Situation machen, in der sich die Anwendung befand, bevor die Exception ausgelöst wurde.

Neben der Analyse von Abstürzen können diese Werkzeuge auch zum Betrachten der Performance benutzt werden. So können auf Knopfdruck die langsamsten Http-Requests aufgelistet werden. Von diesen kann dann ein einzelner als Beispiel betrachtet werden. Das eben beschriebene Wasserfalldiagramm zeigt effektiv auf, ob ein besagter Http-Request zu viele Datenbankanfragen abschickt, oder einige davon zu lange brauchen, um eine Antwort zu liefern. Des Weiteren ist es möglich für eine kurze Zeit einen Profiler mitlaufen zulassen. Dessen Ergebnis hilft beim Aufspüren von langsamem Anwendungscode.

Als Zweites wollen wir hier noch einen Blick auf die Skalierung der Anwendung unter Azure App Services werfen. Die Skalierung selbst funktioniert ganz bequem über das Bedienen eines Sliders in der UI. Danach dauert es wenige Minuten und die gewünschten Änderungen sind aktiv. Man unterscheidet hier zwischen horizontalem und vertikalem Skalieren. Beim horizontalen Skalieren wird einer Instanz mehr oder weniger Rechenleistung zur Verfügung gestellt. Skaliert man hingegen vertikal, wird die Anzahl der Instanzen verändert.

Während im vertikalen Fall auf Architektur-Ebene in der Anwendung nichts verändert werden muss, gibt es im horizontalen Fall ein paar Sachen zu beachten:

  • Die Anwendung selbst muss damit auskommen, dass nun mehr als eine Instanz existiert.
  • Locks in der Anwendung, um Serialität zu garantieren, funktionieren nun zum Beispiel nicht mehr.
  • Außerdem ist ein Load-Balancer vonnöten, welcher ankommende Anfragen auf die Instanzen verteilt.
  • Dieser Teil wird von den App Services automatisch übernommen.
  • Um das Load-Balancing müssen sich die Entwickler also keine Gedanken machen.



Ein drittes Feature ist ebenfalls Teil der App Services Welt und nennt sich "Deployment Slots". Hiermit steht den Entwicklern eine fertige Staging Lösung bereit. Somit lassen sich Appliktionsupdates ohne Ausfallzeit und ohne Aufwärmzeit vollziehen. Als Beispiel kann es einen Produktions- und einen Stagingslot geben. Während die aktuelle Version der App im Produktionsslot läuft, kann eine neue Version im Stagingslot deployt werden. Azure ruft dann bestimmte Routen der Anwendung auf, um diese "aufzuwärmen"--also zu starten und zu initialisieren. Sobald die Anwendung im Stagingslot läuft, hört der vorgeschaltete Load-Balancer auf Request zum Produktionsslot zu routen und leitet diese stattdessen zum Stagingslot. Somit bekommen die Endanwender gar nicht mit, dass die Anwendung gerade neu deployt wurde.

Es gibt noch viele weitere Features im Bereich der App Services im Speziellen und in Azure im Generellen. Dieser Artikel sollte erst mal einen kleinen Einblick geben.

Unser Fazit ist ein sehr positives!

Durchweg ist es einfacher infrastruktur-nahe Aufgaben zu erledigen. Dies gelingt oft auch mit nur geringen administrativen Kenntnissen. Außerdem erhält man mit geringem Aufwand eine sehr tiefgehende Sicht auf die eigenen Systeme. Zu guter Letzt ist hier die Flexibilität hervorzuheben. Es ist ein Leichtes in wenigen Minuten eine Web-Anwendung samt Datenbank und Storage-Backend aufzuziehen. Gerade im Entwicklungsbetrieb lassen sich so Testsysteme schnell bereitstellen und dann auch wieder abreißen.

In Zukunft wäre es interessant sich genauer mit den Geo-Replikationsfeatures von SQL-Server zu beschäftigen, um so Antwortzeiten für Klienten weltweit kleinzuhalten. In dem Zuge ist dann auch der Vergleich mit dem Cosmos DB Ökosystem empfehlenswert. Die Fähigkeiten der Cloud-basierten Load- und Performancetests werden wir ebenfalls im Blick behalten. Momentan kommen diese leider noch bei Weitem nicht mit den Features aus, die ein halbwegs realistischer Lasttest benötigt.