Azure PowerShell ile Yönetim Otomasyonu
Bulut ortamlarını elle yönetmek, belirli bir ölçeğin ötesinde gerçekten sürdürülemez hale geliyor. Onlarca sanal makine, yüzlerce kaynak grubu, periyodik yedekleme kontrolleri… Bunları Azure portalından tek tek yapmak hem zaman kaybı hem de insan hatasına açık kapı bırakmak demek. İşte tam bu noktada Azure PowerShell devreye giriyor ve hayatı ciddi ölçüde kolaylaştırıyor.
Bu yazıda Azure PowerShell’i sıfırdan kurup yapılandırmaktan başlayarak gerçek dünya senaryolarında nasıl kullanacağınızı adım adım anlatacağım. Script örnekleri mümkün olduğunca production ortamında işe yarar şeyler olacak, akademik “hello world” örneklerinden uzak duracağız.
Azure PowerShell Nedir ve Neden Kullanmalısınız?
Azure PowerShell, Microsoft’un Azure kaynaklarını yönetmek için geliştirdiği bir modül koleksiyonudur. Temel olarak iki sürümü var: eski AzureRM modülleri ve şu an aktif geliştirilen Az modülleri. Eğer hâlâ AzureRM kullanıyorsanız, o modüller artık bakım almıyor, bir an önce geçiş yapın.
Azure CLI ile karşılaştırıldığında PowerShell’in öne çıktığı noktalar şunlar:
- Nesne tabanlı çıktı: Her komut string değil, gerçek .NET nesnesi döndürür. Bu sayede pipe ile işlem yapmak çok daha güçlü hale gelir.
- Windows ekosistemi entegrasyonu: Active Directory, Exchange, Teams gibi Microsoft ürünleriyle aynı script içinde çalışabilirsiniz.
- Hata yönetimi: Try/Catch blokları, özelleştirilmiş hata mesajları gibi tam bir programlama dili altyapısı.
- Cross-platform: PowerShell 7+ ile Linux ve macOS’ta da çalışıyor.
Kurulum ve İlk Yapılandırma
Windows’a Kurulum
Windows’ta PowerShell 7+ üzerinde çalışmanızı öneririm. Windows PowerShell 5.1 de destekleniyor ama yeni özellikler için 7’yi tercih edin.
# PowerShell 7 kurulumu (winget ile)
winget install --id Microsoft.PowerShell --source winget
# Az modülünü yükle
Install-Module -Name Az -Scope CurrentUser -Repository PSGallery -Force
# Sadece belirli modülleri de yükleyebilirsiniz
Install-Module -Name Az.Compute -Scope CurrentUser
Install-Module -Name Az.Storage -Scope CurrentUser
Linux’a Kurulum (Ubuntu/Debian)
# PowerShell 7 kurulumu
sudo apt-get update
sudo apt-get install -y wget apt-transport-https software-properties-common
wget -q "https://packages.microsoft.com/config/ubuntu/$(lsb_release -rs)/packages-microsoft-prod.deb"
sudo dpkg -i packages-microsoft-prod.deb
sudo apt-get update
sudo apt-get install -y powershell
# PowerShell başlatın ve Az modülünü yükleyin
pwsh
Install-Module -Name Az -Scope CurrentUser -Repository PSGallery -Force
Authentication Yöntemleri
Günlük kullanımda en çok karşılaşacağınız iki yöntem var:
# İnteraktif login (development için)
Connect-AzAccount
# Service Principal ile login (CI/CD ve otomasyon için)
$credential = New-Object System.Management.Automation.PSCredential(
$env:AZURE_CLIENT_ID,
(ConvertTo-SecureString $env:AZURE_CLIENT_SECRET -AsPlainText -Force)
)
Connect-AzAccount -ServicePrincipal `
-Credential $credential `
-Tenant $env:AZURE_TENANT_ID
# Managed Identity ile login (Azure VM veya Azure Functions üzerinde çalışıyorsa)
Connect-AzAccount -Identity
Production scriptlerinde kesinlikle Service Principal veya Managed Identity kullanın. Interactive login’i script içine gömmek güvenlik açığı demektir.
Birden fazla subscription yönetiyorsanız aktif subscription’ı şöyle ayarlarsınız:
# Subscription listesi
Get-AzSubscription
# Aktif subscription'ı değiştir
Set-AzContext -SubscriptionId "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
# Ya da subscription adıyla
Set-AzContext -SubscriptionName "Production-Sub"
Temel Kaynak Yönetimi
Resource Group ve VM Yönetimi
Yeni bir environment ayağa kaldırmak için sıklıkla kullandığım bir temel script:
# Değişkenler
$resourceGroup = "prod-webapp-rg"
$location = "westeurope"
$vmName = "prod-web-01"
$vmSize = "Standard_D2s_v3"
# Resource group oluştur
New-AzResourceGroup -Name $resourceGroup -Location $location -Tag @{
Environment = "Production"
Owner = "[email protected]"
CostCenter = "IT-OPS"
}
# VM oluştur
$credential = Get-Credential -Message "VM admin şifresini girin"
New-AzVM -ResourceGroupName $resourceGroup `
-Name $vmName `
-Location $location `
-VirtualNetworkName "prod-vnet" `
-SubnetName "web-subnet" `
-SecurityGroupName "$vmName-nsg" `
-PublicIpAddressName "$vmName-pip" `
-Credential $credential `
-Size $vmSize `
-Image "Win2022Datacenter"
Write-Host "VM başarıyla oluşturuldu: $vmName" -ForegroundColor Green
Toplu VM Durumu Kontrolü
Birden fazla subscription’daki tüm VM’lerin durumunu tek seferde görmek için:
function Get-AllVMStatus {
param(
[string[]]$SubscriptionIds
)
$results = @()
foreach ($subId in $SubscriptionIds) {
Set-AzContext -SubscriptionId $subId | Out-Null
$subName = (Get-AzContext).Subscription.Name
$vms = Get-AzVM -Status
foreach ($vm in $vms) {
$powerState = ($vm.Statuses | Where-Object { $_.Code -like "PowerState/*" }).DisplayStatus
$results += [PSCustomObject]@{
Subscription = $subName
ResourceGroup = $vm.ResourceGroupName
VMName = $vm.Name
Size = $vm.HardwareProfile.VmSize
PowerState = $powerState
Location = $vm.Location
OSType = $vm.StorageProfile.OsDisk.OsType
}
}
}
return $results
}
# Kullanım
$subscriptions = @(
"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"
)
$vmStatus = Get-AllVMStatus -SubscriptionIds $subscriptions
$vmStatus | Where-Object { $_.PowerState -eq "VM running" } |
Sort-Object Subscription, ResourceGroup |
Format-Table -AutoSize
# Çalışan VM sayısını subscription bazında özetle
$vmStatus | Group-Object Subscription | ForEach-Object {
Write-Host "`nSubscription: $($_.Name)" -ForegroundColor Cyan
$_.Group | Group-Object PowerState | ForEach-Object {
Write-Host " $($_.Name): $($_.Count) VM"
}
}
Gerçek Dünya Senaryosu: Maliyet Optimizasyonu Otomasyonu
En sık karşılaştığım problemlerden biri: dev/test ortamlarındaki VM’ler mesai saatleri dışında çalışmaya devam ediyor ve gereksiz maliyet yaratıyor. Bu scripti hafta içi akşam 20:00’de çalışacak şekilde Task Scheduler veya Azure Automation’a ekleyebilirsiniz:
# dev-test-shutdown.ps1
# Bu scripti Azure Automation Runbook olarak kullanabilirsiniz
param(
[string]$TargetTag = "AutoShutdown",
[string]$TagValue = "true",
[bool]$WhatIf = $false
)
# Managed Identity ile bağlan (Azure Automation'da)
Connect-AzAccount -Identity
# Tüm subscription'ları dolaş
$subscriptions = Get-AzSubscription | Where-Object { $_.State -eq "Enabled" }
$shutdownCount = 0
$errorCount = 0
foreach ($sub in $subscriptions) {
Set-AzContext -SubscriptionId $sub.Id | Out-Null
# Tag'e göre çalışan VM'leri bul
$vms = Get-AzVM -Status | Where-Object {
$_.Tags[$TargetTag] -eq $TagValue -and
($_.Statuses | Where-Object { $_.Code -eq "PowerState/running" })
}
foreach ($vm in $vms) {
try {
if ($WhatIf) {
Write-Output "WhatIf: $($vm.ResourceGroupName)/$($vm.Name) kapatılacaktı"
} else {
Stop-AzVM -ResourceGroupName $vm.ResourceGroupName `
-Name $vm.Name `
-Force `
-NoWait
Write-Output "Kapatıldı: $($vm.ResourceGroupName)/$($vm.Name)"
$shutdownCount++
}
}
catch {
Write-Error "Hata - $($vm.Name): $_"
$errorCount++
}
}
}
Write-Output "Tamamlandi. Kapatilan: $shutdownCount, Hata: $errorCount"
Storage Hesabı Yönetimi ve Blob Operasyonları
Storage operasyonları da sık ihtiyaç duyulan alanlardan. Özellikle backup dosyalarını taşımak, eski blob’ları silmek gibi işler için:
# storage-cleanup.ps1
# 90 günden eski backup blob'larını sil
param(
[string]$ResourceGroupName = "backup-rg",
[string]$StorageAccountName = "companybackupsa",
[string]$ContainerName = "vm-backups",
[int]$RetentionDays = 90,
[switch]$DryRun
)
# Storage context oluştur
$storageAccount = Get-AzStorageAccount `
-ResourceGroupName $ResourceGroupName `
-Name $StorageAccountName
$ctx = $storageAccount.Context
# Tüm blob'ları listele
$allBlobs = Get-AzStorageBlob -Container $ContainerName -Context $ctx
$cutoffDate = (Get-Date).AddDays(-$RetentionDays)
$oldBlobs = $allBlobs | Where-Object {
$_.LastModified.DateTime -lt $cutoffDate
}
$totalSize = ($oldBlobs | Measure-Object -Property Length -Sum).Sum
$totalSizeMB = [math]::Round($totalSize / 1MB, 2)
Write-Host "Silinecek blob sayisi: $($oldBlobs.Count)" -ForegroundColor Yellow
Write-Host "Toplam boyut: $totalSizeMB MB" -ForegroundColor Yellow
if ($DryRun) {
Write-Host "DRY RUN modu - hicbir sey silinmedi" -ForegroundColor Cyan
$oldBlobs | Select-Object Name, LastModified, Length | Format-Table
exit 0
}
$deletedCount = 0
foreach ($blob in $oldBlobs) {
try {
Remove-AzStorageBlob -Blob $blob.Name `
-Container $ContainerName `
-Context $ctx `
-Force
$deletedCount++
Write-Verbose "Silindi: $($blob.Name)"
}
catch {
Write-Warning "Silinemedi: $($blob.Name) - $($_.Exception.Message)"
}
}
Write-Host "Tamamlandi. $deletedCount blob silindi, $totalSizeMB MB temizlendi." -ForegroundColor Green
Azure Key Vault ile Secret Yönetimi
Script’lerde şifre ve connection string’leri plaintext tutmak en büyük güvenlik açıklarından biri. Key Vault kullanımını bir alışkanlık haline getirin:
# keyvault-helper.ps1
# Sık kullanılan Key Vault işlemleri için yardımcı fonksiyonlar
function Get-SecretValue {
param(
[Parameter(Mandatory)]
[string]$VaultName,
[Parameter(Mandatory)]
[string]$SecretName
)
try {
$secret = Get-AzKeyVaultSecret -VaultName $VaultName -Name $SecretName
return $secret.SecretValue | ConvertFrom-SecureString -AsPlainText
}
catch {
Write-Error "Secret alinirken hata: $VaultName/$SecretName - $_"
return $null
}
}
function Set-SecretValue {
param(
[Parameter(Mandatory)]
[string]$VaultName,
[Parameter(Mandatory)]
[string]$SecretName,
[Parameter(Mandatory)]
[string]$Value,
[DateTime]$ExpiresOn = (Get-Date).AddYears(1)
)
$secureValue = ConvertTo-SecureString $Value -AsPlainText -Force
Set-AzKeyVaultSecret -VaultName $VaultName `
-Name $SecretName `
-SecretValue $secureValue `
-Expires $ExpiresOn
Write-Host "Secret guncellendi: $SecretName (Gecerlilik: $ExpiresOn)" -ForegroundColor Green
}
# Süresi dolmak üzere olan secretları raporla
function Get-ExpiringSecrets {
param(
[string]$VaultName,
[int]$DaysWarning = 30
)
$secrets = Get-AzKeyVaultSecret -VaultName $VaultName
$warningDate = (Get-Date).AddDays($DaysWarning)
$expiring = $secrets | Where-Object {
$_.Expires -ne $null -and $_.Expires -lt $warningDate
}
if ($expiring.Count -eq 0) {
Write-Host "Surecek: Onumuzdeki $DaysWarning gun icinde dolan secret yok." -ForegroundColor Green
return
}
Write-Host "UYARI: $($expiring.Count) secret yakinda doluyor!" -ForegroundColor Red
foreach ($s in $expiring) {
$daysLeft = ($s.Expires - (Get-Date)).Days
Write-Host " - $($s.Name): $daysLeft gun kaldi ($(($s.Expires).ToString('yyyy-MM-dd')))" `
-ForegroundColor $(if ($daysLeft -le 7) { "Red" } else { "Yellow" })
}
}
# Kullanim ornekleri
# $dbPassword = Get-SecretValue -VaultName "prod-keyvault" -SecretName "db-admin-password"
# Get-ExpiringSecrets -VaultName "prod-keyvault" -DaysWarning 30
Network Güvenlik Grupları Denetimi
NSG kurallarını düzenli olarak denetlemek, güvenlik açısından kritik. Özellikle 0.0.0.0/0 kaynaklı izinleri tespit etmek için:
# nsg-audit.ps1
# Tehlikeli NSG kurallarını raporla
function Get-RiskyNSGRules {
param(
[string]$ResourceGroupName = "*"
)
if ($ResourceGroupName -eq "*") {
$nsgs = Get-AzNetworkSecurityGroup
} else {
$nsgs = Get-AzNetworkSecurityGroup -ResourceGroupName $ResourceGroupName
}
$riskyRules = @()
foreach ($nsg in $nsgs) {
$inboundRules = $nsg.SecurityRules | Where-Object {
$_.Direction -eq "Inbound" -and
$_.Access -eq "Allow" -and
($_.SourceAddressPrefix -eq "*" -or $_.SourceAddressPrefix -eq "0.0.0.0/0" -or
$_.SourceAddressPrefix -eq "Internet") -and
$_.DestinationPortRange -in @("22", "3389", "1433", "3306", "5432", "27017", "*")
}
foreach ($rule in $inboundRules) {
$riskyRules += [PSCustomObject]@{
NSGName = $nsg.Name
ResourceGroup = $nsg.ResourceGroupName
RuleName = $rule.Name
Priority = $rule.Priority
Port = $rule.DestinationPortRange
Source = $rule.SourceAddressPrefix
RiskLevel = if ($rule.DestinationPortRange -in @("3389","22")) { "KRITIK" } else { "YUKSEK" }
}
}
}
return $riskyRules
}
$riskyRules = Get-RiskyNSGRules
if ($riskyRules.Count -gt 0) {
Write-Host "`n=== RISKLI NSG KURALLARI ===" -ForegroundColor Red
$riskyRules | Sort-Object RiskLevel, NSGName | ForEach-Object {
$color = if ($_.RiskLevel -eq "KRITIK") { "Red" } else { "Yellow" }
Write-Host "[$($_.RiskLevel)] $($_.NSGName) - $($_.RuleName) | Port: $($_.Port) | Kaynak: $($_.Source)" -ForegroundColor $color
}
# CSV'ye de kaydet
$riskyRules | Export-Csv -Path "nsg-audit-$(Get-Date -Format 'yyyyMMdd').csv" -NoTypeInformation -Encoding UTF8
Write-Host "`nRapor kaydedildi: nsg-audit-$(Get-Date -Format 'yyyyMMdd').csv"
} else {
Write-Host "Tehlikeli NSG kurali bulunamadi." -ForegroundColor Green
}
Azure Automation ile Scheduling
Scriptleri elle çalıştırmak yerine Azure Automation Runbook olarak zamanlamak, tam anlamıyla “set and forget” otomasyonu sağlar.
Runbook oluştururken dikkat etmeniz gerekenler:
- Managed Identity: Automation account’a sistem assigned managed identity verin, Service Principal yerine bu daha güvenli ve yönetimi kolay.
- Hata yönetimi: Runbook’larda
$ErrorActionPreference = "Stop"ile başlayın, sessiz hatalar peşinizi bırakmaz. - Output:
Write-Outputkullanın,Write-HostRunbook log’larında görünmeyebilir. - Parametre doğrulama:
[Parameter(Mandatory)]kullanarak zorunlu parametreleri belirtin.
# azure-automation-runbook-template.ps1
# Azure Automation Runbook için temel şablon
[CmdletBinding()]
param(
[Parameter(Mandatory = $false)]
[string]$TargetSubscription = "",
[Parameter(Mandatory = $false)]
[bool]$SendNotification = $true
)
$ErrorActionPreference = "Stop"
$VerbosePreference = "Continue"
try {
# Managed Identity ile bağlan
Write-Output "Azure'a baglaniliyor..."
Connect-AzAccount -Identity | Out-Null
if ($TargetSubscription -ne "") {
Set-AzContext -SubscriptionId $TargetSubscription | Out-Null
}
$context = Get-AzContext
Write-Output "Baglanti basarili. Subscription: $($context.Subscription.Name)"
# Ana islemleri buraya ekleyin
# ...
Write-Output "Runbook basariyla tamamlandi: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
}
catch {
Write-Error "Runbook hatasi: $($_.Exception.Message)"
Write-Error "Stack Trace: $($_.ScriptStackTrace)"
# Hata bildirimini burada yapabilirsiniz
# Send-MailMessage veya Teams webhook ile
exit 1
}
İpuçları ve En İyi Pratikler
Yıllarca Azure PowerShell kullanan biri olarak şu noktalara dikkat edin:
- Modülleri güncel tutun:
Update-Module -Name Azkomutunu düzenli çalıştırın. Eski versiyonlarda bug’lar ve eksik özellikler sizi çıldırtabilir. - WhatIf parametresini kullanın: Destructive komutları çalıştırmadan önce
-WhatIfile simüle edin. Silme, durdurma gibi işlemlerde hayat kurtarır. - Parallel işlem: PowerShell 7’de
ForEach-Object -Parallelile büyük VM listelerini çok daha hızlı işleyebilirsiniz. - Profil dosyası: Sık kullandığınız fonksiyonları
$PROFILEdosyasına ekleyin, her session’da yeniden tanımlamaktan kurtulursunuz. - Az.Accounts cache:
Connect-AzAccounther seferinde token alıyor.Disconnect-AzAccountsonrası tekrar bağlanmak zorunda kalmamak içinEnable-AzContextAutosavekullanın. - Rate limiting: Büyük ortamlarda binlerce API çağrısı yapıyorsanız Azure’un throttle limitine takılabilirsiniz. Döngülerde
Start-Sleep -Milliseconds 100gibi küçük beklemeler eklemek bu sorunu önler. - Log yönetimi: Kritik scriptlerde
Start-Transcriptile tüm çıktıyı dosyaya kaydedin, sorun çıktığında neyin ne zaman olduğunu anlayın.
Sonuç
Azure PowerShell, sadece birkaç tıklama işlemini script haline getirmekten çok daha fazlasını sunuyor. Doğru kullanıldığında multi-subscription yönetimi, otomatik uyumluluk kontrolleri, maliyet optimizasyonu ve güvenlik denetimleri gibi karmaşık senaryoları tamamen otomatik hale getirmenizi sağlıyor.
Bu yazıda anlattığım scriptleri doğrudan production’a atmayın tabii. Her ortamın kendine özgü gereksinimleri var. Önce test subscription’ında -WhatIf ve -DryRun modlarıyla çalıştırın, sonuçlara bakın, sonra production’a taşıyın.
Bir sonraki adım olarak Azure Automation, Logic Apps veya GitHub Actions ile bu scriptleri tam bir pipeline’a dönüştürmeyi düşünebilirsiniz. Özellikle maliyet yönetimi ve güvenlik denetimleri için haftalık otomatik raporlar oluşturmak, pek çok küçük problemi büyümeden yakalamanızı sağlıyor.
Sorularınız veya farklı bir senaryo için örnek script isterseniz yorumlarda belirtin.
