Použití databázového serveru spuštěného jako kontejneru
Tip
Tento obsah je výňatek z eBooku, architektury mikroslužeb .NET pro kontejnerizované aplikace .NET, které jsou k dispozici na .NET Docs nebo jako zdarma ke stažení PDF, které lze číst offline.
Databáze (SQL Server, PostgreSQL, MySQL atd.) můžete mít na běžných samostatných serverech, v místních clusterech nebo ve službách PaaS v cloudu, jako je Azure SQL DB. Pro vývojová a testovací prostředí je ale praktické mít databáze spuštěné jako kontejnery, protože nemáte žádnou externí závislost a jednoduše spustíte docker-compose up
příkaz, spustí se celá aplikace. Tyto databáze jako kontejnery jsou také skvělé pro testy integrace, protože databáze je spuštěná v kontejneru a vždy se naplní stejnými ukázkovými daty, takže testy mohou být předvídatelnější.
SQL Server spuštěný jako kontejner s databází související s mikroslužbou
V eShopOnContainers existuje kontejner s názvem sqldata
, jak je definováno v souboru docker-compose.yml , který spouští SQL Server pro Linux s databázemi SQL pro všechny mikroslužby, které ho potřebují.
Klíčovým bodem mikroslužeb je, že každá mikroslužba vlastní svá související data, takže by měla mít vlastní databázi. Databáze ale můžou být kdekoli. V tomto případě jsou všechny ve stejném kontejneru, aby požadavky na paměť Dockeru byly co nejnižší. Mějte na paměti, že toto je vhodné řešení pro vývoj a možná i testování, ale ne pro produkční prostředí.
Kontejner SQL Serveru v ukázkové aplikaci je nakonfigurovaný s následujícím kódem YAML v souboru docker-compose.yml, který se spustí při spuštění docker-compose up
. Všimněte si, že kód YAML konsolidoval konfigurační informace z obecného souboru docker-compose.yml a souboru docker-compose.override.yml. (Obvykle byste nastavení prostředí rozdělili od základní nebo statické informace související s imagí SQL Serveru.)
sqldata:
image: mcr.microsoft.com/mssql/server:2017-latest
environment:
- SA_PASSWORD=Pass@word
- ACCEPT_EULA=Y
ports:
- "5434:1433"
Podobně můžete místo použití docker-compose
spustit tento kontejner pomocí následujícího docker run
příkazu:
docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=Pass@word' -p 5433:1433 -d mcr.microsoft.com/mssql/server:2017-latest
Pokud ale nasazujete vícekontejnerovou aplikaci, jako je eShopOnContainers, je vhodnější použít docker-compose up
příkaz, aby nasadil všechny požadované kontejnery pro aplikaci.
Při prvním spuštění tohoto kontejneru SQL Serveru inicializuje kontejner SQL Server heslem, které zadáte. Jakmile SQL Server běží jako kontejner, můžete databázi aktualizovat připojením přes jakékoli běžné připojení SQL, jako je SQL Server Management Studio, Visual Studio nebo kód jazyka C#.
Aplikace eShopOnContainers inicializuje každou databázi mikroslužeb s ukázkovými daty tak, že ji založí s daty při spuštění, jak je vysvětleno v následující části.
Použití SQL Serveru jako kontejneru není jen užitečné pro ukázku, kde možná nemáte přístup k instanci SQL Serveru. Jak jsme si poznamenali, je také skvělé pro vývojová a testovací prostředí, abyste mohli snadno spouštět integrační testy počínaje čistou imagí SQL Serveru a známými daty tak, že odsadíte nová ukázková data.
Další materiály
Spuštění image Dockeru SQL Serveru v Linuxu, Macu nebo Windows
https://learn.microsoft.com/sql/linux/sql-server-linux-setup-dockerPřipojení a dotazování SQL Serveru v Linuxu pomocí sqlcmd
https://learn.microsoft.com/sql/linux/sql-server-linux-connect-and-query-sqlcmd
Počáteční nasazení s testovacími daty při spuštění webové aplikace
Pokud chcete přidat data do databáze při spuštění aplikace, můžete do metody ve Program
třídě projektu webového rozhraní API přidat kód podobný následujícímuMain
:
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();
}
}
Při použití migrací a počátečního nasazení databáze během spouštění kontejneru je důležité upozornění. Vzhledem k tomu, že databázový server nemusí být z jakéhokoli důvodu dostupný, je nutné zpracovat opakování při čekání na dostupnost serveru. Tuto logiku MigrateDbContext()
opakování zpracovává metoda rozšíření, jak je znázorněno v následujícím kódu:
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;
}
Následující kód ve vlastní třídě CatalogContextSeed naplní data.
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" }
};
}
}
Při spouštění integračních testů je užitečné generovat data konzistentní s integračními testy. Schopnost vytvářet vše od začátku, včetně instance SQL Serveru spuštěného v kontejneru, je skvělá pro testovací prostředí.
Databáze EF Core InMemory versus SQL Server spuštěný jako kontejner
Další dobrou volbou při spouštění testů je použití zprostředkovatele databáze Entity Framework InMemory. Tuto konfiguraci můžete zadat v metodě ConfigureServices třídy Startup v projektu webového rozhraní API:
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 ...
}
Je tu ale důležitý úlovek. Databáze v paměti nepodporuje mnoho omezení specifických pro konkrétní databázi. Můžete například přidat jedinečný index do sloupce v modelu EF Core a napsat test pro vaši databázi v paměti, abyste zkontrolovali, že neumožňuje přidat duplicitní hodnotu. Pokud ale používáte databázi v paměti, nemůžete zpracovat jedinečné indexy ve sloupci. Proto se databáze v paměti nechová přesně stejně jako skutečná databáze SQL Serveru – nenapodobuje omezení specifická pro databázi.
I tak je databáze v paměti stále užitečná pro testování a vytváření prototypů. Pokud ale chcete vytvořit přesné integrační testy, které berou v úvahu chování konkrétní implementace databáze, musíte použít skutečnou databázi, jako je SQL Server. Pro tento účel je spuštění SQL Serveru v kontejneru skvělou volbou a přesnější než poskytovatel databáze EF Core InMemory.
Použití služby Mezipaměti Redis spuštěné v kontejneru
Redis můžete spustit v kontejneru, zejména pro vývoj a testování a testování scénářů testování konceptu. Tento scénář je vhodný, protože můžete mít všechny závislosti spuštěné na kontejnerech – nejen pro místní vývojové počítače, ale pro testovací prostředí v kanálech CI/CD.
Když ale spustíte Redis v produkčním prostředí, je lepší vyhledat řešení s vysokou dostupností, jako je Redis Microsoft Azure, které běží jako PaaS (platforma jako služba). V kódu stačí změnit připojovací řetězec.
Redis poskytuje image Dockeru s Redisem. Tato image je k dispozici v Docker Hubu na této adrese URL:
https://hub.docker.com/_/redis/
Kontejner Docker Redis můžete přímo spustit spuštěním následujícího příkazu Rozhraní příkazového řádku Dockeru na příkazovém řádku:
docker run --name some-redis -d redis
Image Redis zahrnuje zveřejnění:6379 (port používaný Redisem), takže standardní propojení kontejnerů zajistí, aby bylo pro propojené kontejnery automaticky dostupné.
V eShopOnContainers basket-api
používá mikroslužba mezipaměť Redis spuštěnou jako kontejner. Tento basketdata
kontejner je definován jako součást souboru docker-compose.yml s více kontejnery, jak je znázorněno v následujícím příkladu:
#docker-compose.yml file
#...
basketdata:
image: redis
expose:
- "6379"
Tento kód v docker-compose.yml definuje kontejner pojmenovaný basketdata
na základě image Redis a interně publikuje port 6379. Tato konfigurace znamená, že bude přístupná jenom z jiných kontejnerů spuštěných v rámci hostitele Dockeru.
Nakonec v souboru basket-api
docker-compose.override.yml definuje mikroslužba pro ukázku eShopOnContainers připojovací řetězec, která se má použít pro tento kontejner Redis:
basket-api:
environment:
# Other data ...
- ConnectionString=basketdata
- EventBusConnection=rabbitmq
Jak už bylo zmíněno dříve, název mikroslužby basketdata
se překládá interním síťovým DNS Dockeru.