August 2019

Band 34, Nummer 8

[Azure]

Wichtige Angelegenheiten: Server- und zustandslose Codeausführung mit Azure Functions

Von Srikantan Sankaran | August 2019

Das serverlose Architekturmuster hat sich als Grundpfeiler der Legacymodernisierung in der öffentlichen Cloud etabliert. Die meisten Plattformdienste werden heute mit dem Schwerpunkt auf hohem Durchsatz, niedrigen Kosten und überlegener Benutzerfreundlichkeit und Akzeptanz konzipiert, verpackt und angeboten. Sie berücksichtigen im Allgemeinen Skalierungsaspekte und bieten eine Plattform für Lösungsdesigner, um Funktionen umzusetzen, die ansonsten eine erhebliche Komplexität und Infrastruktur erfordern würden.

Natürlich ist „nutzungsbasierte Bezahlung“ als Geschäftsmodell einer der wichtigsten Grundsätze der öffentlichen Cloud. „Serverlos“ erweitert dieses Modell zusätzlich, indem wichtige Entscheidungen darüber getroffen werden, wann und wie Computeressourcen bereitgestellt werden, was zur Kostenkontrolle während der Laufzeit beiträgt. Um diese Philosophie zu vertiefen und dieses Muster erfolgreich umzusetzen, hilft es, Azure Functions als einen Schlüsseldienst zu verstehen, wenn wir Entwurfs- und Implementierungsgrundsätze ausarbeiten.

Azure Functions hostet benutzerdefinierten Code, ohne dass der Benutzer die Computeressourcen angeben oder bereitstellen muss, auf denen der Code ausgeführt werden soll. Durch die Bereitstellung von Computeressourcen bei Bedarf werden unvorhersehbare Workloads unterstützt, und die Bezahlung erfolgt tatsächlich nutzungsbasiert. Ressourcen werden spontan skaliert, um eingehende Auslastungen abzugleichen, Hochverfügbarkeit zu gewährleisten und den Verwaltungsmehraufwand zu minimieren.

Azure Functions wurde als ein Dienst konzipiert, der in erster Linie damit betraut ist, schnelle Bursts von Ereignissen im richtigen Maßstab zustandslos zu verarbeiten. Aber die Rolle der Technologie hat sich erheblich erweitert. In diesem Artikel untersuchen wir die verschiedenen Workload- und Bereitstellungsszenarien von Azure Functions und wie sie Unternehmen bei der Implementierung serverloser Funktionen unterstützen können.

Designoptionen

Die serverlosen Möglichkeiten von Azure Functions können in einer Vielzahl von Anwendungsszenarien genutzt werden, sodass Unternehmen die Funktionalität effizient einsetzen können, ohne sich in Mehraufwand und Infrastruktur zu verstricken. Es gibt wichtige Entwurfsüberlegungen, die die Teams bei der Planung berücksichtigen sollten. Schauen wir uns einige davon hier an.

Zustandslose im Vergleich zu zustandsbehafteter Ausführung: Der häufigste Anwendungsfall für Azure Functions ist die Ausführung schneller Bursts von zustandslosem benutzerdefiniertem Code im richtigen Maßstab. Diese Szenarien zeichnen sich durch ihre kurze Dauer (nicht mehr als fünf Minuten) und Code aus, der keinen Zustand oder Sperren über Anforderungen hinweg aufrecht enthält. Es gibt keine Anforderung, eine strenge Reihenfolge bei der Ausführung von Anforderungen oder der Implementierung eines Transaktionskontexts über Anforderungen hinweg einzuhalten.

Beachten Sie, dass einige dieser Szenarien zwar nicht für die Ausführung durch Stateless Azure Functions gedacht sind, aber durch zusätzliche Entwurfselemente implementiert werden können.

Azure Functions bietet Hooks für die Integration mit einer Vielzahl von Azure-Diensten durch Trigger und Ein- und Ausgabebindung. Azure Functions kann auch in Prozessabläufen verwendet werden, die mit Azure Logic App mit benutzerdefinierter Logik implementiert wurden.

Azure Functions kann entweder über den Verbrauchstarif gehostet werden, der echte serverlose Merkmale bietet, oder über einen App Service-Plan, der eine dedizierte Kapazitätsoption verwendet. Im letzteren Fall gelten einige der Einschränkungen des Verbrauchstarifs nicht, z.B. das Timeout pro Anforderung nach fünf Minuten. Mehr über die Unterschiede zwischen diesen Hostingoptionen erfahren Sie unter bit.ly/2xcCXsO.

Was geschieht, wenn Ihr benutzerdefinierter Code eine Reihe von zustandslosen Tasks für die sequenzielle Ausführung orchestrieren muss, oder wenn er die parallele Ausführung von zustandslosen Tasks im richtigen Maßstab mit einer Aggregation der Ergebnisse in einem Auffächerungs- und Einfächerungsszenario erfordert? Oder denken Sie an langlebige Orchestrierungen, die auf Zustandsänderungen durch externe Ereignisse überwacht werden müssen oder während des Ausführungslebenszyklus menschliche Eingriffe erfordern. Auch die Notwendigkeit einer zuverlässigen Ausführung, die das Konzept der Wiederholung vom letzten Fehlerpunkt über Prüfpunkte und Wiedergabe implementiert, anstatt eine regenerative Ausführung aller Tasks vorzunehmen, ist wichtig.

Diese Aufgaben erfordern viel mehr, als ein einfacher zustandsloser Azure Functions-Dienst bereitstellen kann. In diesen Fällen kann die Erweiterung Azure Durable Functions verwendet werden, um robuste Entwurfsmuster zu implementieren. Durable Functions basiert auf einer Orchestrierungsfunktion, die für die Verarbeitung des Zustands und die Gewährleistung der Zuverlässigkeit über zahlreiche Aktivitätsfunktionen hinweg verantwortlich ist.

Kurz gesagt, ähnelt die Aktivitätsfunktion Stateless Azure Functions insofern, dass sie alle E/A-Vorgänge über Eingabe- und Ausgabebindungen verarbeitet. Aber im Gegensatz zu Stateless Azure Functions wird sie ausschließlich durch eine Orchestrierungsfunktion ausgelöst.

Der Code in Abbildung 1 zeigt ein Beispiel für eine Orchestrierungsfunktion, die eine Verkettung mehrerer Aktivitätsfunktionen implementiert.

Abbildung 1: Verkettung mehrerer Aktivitätsfunktionen

public static async Task<object> Run (DurableOrchestrationContext context)
{
  try
  {
    var output1 = await context.CallActivityAsync<object>("ActivityFunction1");
    var output2 = await context.CallActivityAsync<object>(" ActivityFunction2",
      output1);
    var output3 = await context.CallActivityAsync<object>(" ActivityFunction3",
      output2);
    return  await context.CallActivityAsync<object>(" ActivityFunction4", output3);
  }
  catch (Exception)
  {
    // Error handling or compensation goes here.
  }
}

Durable Entities, eine Funktion, die Durable Functions kürzlich hinzugefügt wurde, implementiert das Muster „Virtueller Akteur“ als Funktion. Zustandsbehaftete Durable Entities können mithilfe von Orchestrierungsfunktionen verwaltet werden, und der Zugriff darauf kann über Orchestrierungsclients erfolgen. Als ich diesen Artikel geschrieben habe, befand sich der Support für Durable Entities im Alphastadium und war noch nicht bereit für Produktionsbereitstellungen. Um zu verstehen, was Durable Entities sind und wie sie von Durable Functions bereitgestellt werden, lesen Sie bit.ly/2RAA12B.

Workloads und Bindung: Wir haben bereits herausgestellt, dass serverlose Architekturen wie Azure Functions von Natur aus für unvorhersehbare Workloads geeignet sind, bei denen Perioden der Inaktivität von plötzlichen Nachfragebursts unterbrochen werden. Für diese Szenarien wäre der Verbrauchstarif oder der Premium-Tarif wahrscheinlich die beste Wahl.

Für kontinuierliche und vorhersehbare Auslastungen kann es jedoch kostengünstiger sein, Lösungen für vorab bereitgestellte, dedizierte Ressourcen bereitzustellen, indem der App Service-Tarif für Azure Functions verwendet wird.

Ein weiterer Aspekt, der zu berücksichtigen ist, ist das Verhältnis zwischen im Code definierten Verbindungen und deklarativen Bindungen. Letztere sind Konfigurationen zur Entwurfszeit für Funktionen, die es überflüssig machen, dass der Funktionscode Verbindungen zu Triggern und Ein- und Ausgabebindungsquellen verwaltet. Wenn die Funktions-App unter Last auf mehrere Instanzen horizontal hochskaliert wird, werden Verbindungen, die über deklarative Bindung implementiert wurden, über alle Instanzen hinweg wiederverwendet.

Im Gegensatz dazu erfordern Verbindungen, die im Code implementiert werden, dass der Entwickler die Verbindungen der Datenquelle über Anforderungen hinweg verwaltet. Das Erstellen und Verwerfen von Verbindungen im Funktionscode für jede Anforderung schränkt die Skalierbarkeit und Leistung unter Last ein. Dieser Ansatz ist jedoch beispielsweise dann unvermeidlich, wenn die Datenquelle durch Bindungen zur Entwurfszeit nicht unterstützt wird. In einem solchen Fall sollte darauf geachtet werden, dass clientseitige Verbindungen über Anforderungen hinweg wiederverwendet werden, indem statische oder Singleton-Clientverbindungen genutzt werden.

Anleitungen zur Verbindungsverwaltung in Functions finden Sie unter bit.ly/2ZSf5a3.

Kontrolle bei der Ausführung

Serverlose Architekturen zeichnen sich durch Hochverfügbarkeit, automatische Skalierung und Resilienz bei minimalem Benutzereingriff aus. In dieser Hinsicht sind sie sogar PaaS-Diensten (Platform-as-a-Service) überlegen. In serverlosen Umgebungen stehen jedoch bestimmte Konfigurationsoptionen zur Verfügung, mit denen die Leistungsmerkmale von Azure Functions optimiert werden können. Sehen wir uns diese Konfigurationsoptionen jetzt an.

Batchverarbeitung aus Leistungsgründen: Der Durchsatz und die Leistung einer Funktion verbessern sich, wenn sie so konfiguriert ist, dass sie eingehende Nachrichten in Batches empfängt, anstatt eine Ausführung pro Nachricht zu verwenden. Diese Ausführung ist jedoch abhängig von dem in der Bindung konfigurierten Azure-Dienst und davon, ob der Azure Function-Trigger für diesen Dienst die Batchverarbeitung von Nachrichten unterstützt. Beispielsweise unterstützt Azure Event Hubs die Batchverarbeitung von Nachrichten, die von Clientanwendungen genutzt werden. Die Batchgröße wird in der Datei „host.json“ der Funktions-App angegeben.

Parallelität von Anforderungen: Sie können bestimmen, wie viele Anforderungen von jeder Instanz einer Funktions-App verarbeitet werden, indem Sie die Parallelität der Anforderungstrigger angeben. Sie können diese Zahl an die Art der zu bearbeitenden Workloads anpassen und dann die Funktions-App entsprechend skalieren. Diese Konfiguration wird in der Datei „host.json“ der Funktions-App angegeben.

Horizontale Hochskalierung: Azure Functions skaliert die Infrastruktur automatisch so, dass sie eingehende Lasten ohne Benutzereingriff bewältigen kann. Wie stark die horizontale Hochskalierung erfolgt, hängt von der Anzahl der zu verarbeitenden Ereignisse ab. Es gibt jedoch bestimmte Grenzen, innerhalb derer eine einzelne Funktions-App skaliert werden kann. Im Verbrauchstarif kann jede Instanz der Funktions-App mehrere gleichzeitige Anforderungen verarbeiten, und jede Funktions-App selbst kann auf maximal 200 Instanzen horizontal hochskaliert werden (siehe bit.ly/2JlU7tV). Darüber hinaus bietet der Verbrauchstarif nicht die Möglichkeit, die Kapazität der Computeressourcen für diese Workload auszuwählen.

Wie erfolgt die horizontale Hochskalierung über diese Höchstgrenzen hinaus? Eine Möglichkeit wäre, die Funktions-App zu klonen und die Clientanwendungen die Last auf die Klone verteilen zu lassen. Eine Alternative wäre auch die Nutzung des Premium-Tarifs von Azure Functions, bei dem Sie aus einer Vielzahl von Computegrößen wählen können, die für die jeweilige Workload geeignet sind. Beachten Sie, dass der Premium-Tarif für Funktions-Apps zurzeit als öffentliche Vorschau verfügbar ist und nicht in Produktionsworkloads verwendet werden kann.

Kaltstarts: Nach einer Zeit der Inaktivität, in der für alle Instanzen ein Timeout aufgetreten ist, kann eine plötzliche Last, die auf eine Funktions-App angewendet wird, zu einer Verzögerung führen, da Instanzen wieder aktiv mit der Verarbeitung von Anforderungen beginnen müssen. Diese Verzögerung ist möglicherweise nicht akzeptabel, wenn es um die Verarbeitung bestimmter unternehmenskritischer Szenarien geht.

Kosten-Leistungs-Kompromisse: Der Premium-Tarif in Funktions-Apps bietet Optionen zum Angeben der Anzahl der warmen Instanzen, die immer als Standby verfügbar sind, um Verzögerungen durch Kaltstarts zu vermeiden. Neben den gleichen serverlosen Funktionen wie der Verbrauchstarif bietet er zusätzliche Vorteile, etwa die Möglichkeit, aus einer Vielzahl von Computegrößen zu wählen, auf denen die Instanzen ausgeführt werden sollen, die Möglichkeit, auf Ressourcen in einem virtuellen Netzwerk zuzugreifen und den Wegfall einer Timeoutstandarddauer wie im Verbrauchstarif. Alle diese Funktionen sind mit Kosten verbunden, aber die Vorteile, die sie bieten, können die Kompromisse in unternehmenskritischen Anwendungsfällen mehr als ausgleichen.

Beachten Sie, dass Azure Functions sowohl unter Linux als auch unter Windows verfügbar ist, aber nicht alle Funktionen stehen in beiden Angeboten zur Verfügung. Azure Functions unter Linux ist derzeit als Vorschau verfügbar.

Portabilität

Azure Functions umfasst eine Laufzeit- und eine Skalierungskomponente. Erstere ist als Docker-Container erhältlich, sodass Funktionen lokal, in der Edgeumgebung oder in der Azure-Cloud bereitgestellt werden können. Die Skalierungsmöglichkeiten werden durch die Bereitstellung einer Functions-Laufzeit in einem Kubernetes-Cluster in Verbindung mit der Open-Source-Komponente „Kubernetes-based Event-driven autoscaling (KEDA)“ erreicht.

Wir haben bereits erläutert, wie Azure Functions nativ in Azure ausgeführt werden kann, aber Functions kann auch in einem Kubernetes-Cluster bereitgestellt werden. In Azure kann KEDA in einem Kubernetes-Cluster bereitgestellt werden, der mit Red Hat OpenShift oder Azure Kubernetes Service (AKS) erstellt wurde.

In Edge- oder lokalen Umgebungen kann KEDA in einem Kubernetes-Cluster mithilfe der Core Tools für Functions installiert werden. Funktionen, die als Container verpackt sind, würden in den Kubernetes-Pods ausgeführt werden.

Die Skalierung der Container, in denen Functions ausgeführt wird, erfolgt basierend auf der Anzahl der Nachrichten in den Warteschlangen, die als Trigger für die Funktions-App konfiguriert sind. Weitere Informationen zur Implementierung von KEDA für Functions und Kubernetes finden Sie unter bit.ly/2J9gYIO.

Bewährte Methoden für die Entwicklung

In diesem Abschnitt werden einige wertvolle bewährte Methoden für die Entwicklung von Azure Functions behandelt. Beginnen wir mit der Ausnahmebehandlung von Triggern und Bindungen. Azure Functions bietet deklarative Bindungen für Trigger sowie Eingabe- und Ausgabedatenquellen. Wenn ein Fehler bei der Ausführung von Triggern oder dem Aufrufen von Datenquellen auftritt, müssen diese Fehler explizit im Code abgefangen werden. Der Ausnahmehandler sollte die Semantik von Wiederholungsversuchen mit exponentiellem Backoff implementieren. Dienste wie Azure Service Bus-Warteschlangen und Blobspeicher unterstützen implizit Wiederholungsversuche in den Triggern, aber für andere Dienste müssen diese explizit implementiert werden.

Um Laufzeitausnahmen bei der Verwendung anderer Dienste wie Event Hubs zu erfassen, muss die Ausgabebindung den Typ IAsyncCollector anstelle eines out-Parameters verwenden. Der Aufruf der AddAsync-Methode des Collectors innerhalb eines Ausnahmebehandlungsblocks stellt sicher, dass die Laufzeitausnahmen abgefangen werden.

In Abbildung 2 wird von Event Hubs eine transiente Ausnahme mit dem Code 50002 ausgelöst, die anzeigt, dass die Anforderungen gedrosselt werden. Im Ausnahmehandler sollte eine Wiederholungsversuchlogik implementiert werden, die eine exponentielle Backoffrichtlinie verwendet.

Behandlung von Ausnahmen in der Ausgabebindung
Abbildung 2: Behandlung von Ausnahmen in der Ausgabebindung

Auf der Dokumentationsseite unter bit.ly/31UDLRl wird beschrieben, wie ein exponentielles Backoff in einer dauerhaften Funktions-App implementiert werden kann.

Abbildung 3 zeigt die Metriken aus der Event Hubs-Instanz, nachdem sie einer Last ausgesetzt wurde, um die Anforderungsdrosselung zu simulieren.

Überwachen von Event Hub auf Anforderungsdrosselung
Abbildung 3: Überwachen von Event Hub auf Anforderungsdrosselung

Authentifizierung und Identitäten

Azure Functions unterstützt verschiedene Formen der Authentifizierung von Clientanwendungen. Dies kann in Form von Einbettung eines Authentifizierungsschlüssels in den HTTP-Header der Anforderung an die Funktions-App oder durch Verwendung eines OAuth-Tokens erfolgen, wenn Benutzer über eine Webanwendung auf die Funktion zugreifen, die Azure AD-Anmeldung implementiert (bit.ly/2XzrulR). In einigen Fällen, in denen die Funktions-App für externe Benutzern über Azure API Management bereitgestellt wird, kann die Autorisierung der Benutzeranforderungen am Gateway über die in der Anforderung eingebetteten Abonnementschlüssel erfolgen.

Wenn der benutzerdefinierte Code in Azure Functions auf andere Dienste in Azure zugreifen muss, besteht eine bewährte Methode darin, Bootstrapping der Anmeldeinformationen mit diesem Code auszuführen, indem systembasierte oder benutzerbasierte verwaltete Identitäten verwendet werden (bit.ly/2IPntl0). Derzeit unterstützen nicht alle Dienste in Azure den Zugriff über verwaltete Identitäten. Die Liste der unterstützten Dienste finden Sie unter bit.ly/2ISVUYp.

Im folgenden Codeausschnitt, der aus einem Visual Studio 2017 Azure Functions-Projekt stammt, zeige ich, wie eine verwaltete Identität verwendet werden kann, um eine Verbindung mit Azure SQL-Datenbank herzustellen und darauf zuzugreifen. Aus Sicherheitsgründen müssen Sie keine SQL-Authentifizierungsanmeldeinformationen mehr innerhalb der Verbindungszeichenfolge speichern und dann die Verbindungszeichenfolge selbst in Azure Key Vault speichern.

string connString =
  "Server = tcp:onedbserver.database.windows.net,1433; Database = onedb;";
  using (SqlConnection con = new SqlConnection(connString))
  {
    con.AccessToken = await (
      new AzureServiceTokenProvider()).GetAccessTokenAsync(
        "https://database.windows.net/");
    con.Open();
    try
    {
      using (SqlCommand command =
        new SqlCommand("SELECT * FROM SalesLT.Product", con))
      {
        SqlDataReader reader = command.ExecuteReader();
...

Als nächstes veröffentlichen Sie die Funktions-App in Azure, aktivieren die verwaltete Identität für sie und stellen den Datenbankzugriff darauf bereit. Lesen Sie die unter bit.ly/2JaGOfr dokumentierten Schritte, die den Prozess der Erstellung von Dienstprinzipalidentitäten und der Zuordnung zu einer Azure SQL-Datenbank beschreiben.

Azure App Service und Azure Functions bieten praktische Optionen, um die Integration mit Azure Key Vault zu vereinfachen, indem sie verwaltete Identitäten für den Zugriff auf Geheimnisse verwenden. Lesen Sie den Artikel unter bit.ly/2X1f7ed, um detaillierte Anleitungen zur Implementierung dieser Funktion zu erhalten.

Zusammenfassung

Azure Functions bietet Unternehmen eine serverlose Möglichkeit, benutzerdefinierten Code zu entwickeln, bereitzustellen und auszuführen, der überall ausgeführt werden kann: in Azure, lokal oder in Edgeumgebungen. Das Aufkommen zusätzlicher Funktionen für die Plattform in Form von dauerhaften Funktionen und Entitäten eröffnet die Möglichkeit, das serverlose Konzept auf eine Vielzahl neuer Anforderungen und Entwurfsmuster zu erweitern. Die umfassende Integration von Azure Functions in andere Azure-Dienste und -Entwicklungstools stellt sicher, dass Entwickler beim Erstellen von Anwendungen mit einer IDE ihrer Wahl am produktivsten sind, und zwar unabhängig von der Programmiersprache und Plattform.


Sandeep Alur ist Director von Partner Engagements (ISV) bei Microsoft. Seine Erfahrung reicht von den ersten Dotcom-Tagen über das Zeitalter von verteiltem und Cloudcomputing bis hin zur nächsten Generation des intelligenten Computings. Sie erreichen ihn unter Sandeep.Alur@microsoft.com.

Srikantan Sankaran ist ein leitender Technical Evangelist im One Commercial Partner-Team in Indien und lebt in Bangaluru. Er arbeitet mit zahlreichen ISVs in Indien zusammen und unterstützt sie beim Architekturentwurf und Bereitstellen ihrer Lösungen unter Microsoft Azure. Sie erreichen ihn unter Srikantan.Sankaran@microsoft.com.

Unser Dank gilt dem folgenden technischen Experten bei Microsoft für die Durchsicht dieses Artikels: Hemant Kathuria


Diesen Artikel im MSDN Magazine-Forum diskutieren