如何使用Automation Account Run Book實現自動化
1. 什么是 Runbook?
Azure Automation Account 中的 Runbook 是一套自動化腳本,用于在云中或混合環境中執行常規任務。Runbook 支持多種腳本語言,包括 PowerShell、Python、Graphical、PowerShell Workflow 等。這些腳本可以自動化各種管理任務,如虛擬機管理、資源清理、定期備份、資源配置等。
2. 創建管理 Runbook
-
創建 Runbook:在 Azure 門戶中,可以通過“+ Create a Runbook”按鈕創建新的 Runbook。創建過程中,你可以選擇腳本語言,并為腳本添加邏輯。一個 Runbook 可以直接編寫腳本,或者通過上傳現有腳本實現。
-
語言更新和支持:Runbook 所支持的語言環境可以從 Azure Automation 的“Gallery”中更新,以確保腳本能夠使用最新的模塊和功能。這一點在處理與云服務相關的復雜任務時尤為重要,例如使用最新版本的 Azure PowerShell 模塊管理資源。
3. 靈活的自動化運行
-
調度與觸發:Runbook 支持設置定時調度(Schedule),允許在指定的時間點或間隔自動運行。可以基于 CRON 表達式設定復雜的調度規則,滿足企業的定制化需求。
-
動態參數傳遞:在執行 Runbook 時,可以通過參數化方式動態傳遞運行時數據,使得腳本更加靈活、適應性更強。
4. 混合環境中的自動化
-
混合運行能力:Azure Automation Runbook 的一個強大功能是它不僅可以在 Azure 云環境中運行,還可以在自定義的 Hybrid Runbook Worker 中運行。這些 Worker 可以部署在 Azure VM 中,甚至可以部署在本地數據中心的非 Azure 服務器上,實現對非 Azure 環境的自動化管理。
-
安全與合規性:在非 Azure 環境中運行時,Hybrid Runbook Worker 通過安全的通道與 Azure Automation 進行通信。管理員可以通過本地網絡策略和防火墻確保腳本執行的合規性和安全性。
5. 技術難點與挑戰
-
混合云架構:配置 Hybrid Runbook Worker 需要確保在非 Azure 環境中,網絡連接、權限管理和認證機制的正確配置。這涉及到對企業內部網絡的深度理解,并且要求嚴格遵守安全規范。
-
跨平臺兼容性:在不同平臺上運行 Runbook(例如在 Linux 服務器上執行 PowerShell 腳本)可能會遇到兼容性問題,管理員需要確保腳本在所有目標平臺上的兼容性,并處理潛在的依賴性問題。
-
調試與日志管理:在混合環境中調試 Runbook 可能較為復雜,尤其是當涉及多個系統之間的交互時。管理員需要精細化管理日志,并借助 Azure Monitor 等工具進行深入分析,以確保自動化流程的穩定性。
哪些場景需要用到自動化?
以下三個廣泛的云運營領域需要自動化:
-
部署和管理 - 交付可重復且一致的基礎設施即代碼。
-
響應 - 創建基于事件的自動化來診斷和解決問題。
-
編排 - 編排自動化并將它與其他 Azure 或第三方服務和產品集成。
Azure 自動化提供基于云的自動化、操作系統更新和配置服務,用于支持 Azure 環境和非 Azure 環境之間的一致管理。 Azure 自動化包括流程自動化、配置管理、更新管理、共享功能和異類功能。?
有多個 Azure 服務可滿足上述要求,其中的每個服務包含一組功能,并充當可編程平臺用于生成云解決方案。 例如,Azure Bicep 和資源管理器提供一種語言來為 Azure 資源開發可重復且一致的部署模板。 Azure 自動化可以處理該模板以部署 Azure 資源,然后處理一組部署后配置任務。
在部署、操作和解除分配企業工作負載與資源期間,自動化可以提供全面的控制。
Runbook里支持哪些身份驗證?
名稱 | 方式 | 優先級 | 注意事項 |
Secret | 使用 AAD Service Principal 和 Secret 密鑰 | High | 密鑰需安全存儲,容易過期,需要定期更新。 |
Certificate | 使用 AAD Service Principal 和證書 | Medium | 證書管理復雜,但安全性高,適合長期自動化任務。 |
Run As Connection | 使用預配置的 Azure Automation Run As Connection | Medium | 需要提前配置,適用于已部署的自動化流程。 |
Managed Identity | 使用 Azure 的托管身份進行無憑據登錄 | High | 無需管理憑據,需在支持 Managed Identity 的資源中使用。 |
?
Secret
$ApplicationId = '<Your-Application-Id>'
$TenantId = '<Your-Tenant-Id>'
$SPNKey = '<Your-SPN-Key>'
$SecureSPNKey = ConvertTo-SecureString -String $SPNKey -AsPlainText -Force
$Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $ApplicationId, $SecureSPNKey
Connect-AzAccount -ServicePrincipal -Credential $Credential -Tenant $TenantId -Environment AzureChinaCloud
Certificate
$ApplicationId?=?'<Your-Application-Id>'
$TenantId?=?'<Your-Tenant-Id>'
$CertificateThumbprint?=?'<Your-Certificate-Thumbprint>'
Connect-AzureAD?-TenantId?$TenantId?-ApplicationId?$ApplicationId?-CertificateThumbprint?$CertificateThumbprint?-AzureEnvironmentName?AzureChinaCloud
Run As Connection
$connectionName?=?"*****"
try
{
? ??# Get the connection "*****"
? ??$servicePrincipalConnection?=?Get-AutomationConnection?-Name?"*****"
? ??"*****"
? ??Add-AzureRmAccount?`
? ??-ServicePrincipal?`
? ??-TenantId?***** `
? ??-ApplicationId?***** `
? ??-CertificateThumbprint?***** `
? ??-EnvironmentName?*****
}
catch?{
? ??if?(!$servicePrincipalConnection)
? ? {
? ? ? ??$ErrorMessage?=?"*****"
? ? ? ??throw?$ErrorMessage
? ? }?else?{
? ? ? ??Write-Error?-Message?"*****"
? ? ? ??throw?"*****"
? ? }
}
Managed Identity
Connect-AzAccount?-Identity
日常Cloud運維哪些場景可以在Runbook里使用?
Ex. Scripting Solutions - 資源優化
Clearing Idle Public IP
$report?=?@()
$pips?=?Get-AzPublicIpAddress
$publicaddresss?=?$pips?|?Where-Object?{?$_.IpConfiguration?-eq?$null?}
$publicaddresss?|?Remove-AzPublicIpAddress?-Force?-Verbose
foreach?($publicaddress?in?$publicaddresss) {
? ??$info?=?""?|?Select?Name, ResourceGroupName, IpAddress, AllocationMethod, Fqdn
? ??$info.Name =?$publicaddress.Name
? ??$info.ResourceGroupName =?$publicaddress.ResourceGroupName
? ??$info.IpAddress =?$publicaddress.IpAddress
? ??$info.AllocationMethod =?$publicaddress.PublicIpAllocationMethod
? ??$info.Fqdn =?$publicaddress.dnssettings.Fqdn
? ??$info
? ??$report?+=?$info
}
$report?|?Format-Table
Clearing Idle Snapshots
$snapshots?= *****
$report?=?@()
$snapshots?|?ForEach-Object?{
? ??$info?=?""?|?Select?*****, *****, *****, *****
? ??$Thresholddate?= *****
? ??$snapshot?= *****?-SnapshotName?*****
? ??$snapshotname?= *****
? ??$snapcreatetime?= *****?
? ??$Thresholddatedis?= *****
? ??$datacheck?= *****
? ??If?($datacheck) {
? ? ? ??Write-Host?"*****"?-ForegroundColor?Red
? ? ? ??$deletetag?=?"*****"
? ? ? ? *****?-SnapshotName?*****?-ResourceGroupName?*****?-Force
? ? }?else?{
? ? ? ??Write-Host?"*****"?-ForegroundColor?Green
? ? ? ??$deletetag?=?"*****"
? ? }
? ??$info.SnapshotName = *****
? ??$info.ResourceGroupName = *****
? ??$info.TimeCreated = *****
? ??$info.TAG = *****
? ??$report?+=?$info
}
$report?| *****
Ex. Scripting Solutions - 過期通知
SP Expiration
$resultsSP = @()
Get-AzureADApplication -All $true | %{
? ? $app =?*****
? ? $owner = Get-AzureADApplicationOwner -ObjectId?***** -Top 1
? ? (Get-AzureADApplication -ObjectId?*****).PasswordCredentials |
? ? %{
? ? ? ? $resultsSP += [PSCustomObject] @{
? ? ? ? ? ? CredentialType = "PasswordCredentials"
? ? ? ? ? ? DisplayName =?*****;
? ? ? ? ? ? ExpiryDate =?*****;
? ? ? ? ? ? StartDate =?*****;
? ? ? ? ? ? KeyID =?*****;
? ? ? ? ? ? Type = '*****';
? ? ? ? ? ? Usage = '*****';
? ? ? ? ? ? Owners =?*****;
? ? ? ? }
? ? }
? ? (Get-AzureADApplication -ObjectId?*****).KeyCredentials |
? ? %{
? ? ? ? $resultsSP += [PSCustomObject] @{
? ? ? ? ? ? CredentialType = "KeyCredentials"
? ? ? ? ? ? DisplayName =?*****;
? ? ? ? ? ? ExpiryDate =?*****;
? ? ? ? ? ? StartDate =?*****;
? ? ? ? ? ? KeyID =?*****;
? ? ? ? ? ? Type =?*****;
? ? ? ? ? ? Usage =?*****;
? ? ? ? ? ? Owners =?*****;
? ? ? ? }
? ? }
}
AAD User Expiration
$scriptPath = "$env:TEMP\*****.ps1"
$content = @'
[CmdletBinding()]
param ([parameter(Mandatory=$true)][String]$aduser
)
function New-RandomPassword {param ([int]$MinLength = *****,[int]$MaxLength = *****,[int]$AlphaNumChars = *****,[switch]$SecureString)Add-Type -AssemblyName "System.Web"$password = [System.Web.Security.Membership]::GeneratePassword((Get-Random -Minimum $MinLength -Maximum $MaxLength), $AlphaNumChars)if ($SecureString) {ConvertTo-SecureString -String $password -AsPlainText -Force} else {$password}
}
$psw = New-RandomPassword -MinLength ***** -MaxLength ***** -AlphaNumChars *****
$securePsw = ConvertTo-SecureString -String $psw -AsPlainText -Force
try {Get-ADUser -Identity $aduser
} catch {Write-Error *****return
}
Unlock-ADAccount -Identity *****
Set-ADAccountControl -Identity ***** -Enabled $true
Set-ADAccountPassword -Identity ***** -NewPassword ***** -Reset
$out = Get-ADUser -Identity ***** -Properties ***** | Select-Object -Property *****, *****
Write-Output $out
Write-Output "password is reset to *****"
'@
$content > $scriptPath
$params = @{aduser = *****
}
$run = lw-Invoke-AzVMRunCommand -ResourceGroupName "*****" -Name "*****" -CommandId 'RunPowerShellScript' -ScriptPath $scriptPath -Parameter $params
?
Ex. Scripting Solutions - 自動化解決方案
Server Metrics Summary
function Get-VMmetrics
{$subscription = (Get-AzContext).name$patten = '*****'$cutpoint = $subscription.LastIndexOf($patten)$subname = $subscription.Substring(0, ($cutpoint - 1))$subid = ((($subscription.Substring($cutpoint) -split '*****')[0] -replace '*****', '') -replace '*****', '') -replace '*****', ''$vmlist = Get-AzVM -status | Where-Object {$_.PowerState -eq 'VM running'}Write-host "*****"$vmInfo = @()$start = ((Get-Date).AddDays(-30).ToUniversalTime())$end = ((Get-Date).ToUniversalTime())$cnn1size = Get-AzVMSize -Location '*****'$cnn2size = Get-AzVMSize -Location '*****'$cne1size = Get-AzVMSize -Location '*****'$cne2size = Get-AzVMSize -Location '*****'foreach ($vm in $vmlist) {$cpu = Get-AzMetric -ResourceId ***** -TimeGrain ***** -StartTime ***** -EndTime ***** -MetricName '*****' -WarningAction SilentlyContinue$cpuavg = $cpu.data | Measure-Object -Property Average -Average$cpumax = $cpu.Data | Measure-Object -Property Average -Maximum$cpumin = $cpu.Data| Measure-Object -Property Average -Minimum$mem = Get-AzMetric -ResourceId ***** -TimeGrain ***** -StartTime ***** -EndTime ***** -MetricName '*****' -WarningAction SilentlyContinue$memavg = ($mem.data | Measure-Object -Property Average -Average).Average/1024/1024$memmax = ($mem.Data | Measure-Object -Property Average -Maximum).Maximum/1024/1024$memmin = ($mem.Data| Measure-Object -Property Average -Minimum).Minimum/1024/1024$vmsize = "*****"switch ($($vm.Location)) {"*****" { $vmcfg = ($cnn1size | Where-Object {$_.Name -eq $vmsize}) }"*****" { $vmcfg = ($cnn2size | Where-Object {$_.Name -eq $vmsize}) }"*****" { $vmcfg = ($cne1size | Where-Object {$_.Name -eq $vmsize}) }"*****" { $vmcfg = ($cne2size | Where-Object {$_.Name -eq $vmsize}) }}$cputotal = $vmcfg.NumberOfCores$memtotal = $vmcfg.MemoryInMB$memavgpct = $memavg / $memtotal$memmaxpct = $memmax / $memtotal$memminpct = $memmin / $memtotalWrite-host "*****"$vmobj = [pscustomobject]@{'SubscriptionName' = $subname'Subscriptionid' = $subid'ResourceGroup' = $vm.ResourceGroupName'VMName' = $vm.Name'CPU Cores' = $cputotal'Memory Total (MB)' = $memtotal'CPU Average %' = $cpuavg.Average'CPU Maximum %' = $cpumax.Maximum 'CPU Minimum %' = $cpumin.Minimum'Memory Used % Avg' = $memavgpct'Memory Used % Max' = $memmaxpct 'Memory Used % Min' = $memminpct }$vmInfo = $vmInfo + $vmobj}return $vmInfo
}
Server PIP Summary
$resource_groups = az group list --subscription ***** --query "[?!(starts_with(name, '*****') || starts_with(name, '*****') || starts_with(name, '*****'))].name" -o tsv
$results = @()
foreach ($resource_group in $resource_groups) {Write-Host "Resource Group: *****"$vm_details = az vm list -g ***** -o json | ConvertFrom-Jsonforeach ($vm in $vm_details) {$nic_id = $vm.networkProfile.networkInterfaces[0].id$nic = az network nic show --ids ***** -o json | ConvertFrom-Json$private_ip = $nic.ipConfigurations[0].privateIpAddress$public_ip_id = $nic.ipConfigurations[0].publicIpAddress.id$public_ip = if ($public_ip_id) { az network public-ip show --ids ***** --query "ipAddress" -o tsv } else { $null }$computer_name = if ($vm.osProfile.computerName) { ***** } else { "*****" }$agent_statuses = az vm get-instance-view --name ***** --resource-group ***** --query "instanceView.vmAgent.statuses" -o json | ConvertFrom-Json$agent_status = "*****"if ($agent_statuses -and $agent_statuses.Count -gt 0) {$agent_status = ***** + " - " + *****}$object = New-Object PSObject -property @{"Resource Group" = $resource_group"VM Name" = *****"Computer Name" = $computer_name"Location" = *****"Status" = *****"OS Type" = *****"Image" = *****"Size" = *****"IP Address (Private)" = $private_ip"IP Address (Public)" = $public_ip"Agent Status" = $agent_status"Resource ID" = *****}$results += $object}
}
Server Deallocated Status
$stoppedVMs = @()
$deallocatedVMs = @()
foreach ($subscription in *****) {Set-AzContext -SubscriptionId *****$allVMs = Get-AzVM -Statusforeach ($vm in $allVMs) {$resourceGroupName = $vm.ResourceGroupName$vmName = $vm.Nameif ($resourceGroupName -notmatch '*****' -and $resourceGroupName -notmatch '*****' -and $vmName.Length -le *****) {if ($vm.PowerState -eq '*****') {$stoppedVMs += $vm} elseif ($vm.PowerState -eq '*****') {$deallocatedVMs += $vm}}}
}
Run Book部署后如何通知IT?
步驟 1:檢索 Azure Storage 中的最新報告
腳本首先創建 Azure Storage 上下文,連接到指定的存儲賬戶和容器。在容器中查找當天生成的所有報告文件(以日期命名),并將這些文件下載到本地臨時文件夾中。如果未找到當天的報告文件,腳本會記錄錯誤信息并終止執行,確保團隊不會收到過期或不完整的報告。
Create Storage Connext
$context?=?New-AzStorageContext?-StorageAccountName?$storageAccountName?-StorageAccountKey?$storageAccountKey?$blobs?=?Get-AzStorageBlob?-Container?$containerName?-Context?$context?$blobsToBesend?=?$blobs?|?Where-Object?{?$_.Name?-like?"*$date*"?}?if?($blobsToBesend.Count?-eq?0) {?Write-Error?"No blobs found for today's date. Exiting script."?Exit?}
步驟 2:郵件生成與發送
一旦報告文件下載到本地,腳本會生成一封包含所有報告文件的郵件,并發送給指定的 IT 團隊成員。郵件內容簡潔明了,附有當天生成的所有檢查報告,以便團隊及時查看和處理。
Sending email
$attachments?+=?$destination?$smtpCreds?=?New-Object?System.Management.Automation.PSCredential?-ArgumentList?($smtpUser, (ConvertTo-SecureString?-String?$smtpPassword?-AsPlainText?-Force))?Send-MailMessage?-To?$toEmail?-From?$fromEmail?-Subject?$subject?-Body?$body?-SmtpServer?$smtpServer?-Port?$smtpPort?-Credential?$smtpCreds?-UseSsl?-Attachments?$attachments
步驟 3:清理臨時文件
為了保持系統的整潔和性能,腳本在發送郵件后會自動刪除下載到本地的臨時報告文件,確保不留下任何不必要的存儲占用。
Clean Attachements
foreach?($attachment?in?$attachments) {?Remove-Item?-Path?$attachment?-Force?}
Ex. Screenshot
如何通過云原生Workspace Summary展示資源性能看板?
Note:
The Log Analytics agents (MMA.OMS) used to collect logs from virtual machines and servers will no longer be supported from August 31, 2024. Plan to migrate to Azure Monitor Agent before this date.
第一步:將虛擬機(VM)關聯到 Log Analytics 工作區
-
原理:?要監控和收集虛擬機的利用率數據,必須將 VM 連接到 Azure Log Analytics 工作區。這通過安裝 Azure Monitor 代理(以前稱為 Log Analytics 代理)來實現。代理負責將 VM 的性能數據(如 CPU、內存利用率)收集并發送到工作區。
-
技術實現:?
-
安裝代理:?
-
您可以通過 Azure 門戶、PowerShell 或 Azure CLI 來安裝代理。
-
確保虛擬機已經正確安裝了 Log Analytics 代理(Windows 用的是 MMA,Linux 用的是 OMS)。
-
代理安裝成功后,虛擬機會開始向關聯的 Log Analytics 工作區發送數據。
第二步:在 Log Analytics 中配置數據收集
-
原理:?將 VM 連接到工作區后,需要配置數據收集器,以便 Log Analytics 工作區能夠正確地收集并存儲 VM 的性能數據。
-
技術實現:?
-
進入 Azure 門戶,導航到您的 Log Analytics 工作區。
-
在工作區的設置下,選擇“數據收集規則”。
-
配置要收集的性能計數器,如?
Processor Information(_Total)\% Processor Time,Memory\Available Mbytes
,以及?LogicalDisk(_Total)\% Free Space
。
第三步:創建 VM 利用率洞察圖表(VM Utilization Insights)
-
原理:?一旦 Log Analytics 收集到足夠的數據,您可以創建和配置自定義查詢或使用現有的工作簿(Workbook)來可視化 VM 的利用率數據。
-
技術實現:?
-
在 Azure 門戶中,進入您的 Log Analytics 工作區。
-
選擇“工作簿(Workbook)”選項,創建或編輯一個工作簿。
-
通過 Kusto 查詢語言(KQL)來編寫查詢,以獲取并展示所需的數據。
Perf?|?where?ObjectName?==?"Processor"?and?CounterName?==?"% Processor Time"?and?InstanceName?==?"_Total"?|?summarize AvgProcessorTime?=?avg(CounterValue)?by?bin(TimeGenerated,?1h), Computer?|?order?by?TimeGenerated?desc
-
配置圖表類型(如折線圖、柱狀圖等)來展示這些數據。
第四步:驗證與優化
-
原理: 確保配置無誤,并且 VM 數據在 Log Analytics 工作區中能夠正確采集和展示。
-
技術實現:?
-
驗證數據是否在預期的時間范圍內更新。
-
如果數據未正確顯示,檢查代理的狀態并重新配置數據收集規則。
-
使用查詢工具(如“日志分析”)進一步驗證并優化查詢以提高性能。
Ex.Solutions Kusto 查詢語句
VM CPU Utilization Desc
Perf
| where (CounterName == "% Processor Time" and ObjectName == "Processor")
| summarize cpu = avg(CounterValue) by Computer, CounterName, ObjectName
| extend g1 = (cpu >= 90), g2 = (cpu < 90 and cpu >= 20)
| extend Category=case(g1 == "true", "More Than 90 Percent", g2 == "true", "90 to 20 Percent", "less than 20 Percent")
| join (Heartbeat| distinct Computer, OSType)on Computer
| summarize AggregateValue = count() by Category
VM Memory Utilization Desc
Perf
| where CounterName == "% Used Memory" or CounterName == "% Committed Bytes In Use"
| summarize memory = avg(CounterValue) by Computer, CounterName, ObjectName
| extend g1 = (memory >= 90), g2 = (memory < 90 and memory >= 20)
| extend Category=case(g1 == "true", "More Than 90 Percent", g2 == "true", "90 to 20 Percent", "less than 20 Percent")
| join (Heartbeat| distinct Computer, OSType)on Computer
| summarize AggregateValue = count() by Category
VM Disk Utilization Desc
Perf
| where (CounterName == "% Free Space")
| where InstanceName == "C:" or InstanceName == "/"
| summarize disk = avg(CounterValue) by Computer, CounterName, InstanceName
| extend g1 = (disk <= 10), g2 = (disk > 10 and disk <= 70)
| extend Category=case(g1 == "true", "More Than 90 Percent", g2 == "true", "90 to 30 Percent", "less than 30 Percent")
| join (Heartbeat| distinct Computer, OSType)on Computer
| summarize AggregateValue = count() by Category