周末晚上十一点,手机连续震了四下。打开SkyWalking界面一看,支付服务响应时间连续三个周期超过阈值,告警规则正常触发,按理说应该立刻收到通知。但奇怪的是,我那个负责接收告警的自研平台里一条消息都没有,群里也静悄悄。登上OAP服务器翻日志,一眼就看到后头的HTTP状态码:401 Unauthorized。
那一刻我心里反而踏实了——告警机制本身没坏,真正出问题的是告警推送的最后一公里被鉴权卡住了。这类问题在SkyWalking运维里其实很典型,不是监控链路故障,而是Webhook推送目标对请求方的身份校验没通过。这篇文章就把这次完整的排查过程和处理方案记录下来,给以后遇到同款问题的朋友一个参考。
1. 预警里出现401,先搞清楚它卡在告警链路的哪一环
1.1 SkyWalking告警从触发到送达的完整链路
很多人一看到SkyWalking预警以为就是"监控系统发现异常然后弹出消息"这么简单,实际内部是一条清晰的数据流。Agent负责采集服务指标上报给OAP Server,OAP把指标聚合后存入存储层,同时告警规则引擎会按照alarm-settings.yml里的规则周期性检查指标是否越界。一旦某个指标持续越过阈值,规则引擎会生成一条Alarm消息,然后交给通知组件,通知组件根据配置的webhooks地址列表,把告警内容封装成JSON,通过HTTP POST逐条推送到外部目标。
这条路线上任何一环出问题,都会在运维层面表现出"预警"异常。而401这个状态码,含义非常明确:请求已经到达服务器,但服务器无法确认请求方的身份,所以拒绝处理。它和403有本质区别——403是"我知道你是谁,但你没权限",401是"我压根没认出来你是谁"。
1.2 401可能出现的三个环节
结合SkyWalking的实际架构,401大致出现在以下三个位置:
| 环节 | 方向 | 典型表现 | 影响范围 |
|---|---|---|---|
| Agent上报到OAP | Agent → OAP | OAP启用了认证但Agent没配token,日志里出现401,Agent数据不上报 | 全部监控数据缺失,预警可能完全不触发 |
| UI/API访问OAP | 浏览器/脚本 → OAP | 访问GraphQL接口或UI页面提示401,前端页面空白 | 控制台无法使用 |
| OAP推送Webhook | OAP → 告警目标 | 告警规则已触发,但外部告警平台返回401,消息没送到 | 告警通知丢失,业务异常未被及时感知 |
第三种情况最阴险,因为SkyWalking界面里能看到告警记录,你会以为一切正常,实际上接收方根本没收到。这次我遇到的就是这个环节。所以收到"预警异常"反馈时,第一步不是急着改参数,而是确定401到底发生在哪一段。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 从收到预警到命中根因:我的排查流程复盘
2.1 第一步:确认预警本身是否正常触发
我先把SkyWalking的告警页面打开,找到"告警列表",确认这条"支付服务响应时间过高"的告警确实在列表里,状态是已触发。这一步说明规则引擎判断环节是健康的,指标数据也正常,问题出在告警触发之后的推送环节。
接着我看了一下告警列表里的详情,记录了这条告警的ruleName、scope、name和alarmMessage,方便后续用curl模拟。这里有个经验之谈:不要只凭UI显示"已触发"就觉得万事大吉,一定要核对告警消息里的开始时间是否和当前时间对得上,避免看到的是旧数据。
2.2 第二步:翻OAP日志定位推送错误
SkyWalking OAP把日志写在logs/skywalking-oap-server.log,Webhook发送失败的信息一般会记录在告警通知相关的日志条目里。我的排查命令是这样的:
bash复制grep -n -i "webhook\|401\|unauthorized" logs/skywalking-oap-server.log | tail -50
很快就看到了关键日志,大意是Webhook notification [POST http://alert-platform:8080/alert/notify] got response code: 401。这里有个容易忽略的细节:OAP日志里明确记录了它尝试POST的完整URL,这个URL和我配置里的webhooks地址是否一致要重点核对。我有一次就是因为配置文件里地址末尾少了一个斜杠,导致请求走了完全不同的路由。
2.3 第三步:用curl完整复现OAP发起的请求
看到日志后,我复制了一条真实的告警JSON内容,在OAP服务器上用curl模拟同样请求,目的是确认这个401是稳定复现还是偶发:
bash复制curl -i -X POST 'http://alert-platform:8080/alert/notify' \
-H 'Content-Type: application/json' \
-d '{
"scopeId": 1,
"scope": "SERVICE",
"name": "pay-service",
"id0": "pay-service-id",
"id1": "",
"ruleName": "service_resp_time_rule",
"alarmMessage": "Response time of pay-service is more than 1000ms in 2 minutes of last 10 minutes",
"startTime": 1736500000000
}'
响应是HTTP/1.1 401 Unauthorized,下面还带了WWW-Authenticate响应头,这基本可以断定目标服务要求请求方带上某种认证凭据。到这里,问题范围已经从SkyWalking侧收窄到了"OAP发出的请求不被目标平台接受"。
2.4 第四步:在目标平台侧看访问日志
光在OAP侧复现还不够,我去告警目标平台的网关和应用日志里,按照来源IP和请求路径查了同一时间段的请求记录,看到返回401的具体原因是"缺少有效的Authorization头"。这一步很关键,因为401虽然都叫"未认证",但具体原因可能是token过期、Authorization头缺失、IP不在白名单里、签名校验失败等,完全不同的原因对应的修复手段完全不同。
2.5 第五步:用二分法锁定是"谁"返回的401
如果目标平台前面还有网关、Nginx之类的代理,还需要确认401到底是目标应用本身返回的,还是中间代理返回的。我的判断技巧是看响应头里的Server字段,Nginx返回的401通常带着Server: nginx,而应用自己返回的一般能看到具体的应用框架标识。如果中间隔了多层代理,还可以逐层跳过测试。这一步做扎实了,后面就不会走弯路。
3. 根因拆解:为什么SkyWalking的Webhook配置里不能随便加认证头
3.1 标准Webhook配置模型的限制
先看alarm-settings.yml里webhooks的标准写法:
yaml复制webhooks:
- http://alert-platform:8080/alert/notify
- http://alert-platform:8080/alert/notify2
就这么简单,一个URL列表,仅此而已。SkyWalking内置的WebhookNotifyHandler在发起HTTP POST请求时,默认只会带一些基础请求头比如Content-Type,它没有设计"为每个webhook单独配置自定义Header"的功能。所以在标准配置下,如果你的告警目标平台要求请求头里必须带Authorization: Bearer xxxx或Authorization: Basic base64串,那OAP发出去的请求必然被拒,返回401是一件顺理成章的事情。
这个限制我在处理过的多个项目里几乎都遇到过,它不是SkyWalking的bug,而是配置模型的问题。排查的时候不要一上来就怀疑SkyWalking坏了,先把目标平台的认证要求搞清楚。
3.2 为什么不能无脑在URL里拼Token
网上很多方案会建议"把token拼在URL后面":
yaml复制webhooks:
- http://alert-platform:8080/alert/notify?token=abc123
这种做法在某些平台确实有效,因为它们的服务端支持从query参数里取token。但如果目标平台只认请求头里的Authorization字段,那这么拼了也白搭。所以在选择方案之前,一定要确认目标平台到底支持哪种认证方式的注入,用curl分别测试是最快的办法:
bash复制# 测试Basic Auth
curl -i -u 'username:password' -X POST 'http://alert-platform:8080/alert/notify' ...
# 测试Bearer Token
curl -i -H 'Authorization: Bearer abc123' -X POST 'http://alert-platform:8080/alert/notify' ...
# 测试URL参数
curl -i -X POST 'http://alert-platform:8080/alert/notify?token=abc123' ...
哪种返回200,就说明目标平台认哪种方式,后面的配置就往那个方向调整。
3.3 我的经验:这类问题九成是"配置模型不匹配",不是监控系统故障
处理过多次401预警之后,我的判断标准已经比较固定了。SkyWalking本身只是按照配置把告警消息POST出去,它不会自己去"认证"目标平台,401是目标平台返回的响应,含义是"你的请求没带对身份信息"。真正要解决的是让OAP发出的请求能通过目标平台的鉴权,这需要从两边同时看:OAP侧能配置什么,目标平台侧能接受什么。
有一回,目标平台要求请求头携带动态计算的签名,且签名参数跟时间戳绑定,几分钟就过期。SkyWalking标准Webhook根本没法在每次请求前动态计算签名,我用Nginx转发加固定Header的办法也不行,因为签名是动态的。最后只能协调目标平台单独开放了一个基于固定Token的内网接收端点,才把问题解决。所以接到401预警时,心里要有一根弦:必要的时候需要跟接收方协调接口定义,而不是单方面改配置。
4. 不同场景下的401修复方案与配置示例
4.1 场景一:目标平台支持URL参数Token
这是最简单的场景。如果确认目标平台的接口能通过?token=xxx识别身份,直接把token拼在webhooks地址后面就行:
yaml复制webhooks:
- http://alert-platform:8080/alert/notify?token=abc123
修改后需要重启OAP Server或者确认配置是否支持热加载。不同版本的SkyWalking对配置文件热加载的支持程度不一样,我在8.x版本上通常是重启OAP服务,等启动完成后,再用curl把"带token的地址"完整请求一遍,确认返回200。
4.2 场景二:目标平台要求Authorization头,但SkyWalking侧无法直接配置
这种场景最常遇到。既然SkyWalking标准Webhook不带自定义Header,那就让请求先经过一层Nginx,由Nginx在转发时统一补上认证头。我用的配置示例是这样的:
nginx复制map $http_x_skywalking $auth_header {
default "";
"1" "Bearer abc123";
}
server {
listen 18080;
location /alert/notify {
proxy_set_header Authorization $auth_header;
proxy_pass http://alert-platform:8080;
}
}
然后SkyWalking的webhooks地址指向Nginx:
yaml复制webhooks:
- http://127.0.0.1:18080/alert/notify
这里有个细节:我在请求里让SkyWalking带一个自定义头X-SkyWalking: 1,然后用map指令根据这个头来决定是否注入Authorization。这样做的好处是Nginx的这个端口只对带标记的请求注入认证头,避免把认证凭据暴露给无关请求。实际使用中要注意,不要用Nginx的if指令去设置头,容易踩到if is evil的坑,用map是最稳妥的。
4.3 场景三:目标平台可以配置IP白名单或单独开放匿名端点
如果告警目标平台是自研的,或者你有权限调整它的鉴权逻辑,最省事的方案是让OAP服务器的出口IP加入白名单,或者单独开放一个不需要认证的/alert/notify端点。很多自研告警平台之所以要认证,是为了防止外部随便往里塞假告警。如果OAP和告警平台都在内网,且网络隔离做得足够好,为Webhook单独开放匿名端点是可以接受的。
我的一个项目就是这么干的:告警平台新增了一个/internal/alert/notify路径,只在内网监听,外部无法访问,OAP的webhooks直接指向这个路径,401问题彻底消失。这个方案比加Header更干净,但前提是网络边界要清晰,别把这个端口暴露到公网。
4.4 场景四:OAP自身接口或UI访问返回401
这种场景会表现为整个SkyWalking界面打开都是401,或GraphQL接口调用失败,跟"预警推送401"的表现不同,但也需要一并说明。常见原因有两个:一是OAP配置了SW_AUTHENTICATION环境变量,Agent和UI的请求如果没有带上对应的token,就会被OAP拒绝;二是前端Nginx上配置了auth_basic,浏览器访问时弹窗要求用户名密码。
对第一种,检查Agent配置里的agent.authentication是否和OAP的SW_AUTHENTICATION一致,同时测试GraphQL接口可以这样验证:
bash复制curl -i -H "Authentication: your-token" http://oap-server:12800/graphql
对第二种,Nginx的auth_basic会拦截所有访问,需要确认是不是预期行为,并确认浏览器端是否缓存了错误的凭据。
4.5 各场景修复方式汇总
| 场景 | 判断要点 | 推荐修复方式 |
|---|---|---|
| 目标支持URL Token | curl带?token=返回200 |
webhooks直接拼token |
| 目标要求Authorization头 | curl带Bearer/Basic返回200 | Nginx中转补Header |
| 目标可开匿名端点 | 有权限改目标平台 | 新增内网专用匿名接口 |
| UI/API整体401 | OAP启用了认证或Nginx加了auth_basic | 核对各端token配置 |
5. 修完之后的两件事与避坑清单
5.1 确认恢复通知也能正常送达
很多人在验证告警推送时只测试了"触发"消息,忽略了"恢复"消息。SkyWalking的告警恢复通知(消息里包含[Resolve]标记)同样走webhooks配置。如果只验证了触发消息,恢复消息仍然可能401,导致告警恢复无人知晓,值班同学会一直认为系统还在异常。
我的做法是修完配置后,人为把告警规则阈值调低再调高,让告警先触发再恢复,观察两条消息是否都到达目标平台。这个测试很关键,尤其是对使用自研告警平台的团队,因为很多平台对触发和恢复消息的鉴权逻辑是独立的。
5.2 把Webhook通道做成"可自检"的
处理完这次401后,我给告警通道加了一个定时自检脚本。思路很简单:用cron定时向webhooks地址发送一条测试告警,检查HTTP返回码,如果连续几次不是200就触发本地告警。脚本核心就一条curl命令:
bash复制curl -s -o /dev/null -w "%{http_code}" -X POST \
'http://alert-platform:8080/alert/notify?token=abc123' \
-H 'Content-Type: application/json' \
-d '{"scope":"SERVICE","name":"self-check","alarmMessage":"channel health check"}'
这样以后目标平台token过期、网络策略变更导致401时,第一时间能主动发现,而不是等真正出故障时才发现告警根本没送达。这个自检脚本我建议每个用SkyWalking告警的团队都配上,成本极低,收益很高。
5.3 常见401问题速查清单
根据我这几次排障经验,整理了下面这张表:
| 症状 | 可能原因 | 处理方向 |
|---|---|---|
| 单个webhook地址返回401 | 该平台token过期或地址写错 | 重新生成token,核对URL |
| 所有webhook地址都401 | OAP到目标的网络出口被网关统一拦截 | 检查网关策略,确认来源IP是否在白名单 |
| 偶发401,时好时坏 | 目标平台要求动态签名,token在短期内失效 | 改用固定token的内网端点,或扩展告警callback |
| UI打开页面就401 | Nginx配置了auth_basic,或OAP启用了认证 | 核对Nginx配置和SW_AUTHENTICATION |
| Agent不上报数据且日志有401 | Agent和OAP的认证token不一致 | 重新配置agent.authentication |
5.4 一个让我印象深刻的教训
最后分享一个真实教训。早前处理过一个客户的401问题,我花了大半天排查SkyWalking侧配置,反复改webhooks地址、加query参数、试Nginx加Header,全部无效。最后发现目标平台要求的认证头不是静态的,而是基于时间戳和请求体动态计算的签名,任何静态配置都无法通过验证。后来和接收方开发对齐后才确认,他们的设计初衷就是防止静态token泄露。
那之后我学乖了:遇到webhook返回401,第一件事不是自己折腾SkyWalking配置,而是先跟目标平台的负责人确认三件事——你们的接口支持哪几种认证方式,token是静态还是动态,SkyWalking默认不带自定义Header的情况下你们希望怎么配合。把这三件事问清楚,再动手改配置,效率能提升不止一倍。
