排查 Log Analytics Windows 代理的问题

本文介绍如何排查可能遇到的 Azure Monitor 中的 Log Analytics Windows 代理的相关错误,并提供可能的解决方法建议。

Log Analytics 故障排除工具

Log Analytics Windows 代理故障排除工具是一个 PowerShell 脚本集合,旨在帮助查找和诊断 Log Analytics 代理问题。 安装后,该工具将自动包含在代理中。 应将运行此工具作为诊断问题的第一步。

使用故障排除工具

  1. 在安装了 Log Analytics 代理的计算机上以管理员身份打开 PowerShell 提示符。

  2. 转到该工具所在的目录:

    cd "C:\Program Files\Microsoft Monitoring Agent\Agent\Troubleshooter"

  3. 使用此命令执行主脚本:

    .\GetAgentInfo.ps1

  4. 选择故障排除方案。

  5. 按照控制台上的说明进行操作。 请注意,跟踪日志步骤需要手动干预来停止日志收集。 根据问题的可再现性,等待相应的持续时间,并选择“s”停止日志收集,然后继续下一步。

    完成时将会记录结果文件的位置,并会打开一个新的资源管理器窗口,其中突出显示结果文件的位置。

安装

安装 Log Analytics 代理内部版本 10.20.18053.0 及更高版本时,将会自动包含故障排除工具。

涵盖的方案

故障排除工具检查以下方案:

  • 代理未报告数据或检测信号数据缺失。
  • 代理扩展部署失败。
  • 代理崩溃。
  • 代理消耗大量 CPU 或内存。
  • 安装和卸载体验失败。
  • 自定义日志有问题。
  • OMS 网关有问题。
  • 性能计数器有问题。
  • 无法收集代理日志。

注意

遇到问题时,请运行故障排除工具。 从一开始便记录日志将帮助我们的支持团队更快解决你的问题。

重要的故障排除源

为了帮助排查 Log Analytics Windows 代理相关的问题,该代理会将事件记录到 Windows 事件日志,具体而言,是 Application and Services\Operations Manager 下的日志。

连接问题

如果代理通过代理服务器或防火墙通信,某些限制可能会阻止源计算机和 Azure Monitor 服务发起的通信。 如果由于配置错误而阻止了通信,则在尝试安装代理或在安装后将代理配置为向其他工作区报告时,注册到工作区可能会失败。 成功注册后,代理通信可能会失败。 本部分将介绍排查此类 Windows 代理问题的方法。

请仔细检查防火墙或代理是否配置为允许下表中所述的端口和 URL。 另请确认是否没有为 Web 流量启用 HTTP 检查。 该检查可能会阻止代理与 Azure Monitor 之间的安全 TLS 通道。

代理资源 端口 方向 绕过 HTTPS 检查
*.ods.opinsights.azure.cn 端口 443 出站
*.oms.opinsights.azure.cn 端口 443 出站
*.blob.core.chinacloudapi.cn 端口 443 出站
*.azure-automation.cn 端口 443 出站

如果计划使用 Azure 自动化混合 Runbook 辅助角色连接到自动化服务并在其中注册以在环境中使用 Runbook 或管理解决方案,则它必须可以访问针对混合 Runbook 辅助角色配置网络中所述的端口号和 URL。

可通过多种方法验证代理是否能够成功与 Azure Monitor 通信:

  • 运行以下查询来确认代理是否会将检测信号发送到为其配置的目标报告工作区。 请将 <ComputerName> 替换为计算机的实际名称。

    Heartbeat 
    | where Computer like "<ComputerName>"
    | summarize arg_max(TimeGenerated, * ) by Computer 
    

    如果计算机可成功与服务通信,则该查询应会返回结果。 如果查询未返回结果,请先验证代理是否配置为向正确的工作区报告。 如果配置正确,请转到步骤 3 并搜索 Windows 事件日志,以确定代理是否记录了可能阻止它与 Azure Monitor 通信的问题。

  • 识别连接问题的另一种方法是运行 TestCloudConnectivity 工具。 默认情况下,该工具会连同代理一起安装在 %SystemRoot%\Program Files\Microsoft Monitoring Agent\Agent 文件夹中。 在权限提升的命令提示符下,转到该文件夹并运行该工具。 此工具将返回结果并突出显示测试失败的位置。 例如,指出问题是否与某个已阻止的特定端口或 URL 相关。

    显示 TestCloudConnection 工具执行结果的屏幕截图。

  • 按“事件源”下的“运行状况服务模块”、“运行状况服务”和“服务连接器”筛选“Operations Manager”事件日志,并按“事件级别”下的“警告”和“错误”进行筛选,以确认代理是否写入了下表中所述的事件。 如果已写入,请查看针对每个可能的事件提供的解决方法步骤。

    事件 ID 说明 解决方案
    2133 和 2129 运行状况服务 从代理连接到服务失败。 如果代理无法直接或者通过防火墙或代理服务器来与 Azure Monitor 服务通信,则可能会发生此错误。 验证该代理程序的代理设置,或者网络防火墙或代理是否允许将该计算机的 TCP 流量发送到服务。
    2138 运行状况服务模块 代理要求身份验证。 配置该代理程序的代理设置,并指定在代理服务器上进行身份验证所需的用户名/密码。
    2129 运行状况服务模块 连接失败。 TLS 协商失败。 检查网络适配器的 TCP/IP 设置和代理程序的代理设置。
    2127 运行状况服务模块 无法发送数据所接收的错误代码。 如果此问题只是在某一天中定期发生,原因可能是出现随机异常;可忽略此问题。 通过监视来了解问题发生的频率。 如果在一整天经常发生,请先检查网络配置和代理设置。 如果说明中包含 HTTP 错误代码 404,并且这是代理首次尝试将数据发送到服务,则错误消息中会包含 500 错误和 404 内部错误代码。 错误代码 404 表示“未找到”,即,仍在预配新工作区的存储区域。 下次重试时,数据将成功按预期写入到工作区。 HTTP 错误 403 可能表示出现权限或凭据问题。 403 错误会包含更多信息来帮助排查问题。
    4000 服务连接器 DNS 名称解析失败。 计算机无法解析在向服务发送数据时使用的 Internet 地址。 可能是计算机上的 DNS 解析程序设置有问题、代理设置不正确,或者提供商出现了暂时性的 DNS 问题。 如果此错误定期发生,原因可能是存在暂时性的网络相关问题。
    4001 服务连接器 无法连接到服务。 如果代理无法直接或者通过防火墙或代理服务器来与 Azure Monitor 服务通信,则可能会发生此错误。 验证该代理程序的代理设置,或者网络防火墙或代理是否允许将该计算机的 TCP 流量发送到服务。
    4002 服务连接器 服务返回 HTTP 状态代码 403 以响应查询。 请咨询服务管理员以了解服务的运行状况。 稍后将重试该查询。 此错误是在代理的初始注册阶段写入的。 你将看到类似于以下内容的 URL:https://<workspaceID>.oms.opinsights.azure.cn/AgentService.svc/AgentTopologyRequest。 错误代码 403 表示禁止,可能是由于错误键入工作区 ID 或密钥所致。 或者可能是计算机上的数据和时间不正确。 如果时间比当前时间快/慢 15 分钟,则无法加入。 要纠正此问题,请更新 Windows 计算机的日期和/或时间。

数据收集问题

代理已安装并可以向配置的一个或多个工作区报告后,根据启用的设置以及面向计算机的内容,它可能会停止接收配置,并停止收集性能、日志或其他数据或者将这些数据转发到服务。 需要确定:

  • 工作区中是特定的数据类型不可用,还是所有数据都不可用?
  • 该数据类型是由解决方案指定的,还是指定为工作区数据收集配置的一部分?
  • 有多少台计算机受到影响? 是有一台计算机还是多台计算机向工作区报告?
  • 代理是否只是在一天的特定时间停止,而在其他时间可保持工作,或者,它是否从未收集过数据?
  • 使用的日志搜索查询在语法上是否正确?
  • 代理是否曾经从 Azure Monitor 接收过其配置?

故障排除的第一步是确定计算机是否发送检测信号事件。

Heartbeat 
    | where Computer like "<ComputerName>"
    | summarize arg_max(TimeGenerated, * ) by Computer

如果查询返回了结果,则你需要确定是否未收集特定的数据类型并将其转发到服务。 问题可能是代理未从服务接收更新的配置,或者其他某种症状阻止了代理正常运行。 执行以下步骤进一步进行故障排除。

  1. 在计算机上打开权限提升的命令提示符,并输入 net stop healthservice && net start healthservice 重启代理服务。

  2. 打开“Operations Manager”事件日志,并在“事件源”下的“运行状况服务”中搜索“事件 ID”7023、7024、7025、7028 和 1210。 这些事件表示代理可成功从 Azure Monitor 接收配置,并且它们正在监视计算机。 事件 ID 1210 的事件说明还会在最后一行中指定代理监视范围内的所有解决方法和见解。

    显示事件 ID 1210 说明的屏幕截图。

  3. 等待几分钟。 如果查询结果或可视化效果中未按预期显示数据,请根据你查看的是解决方法还是见解中的数据,在“Operations Manager”事件日志中,搜索“事件源”下的“运行状况服务”和“运行状况服务模块”。 按“事件级别”下的“警告”和“错误”进行筛选,以确认代理是否写入了下表中所述的事件。

    事件 ID 说明 解决方法
    8000 HealthService 此事件将指定与性能、事件或收集的其他数据类型相关的工作流是否无法将这些数据转发到服务,以引入到工作区。 来自源运行状况服务的事件 ID 2136 将连同此事件一起写入,可能表示代理无法与服务通信。 原因可能是代理和身份验证设置的配置不当、网络中断,或者网络防火墙或代理不允许将计算机的 TCP 流量发送到服务。
    10102 和 10103 运行状况服务模块 工作流无法解析数据源。 如果指定的性能计数器或实例在计算机上不存在,或者在工作区数据设置中未正确定义,则可能会发生此问题。 如果这是用户指定的性能计数器,请验证指定的信息是否遵循正确的格式,并在目标计算机上存在。
    26002 运行状况服务模块 工作流无法解析数据源。 如果指定的 Windows 事件日志在计算机上不存在,则可能会发生此问题。 如果预期不需要在计算机上此注册此事件日志,则可以安全忽略此错误。 但如果这是用户指定的事件日志,请验证指定的信息是否正确。

旧版 Microsoft Monitoring Agent 的已固定证书问题 - 中断性变更

根 CA 变更概述

自 2023 年 6 月 30 日起,Azure Log Analytics 后端将不再接受来自引用过时根证书的 Microsoft Monitoring Agent 的连接。 此类 Microsoft Monitoring Agent 是在 2020 年冬季版本(Azure Log Analytics 代理)和 SCOM 2019 UR3 (SCOM) 之前的旧版本。 任何版本、捆绑包:10.20.18053/扩展:1.0.18053.0 或更高版本,以及任何高于 SCOM 2019 UR3 的版本都不会有任何问题。 任何比这更旧的代理都将中断,并且不再工作并上传到 Azure Log Analytics。

具体变更内容有哪些?

作为持续提高各项 Azure 服务安全性的其中一项举措,Azure Log Analytics 将正式从 Baltimore CyberTrust CA 根证书切换到 DigiCert Global G2 CA 根证书。 如果操作系统中缺少新的 DigiCert Global G2 根证书,或者应用程序引用的是旧的 Baltimore CA 根证书,则此变更将影响与 Azure Log Analytics 的 TLS 通信。 也就是说,Azure Log Analytics 在旧的 CA 根证书停用后将不再接受来自使用此旧的 CA 根证书的 Microsoft Monitoring Agent 的连接。

解决方案产品

即使你并未亲自安装 Microsoft Monitoring Agent 也可能已经收到这则中断性变更通知。 这是因为各种 Azure 产品都会使用到 Microsoft Monitoring Agent。 如果你正在使用其中一款产品,则可能会受到影响,因为这些产品会使用到 Windows Log Analytics 代理。 对于具有以下链接的产品,可能会有特定说明,需要升级到最新的代理。

识别并修正中断的代理

如果部署的代理数量有限,强烈建议按照这些管理说明为每个节点升级代理。

我们为具有多个节点的部署编写了一个脚本,它会检测每个订阅任何受影响的中断 Microsoft Monitoring Agent,然后将它们升级到最新版本。 这些脚本需要按顺序运行,开始是 UpdateMMA.ps1,然后是 UpgradeMMA.ps1。 脚本可能需要运行一段时间,具体时间取决于电脑性能。 需要运行 PowerShell 7 或更高版本以避免发生超时。

UpdateMMA.ps1:此脚本将会检测订阅中的所有 VM,检查已安装的现有 Microsoft Monitoring Agent,然后生成一个包含需要升级的代理的 .csv 文件。

UpgradeMMA.ps1:此脚本将使用在 UpdateMMA.ps1 中生成的 .CSV 文件升级所有中断的 Microsoft Monitoring Agent。

这两个脚本可能都需要一些时间才能运行完。

# UpdateMMA.ps1
# This script is to be run per subscription, the customer has to set the az subscription before running this within the terminal scope.
# This script uses parallel processing, modify the $parallelThrottleLimit parameter to either increase or decrease the number of parallel processes
# PS> .\UpdateMMA.ps1 GetInventory
# The above command will generate a csv file with the details of VM's and VMSS that require MMA upgrade. 
# The customer can modify the csv by adding/removing rows if needed
# Update the MMA by running the script again and passing the csv file as parameter as shown below:
# PS> .\UpdateMMA.ps1 Upgrade
# If you don't want to check the inventory, then run the script wiht an additional -no-inventory-check
# PS> .\UpdateMMA.ps1 GetInventory & .\UpdateMMA.ps1 Upgrade


# This version of the script requires Powershell version >= 7 in order to improve performance via ForEach-Object -Parallel
# https://learn.microsoft.com/powershell/scripting/whats-new/migrating-from-windows-powershell-51-to-powershell-7?view=powershell-7.1
if ($PSVersionTable.PSVersion.Major -lt 7) 
{
    Write-Host "This script requires Powershell version 7 or newer to run. Please see https://learn.microsoft.com/powershell/scripting/whats-new/migrating-from-windows-powershell-51-to-powershell-7?view=powershell-7.1."
    exit 1
}

$parallelThrottleLimit = 16
$mmaFixVersion = [version]"10.20.18053.0"

function GetVmsWithMMAInstalled
{
    param(
        $fileName
    )

    $vmList = az vm list --show-details --query "[?powerState=='VM running'].{ResourceGroup:resourceGroup, VmName:name}" | ConvertFrom-Json
    
    if(!$vmList)
    {
        Write-Host "Cannot get the VM list, this script can only detect the running VM's"
        return
    }

    $vmsCount = $vmList.Length
    
    $vmParallelThrottleLimit = $parallelThrottleLimit
    if ($vmsCount -lt $vmParallelThrottleLimit) 
    {
        $vmParallelThrottleLimit = $vmsCount
    }

    if($vmsCount -eq 1)
    {
        $vmGroups += ,($vmList[0])
    }
    else
    {
        # split the vm's into batches to do parallel processing
        for ($i = 0; $i -lt $vmsCount; $i += $vmParallelThrottleLimit) 
        { 
            $vmGroups += , ($vmList[$i..($i + $vmParallelThrottleLimit - 1)]) 
        }
    }

    Write-Host "Detected $vmsCount Vm's running in this subscription."
    $hash = [hashtable]::Synchronized(@{})
    $hash.One = 1

    $vmGroups | Foreach-Object -ThrottleLimit $parallelThrottleLimit -Parallel {
        $len = $using:vmsCount
        $hash = $using:hash
        $_ | ForEach-Object {
            $percent = 100 * $hash.One++ / $len
            Write-Progress -Activity "Getting VM Inventory" -PercentComplete $percent
            $vmName = $_.VmName
            $resourceGroup = $_.ResourceGroup
            $responseJson = az vm run-command invoke --command-id RunPowerShellScript --name $vmName -g $resourceGroup --scripts '@UpgradeMMA.ps1' --parameters "functionName=GetMMAVersion" --output json | ConvertFrom-Json
            if($responseJson)
            {
                $mmaVersion = $responseJson.Value[0].message
                if ($mmaVersion) 
                {
                    $extensionName = az vm extension list -g $resourceGroup --vm-name $vmName --query "[?name == 'MicrosoftMonitoringAgent'].name" | ConvertFrom-Json
                    if ($extensionName) 
                    {
                        $installType = "Extension"
                    }
                    else 
                    {
                        $installType = "Installer"
                    }
                    $csvObj = New-Object -TypeName PSObject -Property @{
                        'Name'           = $vmName
                        'Resource_Group' = $resourceGroup
                        'Resource_Type'  = "VM"
                        'Install_Type'   = $installType
                        'Version'        = $mmaVersion
                        "Instance_Id"    = ""
                    }
                    $csvObj | Export-Csv $using:fileName -Append -Force
                } 
            } 
        }
    }
}

function GetVmssWithMMAInstalled
{
    param(
        $fileName
    )

    # get the vmss list which are successfully provisioned
    $vmssList = az vmss list --query "[?provisioningState=='Succeeded'].{ResourceGroup:resourceGroup, VmssName:name}" | ConvertFrom-Json   

    $vmssCount = $vmssList.Length
    Write-Host "Detected $vmssCount Vmss running in this subscription."
    $hash = [hashtable]::Synchronized(@{})
    $hash.One = 1

    $vmssList | Foreach-Object -ThrottleLimit $parallelThrottleLimit -Parallel {
        $len = $using:vmssCount
        $hash = $using:hash
        $percent = 100 * $hash.One++ / $len
        Write-Progress -Activity "Getting VMSS Inventory" -PercentComplete $percent
        $vmssName = $_.VmssName
        $resourceGroup = $_.ResourceGroup

        # get running vmss instance ids
        $vmssInstanceIds = az vmss list-instances --resource-group $resourceGroup --name $vmssName --expand instanceView --query "[?instanceView.statuses[1].displayStatus=='VM running'].instanceId" | ConvertFrom-Json
        if ($vmssInstanceIds.Length -gt 0) 
        {
            $isMMAExtensionInstalled = az vmss extension list -g $resourceGroup --vmss-name $vmssName --query "[?name == 'MicrosoftMonitoringAgent'].name" | ConvertFrom-Json
            if ($isMMAExtensionInstalled ) 
            {
                # check an instance in vmss, if it needs an MMA upgrade. Since the extension is installed at VMSS level, checking for bad version in 1 instance should be fine.
                $responseJson = az vmss run-command invoke --command-id RunPowerShellScript --name $vmssName -g $resourceGroup --instance-id $vmssInstanceIds[0] --scripts '@UpgradeMMA.ps1' --parameters "functionName=GetMMAVersion" --output json | ConvertFrom-Json
                $mmaVersion = $responseJson.Value[0].message
                if ($mmaVersion) 
                {
                    $csvObj = New-Object -TypeName PSObject -Property @{
                        'Name'           = $vmssName
                        'Resource_Group' = $resourceGroup
                        'Resource_Type'  = "VMSS"
                        'Install_Type'   = "Extension"
                        'Version'        = $mmaVersion
                        "Instance_Id"    = ""
                    }
                    $csvObj | Export-Csv $using:fileName -Append -Force
                }
            }
            else 
            {
                foreach ($instanceId in $vmssInstanceIds) 
                {
                    $responseJson = az vmss run-command invoke --command-id RunPowerShellScript --name $vmssName -g $resourceGroup --instance-id $instanceId --scripts '@UpgradeMMA.ps1' --parameters "functionName=GetMMAVersion" --output json | ConvertFrom-Json
                    $mmaVersion = $responseJson.Value[0].message
                    if ($mmaVersion) 
                    {
                        $csvObj = New-Object -TypeName PSObject -Property @{
                            'Name'           = $vmssName
                            'Resource_Group' = $resourceGroup
                            'Resource_Type'  = "VMSS"
                            'Install_Type'   = "Installer"
                            'Version'        = $mmaVersion
                            "Instance_Id"    = $instanceId
                        }
                        $csvObj | Export-Csv $using:fileName -Append -Force
                    }
                }
            }
        }      
    }
}

function Upgrade
{
    param(
        $fileName = "MMAInventory.csv"
    )
    Import-Csv $fileName | ForEach-Object -ThrottleLimit $parallelThrottleLimit -Parallel {
        $mmaVersion = [version]$_.Version
        if($mmaVersion -lt $using:mmaFixVersion)
        {
            if ($_.Install_Type -eq "Extension") 
            {
                if ($_.Resource_Type -eq "VMSS") 
                {
                    # if the extension is installed with a custom name, provide the name using the flag: --extension-instance-name <extension name>
                    az vmss extension set --name MicrosoftMonitoringAgent --publisher Microsoft.EnterpriseCloud.Monitoring --force-update --vmss-name $_.Name --resource-group $_.Resource_Group --no-wait --output none
                }
                else 
                {
                    # if the extension is installed with a custom name, provide the name using the flag: --extension-instance-name <extension name>
                    az vm extension set --name MicrosoftMonitoringAgent --publisher Microsoft.EnterpriseCloud.Monitoring --force-update --vm-name $_.Name --resource-group $_.Resource_Group --no-wait --output none
                }
            }
            else {
                if ($_.Resource_Type -eq "VMSS") 
                {
                    az vmss run-command invoke --command-id RunPowerShellScript --name $_.Name -g $_.Resource_Group --instance-id $_.Instance_Id --scripts '@UpgradeMMA.ps1' --parameters "functionName=UpgradeMMA" --output none
                }
                else 
                {
                    az vm run-command invoke --command-id RunPowerShellScript --name $_.Name -g $_.Resource_Group --scripts '@UpgradeMMA.ps1' --parameters "functionName=UpgradeMMA" --output none
                }
            }
        }
    }
}

function GetInventory
{
    param(
        $fileName = "MMAInventory.csv"
    )

    # create a new file 
    New-Item -Name $fileName -ItemType File -Force
    GetVmsWithMMAInstalled $fileName
    GetVmssWithMMAInstalled $fileName
}

switch ($args.Count)
{
    0 {
        Write-Host "The arguments provided are incorrect."
        Write-Host "To get the Inventory: Run the script as: PS> .\UpdateMMA.ps1 GetInventory"
        Write-Host "To update MMA from Inventory: Run the script as: PS> .\UpdateMMA.ps1 Upgrade"
        Write-Host "To do the both steps together: PS> .\UpdateMMA.ps1 GetInventory & .\UpdateMMA.ps1 Upgrade"
    }
    1 {
        $funcname = $args[0]
        Invoke-Expression "& $funcname"
    }
    2 {
        $funcname = $args[0]
        $funcargs = $args[1]
        Invoke-Expression "& $funcname $funcargs"
    }
}