Używanie serwera bazy danych uruchomionego jako kontener
Napiwek
Ta zawartość jest fragmentem książki eBook, architektury mikrousług platformy .NET dla konteneryzowanych aplikacji platformy .NET dostępnych na platformie .NET Docs lub jako bezpłatnego pliku PDF, który można odczytać w trybie offline.
Możesz mieć bazy danych (SQL Server, PostgreSQL, MySQL itp.) na zwykłych serwerach autonomicznych, w klastrach lokalnych lub w usługach PaaS w chmurze, takich jak usługa Azure SQL DB. Jednak w przypadku środowisk programistycznych i testowych uruchamianie baz danych jako kontenerów jest wygodne, ponieważ nie masz żadnej zależności zewnętrznej i po prostu uruchamia polecenie uruchamia docker-compose up
całą aplikację. Posiadanie tych baz danych jako kontenerów jest również doskonałe do testów integracji, ponieważ baza danych jest uruchamiana w kontenerze i zawsze jest wypełniana tymi samymi przykładowymi danymi, dzięki czemu testy mogą być bardziej przewidywalne.
Program SQL Server uruchomiony jako kontener z bazą danych związaną z mikrousługą
W usłudze eShopOnContainers istnieje kontener o nazwie sqldata
, zgodnie z definicją w pliku docker-compose.yml , który uruchamia wystąpienie programu SQL Server dla systemu Linux z bazami danych SQL dla wszystkich mikrousług, które ich potrzebują.
Kluczowym punktem w mikrousługach jest to, że każda mikrousługa jest właścicielem powiązanych danych, więc powinna mieć własną bazę danych. Bazy danych mogą jednak znajdować się w dowolnym miejscu. W takim przypadku wszystkie znajdują się w tym samym kontenerze, aby zapewnić możliwie najmniejsze wymagania dotyczące pamięci platformy Docker. Należy pamiętać, że jest to wystarczająco dobre rozwiązanie do programowania i, być może, testowanie, ale nie dla środowiska produkcyjnego.
Kontener programu SQL Server w przykładowej aplikacji jest skonfigurowany przy użyciu następującego kodu YAML w pliku docker-compose.yml, który jest wykonywany po uruchomieniu polecenia docker-compose up
. Należy pamiętać, że kod YAML zawiera skonsolidowane informacje o konfiguracji z ogólnego pliku docker-compose.yml i pliku docker-compose.override.yml. (Zazwyczaj ustawienia środowiska należy oddzielić od podstawowych lub statycznych informacji związanych z obrazem programu SQL Server).
sqldata:
image: mcr.microsoft.com/mssql/server:2017-latest
environment:
- SA_PASSWORD=Pass@word
- ACCEPT_EULA=Y
ports:
- "5434:1433"
W podobny sposób zamiast używać docker-compose
polecenia , następujące docker run
polecenie może uruchomić ten kontener:
docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=Pass@word' -p 5433:1433 -d mcr.microsoft.com/mssql/server:2017-latest
Jeśli jednak wdrażasz aplikację z wieloma kontenerami, na przykład eShopOnContainers, bardziej wygodne jest użycie docker-compose up
polecenia , aby wdrożyć wszystkie wymagane kontenery dla aplikacji.
Po pierwszym uruchomieniu tego kontenera programu SQL Server kontener inicjuje program SQL Server przy użyciu podanego hasła. Po uruchomieniu programu SQL Server jako kontenera możesz zaktualizować bazę danych, łącząc się za pośrednictwem dowolnego zwykłego połączenia SQL, takiego jak program SQL Server Management Studio, program Visual Studio lub kod języka C#.
Aplikacja eShopOnContainers inicjuje każdą bazę danych mikrousług z przykładowymi danymi, rozmieszczając je przy użyciu danych podczas uruchamiania, jak wyjaśniono w poniższej sekcji.
Posiadanie programu SQL Server uruchomionego jako kontener nie jest tylko przydatne w przypadku pokazu, w którym być może nie masz dostępu do wystąpienia programu SQL Server. Jak wspomniano, doskonale nadaje się również do środowisk programistycznych i testowych, dzięki czemu można łatwo uruchamiać testy integracji, zaczynając od czystego obrazu programu SQL Server i znanych danych, rozmieszczając nowe przykładowe dane.
Dodatkowe zasoby
Uruchamianie obrazu platformy Docker programu SQL Server w systemie Linux, Mac lub Windows
https://learn.microsoft.com/sql/linux/sql-server-linux-setup-dockerPołączenie i wykonywanie zapytań dotyczących programu SQL Server w systemie Linux przy użyciu narzędzia sqlcmd
https://learn.microsoft.com/sql/linux/sql-server-linux-connect-and-query-sqlcmd
Rozmieszczanie przy użyciu danych testowych podczas uruchamiania aplikacji internetowej
Aby dodać dane do bazy danych podczas uruchamiania aplikacji, możesz dodać kod podobny do Main
poniższego do metody w Program
klasie projektu internetowego interfejsu API:
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();
}
}
Podczas stosowania migracji i rozmieszczania bazy danych podczas uruchamiania kontenera jest ważne zastrzeżenie. Ponieważ serwer bazy danych może nie być dostępny z jakiegokolwiek powodu, należy obsługiwać ponawianie prób podczas oczekiwania na dostępność serwera. Ta logika ponawiania jest obsługiwana przez metodę MigrateDbContext()
rozszerzenia, jak pokazano w poniższym kodzie:
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;
}
Poniższy kod w klasie custom CatalogContextSeed wypełnia dane.
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" }
};
}
}
Podczas uruchamiania testów integracji przydatne jest generowanie danych spójnych z testami integracji. Możliwość tworzenia wszystkich elementów od podstaw, w tym wystąpienia programu SQL Server uruchomionego w kontenerze, doskonale nadaje się do środowisk testowych.
Baza danych programu EF Core InMemory a program SQL Server uruchomiony jako kontener
Innym dobrym wyborem podczas uruchamiania testów jest użycie dostawcy bazy danych Programu Entity Framework InMemory. Tę konfigurację można określić w metodzie ConfigureServices klasy Startup w projekcie internetowego interfejsu 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 ...
}
Istnieje jednak ważny połowu. Baza danych w pamięci nie obsługuje wielu ograniczeń specyficznych dla określonej bazy danych. Na przykład możesz dodać unikatowy indeks w kolumnie w modelu EF Core i napisać test względem bazy danych w pamięci, aby sprawdzić, czy nie pozwala dodać zduplikowanych wartości. Jednak w przypadku korzystania z bazy danych w pamięci nie można obsługiwać unikatowych indeksów w kolumnie. W związku z tym baza danych w pamięci nie zachowuje się dokładnie tak samo jak rzeczywista baza danych programu SQL Server — nie emuluje ograniczeń specyficznych dla bazy danych.
Mimo to baza danych w pamięci jest nadal przydatna do testowania i tworzenia prototypów. Jeśli jednak chcesz utworzyć dokładne testy integracji, które uwzględniają zachowanie określonej implementacji bazy danych, musisz użyć prawdziwej bazy danych, takiej jak SQL Server. W tym celu uruchomienie programu SQL Server w kontenerze jest doskonałym wyborem i dokładniejsze niż dostawca bazy danych EF Core InMemory.
Korzystanie z usługi Redis Cache uruchomionej w kontenerze
Usługę Redis można uruchamiać w kontenerze, szczególnie na potrzeby programowania i testowania oraz scenariuszy weryfikacji koncepcji. Ten scenariusz jest wygodny, ponieważ możesz mieć wszystkie zależności działające na kontenerach — nie tylko dla lokalnych maszyn deweloperskich, ale także dla środowisk testowych w potokach ciągłej integracji/ciągłego wdrażania.
Jednak po uruchomieniu usługi Redis w środowisku produkcyjnym lepiej jest wyszukać rozwiązanie o wysokiej dostępności, takie jak Redis Microsoft Azure, które działa jako usługa PaaS (platforma jako usługa). W kodzie wystarczy zmienić parametry połączenia.
Usługa Redis udostępnia obraz platformy Docker z usługą Redis. Ten obraz jest dostępny w usłudze Docker Hub pod tym adresem URL:
https://hub.docker.com/_/redis/
Kontener Usługi Docker Redis można uruchomić bezpośrednio, wykonując następujące polecenie interfejsu wiersza polecenia platformy Docker w wierszu polecenia:
docker run --name some-redis -d redis
Obraz usługi Redis zawiera element expose:6379 (port używany przez usługę Redis), dlatego standardowe łączenie kontenerów spowoduje automatyczne udostępnienie go połączonym kontenerom.
W aplikacji eShopOnContainers basket-api
mikrousługa używa pamięci podręcznej Redis działającej jako kontener. Ten basketdata
kontener jest definiowany jako część pliku docker-compose.yml z wieloma kontenerami, jak pokazano w poniższym przykładzie:
#docker-compose.yml file
#...
basketdata:
image: redis
expose:
- "6379"
Ten kod w docker-compose.yml definiuje kontener o nazwie basketdata
na podstawie obrazu redis i publikowanie portu 6379 wewnętrznie. Ta konfiguracja oznacza, że będzie ona dostępna tylko z innych kontenerów uruchomionych na hoście platformy Docker.
Na koniec w pliku basket-api
docker-compose.override.yml mikrousługa dla przykładu eShopOnContainers definiuje parametry połączenia do użycia dla tego kontenera Redis:
basket-api:
environment:
# Other data ...
- ConnectionString=basketdata
- EventBusConnection=rabbitmq
Jak wspomniano wcześniej, nazwa mikrousługi basketdata
jest rozpoznawana przez wewnętrzną sieć platformy Docker DNS.