Cloud Hesabı ve Resource Hiyerarşisi: Tenant, Subscription, Resource Group, Tag'ler ve İsimlendirme
Azure'da her kaynak tenant, management group, subscription ve resource group'tan oluşan bir ağacın yaprağıdır; subscription faturalama sınırı, resource group yaşam döngüsü sınırıdır. Bookstore için prod/nonprod subscription ayrımını, global isim sınırlarını yakalayan bir naming aracını, Azure Policy ile tag zorunluluğunu, az CLI ile ilk resource group'u, ücretsiz hesap kredilerini ve ilk gün budget alarmını AWS karşılıklarıyla anlatıyoruz.
Sürüm notu: Bu yazıdaki örnekler .NET 10 (SDK 10.0.404), Azure CLI 2.92.0 ve ShellCheck 0.11.0 ile denenmiştir. Azure'da kaynak oluşturulmadı; CLI komutlarının parametreleri yerelde --help çıktısıyla, davranışları Microsoft Learn'den (Kasım 2026) doğrulandı.
Önce Tahmin Et
Sitedeki serilerin ortak örnek uygulaması Bookstore (bir online kitapçı) için Azure'da üç resource group açtın: rg-bookstore-dev-weu, rg-bookstore-staging-weu, rg-bookstore-prod-weu. Üçü de aynı subscription'da. Emre'ye dev resource group'unda Contributor rolü verdin. Üç soru: Emre prod resource group'undaki veritabanını görebilir mi? Resource group'a cost-center=cc-1042 tag'ini koyduğunda içindeki App Service bu tag'i alır mı? Ayın sonunda biri dev resource group'unu silerse içindeki SQL veritabanına ne olur? Cevaplar yazının içinde.
Azure'un Hiyerarşisi: Tenant, Management Group, Subscription, Resource Group
Azure'daki her kaynak dört katmanlı bir ağacın yaprağıdır. En üstte Microsoft Entra ID tenant'ı var: şirketin kullanıcılarının, gruplarının ve uygulama kimliklerinin durduğu dizin. Tenant bir identity sınırıdır; bir subscription her zaman tek bir tenant'a güvenir. Tenant'ın altında management group'lar gelir. Bunlar subscription'ları gruplayan klasörlerdir ve üstüne konan rol atamaları ile policy'ler aşağıya miras kalır. Learn'e göre bir dizinde 10.000 management group olabilir, ağaç root ve subscription seviyesi hariç altı seviye derinliğe kadar iner ve her subscription'ın tek bir parent'ı vardır. Her tenant'ta kendiliğinden bir root management group bulunur.
Subscription, faturalama ve kota sınırıdır: her kaynağın bedeli bir subscription'a yazılır, vCPU kotası gibi limitler subscription başına tutulur. Resource group ise yaşam döngüsü sınırıdır: birlikte doğan, birlikte değişen ve birlikte silinen kaynakları bir arada tutar. Resource group'un kendi location'ı vardır ama bu yalnız metadata'nın nerede tutulduğunu belirler; içindeki kaynaklar başka region'larda olabilir. Her kaynağın kimliği bu ağacın yolunu taşır:
/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-bookstore-dev-weu/providers/Microsoft.Web/sites/app-bookstore-dev-weu
Bu yolun ortasındaki Microsoft.Web, resource provider'ın adı. Portaldan, CLI'dan ya da Terraform'dan gelen her istek önce Azure Resource Manager'a (ARM; Azure'daki bütün yönetim isteklerini karşılayan katman) gider. ARM token'ı Entra ID üzerinden doğrular, rol atamalarını ve Azure Policy kurallarını uygular, sonra isteği ilgili resource provider'a iletir. Yetki ve kural kontrolünün tek bir yerden geçmesi, bir sonraki yazıdaki Azure RBAC'ın (Role-Based Access Control; rol tabanlı yetkilendirme) ve bu yazıdaki policy'lerin bütün araçlarda aynı çalışmasını sağlar.
Tahmin sorusunun ilk cevabı buradan çıkıyor: rol ataması verdiğin scope'ta ve altında geçerli. Emre'nin Contributor'ı yalnız rg-bookstore-dev-weu'yu kapsıyor; prod resource group'unu listede bile görmez. Üçüncü sorunun cevabı da sert: resource group'u silmek, içindeki her kaynağı, veritabanı ve storage account dahil, siler.
AWS'de Aynı Yapı: Organizations, OU ve Account
AWS'de (Amazon Web Services; Amazon'un sunduğu bulut platformu) ağacın şekli farklı. En üstte AWS Organizations ile bir organization var, hesaplar OU'lar (Organizational Unit; hesapları gruplayan birim) altında toplanıyor ve OU'ya bağlanan SCP'ler (Service Control Policy; bir hesapta yapılabilecek en geniş işlemleri sınırlayan kural) aşağıya etki ediyor. Asıl fark account'ta: AWS account'u hem faturalama hem de güvenlik ve kaynak sınırı. Azure'da bu roller dağılmış durumda: kimlik tenant'ta, faturalama subscription'da, yaşam döngüsü resource group'ta. AWS'de resource group'un doğrudan karşılığı yok; kaynakları birlikte yönetmek için tag'ler ve CloudFormation stack'leri kullanılıyor. Bu yüzden AWS ekipleri genellikle environment başına ayrı account açar; Azure'da aynı rolü subscription üstlenir. Kullanıcılar ise IAM (Identity and Access Management; AWS'nin kimlik ve erişim yönetimi servisi) ve onun üstündeki IAM Identity Center ile yönetilir.
Environment Ayrımı: Subscription mı, Resource Group mu?
Tahmin sorusundaki düzen, yani üç environment'ın aynı subscription'da durması, küçük bir ekip için başlangıç olarak kabul edilebilir ama iki zayıf noktası var. Birincisi, subscription seviyesinde yetki verdiğin herkes prod'a da erişir. İkincisi, dev'de yanlışlıkla açılan büyük bir VM, prod'un da kullandığı kotayı ve bütçeyi yer. Bookstore için önerimiz iki subscription: sub-bookstore-prod ve sub-bookstore-nonprod. Dev ve staging aynı nonprod subscription'da, kendi resource group'larında; prod tek başına. Böylece prod'a kimin erişebildiği tek bir yerde görünür ve nonprod'a gevşek bir bütçe, prod'a sıkı bir onay süreci koyabilirsin.
Resource group'ları da yaşam döngüsüne göre böl. Bookstore'da rg-bookstore-dev-weu uygulamanın kendisini (App Service, SQL, storage) tutar ve environment silindiğinde hepsi birlikte gider. Platform ekibinin yönettiği paylaşılan Key Vault ya da Terraform state'inin durduğu storage account ise ayrı resource group'larda durur; Infrastructure as Code: Terraform serisinde gördüğün rg-bookstore-tfstate-weu buna örnek. Uygulama resource group'unu silmek state dosyasını silmemeli.
İsimlendirme Standardı
İsimler sonradan değiştirilemez; çoğu kaynakta isim değiştirmek, kaynağı silip yeniden oluşturmak demek. Bu yüzden standart ilk kaynaktan önce yazılmalı. Bookstore'un kuralı Cloud Adoption Framework'ün (Microsoft'un bulut benimseme rehberi) önerdiği kısaltmalarla <type>-<workload>-<environment>-<region> (tür, iş yükü, environment, region); Terraform serisindeki locals.tf de aynı kalıbı kullanıyor. Kuralı bir kez koda döktük ki herkes aynı ismi üretsin ve sınır aşımı deploy anında değil, önceden görünsün:
// Bookstore isimlendirme ve tag standardı: <kısaltma>-<iş yükü>-<environment>-<region>
// Çalıştır: dotnet run tools/cloud/naming.cs -- [iş yükü] [environment...]
using System.Text.RegularExpressions;
string workload = args.Length > 0 ? args[0] : "bookstore";
string[] environments = args.Length > 1 ? args[1..] : ["dev", "prod"];
const string Region = "westeurope";
const string RegionShort = "weu";
// Kısaltmalar Cloud Adoption Framework listesinden, sınırlar "Naming rules and restrictions for Azure resources" sayfasından.
NamingRule[] rules =
[
new("Resource group", "rg", 1, 90, @"^[\w\-\.\(\)]*[\w\-\(\)]$", Scope.Subscription),
new("App Service plan", "asp", 1, 60, "^[a-zA-Z0-9-]+$", Scope.ResourceGroup),
new("Web app", "app", 2, 60, "^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$", Scope.Global),
new("Container Apps environment", "cae", 2, 32, "^[a-z][a-z0-9-]*[a-z0-9]$", Scope.ResourceGroup),
new("Container app", "ca", 2, 32, "^[a-z][a-z0-9-]*[a-z0-9]$", Scope.ResourceGroup),
new("Storage account", "st", 3, 24, "^[a-z0-9]+$", Scope.Global, Compact: true),
new("SQL logical server", "sql", 1, 63, "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$", Scope.Global),
new("Key vault", "kv", 3, 24, "^[a-zA-Z](?!.*--)[a-zA-Z0-9-]*[a-zA-Z0-9]$", Scope.Global),
new("User-assigned identity", "id", 3, 128, "^[a-zA-Z0-9][a-zA-Z0-9_-]*$", Scope.ResourceGroup),
];
Dictionary<string, string> baseTags = new()
{
["app"] = workload,
["owner"] = "platform-team@example.com",
["cost-center"] = "cc-1042",
};
int errors = 0;
foreach (string environment in environments)
{
Console.WriteLine($"== {environment} ({Region})");
foreach (NamingRule rule in rules)
{
string name = rule.Compact
? $"{rule.Abbreviation}{workload}{environment}{RegionShort}".Replace("-", "").ToLowerInvariant()
: $"{rule.Abbreviation}-{workload}-{environment}-{RegionShort}".ToLowerInvariant();
string? problem = rule.Check(name);
errors += problem is null ? 0 : 1;
string scope = rule.Scope == Scope.Global ? "global" : rule.Scope.ToString().ToLowerInvariant();
Console.WriteLine($" {rule.Type,-27} {name,-40} {name.Length,3}/{rule.MaxLength,-3} {scope,-14} {problem ?? "OK"}");
}
Dictionary<string, string> tags = new(baseTags) { ["environment"] = environment };
Console.WriteLine(" tags: " + string.Join(" ", tags.Select(t => $"{t.Key}={t.Value}")));
}
return errors == 0 ? 0 : 1;
enum Scope { Global, Subscription, ResourceGroup }
record NamingRule(string Type, string Abbreviation, int MinLength, int MaxLength, string Pattern, Scope Scope, bool Compact = false)
{
public string? Check(string name) =>
name.Length < MinLength || name.Length > MaxLength ? $"ERROR length must be {MinLength}-{MaxLength}"
: !Regex.IsMatch(name, Pattern) ? "ERROR invalid characters"
: null;
}
dotnet run tools/cloud/naming.cs varsayılan olarak dev ve prod için isimleri üretir; ilk environment'ın çıktısı:
== dev (westeurope)
Resource group rg-bookstore-dev-weu 20/90 subscription OK
App Service plan asp-bookstore-dev-weu 21/60 resourcegroup OK
Web app app-bookstore-dev-weu 21/60 global OK
Container Apps environment cae-bookstore-dev-weu 21/32 resourcegroup OK
Container app ca-bookstore-dev-weu 20/32 resourcegroup OK
Storage account stbookstoredevweu 17/24 global OK
SQL logical server sql-bookstore-dev-weu 21/63 global OK
Key vault kv-bookstore-dev-weu 20/24 global OK
User-assigned identity id-bookstore-dev-weu 20/128 resourcegroup OK
tags: app=bookstore owner=platform-team@example.com cost-center=cc-1042 environment=dev
Sütunlardan biri özellikle önemli: scope. global yazan kaynakların adı bütün Azure'da tek olmalı, çünkü adları bir DNS kaydına dönüşüyor: storage account stbookstoredevweu.blob.core.windows.net, Key Vault kv-bookstore-dev-weu.vault.azure.net olur. Storage account adı yalnız küçük harf ve rakam alır, en fazla 24 karakterdir; tire kullanamazsın. Kurumsal Sipariş için ayrı bir iş yükü açmaya kalkınca sınırlar hemen kendini gösteriyor:
$ dotnet run tools/cloud/naming.cs -- bookstore-institutional staging
== staging (westeurope)
Resource group rg-bookstore-institutional-staging-weu 38/90 subscription OK
App Service plan asp-bookstore-institutional-staging-weu 39/60 resourcegroup OK
Web app app-bookstore-institutional-staging-weu 39/60 global OK
Container Apps environment cae-bookstore-institutional-staging-weu 39/32 resourcegroup ERROR length must be 2-32
Container app ca-bookstore-institutional-staging-weu 38/32 resourcegroup ERROR length must be 2-32
Storage account stbookstoreinstitutionalstagingweu 34/24 global ERROR length must be 3-24
SQL logical server sql-bookstore-institutional-staging-weu 39/63 global OK
Key vault kv-bookstore-institutional-staging-weu 38/24 global ERROR length must be 3-24
User-assigned identity id-bookstore-institutional-staging-weu 38/128 resourcegroup OK
tags: app=bookstore-institutional owner=platform-team@example.com cost-center=cc-1042 environment=staging
Çıkış kodu 1. Çözüm, iş yükü kısaltmasını standarda eklemek: bkinst gibi kısa bir ad hem storage hem Key Vault sınırına sığar. Bunu ilk günden bir CI adımına bağlarsan, uzun bir ad deploy'u değil pull request'i durdurur.
Tag'ler ve Azure Policy ile Zorunluluk
Tag, kaynağa eklenen bir key/value çifti. Bookstore'un standardı dört tag: app, environment, owner ve cost-center; Terraform serisi bunlara managed_by=terraform'u ekliyordu. Learn'e göre bir kaynak, resource group ya da subscription en fazla 50 tag alabilir; tag adı en fazla 512, değeri 256 karakterdir (storage account'ta ad sınırı 128). Tag adlarında büyük-küçük harf farkı yoktur, değerlerinde vardır: environment=Prod ile environment=prod maliyet raporunda iki ayrı satırdır. Tag'ler düz metin olarak saklanır ve maliyet raporlarına, export'lara düşer; içine asla parola ya da kişisel veri yazma.
Tahmin sorusunun ikinci cevabı: hayır, kaynaklar resource group'un tag'lerini miras almaz. Bunu bir kurala bağlamak Azure Policy'nin işi. Bookstore'un subscription'ına bir kez uygulanan script iki built-in policy atıyor:
#!/usr/bin/env bash
# Subscription başına bir kez çalışır: her Bookstore resource group'u ve kaynağı için tag kuralları.
set -euo pipefail
: "${AZURE_SUBSCRIPTION_ID:?Set AZURE_SUBSCRIPTION_ID to the target subscription}"
SCOPE="/subscriptions/${AZURE_SUBSCRIPTION_ID}"
az account set --subscription "$AZURE_SUBSCRIPTION_ID"
# Built-in "Require a tag on resource groups": cost-center tag'i olmayan resource group'u reddet
az policy assignment create \
--name require-cost-center-on-rg \
--display-name "Require cost-center tag on resource groups" \
--policy 96670d01-0a4d-4649-9c89-2d3abc0a5025 \
--params '{ "tagName": { "value": "cost-center" } }' \
--scope "$SCOPE"
# Built-in "Inherit a tag from the resource group if missing": modify etkisi, identity gerektirir
az policy assignment create \
--name inherit-cost-center \
--display-name "Inherit cost-center tag from the resource group" \
--policy ea3f2387-9b95-492a-a190-fcdc54f7b070 \
--params '{ "tagName": { "value": "cost-center" } }' \
--scope "$SCOPE" \
--mi-system-assigned --location westeurope \
--identity-scope "$SCOPE" --role Contributor
İlk atama "Require a tag on resource groups" tanımı: cost-center tag'i olmayan bir resource group oluşturma isteği ARM'de reddedilir. İkincisi "Inherit a tag from the resource group if missing": tag'i olmayan kaynağa resource group'unkini ekler. Bu policy'nin etkisi modify, yani kaynağı değiştiriyor; bu yüzden atamanın kendi managed identity'si ve o identity'nin Contributor rolü olmak zorunda. --mi-system-assigned, --identity-scope ve --role parametreleri bunun için. Policy'ler yeni oluşturulan ya da güncellenen kaynaklara uygulanır; atamadan önce var olan kaynaklar için ayrıca bir remediation (düzeltme) görevi çalıştırman gerekir.
Portal, Cloud Shell ve az CLI ile İlk Resource Group
Azure'u yönetmenin üç yolu var ve üçü de aynı ARM API'sini çağırıyor. Portal, bir şeyi ilk kez yaparken ve neyin mümkün olduğunu görürken en hızlısı. Cloud Shell, portalın içinde açılan ve az ile PowerShell'in hazır, oturumun açık geldiği bir terminal; dosyalarının kalıcı olmasını istiyorsan bir storage account bağlarsın. Yerelde ise Azure CLI kurarsın; Windows'ta winget install Microsoft.AzureCLI ya da Microsoft'un MSI (Windows kurulum paketi biçimi) paketi. Bu yazı için CLI'ı resmi container image'ından çalıştırdık:
$ az version
{
"azure-cli": "2.92.0",
"azure-cli-core": "2.92.0",
"azure-cli-telemetry": "1.1.0",
"extensions": {}
}
Bu seri altyapıyı kodla yönetmeyi Infrastructure as Code: Terraform serisine bırakıyor; burada CLI'ı, her adımın ARM'de neye karşılık geldiğini görmek için kullanıyoruz. Resource group'u oluşturan script kısa:
#!/usr/bin/env bash
# Bir Bookstore environment'ının resource group'unu standart tag'lerle oluşturur.
set -euo pipefail
: "${AZURE_SUBSCRIPTION_ID:?Set AZURE_SUBSCRIPTION_ID to the target subscription}"
ENVIRONMENT="${1:?Usage: 02-resource-group.sh <dev|staging|prod>}"
LOCATION="westeurope"
RG="rg-bookstore-${ENVIRONMENT}-weu"
az account set --subscription "$AZURE_SUBSCRIPTION_ID"
az group create \
--name "$RG" \
--location "$LOCATION" \
--tags app=bookstore environment="$ENVIRONMENT" \
owner=platform-team@example.com cost-center=cc-1042 \
--output table
Script'teki : "${AZURE_SUBSCRIPTION_ID:?...}" satırı bilinçli: subscription ID'si verilmezse script hiçbir şey yapmadan durur. CLI'da o an hangi subscription seçiliyse komutun oraya gitmesi, Azure'da en sık yapılan kazalardan biri; Terraform serisi de AzureRM 4.0'ın subscription ID'yi zorunlu kılmasını aynı gerekçeyle anlatıyordu. Script'leri bash -n ve ShellCheck ile denetledik; Azure'a karşı çalıştırmadık. Kendi subscription'ında bash infra/cli/02-resource-group.sh dev ile çalıştırabilirsin.
Ücretsiz Hesap, Krediler ve İlk Gün Budget Alarmı
Denemek için para harcamak zorunda değilsin. Azure'un ücretsiz hesabı 14 Kasım 2026'da kontrol ettiğimiz sayfaya göre ilk 30 gün için 200 dolarlık kredi, yeni müşterilere 12 ay boyunca 20'den fazla popüler serviste aylık ücretsiz kullanım ve 65'ten fazla serviste süresiz ücretsiz kotalar veriyor (örneğin Azure SQL Database serverless için aylık 100.000 vCore saniyesi ve Functions için ayda 1 milyon istek). AWS, 15 Temmuz 2025'ten itibaren açılan hesaplar için modeli değiştirdi: yeni müşteri en fazla 200 dolar kredi alıyor ve ücretsiz plan, kayıttan 6 ay sonra ya da krediler bittiğinde (hangisi önce gelirse) sona eriyor. Bu teklifler sık değişiyor; hesap açmadan önce sağlayıcının güncel sayfasına bak.
Hesabı açtığın gün yapılacak ilk iş bir budget alarmı. Portalda Cost Management içinde Budgets'a girip subscription scope'unda aylık bir tutar tanımlarsın; gerçekleşen harcama tutarın yüzde 80'ine, tahmini harcama yüzde 100'üne ulaştığında e-posta gönderen iki koşul iyi bir başlangıç. Bilmen gereken iki şey var. Budget yalnız haber verir, kaynakları durdurmaz; harcamayı durdurmak istiyorsan alarmı bir action group üzerinden otomasyona bağlaman gerekir, bu 10. yazının konusu. CLI tarafı da henüz tam değil: az consumption budget create komutu preview durumunda ve --help çıktısında e-posta ya da eşik tanımlayacak bir parametre yok. Bu yüzden budget'ı ya portaldan ya da Terraform gibi bir IaC aracıyla kur.
Son olarak tablo: Azure'daki her katmanın AWS'deki karşılığı.
| Azure | AWS | Ne işe yarar |
|---|---|---|
| Entra ID tenant | IAM Identity Center dizini | Kullanıcı ve grupların durduğu identity sınırı |
| Management group | Organizations, OU | Hesapları gruplamak, kuralı yukarıdan uygulamak |
| Subscription | Account | Faturalama ve kota sınırı (AWS'de güvenlik sınırı da) |
| Resource group | Doğrudan karşılığı yok: tag, CloudFormation stack | Birlikte yaşayan kaynakları bir arada yönetmek |
| Azure Policy | SCP, AWS Config kuralları | İzin verilmeyen ayarı reddetmek ya da düzeltmek |
| Cost Management budget | AWS Budgets | Harcama eşiğinde haber vermek |
Üretimde Dikkat
Prod resource group'una bir delete lock koy. CanNotDelete türündeki resource lock, Owner bile olsan resource group'un ve içindeki kaynakların silinmesini engeller; önce lock'u kaldırmak gerekir. Bu, yanlış sekmede yanlış resource group'u silmenin önündeki son bariyer.
Policy'leri önce denetim modunda dene. --enforcement-mode DoNotEnforce ile atanan bir policy kimseyi engellemez ama uyumsuz kaynakları raporlar. Mevcut bir subscription'a deny etkili bir policy'yi doğrudan atarsan, o sabah çalışan deploy pipeline'ları kırılır.
Kotaları erken kontrol et. Yeni bir subscription'ın bölgesel vCPU kotası, kampanya haftasında Bookstore'u ölçeklemek için yetmeyebilir; kota artışı bir destek talebidir ve dakikalar içinde sonuçlanmayabilir.
Sık Düşülen Tuzaklar
Her şeyi tek resource group'a koymak. Dev ile prod'un, uygulama ile paylaşılan Key Vault'un aynı resource group'ta durması, yetkiyi ayırmayı ve bir environment'ı temizce silmeyi imkânsız kılar.
Kurumsal subscription'ı kişisel kredi kartıyla açmak. Kartın sahibi şirketten ayrıldığında subscription'ın sahipliği ve faturası bir kişiye bağlı kalır. Kurumsal hesaplar şirketin faturalama anlaşmasıyla (Microsoft Customer Agreement ya da bir iş ortağı üzerinden) açılmalı ve sahiplik bir kişiye değil bir gruba verilmeli.
İsmi "sonra düzeltiriz" diye geçmek. Storage account, SQL server ve Key Vault adları global ve değiştirilemez; yanlış ad, verinin yeni bir kaynağa taşınması demek.
Tag'leri zorunlu kılmadan raporlamaya güvenmek. Policy olmadan konan tag'ler ilk ay düzgün görünür, üçüncü ayda kaynakların yarısında yoktur.
Küçük Bir Deneme
★☆☆ tools/cloud/naming.cs'e Log Analytics workspace (log) ve Application Insights (appi) kısaltmalarını ekle; sınırlarını Learn'deki "Naming rules and restrictions" sayfasından bul.
★★☆ Policy script'ine, environment tag'inin yalnız dev, staging ve prod değerlerini almasını zorunlu kılan bir atama ekle. Built-in tanımlar arasında işini görecek olanı bul, yoksa kendi policy tanımını JSON olarak yaz.
★★★ Bookstore için management group ağacını çiz: root altında platform ve uygulama grupları, prod ve nonprod subscription'ları. Her seviyeye hangi policy'yi ve hangi rol atamasını koyacağını yaz; AWS'de aynı yapıyı OU ve SCP'lerle karşılaştır.
Bir sonraki yazıda bu ağacın içinde kimin neye erişebildiğine geçiyoruz: Entra ID, Azure RBAC, managed identity ve Bookstore.Api'nin access key kullanmadan storage'a erişmesi.