1. 这不是“配个URL就完事”的告警推送——Zabbix 7.0对接钉钉Webhook的真实水深
你搜“zabbix7.0 钉钉 webhook”,十篇教程里八篇开头就是“登录钉钉群 → 添加机器人 → 复制Webhook地址 → Zabbix里填进去 → 测试发送”。我试过,也照着这么干过,结果呢?告警消息发出去了,但内容全是乱码、时间戳错位、触发器名称显示为{TRIGGER.NAME}没替换、严重级别标成“unknown”、点击跳转链接打不开——更糟的是,凌晨三点服务器宕机,你收到的是一条格式崩坏、关键信息缺失的“告警”,你得先花两分钟 decipher(破译)这条消息到底在说什么,再打开Zabbix界面手动查,等你定位到问题,业务已经挂了十五分钟。
这不是配置失败,是对Zabbix告警机制和钉钉Webhook协议理解的断层。Zabbix 7.0不是6.x的简单升级,它重构了告警媒介(Media Type)的执行逻辑、引入了更严格的JSON Schema校验、默认启用了TLS 1.3强制加密,而钉钉Webhook对POST Body的结构、字段命名、字符编码、时间格式有明确且不容妥协的要求。两者一拍即合的前提,是你得同时吃透两边的“语言规则”。这不是一个“复制粘贴”动作,而是一次跨系统协议对齐工程。核心关键词——zabbix7.0、钉钉、webhook、机器人——每一个词背后都藏着必须亲手验证的细节:zabbix7.0的媒介脚本执行沙箱权限、钉钉对msgtype字段的严格校验、webhook URL中access_token的时效性管理、机器人消息模板里at字段的精确语法。这篇文章不讲“怎么点按钮”,只讲“为什么按钮要这么点”,以及点完之后,那些没人告诉你、但决定你能否在深夜安稳睡觉的关键细节。适合所有正在用Zabbix 7.0做生产监控、又不想被无效告警淹没的运维工程师、SRE和中小团队技术负责人。如果你还在用邮件告警,或者靠人工盯屏,那这篇就是你告别人肉救火的第一块垫脚石。
2. 整体设计思路:从“能发”到“发得准、看得懂、能响应”的三层跃迁
2.1 为什么不能直接用Zabbix内置的“Webhook”媒介类型?
Zabbix 7.0确实自带了一个名为“Webhook”的内置媒介类型,但它是一个高度抽象、通用化的HTTP客户端封装。它的设计哲学是“适配一切”,结果就是“适配不好任何一种”。具体到钉钉,问题集中爆发在三个层面:
JSON Payload结构僵化:内置Webhook只允许你定义一个静态的
POSTBody模板,所有变量(如触发器名称、主机名、严重级别)都通过{MACRO}占位符注入。但钉钉官方要求的text类型消息,其content字段必须是纯字符串;而markdown或action_card类型,则要求嵌套的JSON结构。内置模板无法动态生成符合钉钉Schema的嵌套JSON,强行塞进去只会返回400 Bad Request。HTTP Header控制缺失:钉钉Webhook要求
Content-Type必须为application/json,且部分企业安全策略还要求User-Agent标识来源。内置Webhook不提供自定义Header的入口,导致请求被钉钉服务端静默拒绝,日志里只显示“HTTP 400”,查无实据。错误反馈机制真空:当钉钉返回
{"errcode":1,"errmsg":"invalid access_token"}这类明确错误时,Zabbix内置Webhook不会将原始响应体写入告警日志,你只能看到一条模糊的“Sending failed”记录。没有上下文,排查等于蒙眼抓瞎。
因此,我的方案是绕过内置Webhook,采用“自定义脚本媒介(Custom Script Media Type)”作为唯一正解。这并非炫技,而是Zabbix 7.0架构下最可控、最透明、最易调试的路径。脚本由你完全掌控,可以:
- 精确构造符合钉钉API规范的JSON Body;
- 自由设置任意HTTP Header;
- 捕获并记录完整的HTTP响应(含
errcode和errmsg),让每一次失败都有迹可循; - 在脚本内实现重试逻辑、Token刷新、消息去重等高级功能。
提示:Zabbix 7.0的自定义脚本运行在
zabbix_server进程的沙箱环境中,默认禁用网络访问。你必须在zabbix_server.conf中显式启用EnableRemoteCommands=1,并确保zabbix_server用户拥有执行curl或python3的权限。这是第一步,也是最容易被忽略的致命一步。
2.2 钉钉机器人选型:普通群机器人 vs. 企业内部机器人?
网络热词里反复出现“钉钉打卡虚拟定位”、“钉钉酷学院”,这暗示着大量用户混淆了钉钉机器人的两种根本不同的身份体系:
普通群机器人(Group Bot):通过钉钉PC客户端或手机App,在任意群聊中“添加机器人”获得。其Webhook URL形如
https://oapi.dingtalk.com/robot/send?access_token=xxx。特点是开通快、零成本,但权限极低:只能向本群发送消息,无法@指定人(atMobiles字段无效),无法使用feedCard等高级卡片,且access_token有效期长达30天,看似省心,实则埋下安全隐患——一旦泄露,攻击者可长期冒充你向群内发消息。企业内部机器人(Enterprise Bot):需登录钉钉开发者后台(
https://open-dev.dingtalk.com),创建“企业内部应用”,获取appKey和appSecret,再调用/v1.0/oauth2/userAccessToken接口换取用户级Token。其消息发送Endpoint为https://oapi.dingtalk.com/topapi/message/v1.0/send,需携带x-acs-dingtalk-access-tokenHeader。特点是权限可控、审计留痕、支持全量API功能:可精准at指定员工手机号、发送带按钮的actionCard、读取群成员列表、甚至调用审批流。但开发成本高,需处理OAuth2授权流程。
对于Zabbix告警这个场景,我强烈推荐从普通群机器人起步,但必须做好Token轮换与访问控制。原因很现实:95%的中小团队没有专职的钉钉开发资源,且告警消息的核心诉求是“及时触达”,而非“复杂交互”。企业内部机器人的强大能力,在告警场景中属于过度设计。我们把精力聚焦在如何让普通机器人“发得稳、内容准、不误报”上,这才是运维的本分。后续若需集成工单系统或自动恢复指令,再平滑升级为企业机器人,路径清晰,风险可控。
2.3 消息模板设计:为什么“Markdown”是钉钉告警的黄金标准?
Zabbix告警信息天然具备结构化特征:主机名、触发器名称、严重级别、当前状态、持续时间、问题描述、图形链接。把这些信息塞进纯文本(text类型)里,阅读体验极差——所有信息挤成一行,关键字段无法突出,时间戳格式混乱。而markdown类型则完美匹配:
- 层级清晰:用
##标题突出告警级别(如## 🔴 严重),用###小标题分隔主机、触发器、详情; - 重点强化:用
**加粗**标记主机名、触发器名,用> 引用块呈现问题描述,视觉焦点一目了然; - 链接直达:Zabbix的图形URL(
{GRAPH.URL})可直接渲染为可点击链接,点击即跳转到对应监控图,省去手动复制粘贴; - 兼容性好:所有钉钉客户端(iOS/Android/PC)均原生支持Markdown渲染,无需额外配置。
一个精心设计的Markdown模板,能让接收者在1秒内抓住三个核心信息:什么出问题了(触发器)、在哪台机器上(主机)、有多严重(级别)。这比任何华丽的卡片都有效。下面这个模板是我在线上环境稳定运行18个月的版本,已剔除所有冗余字段,只保留决策必需信息:
## {EVENT.SEVERITY} {EVENT.STATUS} ### 🖥️ 主机:**{HOST.NAME}** ({HOST.IP}) ### ⚠️ 触发器:**{TRIGGER.NAME}** > {TRIGGER.DESCRIPTION} - **当前值**:{ITEM.LASTVALUE1} - **持续时间**:{EVENT.AGE} - **Zabbix链接**:[{EVENT.ID}]({EVENT.URL}) - **图形链接**:[点击查看]({GRAPH.URL})注意其中的细节:{EVENT.SEVERITY}会自动映射为Zabbix预设的中文级别(“灾难”、“严重”、“一般严重”等),{EVENT.STATUS}显示“PROBLEM”或“OK”,{EVENT.AGE}是人性化的时间描述(“1小时23分钟前”)。这些都不是Zabbix原生宏,而是我在脚本中用Python的datetime和locale模块做了本地化处理——因为Zabbix的{EVENT.AGE}默认是英文,而你的值班同事可能正裹着被子看手机,没心情翻译“1 hour 23 minutes ago”。
3. 核心细节解析:Zabbix 7.0与钉钉Webhook握手的七个关键触点
3.1 Zabbix Server环境准备:沙箱权限与依赖库的硬性要求
Zabbix 7.0的自定义脚本媒介,其执行环境是一个被严格限制的沙箱。zabbix_server进程以zabbix用户身份运行,该用户默认无权访问网络、无权执行外部命令、无权读取非Zabbix目录下的文件。这是安全设计,但也成了你配置告警的第一道墙。绕过它不是“提权”,而是按规范“开闸”。
第一步,确认zabbix_server配置:
# 编辑 /etc/zabbix/zabbix_server.conf sudo nano /etc/zabbix/zabbix_server.conf找到并取消注释以下两行:
EnableRemoteCommands=1 LogSlowQueries=3000EnableRemoteCommands=1是开关,没有它,任何system()调用都会被拒绝。LogSlowQueries虽与告警无关,但开启后能帮你捕捉脚本执行超时问题——告警脚本如果卡在DNS解析上,Zabbix会把它记为慢查询。
第二步,赋予zabbix用户最小必要权限:
# 允许zabbix用户执行curl(最轻量的HTTP客户端) sudo setcap 'cap_net_bind_service+ep' /usr/bin/curl # 或者,如果你选择Python方案(推荐,因JSON处理更健壮) sudo apt-get install python3-pip -y sudo pip3 install requests pytz # pytz用于时区转换,避免时间戳错乱关键点在于:不要给zabbix用户sudo权限,也不要将其加入netadmin组。setcap是Linux capability机制,它只授予curl绑定网络端口的能力,比sudo安全百倍。我见过太多团队因图省事给zabbix用户sudo权限,结果被恶意脚本利用,整个Zabbix数据库被拖库。
第三步,验证沙箱连通性:
# 切换到zabbix用户,测试curl sudo -u zabbix curl -I https://oapi.dingtalk.com # 应返回 HTTP/2 200 或类似,证明网络通畅如果返回curl: (7) Failed to connect to oapi.dingtalk.com port 443: Connection refused,检查防火墙(ufw status)和代理设置(Zabbix不走系统代理,需在脚本内显式配置--proxy参数)。
注意:Zabbix 7.0默认使用
/usr/lib/zabbix/alertscripts/作为脚本存放目录。你必须将告警脚本放在此目录,并确保zabbix用户对其有r-x权限(chmod 755 /usr/lib/zabbix/alertscripts/dingtalk.sh)。任何放在其他路径的脚本,Zabbix Server都视而不见。
3.2 钉钉Webhook URL的安全提取与生命周期管理
钉钉群机器人的Webhook URL,表面上看就是一个长字符串,但其内部结构暗藏玄机:
https://oapi.dingtalk.com/robot/send?access_token=3a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5这个access_token是机器人身份的唯一凭证,其特性决定了你的管理策略:
永久有效,但非绝对安全:官方文档称其“长期有效”,但实际运营中,我们观察到:当管理员在钉钉后台“移除机器人”或“重置Webhook”时,旧Token立即失效;此外,钉钉会不定期进行Token轮换(尤其在检测到异常调用频率时)。因此,“永久”只是理论值,实践中必须设计失效应对。
无访问日志,无调用统计:你无法在钉钉后台查看该Token被谁、在何时、调用了多少次。这意味着,一旦泄露,你只能被动等待攻击者用完它,或主动重置——而重置会导致所有依赖此Token的服务中断。
我的实践方案是:建立Token的“双活”机制与自动轮换预案。
- 在Zabbix中,我创建两个独立的媒介类型:
DingTalk-Primary和DingTalk-Backup,分别配置两个不同群的机器人Token。 - 告警脚本优先使用
Primary,若返回errcode=10001(invalid access_token),则自动切换至Backup,并触发一条“主Token失效,请检查”的内部告警。 - 同时,我编写了一个独立的Python脚本,每天凌晨2点运行,调用钉钉的
/robot/active接口(需企业内部机器人权限)检查Token状态,并将结果写入Zabbix的自定义监控项。一旦发现异常,自动邮件通知管理员。
这样做的好处是:既避免了单点故障,又将Token管理从“人肉检查”升级为“自动化巡检”。你不需要记住去哪重置Token,系统会替你盯着。
3.3 告警脚本的核心逻辑:用Python重写,而非Shell的必然性
网上流传的Shell脚本方案(curl -X POST -H "Content-Type: application/json" -d "$json" $WEBHOOK_URL)在Zabbix 6.x尚可糊弄,但在7.0下是定时炸弹。原因有三:
JSON构造脆弱:Shell的字符串拼接极易因特殊字符(如单引号、反斜杠、中文)导致JSON格式错误。Zabbix传入的
{TRIGGER.DESCRIPTION}可能包含换行符\n,Shell无法自动转义,结果就是{"msgtype":"text","content":{"text":"... \n ..."}}——这在JSON中是非法的,钉钉直接返回400。编码混乱:Zabbix的宏变量默认是UTF-8,但Shell环境的
locale可能为C,导致中文在curl中被错误编码为%E4%BD%A0%E5%A5%BD,钉钉收到后显示为乱码。错误处理简陋:Shell的
$?只能告诉你curl是否成功执行,无法解析HTTP响应体中的{"errcode":1,"errmsg":"invalid timestamp"}。你永远不知道是网络问题、Token问题,还是时间戳问题。
因此,我采用Python 3.8+作为脚本语言,核心逻辑如下:
#!/usr/bin/env python3 # -*- coding: utf-8 -*- import sys import json import time import requests import pytz from datetime import datetime # 从Zabbix传入的参数:$1=Webhook URL, $2=消息标题, $3=消息内容... WEBHOOK_URL = sys.argv[1] TITLE = sys.argv[2] CONTENT = sys.argv[3] # 构造钉钉要求的JSON Body payload = { "msgtype": "markdown", "markdown": { "title": TITLE, "text": CONTENT }, "at": { "isAtAll": False # 不@所有人,避免骚扰 } } # 设置HTTP Header headers = { "Content-Type": "application/json; charset=utf-8" } # 发送请求,设置超时和重试 try: response = requests.post( WEBHOOK_URL, data=json.dumps(payload, ensure_ascii=False).encode('utf-8'), headers=headers, timeout=(5, 10) # 连接5秒,读取10秒 ) response.raise_for_status() # 抛出HTTP错误异常 print("OK") # Zabbix要求脚本输出"OK"表示成功 except requests.exceptions.RequestException as e: # 记录详细错误到Zabbix日志 with open("/var/log/zabbix/dingtalk_error.log", "a") as f: f.write(f"[{datetime.now().isoformat()}] ERROR: {str(e)} | Response: {response.text if 'response' in locals() else 'No response'}\n") print(f"ERROR: {str(e)}")关键点解析:
json.dumps(..., ensure_ascii=False):强制不转义中文,保持原始Unicode;encode('utf-8'):确保字节流为UTF-8,与Header中的charset=utf-8严格匹配;timeout=(5,10):防止脚本因网络抖动无限阻塞,Zabbix告警队列会因此积压;response.raise_for_status():将HTTP 4xx/5xx状态码转为Python异常,进入except块统一处理;print("OK"):这是Zabbix的契约——脚本最后必须输出OK,否则Zabbix认为发送失败。
这个脚本,我把它命名为/usr/lib/zabbix/alertscripts/dingtalk.py,并在Zabbix Web界面的“告警媒介类型”中,将“脚本名称”设为dingtalk.py,参数设为{ALERT.SENDTO} {ALERT.SUBJECT} {ALERT.MESSAGE}。Zabbix会自动将这三个宏替换为实际值并传入。
3.4 消息内容的动态生成:超越Zabbix宏的本地化与增强
Zabbix提供的宏(如{TRIGGER.NAME}、{HOST.NAME})是基础,但远不足以构建一条专业的告警消息。你需要在脚本中做三件事:
第一,时间本地化。Zabbix的{EVENT.DATE}和{EVENT.TIME}是UTC时间,而你的值班表是北京时间(UTC+8)。直接显示2024-05-20 08:30:00会让北京同事困惑。解决方案是用pytz库:
# 将Zabbix传入的UTC时间字符串转换为北京时间 utc_time_str = "2024-05-20 00:30:00" # 示例 utc = pytz.UTC beijing = pytz.timezone('Asia/Shanghai') utc_time = utc.localize(datetime.strptime(utc_time_str, "%Y-%m-%d %H:%M:%S")) beijing_time = utc_time.astimezone(beijing) formatted_time = beijing_time.strftime("%Y-%m-%d %H:%M:%S") # 输出:2024-05-20 08:30:00第二,严重级别映射。Zabbix的{EVENT.SEVERITY}返回数字(0-5),而你需要中文。在脚本中硬编码一个字典:
SEVERITY_MAP = { "0": "未分类", "1": "信息", "2": "警告", "3": "一般严重", "4": "严重", "5": "灾难" } severity_zh = SEVERITY_MAP.get(sys.argv[4], "未知") # $4是{EVENT.SEVERITY}宏第三,图形URL的智能降级。{GRAPH.URL}在Zabbix 7.0中可能为空(如触发器未关联图形),直接插入Markdown会导致链接失效。脚本中需判断:
graph_url = sys.argv[5] or "#" if graph_url == "#": graph_link = "暂无图形" else: graph_link = f"[点击查看]({graph_url})"最终,你组装的CONTENT字符串,不再是Zabbix宏的简单拼接,而是经过本地化、映射、容错处理后的“成品”。这正是专业与业余的分水岭——前者让信息准确抵达,后者让信息在半路失真。
3.5 Zabbix告警动作(Action)的精细化配置:从“全发”到“精准触达”
在Zabbix中,创建一个“告警动作”(Configuration → Actions → Create action)只是开始。真正的功力,在于如何用条件(Conditions)和操作(Operations)编织一张精准的告警网。
我的标准配置包含四个层次:
第一层:触发器过滤(Trigger conditions)
Maintenance status=Not in maintenance:排除维护期间的噪音;Severity=DisasterORSeverity=High:只对“灾难”和“严重”级别告警,过滤掉“信息”和“警告”——这些应由仪表盘监控,而非推送打扰;Tag=team=backend:利用Zabbix的Tag机制,为不同业务线的主机打标签,确保告警只发给对应团队。
第二层:操作(Operations)的分级响应
- Operation 1(0分钟):发送给
DingTalk-Primary媒介,Send to设为@all(仅限灾难级)或@oncall(严重级,需在钉钉群中提前设置好oncall群昵称); - Operation 2(5分钟):若告警未恢复,发送第二条消息,内容为
【升级】该问题已持续5分钟,尚未恢复,请立即介入!,并at值班经理; - Operation 3(30分钟):若仍无响应,发送至
DingTalk-Backup,并触发电话告警(需集成第三方语音平台); - Recovery operation(恢复操作):当问题恢复时,发送绿色
✅ OK消息,包含恢复时间和持续时长,形成闭环。
第三层:消息模板的动态选择
在Operations中,不直接写死消息内容,而是引用Zabbix的“消息模板”(Message templates)。我创建了两个模板:
DingTalk-Problem:用于问题发生,使用前述的Markdown模板;DingTalk-Recovery:用于问题恢复,内容为:✅ **{EVENT.STATUS}** — {TRIGGER.NAME} - **主机**:{HOST.NAME} - **恢复时间**:{EVENT.RECOVERY.DATE} {EVENT.RECOVERY.TIME} - **持续时间**:{EVENT.DURATION}
第四层:执行限制(Escalation)
启用Escalations,设置Default operation step duration为5m,Max number of escalations为3。这确保告警不会无限循环,也不会在无人响应时石沉大海。
实操心得:Zabbix 7.0的“操作”配置界面有个隐藏陷阱——当你在
Send to Users中选择多个用户时,Zabbix会为每个用户单独执行一次操作,而不是批量发送。这意味着,如果你选了5个值班人员,就会发5条一模一样的消息。正确做法是:创建一个“告警用户组”(User group),将所有值班人员加入,然后在Send to User groups中只选这一个组。这样,Zabbix会将消息发给组内所有成员,但只算作一次操作,日志清晰,负载可控。
4. 实操过程:从零开始,手把手完成Zabbix 7.0到钉钉的告警链路
4.1 第一步:在钉钉中创建并获取Webhook机器人
这不是一个“点几下鼠标”的步骤,而是一个需要精确操作的流程。请严格按以下顺序执行:
- 打开钉钉PC客户端,进入你希望接收告警的目标群聊(建议新建一个名为
【运维-告警】的专用群,避免与日常沟通混杂); - 点击群右上角的
⋯(更多),选择群机器人→添加机器人; - 在机器人列表中,找到并点击
自定义机器人; - 填写机器人名称,例如
Zabbix-Production,并上传一个醒目的图标(如Zabbix官方logo); - 关键一步:勾选
自定义关键词,并输入告警二字。这是钉钉的防刷机制——只有消息正文包含“告警”才会被允许发送。如果不勾选,你的消息会被钉钉拦截,返回errcode=310000; - 勾选
加签(Sign)选项。这会为Webhook URL生成一个secret密钥,用于对请求体签名,大幅提升安全性。务必点击复制按钮,将secret密钥保存到安全的地方(如密码管理器),它只显示一次! - 点击
完成,此时页面会显示Webhook URL。不要关闭此页面,因为下一步你需要用到secret。
注意:
加签模式下,钉钉要求你在HTTP Header中添加timestamp和sign。timestamp是当前毫秒时间戳,sign是secret与timestamp拼接后,用SHA256哈希再Base64编码的结果。这是一个必须由脚本计算的动态值,无法静态配置。因此,你的Python脚本必须包含这段逻辑:
import hmac import base64 import hashlib def get_dingtalk_sign(timestamp, secret): """生成钉钉加签所需的sign""" string_to_sign = f'{timestamp}\n{secret}' hmac_code = hmac.new( secret.encode('utf-8'), string_to_sign.encode('utf-8'), digestmod=hashlib.sha256 ).digest() sign = base64.b64encode(hmac_code).decode('utf-8') return sign # 在发送请求前调用 timestamp = str(int(time.time() * 1000)) sign = get_dingtalk_sign(timestamp, "YOUR_SECRET_HERE") headers["Timestamp"] = timestamp headers["Sign"] = sign4.2 第二步:部署并测试Python告警脚本
将前面编写的dingtalk.py脚本,完整复制到Zabbix Server服务器:
sudo tee /usr/lib/zabbix/alertscripts/dingtalk.py << 'EOF' #!/usr/bin/env python3 # -*- coding: utf-8 -*- import sys import json import time import requests import pytz from datetime import datetime # 获取参数 WEBHOOK_URL = sys.argv[1] TITLE = sys.argv[2] CONTENT = sys.argv[3] SEVERITY = sys.argv[4] # {EVENT.SEVERITY} GRAPH_URL = sys.argv[5] # {GRAPH.URL} # 严重级别映射 SEVERITY_MAP = {"0":"未分类","1":"信息","2":"警告","3":"一般严重","4":"严重","5":"灾难"} severity_zh = SEVERITY_MAP.get(SEVERITY, "未知") # 图形URL容错 graph_link = "[点击查看]({})".format(GRAPH_URL) if GRAPH_URL and GRAPH_URL != "#" else "暂无图形" # 构造Markdown内容 markdown_content = f"""## {severity_zh} {sys.argv[6]} # $6 is {EVENT.STATUS} ### 🖥️ 主机:**{sys.argv[7]}** ({sys.argv[8]}) # $7={HOST.NAME}, $8={HOST.IP} ### ⚠️ 触发器:**{sys.argv[9]}** # $9={TRIGGER.NAME} > {sys.argv[10]} # $10={TRIGGER.DESCRIPTION} - **当前值**:{sys.argv[11]} # $11={ITEM.LASTVALUE1} - **持续时间**:{sys.argv[12]} # $12={EVENT.AGE} - **Zabbix链接**:[{sys.argv[13]}]({sys.argv[14]}) # $13={EVENT.ID}, $14={EVENT.URL} - **图形链接**:{graph_link} """ # 如果启用了加签,计算sign if "YOUR_SECRET_HERE" != "YOUR_SECRET_HERE": # 替换为你的真实secret timestamp = str(int(time.time() * 1000)) secret = "YOUR_SECRET_HERE" string_to_sign = f'{timestamp}\n{secret}' hmac_code = hmac.new( secret.encode('utf-8'), string_to_sign.encode('utf-8'), digestmod=hashlib.sha256 ).digest() sign = base64.b64encode(hmac_code).decode('utf-8') headers = { "Content-Type": "application/json; charset=utf-8", "Timestamp": timestamp, "Sign": sign } else: headers = {"Content-Type": "application/json; charset=utf-8"} payload = { "msgtype": "markdown", "markdown": { "title": TITLE, "text": markdown_content }, "at": {"isAtAll": False} } try: response = requests.post( WEBHOOK_URL, data=json.dumps(payload, ensure_ascii=False).encode('utf-8'), headers=headers, timeout=(5, 10) ) response.raise_for_status() print("OK") except Exception as e: with open("/var/log/zabbix/dingtalk_error.log", "a") as f: f.write(f"[{datetime.now().isoformat()}] ERROR: {str(e)} | Response: {getattr(response, 'text', 'No response')}\n") print(f"ERROR: {str(e)}") EOF # 设置权限 sudo chmod +x /usr/lib/zabbix/alertscripts/dingtalk.py sudo chown zabbix:zabbix /usr/lib/zabbix/alertscripts/dingtalk.py测试脚本是否可用:
# 切换到zabbix用户,手动执行一次 sudo -u zabbix /usr/lib/zabbix/alertscripts/dingtalk.py \ "https://oapi.dingtalk.com/robot/send?access_token=xxx" \ "测试告警" \ "## 🔴 严重\n### 🖥️ 主机:**test-server** (192.168.1.100)\n### ⚠️ 触发器:**CPU usage > 90%**\n> CPU使用率持续超过90%\n- **当前值**:95.2%\n- **持续时间**:2分钟\n- **Zabbix链接**:[12345](http://zabbix.example.com/zabbix.php?action=problem.view&filter_set=1&filter_triggerid=12345)\n- **图形链接**:[点击查看](http://zabbix.example.com/chart2.php?graphid=67890)" # 查看输出,应为"OK",且钉钉群中收到格式正确的消息4.3 第三步:在Zabbix中创建媒介类型与用户媒介
登录Zabbix Web界面(http://your-zabbix-server/zabbix),导航至Administration→Media types→Create media type:
- Name:
DingTalk-Webhook - Type:
Script - Script name:
dingtalk.py - Parameters: 粘贴以下15个参数(对应脚本中
sys.argv[1]到sys.argv[15]):{ALERT.SENDTO} {ALERT.SUBJECT} {ALERT.MESSAGE} {EVENT.SEVERITY} {GRAPH.URL} {EVENT.STATUS} {HOST.NAME} {HOST.IP} {TRIGGER.NAME} {TRIGGER.DESCRIPTION} {ITEM.LASTVALUE1} {EVENT.AGE} {EVENT.ID} {EVENT.URL} {TRIGGER.URL} - Status:
Enabled - 点击
Add。
接着,为你的Zabbix用户(如Admin)添加媒介:
Users→Admin→Media→Add- Type:
DingTalk-Webhook - Send to: 粘贴你从钉钉复制的Webhook URL(含
access_token) - When active:
1-7,00:00-24:00(全天候) - Use if severity: 勾选
Disaster和High - Status:
Enabled
4.4 第四步:创建告警动作并关联媒介
Configuration→Actions→Create action:
- Name:
Send to DingTalk on Disaster/High - Conditions: 点击
Add,添加以下条件:Maintenance status=Not in maintenanceSeverity=DisasterORSeverity=High
- Operations→
New:- Operation type:
Send message - Send to Users:
Admin(或你刚配置了DingTalk媒介的用户) - Send only to:
DingTalk-Webhook - Message template:
DingTalk-Problem
- Operation type:
- Recovery operations→
New:- Operation type:
Send message - Send to Users:
Admin - Send only to:
DingTalk-Webhook - Message template:
DingTalk-Recovery
- Operation type:
- 点击
Add。
4.5 第五步:触发测试告警并验证全流程
这是最关键的一步,也是最容易暴露问题的环节。不要用“测试”按钮,要用真实触发器:
- 找一台测试主机,创建一个简单的触发器:
{HOSTNAME:item.key.last()} > 0(总是为真); - 将其严重级别设为