0

PowerShell 钉钉机器人邮件告警双通道通知脚本:让服务器告警不再漏报

2026.05.31 | youres | 24次围观

服务器监控告警只靠邮件通知?很多时候运维人员根本看不到邮件,等到用户投诉才发现服务挂了。钉钉机器人 + 邮件双通道告警才是真正靠谱的方案——钉钉即时提醒 + 邮件留存记录,两条路同时走,告警再也不漏。

本文从实战出发,手把手教你用 PowerShell 写一个双通道告警脚本,覆盖钉钉机器人和邮件两种通知方式,附带防重复告警、告警恢复通知、多服务器批量监控等实用功能。

一、先搞清楚钉钉机器人Webhook怎么配

1.1 创建钉钉自定义机器人

打开钉钉群聊,进入群设置,找到智能群助手,添加机器人,选择自定义机器人。关键步骤:

  • 安全设置选加签:比IP白名单更灵活,适合服务器IP可能变化的场景
  • 记录 Webhook地址加签密钥(Secret),后面脚本要用
  • 消息类型选Markdown,方便格式化展示

1.2 加签算法原理

钉钉的加签机制很简单:用当前时间戳加上密钥生成 HMAC-SHA256 签名,拼到请求参数里。PowerShell 里用 [System.Security.Cryptography.HMACSHA256] 就能实现,不需要任何第三方模块。

二、PowerShell 钉钉发送函数

核心代码如下,直接可用:

function Send-DingTalk {
    param(
        [string]$Webhook,
        [string]$Secret,
        [string]$Title,
        [string]$Content
    )

    $timestamp = [Math]::Floor(
        (Get-Date).ToUniversalTime().Subtract(
            (Get-Date '1970-01-01')
        ).TotalMilliseconds
    )
    $stringToSign = "$timestamp`n$Secret"
    $hmac = [System.Security.Cryptography.HMACSHA256]::new(
        [System.Text.Encoding]::UTF8.GetBytes($Secret)
    )
    $signature = [Convert]::ToBase64String(
        $hmac.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($stringToSign))
    )

    $url = "$Webhook&timestamp=$timestamp&sign="
    $url += [System.Web.HttpUtility]::UrlEncode($signature)

    $body = @{
        msgtype = 'markdown'
        markdown = @{
            title = $Title
            text = $Content
        }
    } | ConvertTo-Json -Depth 5

    try {
        Invoke-RestMethod -Uri $url -Method POST `
            -ContentType 'application/json' `
            -Body ([System.Text.Encoding]::UTF8.GetBytes($body))
        $true
    } catch {
        Write-Error "$_"
        $false
    }
}

关键点:签名生成后必须URL编码,否则请求会被钉钉拒绝。PowerShell 5.1 内置的 [System.Web.HttpUtility]::UrlEncode() 直接可用。

三、邮件发送函数(双通道第二条路)

邮件通知用 System.Net.Mail 实现,比 Send-MailMessage 更稳定可控:

function Send-AlertEmail {
    param(
        [string]$SmtpServer,
        [int]$SmtpPort = 25,
        [string]$From,
        [string[]]$To,
        [string]$Subject,
        [string]$Body
    )

    try {
        $mail = New-Object System.Net.Mail.MailMessage(
            $From, ($To -join ',')
        )
        $mail.Subject = $Subject
        $mail.Body = $Body
        $mail.IsBodyHtml = $true
        $mail.Priority = [System.Net.Mail.MailPriority]::High

        $smtp = New-Object System.Net.Mail.SmtpClient(
            $SmtpServer, $SmtpPort
        )
        $smtp.Send($mail)
        $true
    } catch {
        Write-Error "$_"
        $false
    }
}

四、双通道告警主函数(含防重复机制)

把两个函数组合起来,加入防重复告警逻辑:

function Invoke-DualAlert {
    param(
        [string]$AlertName,
        [string]$Message,
        [string]$AlertType = 'Warning',
        [switch]$Recovery
    )

    # 60秒内同名告警不重复发送
    $cacheFile = "$env:TEMP\alert_cache_$AlertName.txt"
    if (-not $Recovery -and (Test-Path $cacheFile)) {
        $lastAlert = [datetime](Get-Content $cacheFile)
        if (((Get-Date) - $lastAlert).TotalSeconds -lt 60) { return }
    }

    # 钉钉Markdown消息
    $dingContent = "### $AlertName`n"
    $dingContent += "**Type**: $AlertType`n"
    $dingContent += "**Time**: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')`n"
    $dingContent += "**Detail**:`n$Message"

    # HTML邮件
    $mailBody = "<h3>$AlertName</h3>"
    $mailBody += "<p>Type: $AlertType<br>"
    $mailBody += "Time: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')</p>"
    $mailBody += "<p>$Message</p>"

    # 双通道发送
    Send-DingTalk -Webhook $webhookUrl -Secret $secret `
        -Title "$AlertName $AlertType" -Content $dingContent
    Send-AlertEmail -SmtpServer "smtp.example.com" `
        -From "alert@example.com" -To "ops@example.com" `
        -Subject "[$AlertType] $AlertName" -Body $mailBody

    # 更新缓存
    if (-not $Recovery) { Get-Date | Out-File $cacheFile -Encoding utf8 }
    else { Remove-Item $cacheFile -ErrorAction SilentlyContinue }
}

五、实战:CPU/内存/磁盘双通道告警

$webhookUrl = "https://oapi.dingtalk.com/robot/send?access_token=YOUR_TOKEN"
$secret = "SECxxxxxxxx"

# CPU检查
$cpu = (Get-CimInstance Win32_Processor).LoadPercentage
if ($cpu -gt 90) {
    Invoke-DualAlert -AlertName "CPU High" `
        -Message "CPU: $cpu%" -AlertType "Critical"
} elseif ($cpu -gt 80) {
    Invoke-DualAlert -AlertName "CPU Warning" `
        -Message "CPU: $cpu%" -AlertType "Warning"
}

# 内存检查
$os = Get-CimInstance Win32_OperatingSystem
$memPct = [math]::Round(($os.TotalVisibleMemorySize
    - $os.FreePhysicalMemory) / $os.TotalVisibleMemorySize * 100, 1)
if ($memPct -gt 90) {
    Invoke-DualAlert -AlertName "Memory High" `
        -Message "Memory: $memPct%" -AlertType "Critical"
}

# 磁盘检查
Get-CimInstance Win32_LogicalDisk -Filter "DriveType=3" | ForEach-Object {
    $freePct = [math]::Round($_.FreeSpace / $_.Size * 100, 1)
    if ($freePct -lt 10) {
        Invoke-DualAlert -AlertName "Disk $($_.DeviceId) Low" `
            -Message "Free: $freePct%, Avail: $([math]::Round($_.FreeSpace/1GB,1))GB" `
            -AlertType "Critical"
    }
}

六、多服务器批量告警

用 PowerShell Remoting 批量采集多台服务器数据:

$servers = @("192.168.1.10","192.168.1.11","192.168.1.12")

foreach ($srv in $servers) {
    try {
        $session = New-PSSession -ComputerName $srv -ErrorAction Stop
        $cpu = Invoke-Command -Session $session -ScriptBlock {
            (Get-CimInstance Win32_Processor).LoadPercentage
        }
        if ($cpu -gt 90) {
            Invoke-DualAlert -AlertName "${srv}_CPU" `
                -Message "CPU: ${cpu}%" -AlertType "Critical"
        }
        Remove-PSSession $session
    } catch {
        Invoke-DualAlert -AlertName "${srv}_Offline" `
            -Message "Cannot connect" -AlertType "Critical"
    }
}

七、配置Windows定时任务

$action = New-ScheduledTaskAction -Execute "pwsh.exe" `
    -Argument "-File C:\Scripts\DualAlert.ps1"
$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) `
    -RepetitionInterval (New-TimeSpan -Minutes 5)
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" `
    -LogonType ServiceAccount -RunLevel Highest
Register-ScheduledTask -TaskName "ServerDualAlert" `
    -Action $action -Trigger $trigger `
    -Principal $principal

八、5个踩坑经验总结

  1. 签名时效5分钟:确保服务器NTP同步,时间偏差会导致签名校验失败
  2. HTTPS和TLS:钉钉Webhook用HTTPS,SMTP用TLS时端口改587,注意证书问题
  3. 告警风暴:防重复间隔至少60秒,异常持续时避免每5分钟炸群
  4. WinRM防火墙:多服务器场景需开放5985/5986端口
  5. 邮件优先级:Critical告警设High优先级,配合邮件客户端过滤规则

总结

双通道告警的核心思路:即时通知靠钉钉,留存记录靠邮件。两者互补,单点故障时另一条路还能送达。上面的脚本可以直接复制使用,根据实际环境修改SMTP配置和钉钉Webhook地址即可。

相关文章推荐

版权声明

本文仅代表个人观点。
本文系AI辅助作者原创,未经许可,转载请保留原文链接。

发表评论