Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Lesedauer 14 Min.

Konfigurier mich

Jede Anwendung benötigt Konfigurationseinstellungen. Mit Azure App Configuration ­lassen sich diese bequem zentral in der Cloud verwalten.
© Autor
Ganz früher waren es INI-Dateien, dann XML, dann JSON bis hin zu YAML: Seit jeher brauchen Computerprogramme begleitende Dateien, die Konfigurationseinstellungen enthalten. Wem das Synonym 486er noch geläufig ist, der ­erinnert sich zum Beispiel an die Einstellungen zur Soundkarte bei Spielen. Datenbankverbindungsinformationen kommen auch heute noch vor, und Informationen zu allerlei Diensten in der Cloud oder On-Premise haben sich neuerdings dazugesellt. Daneben noch Einstellungen, die das Verhalten der Anwendung beeinflussen: Wohin sollen bestimmte Dateien geschrieben werden, Schriftgröße, Farbschema? All diese Einstellungen wollen irgendwo untergebracht werden. Von App.Config über Web.Config bis hin zu appsettings.json gab es seit dem Bestehen von .NET verschiedene Dateinamen und Formate dafür. Das ganze Spektrum von Einstellungen ist vielfältig (vergleiche Bild 1)
Arten von Softwareeinstellungennach Stufe, Scope und ­Sensibilität(Bild 1) © Autor
Es gibt Einstellungen mit unterschiedlichem Scope: Einige Parameter gelten systemweit und haben Einfluss auf eine oder mehrere Anwendungen. Diese können weitere Eigenschaften festlegen, welche einen oder mehrere Benutzer beeinflussen, die ihre Präferenzen aber ebenfalls gerne zu Protokoll geben möchten. Diese Einstellungen unterscheiden sich in der Regel anhand der Entwicklungsstufe: Bei einer Anwendung im Entwicklungsstadium sind andere Einstellungswerte erforderlich als im Produktivbetrieb. Eine wich­tige Rolle bei der Verwaltung von Einstellungen spielt deren Sensibilität: Mit Passwörtern sollte man sorgsamer umgehen als mit der Einstellung der Hintergrundfarbe.Der vorliegende Artikel behandelt Anwendungskonfigurationen für eine ASP.NET-Core-Anwendung. Dabei ergänzen sich Konfigurationsteile, die zusammen mit der Anwendung (On-Premise) abgelegt werden, mit Einstellungsmöglichkeiten, die in der Cloud gespeichert sind. Es werden Mechanismen vorgestellt, wie sich die Einstellungen hinsichtlich der Entwicklungsstufe und der Sensibilität verwalten lassen. Die hierbei verwendeten Mechanismen sind in Tabelle 1 dargestellt: Der gewohnte Ort für Anwendungseinstellungen, die nicht sensibel sind, ist die Datei appsettings.json. Will man nach Entwicklungsstufe unterscheiden, verwendet man eine spezialisierte Datei nach dem Muster appsettings.{Environment}.json, welche die Einstellungen der appsettings.json selektiv überschreiben und ergänzen kann. Wenn es an sensible Informationen geht, sollte man diese nicht zusammen mit Quelldateien in eine Versionskontrolle bringen. Dann bieten sich die dotnet user-secrets an, in denen Einstellungen im Benutzerprofil gespeichert werden. Später in der Produktion überblendet man diese mithilfe von Umgebungsvariablen.

Tabelle 1: Arten von Anwendungskonfigurationen

Sensibilität Entwicklungsstufe On-Premise Cloud
Nicht sensibel Allgemein appsettings.json Azure App Configuration
Spezifisch appsettings.{env}.json key{label}=value
Sensibel dotnet user-secrets / Umgebungsvariablen Azure Key Vault
Begibt man sich in die Cloud, landen normale Einstellungen im Azure-Dienst App Configuration. Dieser kennt für umgebungsabhängige Konfigurationseinstellungen (Schlüssel genannt) sogenannte Labels, die mehrere Werte pro Schlüssel erlauben – zum Beispiel für die Abbildung von Entwicklung, Staging und Production. Die geheimen Einstellungen wie Passwörter gehören dann in einen Azure Key Vault.Eine spezielle Form der Anwendungseinstellung sind Feature-Flags, sie werden in diesem Teil des Artikels behandelt.

Azure-Objekte anlegen

Im Azure Portal sucht man nach App Configuration, klickt auf Erstellen und gibt die in Bild 2 gezeigten Parameter ein. Neben den üblichen Einstellungen bezüglich Abonnement, Ressourcengruppe und -name sowie Standort ist der Tarif inte­ressant [1]: Es existiert ein freier Tarif, von dem man pro Subscription aber nur eine Instanz anlegen darf, die dann auf 10 MByte Speicher begrenzt ist. Der Standardtarif erlaubt hier bis zu 1 GByte. Veränderte Einstellungen werden nur sieben Tage und nicht 30 Tage gespeichert, die Anzahl der Anfragen ist auf 1000 pro Tag begrenzt. Der Standardtarif erlaubt 200 000 Anfragen ohne Aufpreis und bietet 99,9 Prozent SLA im Gegensatz zum freien Tarif, der hier keine Zusicherung macht. Dafür schlägt der Standardtarif mit einer Grundgebühr von 1,012 Euro pro Tag plus 0,051 Euro pro 10 000 Anfragen zu Buche.
Azure App Configurationerstellen(Bild 2) © Autor
Um später auf die Konfigurationsdaten zugreifen zu können, muss sich eine Anwendung zunächst authentifizieren. Dies klappt gewohnheitsgemäß über eine Verbindungszeichenfolge, die unter Einstellungen | Zugriffsschlüssel einsehbar ist. Es gibt je einen primären und sekun­dären Schlüssel mit reinem Lesezugriff und Lese-/Schreibzugriff. Wenn möglich, sollte eine Applikation einen Schlüssel mit reinem Lesezugriff verwenden – sollte dieser verloren gehen, hält sich der Schaden in Grenzen. Die Verwendung des Connection-Strings beschränkt sich aber ohnehin auf Anwendungen, die On-Premise laufen – für Anwendungen in Azure bietet sich die Nutzung eines Dienst-Prinzipals (oder einer verwalteten Identität) an. Auch der Azure Key Vault bietet eine Authentifizierung über einen solchen Prinzipal an, also ist es sinnvoll, auch die App-Konfiguration darüber zu authentifizieren, selbst wenn das initial etwas mehr Aufwand bedeutet.Da es über das Azure Portal keinen direkten Weg gibt, einen Service-Prinzipal zu erzeugen, legt man diesen am einfachsten über das Azure CLI an. Sollte das noch nicht passiert sein, muss man sich zunächst mittels
<span class="hljs-attribute">az login</span> 
interaktiv anmelden. Dazu öffnet sich ein Browser, in dem man die eigenen Anmeldeinformationen eingibt, danach kann man in der Kommandozeile weiterarbeiten. Ein Prinzipal ist eine Active-Directory-Entität und lässt sich über den Befehl ad anlegen:
<span class="hljs-string">az </span><span class="hljs-string">ad </span><span class="hljs-string">sp </span><span class="hljs-built_in">create-for-rbac</span> -n <span class="hljs-string">"http://leckerito/</span>
<span class="hljs-string">  ServicePrincipal"</span> <span class="hljs-built_in">--sdk-auth</span> 
Das Akronym RBAC steht für Role Based Access Control. Als Antwort erhält man auf der Konsole ein JSON-Objekt, das allerlei GUIDs und URLs enthält. Wichtig für die weitere Verwendung sind vor allem die Felder clientId, clientSecret und tenantId. Wer das Mitschreiben vergessen hat, findet seinen Dienstzugang unter Azure Active Directory | App-Registrierungen wieder. Die tenantId wird in der Übersicht Verzeichnis-Id (Mandant) genannt, die clientId heißt dort Anwendungs-Id (Client). Der Wert des Feldes ist unter Zertifikate & Geheimnisse | Geheime Clientschlüssel untergebracht, kann jedoch nach der Erstellung nicht mehr eingesehen werden. Hier hilft bei Vergesslichkeit nur das erneute Generieren. Wichtig ist auch, dass der Standardschlüssel eine begrenzte Gültigkeitsdauer von einem Jahr hat. Am besten trägt man sich diesen Termin direkt in den Kalender ein, damit man nicht ein Jahr später eine unangenehme Überraschung erlebt – oder man erzeugt einen unbegrenzt gültigen Schlüssel und lebt mit den Implikationen für die Sicherheit.Jetzt haben wir zwar einen Dienst-Prinzipal, der sogar unter der Rolle „Mitwirkender“ im Azure-Abonnement geführt wird, dadurch darf er aber noch nicht mit unserer App Configuration sprechen. Dieser Missstand lässt sich noch nicht per Azure CLI beheben, man muss also den Browser bemühen und wechselt zur App-Konfiguration und wählt Übersicht | Zugriffssteuerung (IAM) | Rollenzuweisung | Hinzufügen aus dem Menü (vergleiche Bild 3). Die erforderliche Rolle heißt App Configuration Datenleser und muss dem soeben erstellten Dienst-Prinzipal zugewiesen werden.
Dem Dienst-PrinzipalZugriff auf die App ­Configuration gewähren(Bild 3) © Autor
Wo wir gerade so schön im Azure-Schwung sind, legen wir für später direkt den Schlüsseltresor – oder auch Azure Key Vault – an (vergleiche Bild 4).
Azure Key Vaultanlegen(Bild 4) © Autor
Beim Preis gönnt Microsoft uns hier keine kostenlose Op­tion [3]. Im Standard-Tarif bleiben die Kosten mit 0,026 Euro pro 10 000 Transaktionen aber überschaubar. Der Zugriff auf einen Key Vault ist nicht wie bei anderen Azure-Diensten per Zugriffsschlüssel beziehungsweise Connection String möglich, sondern nur per Benutzer, Prinzipal oder eine andere Azure-Anwendung. Damit unsere Anwendung später auf den Key Vault zugreifen kann, erteilen wir also unserem Service-Prinzipal Zugriffsrechte per Azure CLI:
<span class="hljs-string">az </span><span class="hljs-string">keyvault </span><span class="hljs-built_in">set-policy</span> 
-n <span class="hljs-string">leckerito-KeyVault </span>
<span class="hljs-built_in">--spn</span> &lt;<span class="hljs-string">clientId-of-</span><span class="hljs-string">your-service-</span><span class="hljs-string">principal&gt;</span> 
<span class="hljs-built_in">--secret-permissions</span> <span class="hljs-string">get </span><span class="hljs-string">list </span>
<span class="hljs-built_in">--key-permissions</span> <span class="hljs-string">decrypt </span><span class="hljs-string">encrypt </span><span class="hljs-string">get </span><span class="hljs-string">list </span> 

Konfigurationen anlegen …

Damit haben wir in Azure alles erstellt und konfiguriert. Jetzt kann es damit losgehen, Anwendungseinstellungen anzulegen. Was direkt zum schwierigsten Problem in der Software­entwicklung führt: der Namensgebung. Denn der Name des Schlüssels ist die erste Eingabe, die nach dem Klick auf Vorgänge | Konfigurations-Explorer | Erstellen verlangt wird.Um für etwas Ordnung zu sorgen, kann man die Schlüsselnamen hierarchisch in Namespaces einordnen. Nach welchem Kriterium die Namen gegliedert werden, bleibt dem Entwickler überlassen. Einige unter [4] vorgeschlagene Möglichkeiten unterscheiden sich nach Komponente oder nach Region. In unserem Beispiel (zu finden unter [5]) verwenden wir den Namespace des Projekts, um das es sich handelt, als Ordnung für die Schlüssel – der Name wird so vergeben, als sei er in einer lokalen appsettings.json definiert, von denen es ja bekanntermaßen in jedem API eine gibt. Im Prinzip handelt es sich also um eine Gliederung nach Komponenten. In dieser Beispiel­applikation soll die Schriftgröße, eine Begrüßungsfloskel und natürlich ein UI-Theme konfigurierbar sein.Das Anlegen der Schlüssel kann zwar über das Azure Portal erledigt werden, es empfiehlt sich aber aus Gründen der Wiederholbarkeit, ein Skript dafür zu schreiben, das man mit der Applikation in der Versionskontrolle verwaltet. Beim Setzen eines Keys über das Azure CLI ist der Endpunkt anzugeben, auf den man sich bezieht. Damit man das nicht jedes Mal tun muss, kann man ihn einmalig konfigurieren:
az configure <span class="hljs-comment">--defaults appconfig_connection_string=</span>
<span class="hljs-comment">  &lt;EndpointConnectionString&gt; </span> 
Ein Schlüssel lässt sich dann mit dem Befehl az appckonfig kv set setzen:
<span class="hljs-selector-tag">az</span> <span class="hljs-selector-tag">appconfig</span> <span class="hljs-selector-tag">kv</span> <span class="hljs-selector-tag">set</span> ^ 
<span class="hljs-selector-tag">--key</span> <span class="hljs-selector-tag">leckerito</span><span class="hljs-selector-pseudo">:Lab</span><span class="hljs-selector-pseudo">:AppSettings</span><span class="hljs-selector-pseudo">:Theme</span> ^ 
<span class="hljs-selector-tag">--value</span> <span class="hljs-selector-tag">Dark</span> 
Der Parameter --key enthält den Namen und der Parameter --value legt den Wert fest. Die Hierarchisierung des Namens geschieht wie auch in einer lokalen appsettings.json über einen Doppelpunkt. Technisch gesehen sind alle Schlüsselwerte Zeichenfolgen. Man kann optional beim Anlegen im Feld Inhaltstyp (Parameter --content-type) einen MIME-Type angeben, der auf den Inhalt zutrifft.Um Konfigurationswerte nach Entwicklungsstufe zu unterscheiden, kann man einem Schlüssel sogenannte Bezeichnungen (labels) zuweisen. Die Benennung ist etwas verwirrend, konkret handelt es sich lediglich um ein weiteres filterbares Auswahlkriterium für Schlüssel. Um die Grußfloskel abhängig von der Umgebung zu definieren, legt man zunächst einen „Hauptschlüssel“ ohne Bezeichnung an und dann je eine Variante für jede Bezeichnung, hier der Kürze halber nur für Development:
az appconfig kv set ^ 
--key <span class="hljs-string">leckerito:</span><span class="hljs-string">Lab:</span><span class="hljs-string">AppSettings:</span>
  Greeting ^ 
--value <span class="hljs-string">"Guten Morgen!!"</span> 

az appconfig kv set ^ 
--key <span class="hljs-string">leckerito:</span><span class="hljs-string">Lab:</span><span class="hljs-string">AppSettings:</span>
  Greeting ^ 
--value <span class="hljs-string">"Guten Morgen </span>
<span class="hljs-string">  Development!!"</span> ^ 
--label <span class="hljs-string">"Development"</span> 
Dabei bleibt der Name des Schlüssels immer identisch, lediglich der Parameter --label und der Schlüsselwert unterscheiden sich.Schlüssel mit Bezeichnungen werden im Portal als Hierarchie dargestellt, die Schlüsselnamen hingegen in einer „flachen“ Liste (vergleiche Bild 5)
Azure App Configurationmit einigen gefüllten Konfigurationsschlüsseln(Bild 5) © Autor

… und auslesen

Jetzt haben wir also Konfigurationseinstellungen in Azure, benötigt werden sie aber in einer Applikation oder einem Dienst. Nehmen wir als Beispiel eine ASP.NET-Core-Applikation. Wer Interesse an anderen Plattformen hat, findet zum Beispiel unter [6] Rat und Hilfe.Da wir neben der Client-ID und der Tenant-ID auch noch das Client Secret unseres Dienst-Prinzipals benötigen, bereiten wir unsere Applikation zur Nutzung von .NET-Core-User-Secrets vor und setzen die entsprechenden Werte:
dotnet user-secrets init 
dotnet user-secrets <span class="hljs-keyword">set</span> Azure:LeckeritoServicePrinzipal:
  ClientId &lt;<span class="hljs-keyword">Client</span> <span class="hljs-keyword">Id</span>&gt; 
<span class="hljs-keyword">dotnet</span> <span class="hljs-keyword">user</span>-secrets <span class="hljs-keyword">set</span> Azure:LeckeritoServicePrinzipal:
  TenantId &lt;Tenant <span class="hljs-keyword">Id</span>&gt; 
<span class="hljs-keyword">dotnet</span> <span class="hljs-keyword">user</span>-secrets <span class="hljs-keyword">set</span> Azure:LeckeritoServicePrinzipal: 
  ClientSecret &lt;<span class="hljs-keyword">Client</span> Secret&gt; 
Dann braucht man noch die passenden NuGet-Pakete:
dotnet add package 
  Microsoft<span class="hljs-selector-class">.Azure</span><span class="hljs-selector-class">.AppConfiguration</span><span class="hljs-selector-class">.AspNetCore</span> 
dotnet add package Azure<span class="hljs-selector-class">.Identity</span> 
Generell werden die Konfigurationsdaten über einen eigenen Configuration Provider in den gewohnten Konfigura­tionsmechanismus von .NET Core integriert, will heißen, die Schlüssel aus der Azure App Configuration werden über die Schnittstelle IConfiguration zur Verfügung gestellt, neben und genau wie lokale Schlüssel aus der appsettings.json. Der zur Konfiguration erforderliche Code ist in Listing 1 zu finden.
Listing 1: Konfiguration von Azure App Configuration in einer ASP.NET-Core-Applikation
namespace Lab.AppConfiguration &lt;br/&gt;{ &lt;br/&gt;  public &lt;span class="hljs-class"&gt;&lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;Program&lt;/span&gt; &lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-class"&gt;  &lt;/span&gt;{ &lt;br/&gt;    public &lt;span class="hljs-keyword"&gt;static&lt;/span&gt; &lt;span class="hljs-keyword"&gt;void&lt;/span&gt; Main(string[] args) &lt;br/&gt;    { &lt;br/&gt;      CreateHostBuilder(args).Build().Run(); &lt;br/&gt;    } &lt;br/&gt;&lt;br/&gt;    public &lt;span class="hljs-keyword"&gt;static&lt;/span&gt; IHostBuilder &lt;br/&gt;        CreateHostBuilder(string[] args) =&amp;gt; &lt;br/&gt;        Host.CreateDefaultBuilder(args) &lt;br/&gt;        .ConfigureWebHostDefaults(&lt;span class="hljs-function"&gt;&lt;span class="hljs-params"&gt;webBuilder&lt;/span&gt; =&amp;gt;&lt;/span&gt; &lt;br/&gt;        { &lt;br/&gt;      webBuilder.ConfigureAppConfiguration(&lt;span class="hljs-function"&gt;(&lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-function"&gt;&lt;span class="hljs-params"&gt;          hostingContext, config&lt;/span&gt;) =&amp;gt;&lt;/span&gt; &lt;br/&gt;      { &lt;br/&gt;        &lt;span class="hljs-keyword"&gt;var&lt;/span&gt; settings = config.Build(); &lt;br/&gt;&lt;br/&gt;        &lt;span class="hljs-keyword"&gt;var&lt;/span&gt; clientId = settings[&lt;span class="hljs-string"&gt;"Azure:Leckerito&lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-string"&gt;          ServicePrinzipal:ClientId"&lt;/span&gt;]; &lt;br/&gt;        &lt;span class="hljs-keyword"&gt;var&lt;/span&gt; clientSecret = settings[&lt;span class="hljs-string"&gt;"Azure:Leckerito  &lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-string"&gt;          ServicePrinzipal:ClientSecret"&lt;/span&gt;]; &lt;br/&gt;        &lt;span class="hljs-keyword"&gt;var&lt;/span&gt; tenantId = settings[&lt;span class="hljs-string"&gt;"Azure:Leckerito&lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-string"&gt;          ServicePrinzipal:TenantId"&lt;/span&gt;]; &lt;br/&gt;&lt;br/&gt;        &lt;span class="hljs-keyword"&gt;var&lt;/span&gt; credentials = &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; ClientSecretCredential(&lt;br/&gt;          tenantId, clientId, clientSecret); &lt;br/&gt;&lt;br/&gt;        config.AddAzureAppConfiguration(&lt;span class="hljs-function"&gt;&lt;span class="hljs-params"&gt;options&lt;/span&gt; =&amp;gt;&lt;/span&gt; &lt;br/&gt;        { &lt;br/&gt;          options &lt;br/&gt;            .Connect(&lt;span class="hljs-keyword"&gt;new&lt;/span&gt; Uri(&lt;span class="hljs-string"&gt;"https://leckeritoapp&lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-string"&gt;              settings.azconfig.io/"&lt;/span&gt;), credentials) &lt;br/&gt;&lt;br/&gt;            .ConfigureRefresh(&lt;span class="hljs-function"&gt;&lt;span class="hljs-params"&gt;refresh&lt;/span&gt; =&amp;gt;&lt;/span&gt; &lt;br/&gt;            { &lt;br/&gt;              refresh.Register(&lt;span class="hljs-string"&gt;"leckerito:Lab:&lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-string"&gt;              AppSettings:ReloadSentinel"&lt;/span&gt;, &lt;span class="hljs-attr"&gt;refreshAll&lt;/span&gt;: &lt;br/&gt;              &lt;span class="hljs-literal"&gt;true&lt;/span&gt;) .SetCacheExpiration(&lt;br/&gt;              &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; TimeSpan(&lt;span class="hljs-number"&gt;0&lt;/span&gt;, &lt;span class="hljs-number"&gt;0&lt;/span&gt;, &lt;span class="hljs-number"&gt;3&lt;/span&gt;)); &lt;br/&gt;            }) &lt;br/&gt;            .ConfigureKeyVault(&lt;span class="hljs-function"&gt;&lt;span class="hljs-params"&gt;kv&lt;/span&gt; =&amp;gt;&lt;/span&gt; { &lt;br/&gt;              kv.SetCredential(credentials); &lt;br/&gt;            }) &lt;br/&gt;            .Select(&lt;span class="hljs-string"&gt;"leckerito:Lab:AppSettings:*"&lt;/span&gt;, &lt;br/&gt;              LabelFilter.Null) &lt;br/&gt;            &lt;span class="hljs-comment"&gt;// Override with any configuration values &lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;            // specific to current hosting env &lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;            .Select("leckerito:Lab:AppSettings:*", &lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;              hostingContext.HostingEnvironment.&lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;              EnvironmentName); &lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;        }); &lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;      }); &lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;      webBuilder.UseStartup&amp;lt;Startup&amp;gt;(); &lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;    }); &lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;  } &lt;/span&gt;&lt;br/&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;  public class Startup &lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;  { &lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;    public Startup(IConfiguration configuration) &lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;    { &lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;      Configuration = configuration; &lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;    } &lt;/span&gt;&lt;br/&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;    public IConfiguration Configuration { get; } &lt;/span&gt;&lt;br/&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;    public void ConfigureServices(&lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;        IServiceCollection services) &lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;    { &lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;      services.Configure&amp;lt;AppSettings&amp;gt;(Configuration.&lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;        GetSection("leckerito:Lab:AppSettings")); &lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;      services.AddRazorPages(); &lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;    } &lt;/span&gt;&lt;br/&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;    public void Configure(IApplicationBuilder app, &lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;        IWebHostEnvironment env) &lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;    { &lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;      // ... &lt;/span&gt;&lt;br/&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;      app.UseAzureAppConfiguration(); &lt;/span&gt;&lt;br/&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;      // ... &lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;    } &lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;  } &lt;/span&gt;&lt;br/&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;  public class AppSettings &lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;  { &lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;    public int FontSize { get; set; } &lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;    public string Theme { get; set; } &lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;    public string Greeting { get; set; } &lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;  } &lt;/span&gt; 
Beim Bauen des Hosts für unsere Anwendungen konfigurieren wir App Configuration über den Aufruf der Erweiterungsmethode ConfigureAppConfiguration des IWebHostBuilder. Zur Authentifizierung lesen wir zunächst die Credentials unsers Dienst-Prinzipals wieder ein und erstellen daraus das ClientSecretCredential. Die eigentliche Konfiguration geschieht über die AzureAppConfigurationOptions, die man über den Aufruf von AddAzureAppConfiguration auf dem IConfigurationBuilder erhält. Die Connect-Methode bekommt einerseits den Endpunkt und die Anmeldeinforma­tionen. Die beiden Aufrufe von ConfigureRefresh und ConfigureKeyVault werden später genauer behandelt. Die beiden Select-Aufrufe am Ende steuern, welche Schlüssel der Anwendung bereitgestellt werden. Der erste Aufruf legt fest, dass alle Schlüssel, deren Namen mit leckerito:Lab:AppSet­tings beginnen, ausgewählt werden. Der zweite Aufruf überschreibt alle Schlüssel mit einem entsprechenden Label mit einem von der Hosting-Umgebung abhängigen Wert.Generell kann man in .NET Core auf zwei Arten auf die eigenen Konfigurationsdaten zugreifen: untypisiert über den Indexer des Interfaces IConfiguration oder typisiert über eine Einstellungsklasse. Wer Letzteres bevorzugt, muss eine passend strukturierte Klasse, in Listing 1 die Klasse AppSettings, definieren und in der Startup über den Aufruf von Configure<T> einlesen:
services.Configure&lt;AppSettings&gt;( 
  Configuration.GetSection("leckerito:Lab:AppSettings")); 
Der eigentliche Zugriff erfolgt dann ebenfalls mit Bordmitteln – so kann man in einer Razor-View wie folgt auf die Konfiguration zugreifen:

@using Microsoft.Extensions.Configuration 
@inject IConfiguration Configuration 
&lt;h1&gt;Dynamisch: @Configuration[
  "leckerito:Lab:AppSettings:Greeting"]&lt;/h1&gt; 
&lt;h2&gt;Strongly typed: @Model.AppSettings.Greeting&lt;/h2&gt; 
 
Damit die streng typisierte Variante funktioniert, holt man sich ein IOptions<AppConfiguration>-Objekt über den DI-Container und bindet es in der Razor-Page an eine Property.

Der Live-Wächter

Das Hello-World der Konfigurationseinstellungen aus der Cloud wäre damit geschafft. Jetzt beginnt der Ernst des Lebens: Was passiert, wenn sich die Konfigurationsdaten ändern? Bei Verwendung der guten alten appsettings.json kann man .NET Core beibringen, die Daten bei Änderungen der Datei zu aktualisieren. Wie jedoch läuft das in App Configuration ab?Es sei schon verraten: Ein „Hot Reload“ ist möglich. Aber wann soll dieser ausgelöst werden? In kontinuierlichen Zeitintervallen? Bei Änderung eines einzelnen Wertes? Und was ist zu tun, wenn zwei Konfigurationsänderungen voneinander abhängig sind und eine Änderung erst in Kraft treten soll, wenn alle Einstellungen wieder konsistent sind, und zwar unabhängig von der Zeit? Unglaublich, aber wahr: Es wurde an alles gedacht.Die Live-Aktualisierung wird von einem Wächterschlüssel (Sentinel) ausgelöst. Der Name dieser Eigenschaft ist beliebig, im Beispiel wird leckerito:Lab:AppSettings:ReloadSenti­nel verwendet. Der Name sollte erkennen lassen, dass es sich hierbei um eine Sentinel-Eigenschaft handelt, deren eigentlicher Wert irrelevant ist – hier zählt ausschließlich die Frage, ob sich der Wert seit der letzten Überprüfung verändert hat. Nur wenn dies der Fall ist, werden die gewünschten Konfigurationseinstellungen neu geladen.Welche Eigenschaft die Rolle des Wächters hat und in welchem Zeitintervall die Änderungsüberprüfung stattfinden soll, wird beim Anwendungsstart konfiguriert (vergleiche Listing 1), und zwar über die Option ConfigureRefresh:
.ConfigureRefresh(refresh =&gt; 
{ 
  refresh.Register("leckerito:Lab:AppSettings:
    ReloadSentinel", refreshAll: true) 
  .SetCacheExpiration(new TimeSpan(0, 0, 3)); 
}) 
Anstelle von refreshAll: true kann auch ein Schlüsselfilter angegeben werden, in diesem Fall werden nicht alle Konfigurationswerte neu geladen, sondern nur solche, die unter die Filterbedingung fallen – gut für Sparfüchse, denn die Kosten für App Configuration richten sich ja nach der Anzahl der Abfragen. Im gleichen Geiste und natürlich auch, um unnötigen Traffic zu vermeiden, heißt es Abwägungen über die Lebenszeit des Cache zu treffen.Um von der dynamischen Aktualisierung in der Anwendung zu profitieren, ist in der Startup noch die folgende Zeile zu ergänzen:
app.UseAzureAppConfiguration(); 
Der grundlegende Zugriff klappt auch ohne diese Zeile, nicht aber der Hot Reload – das ist eine kleine, aber ärgerliche Fehlerquelle.Der dynamische Zugriff via Configuration[”key”] enthält nun unmittelbar die aktualisierte Konfiguration. Wer stattdessen auf IOptions<T> gesetzt hat, muss jetzt IOptions­Snapshot<T> verwenden, damit die aktualisierten Werte sichtbar werden – denn IOptions<T> unterstützt kein Nachladen von Konfigurationsdaten nach dem Start der App [7].

Psssst – geheim!

Damit haben wir die üblichen Applikationseinstellungen fest im Griff. Nicht so aber die kleinen Geheimnisse, die jede Software zu bewahren versucht. Beispiele sind Verbindungszeichenfolgen zu Datenbanken, einem Azure Service Bus oder anderen Diensten sowie Passwörter für den Zugriff auf Drittsysteme.Diese bringt man nicht in Azure App Configuration, sondern besser im Azure Key Vault unter. Wie der Zufall es will, haben wir ja bereits einen Key Vault eingerichtet.Da Azure App Configuration geheime Daten als Verweis auf einen Eintrag in Azure Key Vault speichert, startet man beim Anlegen eines Geheimnisses auch dort, und zwar unter Einstellungen | Geheimnisse | Generieren/importieren. Es wäre zweckmäßig, als Name des Geheimnisses den Namen des Konfigurationsschlüssels zu verwenden – ärgerlich ist nur, dass der Name eines Geheimnisses in Key Vault keine Doppelpunkte, sondern nur Bindestriche enthalten darf. Also ersetzt man notgedrungen die Doppelpunkte mit Bindestrichen.Um einen geheimen Schlüssel einzufügen, wählt man in Azure App Configuration Vorgänge | Konfigurations-Explorer | Erstellen | Key Vault Verweis aus (vergleiche Bild 6).
Geheimen Schlüsselals Key-Vault-Referenz einfügen(Bild 6) © Autor
Wer das Anlegen der Geheimschlüssel auch lieber per Skript erledigt, kann das tun. Unter Windows ist dazu jedoch mindestens eine PowerShell zu bemühen, da man wie über das UI zwei voneinander abhängige Schritte ausführen muss. Zuerst legt man das Geheimnis in Key Vault an und speichert den URI des Ergebnisses:
$uri = az keyvault secret set ` 
--vault-name "leckerito-KeyVault" ` 
--name "leckerito-Lab-AppSettings-SecretKey" ` 
--value "Sehr geheimes Geheimnis" ` 
--query=id -o tsv 
Man beachte die Backticks (`) am Ende jeder Zeile, um eine mehrzeilige Eingabe zu ermöglichen. In der Variablen $uri hat man nun den Schlüsselwert, den man in Azure App Config einträgt:
az appconfig kv set ` 
--key "leckerito:Lab-AppSettings:SecretKey" ` 
--value "{`"uri`":`"$uri`"}" ` 
--content-type "application/vnd.microsoft.appconfig.
  keyvaultref+json;charset=utf-8" 
Erkannt wird der Geheimschlüssel an seinem Content Type, ansonsten wird er gesetzt wie ein normaler Schlüssel.Im Feld Geheimer Schlüssel bezieht man sich auf das im Key Vault gespeicherte Geheimnis. Wer noch einmal Bild 5 genau betrachtet, erkennt, dass dort Key-Vault-Verweise mit einem Schlüsselsymbol dargestellt werden. Bei der Abfrage von geheimen Konfigurationswerten findet keine direkte Kommunikation zwischen Azure App Configuration und Azure Key Vault statt (vergleiche Bild 7). Die Applikation ist für die Kommunikation verantwortlich und muss sich sowohl bei der App Configuration als auch beim Key Vault authentifizieren (1). Wird ein Schlüssel mit einem geheimen Wert angefordert (2), erhält die Applika­tion als Antwort einen URI (3) mit dem Verweis auf den ­Geheimniswert im Azure Key Vault. Dieser URI muss in einer neuen Anfrage (4) vom Key Vault beantwortet werden (5).Damit die Kommunikation gelingen kann, müssen beim Programmstart die Anmeldeinformationen zum Key Vault mitkonfiguriert werden (vergleiche Listing 1):
Ablauf der Auflösungeines geheimen Schlüssels(Bild 7) © Autor
.ConfigureKeyVault(kv =&gt; {kv.SetCredential(
  credentials);}) 
Den gesamten Ablauf der Kommunikation regelt die App-Configuration-Client-Bibliothek für uns – innerhalb der Applikation ist der komplette Ablauf aus Bild 7 also trans­parent. Der Zugriff auf geheime Schlüssel erfolgt auf dem gleichen Weg wie zuvor beschrieben. Auch das Hot Reloading funktioniert für geheime Schlüsselwerte genauso wie für normale Schlüssel.

Fazit

Der Artikel hat die grundlegenden Features von Azure App Configuration und dessen Zusammenspiel mit Azure Key Vault zur Speicherung von geheimen und nicht so geheimen Anwendungseinstellungen beleuchtet.Im Vergleich zu einer einfachen appsettings.json fällt bei der Nutzung von Konfigurationsdaten in der Cloud ein nicht zu vernachlässigender Einrichtungsaufwand an. Dieser ist aber zum Glück nur einmalig zu leisten und lässt sich dank des Azure CLI sogar noch weiter automatisieren, als hier gezeigt werden konnte. Hat man diese Hürde genommen, zahlt sich die Mühe aus: Geheimnisse sind endlich gut aufgehoben und die Anwendungskonfigurationen sind unabhängig vom Deployment der Anwendung – Stichwort: „Wo war noch mal die appsettings.json“ – einsehbar und änderbar.Von dieser Deployment-Transparenz profitiert man besonders, wenn man zum Beispiel ein Container-Deployment mit Kubernetes vor sich hat. Nicht nur ist das Finden der Konfigu­rationsdateien dann schwieriger, man muss auch noch daran denken, die Änderungen gleichzeitig in allen Replikaten durchzuführen. Wenn man alle Anwendungsteile in Azure deployt, zum Beispiel als Azure App Service, läuft das Zusammenspiel der einzelnen Dienste reibungslos, und einiges an dem hier beschriebenen Konfigurationsaufwand entfällt.Neben diesen Vorteilen bietet Azure App Configuration noch andere nützliche kleine Helfer. Da zu jedem Schlüssel eine Änderungshistorie gespeichert wird (was für sich genommen schon praktisch ist), lässt sich der aktuelle Konfigurationsstand mit einem älteren Stand vergleichen. Sollte man einmal unerwarteterweise totalen Murks gebaut haben, lassen sich auch alte Konfigurationsstände wiederherstellen. Um den Einstieg beziehungsweise die Übernahme von Konfigurationsdaten zu erleichtern, lassen sich Einstellungen aus einer appsettings.json importieren oder auch dorthin exportieren.Sobald eine Anwendung oder gar ein System von kleineren Diensten eine gewisse Komplexität erreicht und spätestens wenn Load Balancing ins Spiel kommt, wird das Handling von Konfigurationsdateien langsam, aber sicher unhandlich. Hier kann eine zentralisierte Konfigurationsdatenbank helfen, wie sie von Azure App Configuration bereitgestellt wird. Dank der überschaubaren Kostenstruktur ist dieser Dienst also durchaus eine Erwägung wert.

Fussnoten

  1. Azure App Configuration pricing, http://www.dotnetpro.de/SL2101FeatureFlags1
  2. Install the Azure CLI, http://www.dotnetpro.de/SL2101FeatureFlags2
  3. Key Vault pricing, http://www.dotnetpro.de/SL2101FeatureFlags3
  4. Microsoft Azure, Schlüssel und Werte, http://www.dotnetpro.de/SL2101FeatureFlags4
  5. Lab.AppConfiguration auf GitHub, http://www.dotnetpro.de/SL2101FeatureFlags5
  6. Schnellstart: Erstellen einer Java-Spring-App mit Azure App Configuration, http://www.dotnetpro.de/SL2101FeatureFlags6
  7. Optionenschnittstellen in ASP.NET Core, http://www.dotnetpro.de/SL2101FeatureFlags7

Neueste Beiträge

DWX hakt nach: Wie stellt man Daten besonders lesbar dar?
Dass das Design von Websites maßgeblich für die Lesbarkeit der Inhalte verantwortlich ist, ist klar. Das gleiche gilt aber auch für die Aufbereitung von Daten für Berichte. Worauf besonders zu achten ist, erklären Dr. Ina Humpert und Dr. Julia Norget.
3 Minuten
27. Jun 2025
DWX hakt nach: Wie gestaltet man intuitive User Experiences?
DWX hakt nach: Wie gestaltet man intuitive User Experiences? Intuitive Bedienbarkeit klingt gut – doch wie gelingt sie in der Praxis? UX-Expertin Vicky Pirker verrät auf der Developer Week, worauf es wirklich ankommt. Hier gibt sie vorab einen Einblick in ihre Session.
4 Minuten
27. Jun 2025
„Sieh die KI als Juniorentwickler“
CTO Christian Weyer fühlt sich jung wie schon lange nicht mehr. Woran das liegt und warum er keine Angst um seinen Job hat, erzählt er im dotnetpro-Interview.
15 Minuten
27. Jun 2025
Miscellaneous

Das könnte Dich auch interessieren

UIs für Linux - Bedienoberflächen entwickeln mithilfe von C#, .NET und Avalonia
Es gibt viele UI-Frameworks für .NET, doch nur sehr wenige davon unterstützen Linux. Avalonia schafft als etabliertes Open-Source-Projekt Abhilfe.
16 Minuten
16. Jun 2025
Mythos Motivation - Teamentwicklung
Entwickler bringen Arbeitsfreude und Engagement meist schon von Haus aus mit. Diesen inneren Antrieb zu erhalten sollte für Führungskräfte im Fokus stehen.
13 Minuten
19. Jan 2017
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige