如何使用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 / $memtotal
Write-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-Json
foreach ($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 -Status
foreach ($vm in $allVMs) {
$resourceGroupName = $vm.ResourceGroupName
$vmName = $vm.Name
if ($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