Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Tipp
Dieser Inhalt ist ein Auszug aus dem eBook .NET Microservices Architecture for Containerized .NET Applications, verfügbar auf .NET Docs oder als kostenlose herunterladbare PDF, die offline gelesen werden kann.
Sie können Ihre Datenbanken (SQL Server, PostgreSQL, MySQL usw.) auf regelmäßigen eigenständigen Servern, in lokalen Clustern oder in PaaS-Diensten in der Cloud wie Azure SQL DB haben. Für Entwicklungs- und Testumgebungen ist es jedoch praktisch, dass Ihre Datenbanken als Container ausgeführt werden, da Sie keine externe Abhängigkeit haben und einfach den docker-compose up
Befehl ausführen, die gesamte Anwendung startet. Diese Datenbanken als Container eignen sich auch hervorragend für Integrationstests, da die Datenbank im Container gestartet und immer mit denselben Beispieldaten aufgefüllt wird, sodass Tests vorhersehbarer sein können.
SQL Server, der als Container mit einer mikrodienstbezogenen Datenbank läuft
In eShopOnContainers gibt es einen Container mit dem Namen sqldata
,wie in der docker-compose.yml-Datei definiert, der eine SQL Server für Linux-Instanz mit den SQL-Datenbanken für alle Microservices ausführt, die eine benötigen.
Ein wichtiger Punkt in Microservices ist, dass jeder Microservice seine zugehörigen Daten besitzt, sodass er über eine eigene Datenbank verfügen sollte. Die Datenbanken können jedoch überall sein. In diesem Fall befinden sie sich alle im selben Container, um die Docker-Speicheranforderungen so gering wie möglich zu halten. Denken Sie daran, dass dies eine gute Lösung für die Entwicklung und, vielleicht, Tests, aber nicht für die Produktion ist.
Der SQL Server-Container in der Beispielanwendung ist mit dem folgenden YAML-Code in der Datei docker-compose.yml konfiguriert, der ausgeführt wird, wenn Sie docker-compose up
ausführen. Beachten Sie, dass der YAML-Code konsolidierte Konfigurationsinformationen aus der generischen docker-compose.yml-Datei und der docker-compose.override.yml Datei enthält. (Normalerweise trennen Sie die Umgebungseinstellungen von den Basis- oder statischen Informationen im Zusammenhang mit dem SQL Server-Image.)
sqldata:
image: mcr.microsoft.com/mssql/server:2017-latest
environment:
- SA_PASSWORD=Pass@word
- ACCEPT_EULA=Y
ports:
- "5434:1433"
Auf ähnliche Weise lässt sich der folgende docker-compose
-Befehl verwenden, um den Container auszuführen, anstatt docker run
zu verwenden.
docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=Pass@word' -p 5433:1433 -d mcr.microsoft.com/mssql/server:2017-latest
Wenn Sie jedoch eine Multicontaineranwendung wie eShopOnContainers bereitstellen, ist es praktischer, den docker-compose up
Befehl zu verwenden, damit alle erforderlichen Container für die Anwendung bereitgestellt werden.
Wenn Sie diesen SQL Server-Container zum ersten Mal starten, initialisiert der Container SQL Server mit dem von Ihnen bereitgestellten Kennwort. Sobald SQL Server als Container ausgeführt wird, können Sie die Datenbank aktualisieren, indem Sie eine Verbindung über eine normale SQL-Verbindung herstellen, z. B. über SQL Server Management Studio, Visual Studio oder C#-Code.
Die Anwendung eShopOnContainers initialisiert die einzelnen Microservicedatenbanken mit Beispieldaten mittels Daten-Seeding beim Starten, wie im folgenden Abschnitt erläutert.
Das Ausführen von SQL Server als Container ist nicht nur für eine Demo hilfreich, bei der Sie möglicherweise keinen Zugriff auf eine Instanz von SQL Server haben. Wie bereits erwähnt, eignet es sich auch hervorragend für Entwicklungs- und Testumgebungen, sodass Sie Integrationstests problemlos ausführen können, beginnend mit einem sauberen SQL Server-Image und bekannten Daten, indem Sie neue Beispieldaten erstellen.
Weitere Ressourcen
Ausführen des SQL Server Docker-Images unter Linux, Mac oder Windows
https://learn.microsoft.com/sql/linux/sql-server-linux-setup-dockerVerbinden und Abfragen von SQL Server unter Linux mit sqlcmd
https://learn.microsoft.com/sql/linux/sql-server-linux-connect-and-query-sqlcmd
Befüllung mit Testdaten beim Starten der Webanwendung
Wenn Sie der Datenbank Beim Starten der Anwendung Daten hinzufügen möchten, können Sie der Methode in der Main
Program
Klasse des Web-API-Projekts Code wie den folgenden hinzufügen:
public static int Main(string[] args)
{
var configuration = GetConfiguration();
Log.Logger = CreateSerilogLogger(configuration);
try
{
Log.Information("Configuring web host ({ApplicationContext})...", AppName);
var host = CreateHostBuilder(configuration, args);
Log.Information("Applying migrations ({ApplicationContext})...", AppName);
host.MigrateDbContext<CatalogContext>((context, services) =>
{
var env = services.GetService<IWebHostEnvironment>();
var settings = services.GetService<IOptions<CatalogSettings>>();
var logger = services.GetService<ILogger<CatalogContextSeed>>();
new CatalogContextSeed()
.SeedAsync(context, env, settings, logger)
.Wait();
})
.MigrateDbContext<IntegrationEventLogContext>((_, __) => { });
Log.Information("Starting web host ({ApplicationContext})...", AppName);
host.Run();
return 0;
}
catch (Exception ex)
{
Log.Fatal(ex, "Program terminated unexpectedly ({ApplicationContext})!", AppName);
return 1;
}
finally
{
Log.CloseAndFlush();
}
}
Beim Anwenden von Migrationen und Seeding einer Datenbank während des Containerstarts gibt es eine wichtige Einschränkung. Da der Datenbankserver möglicherweise aus beliebigen Gründen nicht verfügbar ist, müssen Wiederholungsversuche erfolgen, während Sie darauf warten, dass der Server verfügbar ist. Diese Wiederholungslogik wird von der MigrateDbContext()
Erweiterungsmethode behandelt, wie im folgenden Code gezeigt:
public static IWebHost MigrateDbContext<TContext>(
this IWebHost host,
Action<TContext,
IServiceProvider> seeder)
where TContext : DbContext
{
var underK8s = host.IsInKubernetes();
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
var logger = services.GetRequiredService<ILogger<TContext>>();
var context = services.GetService<TContext>();
try
{
logger.LogInformation("Migrating database associated with context {DbContextName}", typeof(TContext).Name);
if (underK8s)
{
InvokeSeeder(seeder, context, services);
}
else
{
var retry = Policy.Handle<SqlException>()
.WaitAndRetry(new TimeSpan[]
{
TimeSpan.FromSeconds(3),
TimeSpan.FromSeconds(5),
TimeSpan.FromSeconds(8),
});
//if the sql server container is not created on run docker compose this
//migration can't fail for network related exception. The retry options for DbContext only
//apply to transient exceptions
// Note that this is NOT applied when running some orchestrators (let the orchestrator to recreate the failing service)
retry.Execute(() => InvokeSeeder(seeder, context, services));
}
logger.LogInformation("Migrated database associated with context {DbContextName}", typeof(TContext).Name);
}
catch (Exception ex)
{
logger.LogError(ex, "An error occurred while migrating the database used on context {DbContextName}", typeof(TContext).Name);
if (underK8s)
{
throw; // Rethrow under k8s because we rely on k8s to re-run the pod
}
}
}
return host;
}
Der folgende Code in der benutzerdefinierten CatalogContextSeed-Klasse füllt die Daten auf.
public class CatalogContextSeed
{
public static async Task SeedAsync(IApplicationBuilder applicationBuilder)
{
var context = (CatalogContext)applicationBuilder
.ApplicationServices.GetService(typeof(CatalogContext));
using (context)
{
context.Database.Migrate();
if (!context.CatalogBrands.Any())
{
context.CatalogBrands.AddRange(
GetPreconfiguredCatalogBrands());
await context.SaveChangesAsync();
}
if (!context.CatalogTypes.Any())
{
context.CatalogTypes.AddRange(
GetPreconfiguredCatalogTypes());
await context.SaveChangesAsync();
}
}
}
static IEnumerable<CatalogBrand> GetPreconfiguredCatalogBrands()
{
return new List<CatalogBrand>()
{
new CatalogBrand() { Brand = "Azure"},
new CatalogBrand() { Brand = ".NET" },
new CatalogBrand() { Brand = "Visual Studio" },
new CatalogBrand() { Brand = "SQL Server" }
};
}
static IEnumerable<CatalogType> GetPreconfiguredCatalogTypes()
{
return new List<CatalogType>()
{
new CatalogType() { Type = "Mug"},
new CatalogType() { Type = "T-Shirt" },
new CatalogType() { Type = "Backpack" },
new CatalogType() { Type = "USB Memory Stick" }
};
}
}
Wenn Sie Integrationstests ausführen, ist es hilfreich, Daten zu generieren, die ihren Integrationstests entsprechen. Die Möglichkeit, alles von Grund auf neu zu erstellen, einschließlich einer Instanz von SQL Server, die auf einem Container ausgeführt wird, eignet sich hervorragend für Testumgebungen.
EF Core InMemory-Datenbank im Vergleich zur SQL Server-Ausführung als Container
Eine weitere gute Wahl beim Ausführen von Tests ist die Verwendung des Entity Framework InMemory-Datenbankanbieters. Sie können diese Konfiguration in der ConfigureServices-Methode der Startup-Klasse in Ihrem Web-API-Projekt angeben:
public class Startup
{
// Other Startup code ...
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IConfiguration>(Configuration);
// DbContext using an InMemory database provider
services.AddDbContext<CatalogContext>(opt => opt.UseInMemoryDatabase());
//(Alternative: DbContext using a SQL Server provider
//services.AddDbContext<CatalogContext>(c =>
//{
// c.UseSqlServer(Configuration["ConnectionString"]);
//
//});
}
// Other Startup code ...
}
Diese Option hat jedoch einen Nachteil. Die In-Memory-Datenbank unterstützt nicht viele Einschränkungen, die für eine bestimmte Datenbank spezifisch sind. Sie können z. B. einen eindeutigen Index zu einer Spalte in Ihrem EF Core-Modell hinzufügen und einen Test für Ihre In-Memory-Datenbank schreiben, um zu überprüfen, ob Sie keinen doppelten Wert hinzufügen können. Wenn Sie die In-Memory-Datenbank verwenden, können Sie jedoch keine eindeutigen Indizes für eine Spalte verarbeiten. Daher verhält sich die In-Memory-Datenbank nicht genau wie eine echte SQL Server-Datenbank– sie emuliert keine datenbankspezifischen Einschränkungen.
Dennoch ist eine In-Memory-Datenbank für Tests und Prototypen nützlich. Wenn Sie jedoch genaue Integrationstests erstellen möchten, die das Verhalten einer bestimmten Datenbankimplementierung berücksichtigen, müssen Sie eine echte Datenbank wie SQL Server verwenden. Zu diesem Zweck ist das Ausführen von SQL Server in einem Container eine großartige Wahl und genauer als der EF Core InMemory-Datenbankanbieter.
Verwenden eines Redis-Cachediensts, der in einem Container ausgeführt wird
Sie können Redis auf einem Container ausführen, insbesondere für Entwicklung und Tests und für Machbarkeitsszenarien. Dieses Szenario ist praktisch, da alle Abhängigkeiten in Containern laufen können, nicht nur für Ihre lokalen Entwicklungscomputer, sondern auch für Ihre Testumgebungen in Ihren CI/CD Pipelines.
Wenn Sie Redis jedoch in der Produktion ausführen, ist es besser, nach einer Hochverfügbarkeitslösung wie Redis Microsoft Azure zu suchen, die als PaaS (Platform as a Service) ausgeführt wird. In Ihrem Code müssen Sie lediglich Ihre Verbindungszeichenfolgen ändern.
Redis stellt ein Docker-Image mit Redis bereit. Dieses Image ist über Docker Hub unter dieser URL verfügbar:
https://hub.docker.com/_/redis/
Sie können einen Docker Redis-Container direkt ausführen, indem Sie den folgenden Docker CLI-Befehl in Ihrer Eingabeaufforderung ausführen:
docker run --name some-redis -d redis
Das Redis-Image enthält „expose:6379“ (von Redis verwendeter Port), sodass es durch die übliche Containerverknüpfung für die verknüpften Container automatisch verfügbar wird.
In eShopOnContainers verwendet der Microservice einen Redis-Cache, der als Container basket-api
ausgeführt wird. Dieser basketdata
Container wird als Teil der Datei mit mehreren Containern docker-compose.yml definiert, wie im folgenden Beispiel gezeigt:
#docker-compose.yml file
#...
basketdata:
image: redis
expose:
- "6379"
Dieser Code im docker-compose.yml definiert einen Container, der basierend auf dem Redis-Image benannt basketdata
ist und den Port 6379 intern veröffentlicht. Diese Konfiguration bedeutet, dass nur auf andere Container zugegriffen werden kann, die im Docker-Host ausgeführt werden.
Schließlich definiert der Microservice für das eShopOnContainers-Beispiel in der basket-api
die verbindungszeichenfolge, die für diesen Redis-Container verwendet werden soll:
basket-api:
environment:
# Other data ...
- ConnectionString=basketdata
- EventBusConnection=rabbitmq
Wie bereits erwähnt, wird der Name des Microservice basketdata
durch das interne Netzwerk-DNS von Docker aufgelöst.