任务失败了,日志却消失了?一次DolphinScheduler异常任务排查

引子:失败了,却没有一行运行日志

41d4ba706d466b185cd3ddb7d485e97a

生产调度中最让人困惑的场景之一,是任务页面明确显示“失败”,告警也已经生成,但任务的开始时间、执行主机、执行目录和日志路径全部为空。没有 Worker 日志,没有 YARN Application ID,更没有Spark Driver、Executor 或 MapReduce Container 日志。

面对这种现象,很多排查会本能地从 Worker、脚本路径、YARN 和网络连通性入手。但本案例最终证明:任务甚至还没有进入 Worker,故障发生在 Master 构造任务执行上下文的阶段。真正的根因不是脚本,也不是虚拟 IP,而是工作流没有绑定有效租户。

核心结论:当 task_instance 已是失败状态,但 host、start_time、log_path、execute_path 全部为 NULL 时,应优先排查 Master 提交与分发阶段,而不是继续寻找不存在的任务运行日志。

1.故障现场:前端失败、告警失败、日志为空

案例中的批量任务每天 14:15 触发。调度前端显示首个 Shell 节点失败,后续节点未执行。告警表中已经产生“scheduler failed”记录,但告警发送日志又出现“no bind plugin instance”。

222e69f9dc8bc30d0e28fa841710a745

从 t_ds_alert 反查最近一条失败告警:

SELECT id, title, content, alert_status, warning_type,
log, alertgroup_id, create_time,
process_instance_id
FROM t_ds_alert
ORDER BY create_time DESC
LIMIT 1;

告警内容里包含工作流实例 ID、任务 Code、任务名称、任务类型和失败状态,因此可以用它作为整条证据链的入口。但这里必须区分两个问题:任务执行失败与告警发送失败是相互独立的。

60dd0decfab5196a3e48d980d2df9280

排查原则:不要把“告警发送失败”当成任务失败原因。先定位任务在哪个生命周期阶段失败,再单独处理告警路由。

2.第一条证据:元数据表证明任务从未进入 Worker

通过告警中的 process_instance_id 和 taskCode,可以精确查询 t_ds_task_instance。结果显示任务实例已创建,状态为 6(FAILURE),但除了 submit_time 外,其余运行字段均为空。

定位任务实例的生命周期字段:

SELECT id AS task_instance_id, name, state, host,
submit_time, start_time, end_time,
log_path, execute_path, app_link
FROM t_ds_task_instance
WHERE process_instance_id = <process_instance_id>
AND task_code = <task_code>
ORDER BY id DESC;

8b8db0c502698bfe1067009675e5a802

132ed5348bde4bf279ecb8bb3a780222

到这里可以做出第一个关键判断:任务失败发生在 Worker 执行之前。继续 SSH 到 Worker 寻找日志、执行 yarn logs 或分析 Spark Executor 没有意义,因为对应的执行实体根本没有创建。

3.一个容易误判的支线:Master 为什么显示成另一个 IP

排查过程中,Master 节点的主 IP 与调度前端显示的 Master 地址不一致。SSH 到前端显示地址后,主机名和 Shell 提示符仍然显示主 IP,一度怀疑发生了错误路由或地址注册异常。

同时确认 SSH 连接四元组、主机名和全部网卡地址:

echo "$SSH_CONNECTION"
hostname -f
ip -br addr

ip -br addr 最终显示主 IP 以 /24 绑定,另一个地址以 /32 绑定在同一块 eth0 上。后者解析为EMR虚拟主机名,因此它不是另一台机器,而是同一节点的辅助/虚拟服务 IP。

132ed5348bde4bf279ecb8bb3a780222

仅在 Master 本机 SSH 虚拟 IP,只能证明本机可达。为了验证调度通信是否真的正常,必须从一台实际 Worker 节点测试 Master 端口。

从实际 Worker 节点测试两个 Master 地址:

nc -vz -w 3 <master-primary-ip> 5678
nc -vz -w 3 <master-virtual-ip> 5678

两个地址的 5678 端口都能连通;Master 心跳日志也持续成功写入 /nodes/master/:5678。至此可以排除 Master 注册地址不可达。

4d06aba445ddef0ca0e4fe68ce316a0f

经验:前端显示 IP 与主机主 IP 不一致,不等于地址错误。先确认是否为同机辅助 IP/VIP,再从真实通信对端验证业务端口;不要只做本机自连接测试。

4.决定性证据:Master 日志中的 Tenant does not exists

既然任务从未进入 Worker,真正的日志应当在 Master。使用工作流实例 ID、任务实例 ID、任务 Code 和任务名称检索 Master 日志后,故障链路被精确还原。

使用多个稳定标识关联同一次调度:

grep -R -nE 'WorkflowInstance-<id>|TaskInstance-<id>|<task_code>|<task_name>' /var/log/.../master-server/

故障发生在几毫秒内,尚未真正分发到 Worker:

14:15:00.650 Task is ready to dispatch to worker
14:15:00.651 Tenant does not exists
14:15:00.651 Task state changes to FAILURE
14:15:00.652 Get taskExecutionContext fail
14:15:00.653 Dispatch standby task failed
14:15:00.690 Workflow state changes to FAILURE

日志对象进一步给出决定性字段:processDefinition.tenantId=-1、tenantCode=null;processInstance.tenantId=-1、tenantCode=null、queue=null。Master 在构造 TaskExecutionContext 时必须确定执行租户,而当前工作流没有任何有效租户,因此任务直接失败。

根因:工作流定义与工作流实例的 tenantId 均为 -1,tenantCode 为空。Master 无法生成任务执行上下文,任务在分配 Worker 之前被置为 FAILURE。

5.为什么没有日志:理解 DolphinScheduler 任务生命周期

DolphinScheduler 的任务日志不是在 TaskInstance 写入数据库时产生,而是在 Master 成功构造上下文、选中 Worker 并由 Worker 创建执行目录后才会拥有 log_path。理解这个顺序,是处理“失败但无日志”的关键。

2750fce63a6addb663ea2f9ae66bb9cf

af2a6ac0e5eba6f0f6a3027fca3b0d76

这套字段判断还能快速指导日志采集策略:host 为空时查 Master;host 有值但 start_time 为空时查分发与 Worker 接收;log_path 有值时查 Worker 日志;出现 Application ID 后才进入 YARN 日志采集。

6.Tenant 到底承担什么角色

在 DolphinScheduler 中,Tenant 不仅是一个界面上的分类字段,它通常关联任务的运行身份与资源队列。Master 构造任务执行上下文时,需要从工作流定义、执行用户和租户表中解析出 tenantCode 与 queue。缺少这组信息,Master 无法安全地告诉 Worker“用谁的身份、在哪个队列执行”。

在启用了 Linux 用户切换、HDFS 权限、Kerberos 或 YARN 队列隔离的环境中,租户配置还会进一步影响系统用户、HDFS 目录、票据身份和资源队列。因此,修复租户后仍应验证对应 OS 用户和大数据平台权限。

7.用元数据确认租户关系

核对工作流、用户与租户的关联:

SELECT pd.id, pd.code, pd.name, pd.version,
pd.user_id, u.user_name,
pd.tenant_id AS workflow_tenant_id,
u.tenant_id AS user_tenant_id,
t.id AS matched_tenant_id,
t.tenant_code, t.queue_id
FROM t_ds_process_definition pd
LEFT JOIN t_ds_user u ON u.id = pd.user_id
LEFT JOIN t_ds_tenant t ON t.id = pd.tenant_id
WHERE pd.code = <process_definition_code>;

确认租户是否存在,并检查历史工作流版本:

SELECT * FROM t_ds_tenant ORDER BY id;

SELECT * FROM t_ds_user
WHERE id = <user_id>
OR user_name = '<user_name>';

SELECT code, name, version, tenant_id, release_state
FROM t_ds_process_definition_log
WHERE code = <process_definition_code>
ORDER BY version DESC;

本案例的预期异常结果是 workflow_tenant_id=-1,并且无法关联到 t_ds_tenant。若用户已有租户但工作流仍为-1,说明工作流创建、导入或版本迁移时没有正确继承租户;只修改用户并不一定会自动回填既有工作流。

8.修复步骤:不要直接 UPDATE 元数据库

推荐通过调度前端完成修复,避免绕过版本日志、缓存、权限关系与审计信息。标准处理步骤如下:

  1. 在安全中心的租户管理中创建或选择有效租户,并关联正确的YARN Queue。
  2. 在用户管理中为工作流负责人绑定该租户。
  3. 编辑故障工作流,重新选择有效租户并保存,生成新的工作流版本。
  4. 重新上线工作流与定时调度,确认最新 t_ds_process_definition.tenant_id 大于 0。
  5. 从新版本发起一次全新的手动运行,不直接恢复旧的失败实例。
  6. 任务进入 Worker 后,再验证 Linux 用户、脚本权限、HDFS/Kerberos 以及 YARN Queue 权限。

​为什么不重跑旧实例​:旧实例保存的是旧工作流版本快照,其中 tenantId 仍为 -1。直接恢复失败节点可能继续使用旧配置;发布新版本后应创建新实例验证。

9.修复后如何验收

修复后的验证不能只看页面变绿,还要确认任务确实跨过了原故障点。建议同时检查定义层、实例层和运行层。

  • 定义层:最新工作流版本 tenant_id 为有效正数,能够关联到 t_ds_tenant。
  • 实例层:新流程实例 tenantCode、queue 不再为空。
  • 分发层:TaskInstance.host 不为空,并能看到 Worker 接收记录。
  • 运行层:start_time、execute_path、log_path 正常生成。
  • 大数据作业层:若脚本提交 Spark/MR,能够提取 Application ID 并查询 YARN 日志。

5930632caebb30faa49a238ba437e0c8

结语:没有日志,本身就是日志

在调度系统里,“没有日志”并不意味着没有线索。相反,host、start_time、execute_path、log_path 和 app_link 同时为空,已经非常明确地告诉我们:任务尚未进入执行层。沿着这个事实回到 Master,几毫秒级的时间线最终指向 Tenant does not exists。

一次高质量排障,不是把所有组件都查一遍,而是用元数据划定边界,用日志建立时间线,用网络测试排除支线,再把任务失败与告警失败分别闭环。只有这样,才能从“经验式排查”走向可解释、可复用、可产品化的诊断体系。

一句话总结:TaskInstance 失败且所有运行字段为 NULL:先查 Master;本案例的直接根因是工作流 tenantId=-1,Master 无法构造 TaskExecutionContext。

posted @ 2026-09-21 21:45  海豚调度  阅读(11)  评论(0)    收藏  举报