Uwaga
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Wskazówka
Ta treść jest fragmentem eBooka "Architektura mikrousług .NET dla konteneryzowanych aplikacji .NET", dostępnego na .NET Docs lub jako bezpłatny plik PDF do pobrania i czytania 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 ma żadnych zależności zewnętrznych, a samo uruchomienie polecenia docker-compose up
rozpoczyna działanie całej aplikacji. 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
, można uruchomić ten kontener za pomocą następującego polecenia docker run
.
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, uzupełniając je na starcie, jak opisano w poniższej sekcji.
Posiadanie SQL Server uruchomionego jako kontener jest użyteczne nie tylko w przypadku demonstracji, gdy nie masz dostępu do instancji 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
Uruchom obraz Dockera SQL Server na Linux, Mac lub Windows
https://learn.microsoft.com/sql/linux/sql-server-linux-setup-dockerNawiązywanie połączenia z programem SQL Server w systemie Linux i wykonywanie zapytań o nie za pomocą narzędzia sqlcmd
https://learn.microsoft.com/sql/linux/sql-server-linux-connect-and-query-sqlcmd
Inicjowanie za pomocą danych testowych podczas uruchamiania aplikacji webowej
Aby dodać dane do bazy danych podczas uruchamiania aplikacji, możesz dodać kod podobny do poniższego do metody w klasie Main
projektu interfejsu Web 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 wypełniania bazy danych podczas uruchamiania kontenera istnieje 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. Logika ponawiania jest obsługiwana przez metodę rozszerzenia MigrateDbContext()
, 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 ...
}
Jest jednak ważny haczyk. Baza danych w pamięci nie obsługuje wielu ograniczeń specyficznych dla określonej bazy danych. Na przykład możesz dodać unikatowy indeks do kolumny w modelu EF Core i napisać test dla bazy danych w pamięci, aby sprawdzić, czy nie pozwala dodać zduplikowanej 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 korzystny, 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/CD.
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.
Redis udostępnia obraz Dockera z Redis. Ten obraz jest dostępny w usłudze Docker Hub pod tym adresem URL:
https://hub.docker.com/_/redis/
Kontener Docker Redis można uruchomić bezpośrednio, wykonując następujące polecenie CLI 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
, który bazuje na obrazie redis i publikuje port 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 docker-compose.override.yml mikrousługa z przykładowego projektu eShopOnContainers definiuje ciąg połączenia 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.