RO EN

Ce înseamnă cloud-native — principii și mentalitate

Ce înseamnă cloud-native — principii și mentalitate
Doru Bulubașa
09 iunie 2026

De ce contează cum ajungi în cloud

Migrezi o aplicație în cloud și te aștepți la facturi mai mici, scalare automată și zero downtime. Câteva luni mai târziu realizezi că plătești mai mult ca înainte, scalarea nu funcționează cum trebuie și deploy-urile sunt tot stresante. Ce s-a întâmplat?

Cel mai probabil ai făcut lift & shift — ai luat aplicația exact cum era și ai mutat-o pe o mașină virtuală în cloud. Aplicația nu știe că e în cloud. Nu folosește nimic din ce oferă platforma. E ca și cum ai cumpărat un Tesla și îl împingi cu mâna.

Cloud-native înseamnă să construiești (sau să transformi) aplicații care exploatează în mod activ caracteristicile mediului cloud: elasticitate, managed services, observabilitate, deployment automat, reziliență la eșecuri parțiale.

În această serie vom parcurge sistematic toate piesele puzzle-ului: de la principii și mentalitate (articolul acesta) până la containerizare, Azure Container Apps, Cosmos DB, observabilitate cu Application Insights, CI/CD și securitate cu Managed Identity.


Lift & shift vs. cloud-native — diferența reală

Lift & shift nu e greșit per se. E o strategie validă când ai constrângeri de timp sau buget. Problema e să confunzi migrarea cu modernizarea.

Criteriu Lift & shift Cloud-native
Infrastructură VM-uri cu OS administrat de tine Managed services (App Service, ACA, AKS)
Scalare Manuală Automată, bazată pe metrici sau KEDA
Configurație Fișiere config pe disk, setate manual Azure App Configuration, Key Vault, env vars injectate
Stare Sesiuni în memorie, fișiere locale Sesiuni externalizate (Redis), storage separat (Blob)
Deploy RDP, FTP, scripturi manuale CI/CD automat, blue-green, canary
Eșecuri Aplicația cade, cineva o repornește Health checks, restart automat, circuit breakers
Observabilitate Log-uri în fișiere, verificate manual Structured logging, distributed tracing, dashboards

Diferența fundamentală e de mentalitate: în cloud-native presupui că lucrurile vor eșua — instanțe cad, rețeaua are latență, servicii externe devin indisponibile — și construiești reziliență în cod, nu în infrastructură.


The Twelve-Factor App — relevanță în 2025

Metodologia 12-Factor App a fost publicată de Heroku în 2012. Pare veche, dar cei 12 factori sunt mai relevanți ca niciodată — toate best practices moderne de cloud (containers, microservices, serverless) se întemeiază pe ei.

I. Codebase — un repository, mai multe deployment-uri

O aplicație = un repository Git. Același cod rulează în Development, Staging și Production — diferența o fac configurațiile, nu codul. Nu ai branch-uri separate pentru Production.

II. Dependencies — declarate explicit, niciodată implicite

Toate dependențele sunt declarate în .csproj. Nu presupui că un tool există deja pe OS. Dacă aplicația ta depinde de wkhtmltopdf instalat global pe server, ai o problemă cloud-native.

<ItemGroup>
  <PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="9.0.0" />
  <PackageReference Include="Azure.Extensions.AspNetCore.Configuration.Secrets" Version="1.3.2" />
  <PackageReference Include="Azure.Identity" Version="1.13.1" />
</ItemGroup>

III. Config — stocată în mediu, nu în cod

Configurația care diferă între deployment-uri nu are ce căuta în cod sau în fișiere comise în Git.

var builder = WebApplication.CreateBuilder(args);

builder.Configuration
    .AddJsonFile("appsettings.json", optional: false)
    .AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true)
    .AddEnvironmentVariables();

// Key Vault in productie, prin Managed Identity
if (builder.Environment.IsProduction())
{
    var kvUri = builder.Configuration["KeyVaultUri"]
        ?? throw new InvalidOperationException("KeyVaultUri nu e configurat");
    builder.Configuration.AddAzureKeyVault(new Uri(kvUri), new DefaultAzureCredential());
}

Regula simplă: dacă ar trebui să schimbi codul sau să refaci build-ul pentru a trece din Development în Production, configurația e în locul greșit.

IV. Backing services — tratate ca resurse atașate

Baza de date, cache-ul Redis, cozile de mesaje — toate sunt resurse externe, identificate prin URL/connection string. Poți înlocui Cosmos DB local (emulatorul) cu cel din cloud schimbând o variabilă de mediu, fără modificări în cod.

V. Logs — tratate ca stream de evenimente

Aplicația scrie log-uri la stdout/stderr. Nu gestionează fișiere de log, nu face rotație. Mediul (Azure Monitor, Application Insights) captează și agregă.

// Structured logging -- nu interpolari de string
_logger.LogInformation("Cerere procesata pentru {CustomerId} in {ElapsedMs}ms",
    customerId, elapsed.TotalMilliseconds);

// NU asa -- pierde structura, devine plain text:
_logger.LogInformation($"Cerere procesata pentru {customerId}");

Structured logging produce JSON în loc de text — mult mai ușor de interogat în Application Insights sau Log Analytics.


Servicii stateless — de ce și cum

Un serviciu stateless nu rețin nicio stare locală între request-uri. Poți rula 1, 10 sau 100 de instanțe ale aceluiași serviciu și orice instanță poate servi orice request. Scalarea orizontală devine trivială — Azure App Service poate scala automat de la 1 la N instanțe. Dacă aplicația are stare locală, scalarea produce inconsistențe.

Anti-pattern: stare în memorie

// GRESIT -- lista exista doar in instanta curenta
public class OrderService
{
    private readonly List<Order> _pendingOrders = new();

    public void AddPendingOrder(Order order)
    {
        _pendingOrders.Add(order); // pierduta la restart sau pe alta instanta
    }
}

Pattern corect: stare externalizată în Redis

// CORECT -- stare in Redis, vizibila tuturor instantelor
public class OrderService
{
    private readonly IDistributedCache _cache;
    public OrderService(IDistributedCache cache) => _cache = cache;

    public async Task AddPendingOrderAsync(Order order)
    {
        var key  = $"pending-order:{order.Id}";
        var json = JsonConvert.SerializeObject(order);
        await _cache.SetStringAsync(key, json, new DistributedCacheEntryOptions
        {
            AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(24)
        });
    }
}

// Program.cs
builder.Services.AddStackExchangeRedisCache(options =>
    options.Configuration = builder.Configuration.GetConnectionString("Redis"));

Fișierele locale urmează aceeași regulă: Azure Blob Storage în loc de disk local. Sesiunile HTTP se externalizează la fel, prin AddStackExchangeRedisCache + AddSession.


Externalizarea configurației — pattern complet

Nivel 1: appsettings.json — structura și valorile default

{
  "Logging": { "LogLevel": { "Default": "Information" } },
  "Features": { "EnableNewDashboard": false, "MaxUploadSizeMb": 10 },
  "CosmosDb": { "DatabaseName": "MyAppDb", "Endpoint": "" }
}

Nivel 2: variabile de mediu — override în Azure App Service

Convenția pentru structuri nested folosește __ (double underscore) în loc de ::

CosmosDb__Endpoint=https://myaccount.documents.azure.com:443/
CosmosDb__DatabaseName=MyAppDb-Prod
Features__EnableNewDashboard=true

Nivel 3: Azure Key Vault — secrete reale, fără credențiale în cod

Adaugă validare la startup ca să detectezi configurația lipsă în momentul deployului, nu la primul request:

builder.Services.AddOptions<CosmosDbOptions>()
    .BindConfiguration("CosmosDb")
    .ValidateDataAnnotations()
    .ValidateOnStart();

public class CosmosDbOptions
{
    [Required] public string Endpoint { get; set; } = default!;
    [Required] public string DatabaseName { get; set; } = default!;
    public string? AccountKey { get; set; } // vine din Key Vault
}

Health Checks — observabilitate de bază

Health checks sunt endpoint-uri HTTP folosite de Azure App Service, Azure Container Apps și Kubernetes pentru a decide dacă o instanță primește trafic sau trebuie repornită.

Separă liveness (aplicația e vie) de readiness (baza de date e accesibilă, cache-ul a pornit). Un serviciu poate fi “viu” dar nu “pregătit” — în acest caz nu repornești instanța, doar o scoți temporar din rotație.

builder.Services.AddHealthChecks()
    .AddCheck("self", () => HealthCheckResult.Healthy(), tags: new[] { "live" })
    .AddAzureCosmosDB(
        sp => sp.GetRequiredService<CosmosClient>(),
        name: "cosmos", tags: new[] { "ready" })
    .AddRedis(
        builder.Configuration.GetConnectionString("Redis")!,
        name: "redis", tags: new[] { "ready" });

app.MapHealthChecks("/health/live", new HealthCheckOptions
    { Predicate = c => c.Tags.Contains("live") });
app.MapHealthChecks("/health/ready", new HealthCheckOptions
    { Predicate = c => c.Tags.Contains("ready") });

Health check custom pentru dependințe externe

public class ExternalApiHealthCheck : IHealthCheck
{
    private readonly HttpClient _http;
    public ExternalApiHealthCheck(IHttpClientFactory f)
        => _http = f.CreateClient("external-api");

    public async Task<HealthCheckResult> CheckHealthAsync(
        HealthCheckContext ctx, CancellationToken ct = default)
    {
        try
        {
            using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
            cts.CancelAfter(TimeSpan.FromSeconds(5));
            var r = await _http.GetAsync("/health", cts.Token);
            return r.IsSuccessStatusCode
                ? HealthCheckResult.Healthy("API extern disponibil")
                : HealthCheckResult.Degraded($"HTTP {(int)r.StatusCode}");
        }
        catch (Exception ex)
        { return HealthCheckResult.Unhealthy("API extern inaccesibil", ex); }
    }
}

builder.Services.AddHealthChecks()
    .AddCheck<ExternalApiHealthCheck>("external-api", tags: new[] { "ready" });

Graceful Shutdown — termini curat

Într-un mediu cloud, instanțele aplicației sunt pornite și oprite frecvent: deploy-uri, scalare down, rebalansare. Fără graceful shutdown request-urile sunt tăiate brusc, tranzacțiile DB rămân deschise, mesajele din cozi sunt pierdute sau procesate de două ori.

La primirea semnalului SIGTERM, host-ul ASP.NET Core triggerează IHostApplicationLifetime.ApplicationStopping și așteaptă finalizarea request-urilor active. Configurează timeout-ul corespunzător:

builder.Services.Configure<HostOptions>(options =>
    options.ShutdownTimeout = TimeSpan.FromSeconds(45));

Background services cu graceful shutdown

CancellationToken-ul primit în ExecuteAsync e anulat automat când host-ul primește semnalul de oprire:

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    _logger.LogInformation("Message processor pornit");

    await foreach (var message in _queue.ReadAllAsync(stoppingToken))
    {
        try
        {
            await ProcessMessageAsync(message, stoppingToken);
        }
        catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
        {
            _logger.LogInformation("Shutdown detectat -- oprire procesare");
            break;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Eroare mesaj {MessageId}", message.Id);
            // Nu re-arunca -- un mesaj gresit nu opreste service-ul
        }
    }

    _logger.LogInformation("Message processor oprit");
}

Activează și health check-ul în Azure App Service portal (Configuration → Health check, path: /health/ready, threshold: 2 eșecuri = instanța scoasă din rotație).


Rezumat: checklist cloud-native

  • Configurație externalizată — niciun secret în cod sau în Git; variabile de mediu sau Key Vault pentru Production
  • Stateless — sesiunile și starea tranzitorie sunt în Redis, nu în memorie
  • Fișiere externalizate — upload-urile merg în Blob Storage, nu pe disk local
  • Health checks configurate/health/live și /health/ready cu verificări reale ale dependințelor
  • Graceful shutdown implementat — background services respectă CancellationToken; ShutdownTimeout setat corespunzător
  • Structured logging — parametri numiți, nu interpolări de string; fără fișiere de log locale
  • Dependencies declarate explicit — nicio dependință implicită de OS sau tool-uri globale

Ce urmează în serie

  • Containerizare cu Docker — Dockerfile optimizat pentru .NET, multi-stage builds
  • Azure Container Apps — deploy, scaling rules, KEDA, managed certificates
  • Cosmos DB cloud-native — partition keys, change feed, consistency levels
  • Observabilitate cu Application Insights — distributed tracing, custom metrics, alerts
  • CI/CD cu GitHub Actions — pipeline complet de la commit la Production
  • Securitate cu Managed Identity — zero credențiale hardcodate, RBAC pentru resurse Azure

Dacă ai întrebări sau vrei să discuți cum aplici aceste principii în proiectul tău, scrie-mi la contact@ludoprogramming.com.