Cloud'da Compute Seçenekleri: VM, App Service, Container Apps, Kubernetes ve Functions Arasında Karar Vermek
Compute seçimi, uygulamanın ihtiyaç duyduğu kontrol ile ekibin taşıyabileceği yönetim yükü arasında bir dengedir: VM'den AKS'e, App Service'ten Container Apps ve Functions'a doğru gittikçe platform daha çok iş üstlenir. AWS karşılıklarını (App Runner'ın kapanması, ECS Express Mode), Bookstore için karar ağacını, ölçekleme modellerini, Bookstore.Api'nin ölçülmüş cold start'ını ve fiyatlandırma birimlerini anlatıyoruz.
Sürüm notu: Bu yazıdaki örnekler .NET 10 (SDK 10.0.404), Docker Engine 29 ve SQL Server 2025 container'ı ile denenmiştir. Cold start ölçümleri yerel Docker'da yapıldı; Azure ve Amazon Web Services bilgileri Microsoft Learn'den ve Amazon'un dokümanlarından, liste fiyatları Azure Retail Prices API'sinden 14 Kasım 2026'da alındı.
Önce Tahmin Et
Sitedeki serilerin ortak örnek uygulaması Bookstore'un (bir online kitapçı) API'si, Container Apps'te minimum replica sayısı sıfır olacak şekilde çalışıyor. Gece trafik yok, replica sıfıra indi. Sabah ilk müşteri katalog sayfasını açıyor. GET /api/books isteği kaç milisaniyede döner: 50 mi, 500 mü, 3000 mi? Ve bu sürenin en büyük parçası ne: container'ın açılması mı, .NET runtime'ın başlaması mı, yoksa başka bir şey mi? Ölçtük; cevap yazının ortasında.
Kontrol ile Yönetim Yükü Ekseni
Compute seçenekleri bir eksen üzerinde dizilir. Sol uçta her şeyi sen kontrol edersin ve her şeyi sen işletirsin; sağ uca doğru gittikçe platform daha çok karar verir, sana daha az iş kalır. Doğru seçenek, uygulamanın gerçekten ihtiyaç duyduğu kontrol ile ekibin taşıyabileceği yönetim yükünün kesiştiği yerdir.

Azure'daki Seçenekler: VM, AKS, App Service, Container Apps, Functions
Azure Virtual Machines, IaaS'in kendisi: işletim sistemini seçer, yamalarını yapar, .NET runtime'ı ve reverse proxy'yi kurarsın. Özel bir işletim sistemi bileşenine, lisansı makineye bağlı bir yazılıma ya da uzun süre çalışan, durum tutan bir sürece ihtiyacın varsa doğru yer burası. Bookstore.Api'nin hiçbirine ihtiyacı yok.
AKS (Azure Kubernetes Service; Azure'ın yönetilen Kubernetes servisi), control plane'i Microsoft'un yönettiği bir Kubernetes cluster'ı. Manifest'leri, node pool'ları, cluster upgrade'lerini ve ingress'i sen yönetirsin. Onlarca servisi olan, ekibinde Kubernetes bilen birileri bulunan yapılar için güçlü; tek bir API için gereğinden ağır. Kubernetes serisinin 17. yazısı Bookstore'u AKS'e ve diğer sağlayıcıların managed Kubernetes'lerine taşıyor.
App Service, web uygulamaları için PaaS. Bir App Service planı (VM boyutu ve instance sayısı) satın alırsın, bir ya da birden çok uygulamayı bu plana yerleştirirsin. Kodu zip olarak ya da container image olarak verebilirsin; deployment slot, health check, kimlik doğrulama ve sertifika yönetimi hazır gelir. Plan çalıştığı sürece ücret işler; trafik olmasa da sıfıra inmez.
Container Apps, Kubernetes ve KEDA (Kubernetes Event-driven Autoscaling; olay sayısına göre ölçekleyen bileşen) üzerine kurulu ama bunları sana göstermeyen serverless bir container platformu. Bir environment içinde container app'ler çalıştırırsın; her değişiklik bir revision üretir, trafiği revision'lar arasında bölebilirsin, HTTP isteği ya da kuyruk uzunluğu gibi kurallara göre sıfıra kadar ölçeklenir. Learn'e göre minimum replica sayısının varsayılanı 0, maksimumun varsayılanı 10.
Azure Functions ise FaaS (Function as a Service; tek tek fonksiyonları olaylara bağlayıp çalıştıran model). Bir trigger (HTTP isteği, zamanlayıcı, kuyruk mesajı, yeni bir blob) bir fonksiyonu çalıştırır. Yeni uygulamalar için önerilen serverless plan Flex Consumption: Linux üzerinde çalışıyor, 512 MB, 2.048 MB ya da 4.096 MB instance boyutu seçiliyor ve fonksiyon grubu başına 1.000 instance'a kadar çıkabiliyor. Linux üzerindeki eski Consumption planı 30 Eylül 2028'de emekliye ayrılıyor. Functions'ı 6. yazıda Bookstore'un kapak resmi işleme işiyle kuruyoruz.
AWS Karşılıkları ve Güncel Durum
AWS'de (Amazon Web Services; Amazon'un sunduğu bulut platformu) aynı eksen var ama son bir yılda önemli bir değişiklik oldu. Container'ı basitçe çalıştırmak için yıllarca önerilen App Runner, 30 Nisan 2026'dan itibaren yeni müşterilere kapandı; mevcut müşteriler kullanmaya devam edebiliyor ama yeni özellik gelmeyecek. AWS, yerine 21 Kasım 2025'te duyurduğu ECS (Elastic Container Service; AWS'nin container çalıştırma servisi) Express Mode'u öneriyor: bir container image ve iki IAM (Identity and Access Management; AWS'nin kimlik ve erişim yönetimi servisi) rolü verdiğinde Fargate üzerinde bir ECS servisi, Application Load Balancer, autoscaling ve ağ ayarlarını tek API çağrısıyla kuruyor; Express Mode'un kendisi için ek ücret yok, oluşan kaynaklar faturalanıyor. Tablodaki EC2 (Elastic Compute Cloud; AWS'de sanal makine çalıştıran servis) ve EKS (Elastic Kubernetes Service; AWS'nin yönetilen Kubernetes'i) de aynı ailenin üyeleri.
| İhtiyaç | Azure | AWS | Not |
|---|---|---|---|
| Sanal makine | Virtual Machines | EC2 | İşletim sistemi ve yamalar senin |
| Web uygulaması için PaaS | App Service | Elastic Beanstalk | Beanstalk, EC2 ve load balancer'ı senin hesabında kurar |
| Serverless container | Container Apps | ECS on Fargate, ECS Express Mode | App Runner Nisan 2026'dan beri yeni müşteriye kapalı |
| Managed Kubernetes | AKS | EKS | Control plane sağlayıcıda, node'lar senin |
| FaaS | Functions (Flex Consumption) | Lambda | Lambda'da tek çalıştırma en fazla 15 dakika |
Bookstore İçin Karar Ağacı
Microsoft'un Azure Architecture Center'daki compute decision tree'si (servis seçimi için soru ağacı) uzun bir akış; Bookstore için dört soruya indirgenebilir. Birincisi: uygulama özel bir işletim sistemi ayarına, makineye bağlı bir lisansa ya da Windows'a özgü bir bileşene ihtiyaç duyuyor mu? Evetse VM. İkincisi: tek bir HTTP API ve bir SPA (Single Page Application; tarayıcıda çalışan tek sayfalık uygulama) mı çalıştırıyorsun? Evetse App Service ya da Container Apps; ikisi arasındaki seçim, image'ı ekibin mi ürettiği ve sıfıra inmenin değerli olup olmadığıyla ilgili. Üçüncüsü: birbirleriyle konuşan çok sayıda servis, service discovery ve servisler arası trafik kuralları mı var? Container Apps bu ihtiyaçların çoğunu karşılar; Kubernetes API'sine, kendi operator'larına ya da node seviyesinde kontrole ihtiyaç varsa AKS. Dördüncüsü: iş bir olayla mı tetikleniyor ve kısa mı sürüyor (yeni kapak resmi, gece raporu)? Evetse Functions; uzun süren ya da sürekli yüklü işler için Container Apps job'ları ya da API'nin içindeki bir hosted service daha uygun.
Bookstore bugün tek bir API, bir Angular uygulaması ve bir veritabanından oluşuyor. Bu yüzden 5. yazıda API'yi iki yoldan, App Service'e kod olarak ve Container Apps'e container olarak deploy ediyoruz; Angular uygulaması Static Web Apps'e gidiyor; kapak resmi işleme 6. yazıda Functions'a ayrılıyor.
Ölçekleme Modelleri
Ölçeklemenin dört biçimi var. Manual'de instance sayısını sen belirlersin; App Service planında "3 instance" demek gibi. Autoscale'de CPU, bellek ya da istek sayısı gibi bir metriğe kural bağlarsın; App Service ve VM scale set'leri bunu yapar, ama alt sınır en az bir instance'tır. Event-driven ölçeklemede kural bir olay kaynağına bakar. Container Apps'te HTTP kuralının varsayılan eşiği 10 eşzamanlı istek; eşik aşılınca replica eklenir, ekleme adımları 1, 4, 8, 16 diye büyür. Learn'e göre ölçek küçültme kararı 300 saniyelik bir pencereyle verilir, son olaydan sonra sıfıra inmeden önce de 300 saniye beklenir. Scale-to-zero ise event-driven ölçeklemenin uç noktası: iş yoksa hiçbir replica çalışmaz, ücret de işlemez. Bedeli cold start.
Cold Start'ı Ölçmek
Cold start'ın Bookstore.Api için ne kadar sürdüğünü tahmin etmek yerine ölçtük. API'nin container image'ını ayrı bir Dockerfile yazmadan .NET SDK'nın container desteğiyle ürettik; image üretiminin ayrıntıları Docker serisinin konusu:
$ dotnet publish src/Bookstore.Api -c Release /t:PublishContainer
Building image 'bookstore-api' with tags 'latest' on top of base image 'mcr.microsoft.com/dotnet/aspnet:10.0'.
Pushed image 'bookstore-api:latest' to local registry via 'docker'.
Ölçümü yapan araç, container'ı belirli bir CPU sınırıyla başlatıyor, uygulamanın cevap vermeye başladığı anı (veritabanına dokunmayan bir 404) ayrıca kaydediyor, sonra ilk ve sonraki /api/books isteklerini ölçüyor:
// Bookstore.Api'nin container'da cold start'ını ölçer: docker run → uygulama cevap verir → ilk ve sonraki /api/books.
// Çalıştır: dotnet run tools/cloud/coldstart.cs -- [cpus] [image]
// BOOKSTORE_DB: container'ın içinden görülen SQL Server bağlantı adresi.
using System.Diagnostics;
string cpus = args.Length > 0 ? args[0] : "1";
string image = args.Length > 1 ? args[1] : "bookstore-api:latest";
string connectionString = Environment.GetEnvironmentVariable("BOOKSTORE_DB")
?? throw new InvalidOperationException("Set BOOKSTORE_DB to the SQL Server connection string.");
string container = Environment.GetEnvironmentVariable("COLDSTART_CONTAINER") ?? "bookstore-coldstart";
const int Port = 18080;
using HttpClient http = new() { BaseAddress = new Uri($"http://localhost:{Port}"), Timeout = TimeSpan.FromSeconds(10) };
await DockerAsync("rm", "-f", container);
Stopwatch clock = Stopwatch.StartNew();
await DockerAsync("run", "-d", "--name", container, "--cpus", cpus, "--memory", "1g", "-p", $"{Port}:8080",
"-e", $"ConnectionStrings__BookstoreDb={connectionString}", image);
TimeSpan containerStarted = clock.Elapsed;
// Var olmayan bir yol: 404 gelmesi, veritabanına dokunmadan Kestrel'in cevap verdiği anlamına gelir
while (true)
{
try
{
using HttpResponseMessage ping = await http.GetAsync("/ping");
break;
}
catch (HttpRequestException)
{
await Task.Delay(20);
}
}
TimeSpan appAnswers = clock.Elapsed;
TimeSpan first = await TimeRequestAsync();
TimeSpan firstTotal = clock.Elapsed;
List<double> warm = [];
for (int i = 0; i < 20; i++)
{
warm.Add((await TimeRequestAsync()).TotalMilliseconds);
}
warm.Sort();
Console.WriteLine($"cpus={cpus}");
Console.WriteLine($" docker run returned {containerStarted.TotalMilliseconds,7:0} ms");
Console.WriteLine($" app answers (404 on /ping) {appAnswers.TotalMilliseconds,7:0} ms");
Console.WriteLine($" first /api/books took {first.TotalMilliseconds,7:0} ms (done at {firstTotal.TotalMilliseconds:0} ms)");
Console.WriteLine($" warm /api/books median/max {warm[warm.Count / 2],7:0.0} / {warm[^1]:0.0} ms");
await DockerAsync("rm", "-f", container);
async Task<TimeSpan> TimeRequestAsync()
{
Stopwatch sw = Stopwatch.StartNew();
using HttpResponseMessage response = await http.GetAsync("/api/books?pageSize=5");
response.EnsureSuccessStatusCode();
await response.Content.ReadAsStringAsync();
return sw.Elapsed;
}
static async Task DockerAsync(params string[] arguments)
{
using Process process = Process.Start(new ProcessStartInfo("docker", arguments) { RedirectStandardOutput = true, RedirectStandardError = true })!;
await process.StandardOutput.ReadToEndAsync();
await process.StandardError.ReadToEndAsync();
await process.WaitForExitAsync();
}
BOOKSTORE_DB, container'ın içinden görülen bağlantı adresi olmalı; Docker Desktop'ta host'taki SQL Server'a host.docker.internal ile ulaşılır:
$ export BOOKSTORE_DB="Server=host.docker.internal,1433;Database=BookstoreDb;User Id=sa;Password=<password>;TrustServerCertificate=True"
$ dotnet run tools/cloud/coldstart.cs -- 0.5
cpus=0.5
docker run returned 253 ms
app answers (404 on /ping) 631 ms
first /api/books took 1200 ms (done at 1833 ms)
warm /api/books median/max 3.0 / 71.0 ms
Her CPU sınırını üçer kez çalıştırdık; tablo, üç koşunun ortanca değerleri:
| CPU sınırı | Uygulama cevap veriyor | İlk /api/books süresi | İlk cevaba toplam | Sonraki istekler (ortanca) |
|---|---|---|---|---|
| 2 vCPU | 348 ms | 598 ms | 954 ms | 4,3 ms |
| 1 vCPU | 356 ms | 558 ms | 914 ms | 2,9 ms |
| 0,5 vCPU | 631 ms | 1.200 ms | 1.768 ms | 3,1 ms |
| 0,25 vCPU | 745 ms | 2.109 ms | 2.846 ms | 3,7 ms |
Tahmin sorusunun cevabı: Bookstore.Api'nin ilk isteği 0,5 vCPU'da yaklaşık 1,8 saniyede dönüyor, ısındıktan sonra aynı istek 3 milisaniye. En büyük parça container'ın ya da runtime'ın açılması değil, ilk veritabanı isteği: Entity Framework Core'un (EF Core; .NET'in veritabanı erişim kütüphanesi) modeli kurması, SqlClient'ın ilk bağlantıyı açması ve JIT'in (Just-In-Time; kodu çalışırken makine koduna derleyen derleyici) o kod yolunu ilk kez derlemesi. CPU yarıya indikçe bu kısım neredeyse doğrusal uzuyor. Bu sayılar bir alt sınır: image yerelde hazırdı. Bulutta sıfırdan kalkan bir replica için önce bir node'a yerleşmek ve image'ı registry'den çekmek gerekir; image büyüdükçe bu süre de büyür. Senin makinende sayılar farklı çıkacaktır; önemli olan sıralama ve oranlar.
Pratikteki sonuç iki seçenek. Gecikmeye duyarlı bir API için minimum replica'yı 1 yaparsın; Container Apps boştaki replica'yı daha düşük "idle" ücretiyle faturalar. Ya da sıfıra inmeyi kabul edip ilk isteğin maliyetini küçültürsün: startup sırasında veritabanına küçük bir sorgu atan bir warm-up adımı, readiness probe'u ve daha küçük image. Functions'ta aynı rolü Flex Consumption'ın always ready instance'ları oynar.
Fiyatlandırma Modelleri
Her seçeneğin faturası farklı bir birimle işler. Aşağıdaki rakamlar 14 Kasım 2026'da Azure Retail Prices API'sinden alınan West Europe liste fiyatları; indirimsiz ve değişken, karar vermeden önce Pricing Calculator ile kendi senaryonu hesapla.
- App Service: plan instance saati. Linux B1 (Basic katmanın en küçük boyutu) saatte 0,018 dolar, Premium v3 P0v3 saatte 0,089 dolar; trafik olsun olmasın plan çalıştıkça ödenir.
- Container Apps (Consumption): aktif vCPU saniyesi 0,000034 dolar, GiB saniyesi 0,000004 dolar, boştaki (idle) vCPU saniyesi 0,000004 dolar, ayrıca milyon istek başına ücret. Her subscription için her ay ilk 180.000 vCPU saniyesi, 360.000 GiB saniyesi ve 2 milyon istek ücretsiz; sıfırdaki revision için kaynak ücreti yok.
- Functions (Flex Consumption): on-demand çalışma süresi GB saniyesi 0,000026 dolar ve 10 çalıştırma başına 0,000004 dolar; ayda ilk 100.000 GB saniye ve 250.000 çalıştırma ücretsiz. Always ready instance'lar için ayrı, daha düşük bir taban ücret işler.
- AKS ve VM: node ya da VM saati, ayrıca disk, load balancer ve public IP gibi yan kalemler.
Kaba bir kıyas: 0,5 vCPU ve 1 GiB bellekle her zaman bir replica'sı açık duran Bookstore.Api, ücretsiz kotalar düşüldükten sonra Container Apps'te ayda yaklaşık 13 dolar (replica hep boşta) ile 47 dolar (hep aktif) arasında tutar; B1 App Service planı ayda yaklaşık 13 dolar. Retail Prices API'sinde 1 Kasım 2026'dan geçerli, saatte 0,10 dolarlık "Environment Management Hour" adlı bir Container Apps meter'ı da görünüyor; Learn'deki billing sayfası private endpoint ve planned maintenance gibi özellikler için ayrı bir yönetim ücretinden söz ediyor. Hangi environment'lara uygulandığını, karar vermeden önce pricing sayfasından kontrol et.
Üretimde Dikkat
Instance sayısını SLA'yla (Service Level Agreement; sağlayıcının erişilebilirlik taahhüdü) birlikte düşün. App Service'te tek instance, platform bakımında ya da bir instance değiştirilirken kısa kesintiler demek; Learn bu yüzden birden çok instance öneriyor. Zone redundancy için Premium v2 ve üstü planlarda en az iki instance gerekiyor.
Scale-out'un başka kaynakları zorladığını unutma. Bookstore.Api 10 replica'ya çıktığında veritabanına giden bağlantı sayısı da on katına çıkar; Azure SQL'in seçtiğin katmanındaki bağlantı ve işçi sınırları ölçeklemenin gerçek tavanıdır. Bu konu 8. yazıda.
Uygulamayı stateless tut. Instance'ın diskine ya da belleğine yazılan sepet, oturum ya da dosya, instance değiştiğinde kaybolur. Bookstore'da sepet veritabanında, kapak resimleri Blob Storage'da duruyor.
Sık Düşülen Tuzaklar
Tek bir API için Kubernetes kurmak. AKS'in control plane'i yönetiliyor olsa da node güncellemeleri, ingress, sertifikalar ve manifest'ler ekibe kalır. Bir servis ve küçük bir ekip için bu yük karşılığını vermez.
Scale-to-zero'yu her şey için açmak. Gece yarısı ilk müşteriye 2 saniye bekletmek bir katalog sayfası için kabul edilebilir, ödeme callback'i için değil. Kritik endpoint'ler için minimum replica 1.
App Runner'ı yeni projeye seçmek. Eski blog yazıları ve örnekler hâlâ App Runner'ı öneriyor; 30 Nisan 2026'dan beri yeni müşteri kabul etmiyor.
Cold start'ı yalnız uygulama süresi sanmak. Bulutta image çekme ve yerleşme süresi de eklenir; 500 MB'lık bir image ile 100 MB'lık bir image arasındaki fark ilk istekte doğrudan hissedilir.
Küçük Bir Deneme
★☆☆ tools/cloud/coldstart.cs'i kendi makinende 1 ve 0,5 vCPU ile çalıştır; tablodaki oranların seninkilerle aynı kalıp kalmadığına bak.
★★☆ Bookstore.Api'ye uygulama başlarken BookstoreContext üzerinden küçük bir sorgu çalıştıran bir warm-up adımı ekle (örneğin bir IHostedService). Aynı ölçümü tekrarla: "app answers" ile "first /api/books" sürelerinin toplamı nasıl değişiyor?
★★★ Bookstore için compute kararını bir ADR (Architecture Decision Record; mimari kararı gerekçesiyle kaydeden kısa doküman) olarak yaz: App Service ile Container Apps'i maliyet, cold start, deploy yöntemi, ölçekleme ve ekip bilgisi açısından karşılaştır, AWS'de karşılık gelen seçimi de ekle.
Bir sonraki yazıda kararı uyguluyoruz: Bookstore.Api'yi App Service'e ve Container Apps'e deploy ediyoruz, health check ve forwarded header ayarlarını yapıyoruz, Angular uygulamasını Static Web Apps'e koyuyoruz.