Kolumne: Stropek as a Service

C#-Innovationen machen die Sprache fit für Serverless in der Cloud
Keine Kommentare

Was macht eine Plattform fit für moderne Cloud-Anwendungen? Die Liste der möglichen Antworten wäre lang. In dieser Kolumne greife ich drei grundlegende, aus meiner Sicht wichtige Punkte heraus und möchte zeigen, dass .NET und C# in dieser Hinsicht Musterschüler sind.

Die drei Punkte, die ich in dieser Ausgabe der Kolumne beleuchten will und die meiner Meinung nach wichtig für die Attraktivität einer Plattform für moderne Cloud-Anwendungen sind, umfassen Folgendes:

  • Die Containerunterstützung muss gut sein.
  • Die Startup Time spielt eine immer größere Rolle, da elastische Skalierung und Serverless wichtiger werden. Server, die einmal gestartet werden und dann eine gefühlte Ewigkeit unverändert laufen, werden selten.
  • Unterstützung für moderne Kommunikationsprotokolle muss vorhanden und Entwicklungsarbeiten hinsichtlich kommender Protokollinnovationen müssen erkennbar sein.

Containerunterstützung

Microsoft hat seine Liebe zu Docker-Containern relativ früh erkannt und das .NET-Team kam zu einem frühen Zeitpunkt mit offiziellen Base Images für Linux und Windows heraus. Eine .NET-Anwendung in Container zu packen wurde dadurch ziemlich einfach. Die gute Unterstützung von Containern in Microsoft Azure und verbundenen Technologien (z. B. Azure Pipelines) tat das ihre dazu, dass .NET und Container gute Freunde geworden sind.

Es ist eine Sache, auf einen populären Zug aufzuspringen. Es ist eine andere, eine Technologie langfristig zu unterstützen. In Sachen Container macht Microsoft in .NET aus meiner Sicht alles richtig. Ein Blick in den Docker Hub [1] zeigt, dass Microsoft konsequent ist, was die Veröffentlichung aller wichtigen .NET-Versionen betrifft. Entwicklungsteams, die auf .NET setzen, brauchen seit Jahren keine Angst zu haben, dass Base Images für aktuelle Versionen zeitlich verspätet kommen. Auch wer Previews wie .NET 5 probieren will, findet alles dafür Notwendige in den Docker Registries von Microsoft.

Neben den Base Images spielt die Toolunterstützung eine große Rolle. Warum soll schließlich jede .NET-Entwicklerin ihr eigenes Dockerfile für ihre Microservices erfinden und dafür erst alle einzuhaltenden Best Practices lernen? Visual Studio hat gute Docker-Unterstützung für .NET-Core-Projekte eingebaut. Egal, ob man nur ein Dockerfile will oder eine komplexere Anwendung entwickelt, die Docker Compose oder Kubernetes braucht, Visual Studio kann mit all diesen Dingen gut umgehen [2].

So richtig zu schätzen lernt man die Docker-Unterstützung in Visual Studio in der täglichen Arbeit. Hier nimmt einem die IDE eine Menge ab und sorgt für reibungslose Entwicklungsprozesse. Einige Beispiele:

  • Debugging mit F5 trotz Betrieb im Docker-Container oder in Kubernetes? Kein Problem, funktioniert einfach, sowohl im lokalen Docker Host als auch mit Azure Dev Spaces in Kubernetes [3].

  • Dev-Zertifikate für ASP.NET-Core-Webanwendungen mit SSL-Support? Nichts leichter als das, Visual Studio kümmert sich um die Zertifikate automatisch.

  • Überblick über die Container, ihre Konfiguration, Port-Mappings etc. verloren? Visual Studio hat ein eigenes Containers-Fenster bekommen, mit dem man den Überblick behält, die Container-Logs im Blick hat, in das Dateisystem der Container schauen kann und vieles mehr [4].

  • Auch der kleine Bruder von Visual Studio, Visual Studio Code, hat ausgezeichnete Docker-Unterstützung erhalten. Als ein Beispiel möchte ich die Visual-Studio-Code-Remote-Containers-Erweiterung erwähnen. Damit kann man seine gesamte Entwicklungsarbeit in einen Container verlegen, ohne auf die Annehmlichkeiten von Visual Studio Code verzichten zu müssen.

Startup Time

Früher machten sich Entwickler von Serverkomponenten im Gegensatz zu Entwicklern von Desktop-Apps oder Mobile Apps nicht allzu viele Sorgen um die Startup Time. Im Gegenteil, man verlagerte zeitaufwendige Aufgaben bewusst in den Startvorgang, um im laufenden Betrieb (aka Steady State) möglichst schnell zu sein. Hintergrund war, dass Serverumgebungen relativ statisch waren. Das Neustarten war die Ausnahme, nicht die Regel.

Durch automatische Skalierung oder sogar Serverless-Betrieb in der Cloud verändert sich die Situation grundlegend. Serverumgebungen sind dynamisch, Cold Starts sind nicht selten. Ihre Geschwindigkeit bestimmt bei Verwendung von Serverless sogar die Antwortzeiten, die der Konsument der App oder des API wahrnimmt, da Serverkomponenten nicht selten erst gestartet werden, wenn sie jemand braucht. Auch wenn Serverless-Dienste wie Azure Functions immer mehr Tricks lernen, um die Anzahl an Cold Starts zu reduzieren, ganz vermeiden lassen sie sich nicht (vgl. Microsoft Research: Serverless in the Wild: Characterizing and Optimizing the Serverless Workload at a Large Cloud Provider [5]).

Noch vor ein paar Jahren war ASP.NET berühmt-berüchtigt für eine unangenehm lange Startzeit. Die Zeit, die der Just-in-Time-(JIT-)Compiler verbrauchte, war dabei in vielen Fällen ein bestimmender Faktor. Hätte sich hier nichts verändert, wäre C# keine gute Plattform für moderne Cloud-Anwendungen. Microsoft hat aber in den letzten Jahren konsequent am JIT-Compiler gearbeitet und plant für .NET 5 und folgende Versionen weitere Verbesserungen. Hier einige Beispiele:

  • Mit .NET Core 3 kam Tiered Compilation [6]. Aktiviert man diese Plattformfunktion, verzichtet der JITer in vielen Fällen auf gewisse Optimierungsschritte und kann dadurch rascher ausführbaren Code anbieten. Häufig aufgerufene Methoden werden erst im Hintergrund optimiert und ersetzen den beim Startup erzeugten, weniger performanten Code.

  • Ebenfalls mit .NET Core 3 wurden Ready-to-Run Images [7] (R2R) eingeführt. R2R ist eine Form von Ahead-of-Time-(AoT-)Kompilierung. Der erzeugte Code ist zwar noch kein direkt ausführbarer Maschinencode, er ist aber viel näher dran als der bekannte IL-Code, den der C#-Compiler erzeugt. Dadurch nimmt man dem JITer Arbeit ab, die Startup Performance wird verbessert. Leider hat dieser Vorteil auch einen Nachteil: Der R2R-Code ist größer als der IL-Code. Insofern muss von Projekt zu Projekt gemessen werden, ob man von R2R profitiert.

  • Die beste Startup Performance hätte man mit einer AoT-Variante, die aus C# direkt ausführbaren Maschinencode erzeugt (wie z. B. in C++, Go oder Rust). Für Windows-UWP-Anwendungen hat Microsoft .NET Native, einen AoT-Compiler für C#, entwickelt, der allerdings nur einen Teil des .NET API unterstützte. Das Mono-Projekt war ebenfalls gezwungen, diesen Schritt zu gehen, da er für iOS-Apps notwendig ist. Mit .NET 5 wachsen die verschiedenen .NET-Varianten und damit auch die AoT-Implementierungen zusammen. Die erste .NET-Variante, die davon profitieren wird, ist Blazor WebAssembly. Microsoft hat angekündigt, dass AoT-Kompilierung als Option aber auch für andere Anwendungen verfügbar sein wird, die hohe Anforderungen an die Startup Performance haben [8].

  • Ein wichtiger Puzzlestein, der notwendig ist, um das volle Potenzial von AoT auszuschöpfen, sind Mechanismen zur Reduktion von C#-Code, der auf Runtime Reflection basiert. Reflection beeinflusst die Startup Performance negativ, da es häufig beim Programmstart zum Konfigurieren von Dependency Injection genutzt wird. Außerdem ist Reflection-basierender Code schlecht für die Optimierung des erzeugten Codes durch den Linker, da zur Übersetzungszeit nicht festgestellt werden kann, welche Codeteile zur Laufzeit nicht benötigt und daher entfernt werden können. Eine Alternative zu Runtime Reflection wäre Compile-Time Reflection mit verbundener Codegenerierung. Genau diese Funktion fügt Microsoft gerade zu .NET hinzu. Sie heißt C# Source Generators und wurde kürzlich in einer Preview-Version vorgestellt [9].

Moderne Kommunikationsprotokolle

Größere Cloud-Lösungen bestehen heute im Backend in der Regel aus einer Vielzahl von Services. Darüber hinaus ist die Programmlogik auf Clients und Server verteilt. Der Effizienz von Kommunikationsprotokollen kommt daher eine große Bedeutung zu.

Das .NET-Team hat in den letzten Jahren viel in dieser Hinsicht investiert. Mit Project Bedrock [10] wurden Abstraktionen und Cross-Cutting Concerns entwickelt, die es Bibliotheken wie zum Beispiel SignalR erlauben, auf unterschiedlichen Basisprotokollen (z. B. TCP, Websockets) aufzusetzen. Kestrel, der Webserver von .NET Core, nutzt ebenfalls die Ergebnisse von Bedrock, um nicht nur HTTP zu unterstützen, sondern auch beispielsweise TCP Sockets (System.Net.Sockets) oder libuv. Kommende Protokollinnovationen wie QUIC werden mit Hilfe von Bedrock einfacher und schneller in .NET genutzt werden können.

Verbesserungen bei Protokollen geschehen nicht nur im inneren des .NET Frameworks. Die Unterstützung von gRPC (google RPC) in C# ist mittlerweile sehr gut. Das Tooling ist nahtlos in Visual Studio eingebettet und das Zusammenspiel mit ASP.NET Core ist reibungslos. In Sachen Performance wurden kürzlich enorme Fortschritte erzielt. C# überholt in manchen Szenarien mittlerweile die gRPC Performance von Go [11]. Funktional zeigen experimentelle Projekte wie gRPC HTTP API [12], dass das .NET-Team eine Menge spannender Ideen für die Zukunft hat, die für Cloud-Anwendungen eine große Rollen spielen können.

Fazit

Langjährig etablierte Plattformen haben mit fundamentalen Veränderungen oft Schwierigkeiten. Unterschiedliche Interessen in der großen Nutzergruppe müssen unter einen Hut gebracht werden. Die einen wollen Stabilität und möglichst wenig Änderung. Die anderen fordern Innovationen und erwarten, dass die Plattform am Puls der Zeit bleibt, vielleicht sogar Innovationstreiber ist. In der Vergangenheit gab es Zeiten, in denen ich daran gezweifelt habe, dass Microsoft mit .NET den richtigen Weg geht und die Plattform erfolgreich in die neue Ära des Cloud Computings bringen kann. Die Entwicklungen der letzten Monate und Jahre haben mich davon überzeugt, dass .NET und C# eine wirklich gute und zukunftsträchtige Wahl für die Entwicklung von Services und Apps in der Cloud sind.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Abonnieren
Benachrichtige mich bei
guest
0 Comments
Inline Feedbacks
View all comments
X
- Gib Deinen Standort ein -
- or -