Aracılığıyla paylaş


Kapsayıcı olarak çalışan bir veritabanı sunucusu kullanma

Tavsiye

Bu içerik, .NET Docs veya çevrimdışı olarak okunabilen ücretsiz indirilebilir bir PDF olarak sağlanan Kapsayıcılı .NET Uygulamaları için .NET Mikro Hizmet Mimarisi adlı e-Kitap'tan bir alıntıdır.

.NET Mikro Hizmetler Mimarisi Kapsayıcılı .NET Uygulamaları için eKitabın kapak küçük resmi .

Veritabanlarınızı (SQL Server, PostgreSQL, MySQL vb.) normal tek başına sunucularda, şirket içi kümelerde veya Azure SQL DB gibi buluttaki PaaS hizmetlerinde kullanabilirsiniz. Ancak, geliştirme ve test ortamlarında veritabanlarınızın kapsayıcı olarak çalıştırılması kullanışlıdır çünkü dış bağımlılığınız yoktur ve yalnızca komutu çalıştırmak docker-compose up uygulamanın tamamını başlatır. Veritabanı kapsayıcıda başlatıldığından ve her zaman aynı örnek verilerle dolduruldığından, bu veritabanlarının kapsayıcı olarak bulunması tümleştirme testleri için de harikadır, bu nedenle testler daha öngörülebilir olabilir.

eShopOnContainers'da, sqldata dosyasında tanımlandığı gibi, SQL veritabanlarına ihtiyaç duyan tüm mikro hizmetler için Linux üzerinde SQL Server örneği çalıştıran bir adlı kapsayıcı bulunmaktadır.

Mikro hizmetlerin önemli bir noktası, her mikro hizmetin kendi ilgili verilerine sahip olması ve bu nedenle kendi veritabanına sahip olmasıdır. Ancak veritabanları her yerde olabilir. Bu durumda, Docker bellek gereksinimlerini mümkün olduğunca düşük tutmak için hepsi aynı kapsayıcıdadır. Bunun geliştirme ve test için yeterli bir çözüm olduğunu ama üretim için olmadığını unutmayın.

Örnek uygulamadaki SQL Server kapsayıcısı, çalıştırdığınızda yürütülen docker-compose updocker-compose.yml dosyasında aşağıdaki YAML koduyla yapılandırılır. YAML kodunun genel docker-compose.yml dosyasından ve docker-compose.override.yml dosyasından yapılandırma bilgilerini birleştirdiğini unutmayın. (Genellikle ortam ayarlarını SQL Server görüntüsüyle ilgili temel veya statik bilgilerden ayırırsınız.)

  sqldata:
    image: mcr.microsoft.com/mssql/server:2017-latest
    environment:
      - SA_PASSWORD=Pass@word
      - ACCEPT_EULA=Y
    ports:
      - "5434:1433"

Benzer şekilde, docker-compose yerine aşağıdaki docker run komutu bu kapsayıcıyı çalıştırabilir:

docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=Pass@word' -p 5433:1433 -d mcr.microsoft.com/mssql/server:2017-latest

Ancak, eShopOnContainers gibi çok kapsayıcılı bir uygulama dağıtıyorsanız, uygulama için gerekli tüm kapsayıcıları dağıtacak şekilde komutunu kullanmak docker-compose up daha uygundur.

Bu SQL Server kapsayıcısını ilk kez başlattığınızda kapsayıcı, sağladığınız parolayla SQL Server'ı başlatır. SQL Server kapsayıcı olarak çalıştırıldıktan sonra, SQL Server Management Studio, Visual Studio veya C# kodu gibi herhangi bir normal SQL bağlantısı üzerinden bağlanarak veritabanını güncelleştirebilirsiniz.

eShopOnContainers uygulaması, aşağıdaki bölümde açıklandığı gibi, başlangıçta veri ekleyerek her mikro hizmetin veritabanını örnek verilerle başlatır.

SQL Server'ın kapsayıcı olarak çalıştırılması, yalnızca SQL Server örneğine erişiminiz olmayan bir tanıtım için değil, başka durumlar için de kullanışlıdır. Belirtildiği gibi, temiz bir SQL Server görüntüsünden ve bilinen verilerden başlayarak yeni örnek verileri dağıtarak tümleştirme testlerini kolayca çalıştırabilmeniz için geliştirme ve test ortamları için de idealdir.

Ek kaynaklar

Web uygulaması başlatmada test verileriyle tohumlama

Uygulama başlatıldığında veritabanına veri eklemek için, Web API projesinin sınıfındaki MainProgram yöntemine aşağıdaki gibi bir kod ekleyebilirsiniz:

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();
    }
}

Kapsayıcı başlatma sırasında geçişleri uygularken ve veritabanının tohumlarını oluştururken önemli bir uyarı vardır. Veritabanı sunucusu her nedenden dolayı kullanılamayabileceği için, sunucunun kullanılabilir olmasını beklerken yeniden denemeleri işlemeniz gerekir. Bu yeniden deneme mantığı, aşağıdaki kodda gösterildiği gibi uzantı yöntemi tarafından MigrateDbContext() işlenir:

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;
}

Özel CatalogContextSeed sınıfındaki aşağıdaki kod verileri doldurur.

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" }
        };
    }
}

Tümleştirme testlerini çalıştırdığınızda, tümleştirme testlerinizle tutarlı veri oluşturmanın bir yolu olması yararlıdır. Kapsayıcı üzerinde çalışan bir SQL Server örneği de dahil olmak üzere her şeyi sıfırdan oluşturabilmek, test ortamları için idealdir.

EF Core InMemory veritabanı ile kapsayıcı olarak çalışan SQL Server karşılaştırması

Testleri çalıştırırken bir diğer iyi seçenek de Entity Framework InMemory veritabanı sağlayıcısını kullanmaktır. Bu yapılandırmayı Web API projenizdeki Başlangıç sınıfının ConfigureServices yönteminde belirtebilirsiniz:

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 ...
}

Ancak önemli bir sakınca var. Bellek içi veritabanı, belirli bir veritabanına özgü birçok kısıtlamayı desteklemez. Örneğin, EF Core modelinizdeki bir sütuna benzersiz bir dizin ekleyebilir ve yinelenen bir değer eklemenize izin vermediğini denetlemek için bellek içi veritabanınıza bir test yazabilirsiniz. Ancak bellek içi veritabanını kullanırken, bir sütundaki benzersiz dizinleri işleyemezsiniz. Bu nedenle, bellek içi veritabanı gerçek bir SQL Server veritabanıyla tam olarak aynı şekilde davranmaz; veritabanına özgü kısıtlamalara öykünmez.

Yine de bellek içi veritabanı test ve prototip oluşturma için kullanışlıdır. Ancak belirli bir veritabanı uygulamasının davranışını dikkate alan doğru tümleştirme testleri oluşturmak istiyorsanız SQL Server gibi gerçek bir veritabanı kullanmanız gerekir. Bu amaçla, SQL Server'ı bir kapsayıcıda çalıştırmak, EF Core InMemory veritabanı sağlayıcısından daha iyi ve daha doğru bir seçimdir.

Bir kapsayıcıda çalışan redis önbellek hizmetini kullanma

Redis'i özellikle geliştirme ve test ve kavram kanıtı senaryoları için bir kapsayıcıda çalıştırabilirsiniz. Yalnızca yerel geliştirme makineleriniz için değil, CI/CD işlem hatlarınızdaki test ortamlarınız için de tüm bağımlılıklarınızın kapsayıcılarda çalıştırılmasını sağladığınızdan bu senaryo kullanışlıdır.

Ancak, Redis'i üretim ortamında çalıştırdığınızda, PaaS (Hizmet Olarak Platform) olarak çalışan Redis Microsoft Azure gibi yüksek kullanılabilirlikli bir çözüm aramak daha iyidir. Kodunuzda bağlantı dizelerinizi değiştirmeniz yeterlidir.

Redis, Redis ile bir Docker görüntüsü sağlar. Bu görüntü şu URL'deki Docker Hub'dan edinilebilir:

https://hub.docker.com/_/redis/

Komut isteminizde aşağıdaki Docker CLI komutunu yürüterek docker Redis kapsayıcısını doğrudan çalıştırabilirsiniz:

docker run --name some-redis -d redis

Redis görüntüsü, expose:6379 (Redis tarafından kullanılan bağlantı noktası) içerir, bu nedenle standart kapsayıcı bağlama, bağlantılı kapsayıcılar için otomatik olarak kullanılabilir hale getirir.

eShopOnContainers'da basket-api mikro hizmet, kapsayıcı olarak çalışan bir Redis önbelleği kullanır. Bu basketdata kapsayıcı, aşağıdaki örnekte gösterildiği gibi çok kapsayıcılı docker-compose.yml dosyasının bir parçası olarak tanımlanır:

#docker-compose.yml file
#...
  basketdata:
    image: redis
    expose:
      - "6379"

docker-compose.yml dosyasındaki bu kod, redis görüntüsünü temel alan ve 6379 numaralı bağlantı noktasını dahili olarak yayımlayan basketdata adlı bir kapsayıcı tanımlar. Bu yapılandırma, yalnızca Docker konağı içinde çalışan diğer kapsayıcılardan erişilebileceği anlamına gelir.

Son olarak, docker-compose.override.yml dosyasında, eShopOnContainers örneği için basket-api mikro hizmet bu Redis kapsayıcısı için kullanılacak bağlantı dizesini tanımlar:

  basket-api:
    environment:
      # Other data ...
      - ConnectionString=basketdata
      - EventBusConnection=rabbitmq

Daha önce belirtildiği gibi, mikro hizmetin basketdata adı Docker'ın iç ağ DNS'si tarafından çözümlenir.