Menggunakan server database yang berjalan sebagai kontainer
Tip
Konten ini adalah kutipan dari eBook, .NET Microservices Architecture for Containerized .NET Applications, tersedia di .NET Docs atau sebagai PDF yang dapat diunduh gratis dan dapat dibaca secara offline.
Anda dapat memiliki database Anda (SQL Server, PostgreSQL, MySQL, dll.) di server mandiri reguler, di kluster lokal, atau di layanan PaaS di cloud seperti Azure SQL DB. Namun, untuk lingkungan pengembangan dan pengujian, menjalankan database Anda sebagai kontainer nyaman, karena Anda tidak memiliki dependensi eksternal dan hanya menjalankan docker-compose up
perintah memulai seluruh aplikasi. Memiliki database tersebut sebagai kontainer juga bagus untuk pengujian integrasi, karena database dimulai dalam kontainer dan selalu diisi dengan data sampel yang sama, sehingga pengujian dapat lebih dapat diprediksi.
SQL Server berjalan sebagai kontainer dengan database terkait layanan mikro
Dalam eShopOnContainers, ada kontainer bernama sqldata
, seperti yang ditentukan dalam file docker-compose.yml, yang menjalankan SQL Server untuk instans Linux dengan database SQL untuk semua layanan mikro yang membutuhkannya.
Titik kunci dalam layanan mikro adalah bahwa setiap layanan mikro memiliki data terkait, sehingga harus memiliki database sendiri. Namun, database dapat berada di mana saja. Dalam hal ini, semuanya berada dalam kontainer yang sama untuk menjaga persyaratan memori Docker serendah mungkin. Perlu diingat bahwa ini adalah solusi yang cukup baik untuk pengembangan dan, mungkin, pengujian tetapi tidak untuk produksi.
Kontainer SQL Server dalam aplikasi sampel dikonfigurasi dengan kode YAML berikut dalam file docker-compose.yml, yang dijalankan saat Anda menjalankan docker-compose up
. Perhatikan bahwa kode YAML telah mengkonsolidasikan informasi konfigurasi dari file docker-compose.yml generik dan file docker-compose.override.yml. (Biasanya Anda akan memisahkan pengaturan lingkungan dari informasi dasar atau statis yang terkait dengan gambar SQL Server.)
sqldata:
image: mcr.microsoft.com/mssql/server:2017-latest
environment:
- SA_PASSWORD=Pass@word
- ACCEPT_EULA=Y
ports:
- "5434:1433"
Dengan cara yang sama, alih-alih menggunakan docker-compose
, perintah berikut dapat docker run
menjalankan kontainer tersebut:
docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=Pass@word' -p 5433:1433 -d mcr.microsoft.com/mssql/server:2017-latest
Namun, jika Anda menyebarkan aplikasi multikontainer seperti eShopOnContainers, lebih mudah untuk menggunakan docker-compose up
perintah sehingga menyebarkan semua kontainer yang diperlukan untuk aplikasi.
Saat Anda memulai kontainer SQL Server ini untuk pertama kalinya, kontainer menginisialisasi SQL Server dengan kata sandi yang Anda berikan. Setelah SQL Server berjalan sebagai kontainer, Anda dapat memperbarui database dengan menyambungkan melalui koneksi SQL reguler, seperti dari kode SQL Server Management Studio, Visual Studio, atau C#.
Aplikasi eShopOnContainers menginisialisasi setiap database layanan mikro dengan data sampel dengan menyemainya dengan data saat startup, seperti yang dijelaskan di bagian berikut.
Memiliki SQL Server berjalan sebagai kontainer tidak hanya berguna untuk demo ketika Anda mungkin tidak memiliki akses ke instans SQL Server. Seperti disebutkan, ini juga bagus untuk lingkungan pengembangan dan pengujian sehingga Anda dapat dengan mudah menjalankan pengujian integrasi mulai dari gambar SQL Server yang bersih dan data yang diketahui dengan menyemai data sampel baru.
Sumber daya tambahan
Jalankan gambar Docker SQL Server di Linux, Mac, atau Windows
https://learn.microsoft.com/sql/linux/sql-server-linux-setup-dockerKoneksi dan SQL Server kueri di Linux dengan sqlcmd
https://learn.microsoft.com/sql/linux/sql-server-linux-connect-and-query-sqlcmd
Seeding dengan data pengujian pada startup aplikasi Web
Untuk menambahkan data ke database saat aplikasi dimulai, Anda dapat menambahkan kode seperti berikut ini ke Main
metode di Program
kelas proyek API Web:
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();
}
}
Ada peringatan penting saat menerapkan migrasi dan menyemai database selama startup kontainer. Karena server database mungkin tidak tersedia karena alasan apa pun, Anda harus menangani percobaan ulang sambil menunggu server tersedia. Logika coba lagi ini ditangani oleh MigrateDbContext()
metode ekstensi, seperti yang ditunjukkan dalam kode berikut:
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;
}
Kode berikut di kelas CatalogContextSeed kustom mengisi 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" }
};
}
}
Saat Anda menjalankan pengujian integrasi, memiliki cara untuk menghasilkan data yang konsisten dengan pengujian integrasi Anda berguna. Mampu membuat semuanya dari awal, termasuk instans SQL Server yang berjalan pada kontainer, sangat bagus untuk lingkungan pengujian.
Database EF Core InMemory versus SQL Server berjalan sebagai kontainer
Pilihan lain yang baik saat menjalankan pengujian adalah menggunakan penyedia database Entity Framework InMemory. Anda dapat menentukan konfigurasi tersebut dalam metode ConfigureServices dari kelas Startup di proyek API Web Anda:
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 ...
}
Ada pengambilan penting, meskipun. Database dalam memori tidak mendukung banyak batasan yang khusus untuk database tertentu. Misalnya, Anda dapat menambahkan indeks unik pada kolom dalam model EF Core Anda dan menulis pengujian terhadap database dalam memori Anda untuk memeriksa bahwa itu tidak memungkinkan Anda menambahkan nilai duplikat. Tetapi saat Anda menggunakan database dalam memori, Anda tidak dapat menangani indeks unik pada kolom. Oleh karena itu, database dalam memori tidak bersifat sama persis dengan database SQL Server nyata—database tersebut tidak meniru batasan khusus database.
Meskipun demikian, database dalam memori masih berguna untuk pengujian dan pembuatan prototipe. Tetapi jika Anda ingin membuat pengujian integrasi akurat yang mempertimbangkan perilaku implementasi database tertentu, Anda perlu menggunakan database nyata seperti SQL Server. Untuk tujuan itu, menjalankan SQL Server dalam kontainer adalah pilihan yang bagus dan lebih akurat daripada penyedia database EF Core InMemory.
Menggunakan layanan cache Redis yang berjalan dalam kontainer
Anda dapat menjalankan Redis pada kontainer, terutama untuk pengembangan dan pengujian dan untuk skenario bukti konsep. Skenario ini nyaman, karena Anda dapat menjalankan semua dependensi Anda pada kontainer—bukan hanya untuk mesin pengembangan lokal Anda, tetapi untuk lingkungan pengujian Anda di alur CI/CD Anda.
Namun, ketika Anda menjalankan Redis dalam produksi, lebih baik mencari solusi ketersediaan tinggi seperti Redis Microsoft Azure, yang berjalan sebagai PaaS (Platform as a Service). Dalam kode, Anda hanya perlu mengubah string koneksi Anda.
Redis menyediakan gambar Docker dengan Redis. Gambar tersebut tersedia dari Docker Hub di URL ini:
https://hub.docker.com/_/redis/
Anda dapat langsung menjalankan kontainer Docker Redis dengan menjalankan perintah Docker CLI berikut di prompt perintah Anda:
docker run --name some-redis -d redis
Gambar Redis mencakup expose:6379 (port yang digunakan oleh Redis), sehingga penautan kontainer standar akan membuatnya tersedia secara otomatis ke kontainer yang ditautkan.
Di eShopOnContainers, basket-api
layanan mikro menggunakan cache Redis yang berjalan sebagai kontainer. Kontainer basketdata
tersebut ditentukan sebagai bagian dari file docker-compose.yml multikontainer, seperti yang ditunjukkan dalam contoh berikut:
#docker-compose.yml file
#...
basketdata:
image: redis
expose:
- "6379"
Kode dalam docker-compose.yml ini menentukan kontainer bernama basketdata
berdasarkan gambar redis dan menerbitkan port 6379 secara internal. Konfigurasi ini berarti bahwa konfigurasi ini hanya akan dapat diakses dari kontainer lain yang berjalan dalam host Docker.
Terakhir, dalam file docker-compose.override.yml, basket-api
layanan mikro untuk sampel eShopOnContainers menentukan string koneksi yang akan digunakan untuk kontainer Redis tersebut:
basket-api:
environment:
# Other data ...
- ConnectionString=basketdata
- EventBusConnection=rabbitmq
Seperti disebutkan sebelumnya, nama layanan basketdata
mikro diselesaikan oleh DNS jaringan internal Docker.
Saran dan Komentar
https://aka.ms/ContentUserFeedback.
Segera hadir: Sepanjang tahun 2024 kami akan menghentikan penggunaan GitHub Issues sebagai mekanisme umpan balik untuk konten dan menggantinya dengan sistem umpan balik baru. Untuk mengetahui informasi selengkapnya, lihat:Kirim dan lihat umpan balik untuk