一次DolphinScheduler失败调度的排查
问题现象
DolphinScheduler任务调度执行大面积失败

且无法重跑/

排查要点
伪集群形态部署,各容器运行状态、是否健康运行。
docker-dolphinscheduler-*-1这组容器(api / master / worker / alert / postgresql / zookeeper 各自独立容器

- 容器日志排查失败信息
容器失败日志查看
worker
docker logs docker-dolphinscheduler-worker-1 --since 24h | grep -iE "error|fail|exception"
里面有错误:
/tmp/dolphinscheduler/exec/process/default/146212983125760/158234529507105_2/25887/31016/25887_31016.sh: line 4: ssh: command not found
master
docker logs docker-dolphinscheduler-master-1 --since 24h | grep -iE "datasource|connection|fail"
根因
验证:(安装openssh-client)
docker exec -u 0 docker-dolphinscheduler-worker-1 bash -c 'apt-get update && apt-get install -y openssh-client'
安装完成后alert可以正常工作。
ssh仍然失败,但原因不再是command not found,而是**Host key verification failed.**相关错误,确认此前问题为手工在容器内安装ssh并配置相关认证信息,但由于未将环境依赖修改同步到镜像中,随着容器重建这些修改被丢失。
总结问题因果链
- worker 容器镜像本来就不含
openssh-client; - 但早某个时候,
openssh-client被人手动装进了运行中的 worker 容器(装在容器的可写层里,不在镜像里)→ 那段时间用ssh的任务正常; - 你改
daemon.json调日志配置 → 改 log-driver / log-opts 对"已存在的容器"不生效,必须重建容器才能应用; - 重建容器 = 从镜像重新拉起 → 可写层被丢弃 → 手动装的
openssh-client没了; - 于是:脚本里用
ssh的任务全部command not found(exit 127),不用ssh的任务照常 → “大面积但非全部失败”。
措施
临时方案(兼容性,让历史任务快速正常运行):worker容器添加对target节点的信息,提供公钥给target节点
#!/usr/bin/env bash
#
# ds_ssh_setup.sh — DolphinScheduler worker 容器 → 目标主机 无密 SSH 一键配置
#
# 背景:DS 的 Shell 任务以 `sudo -u default -i` 在 worker 容器内执行,
# 通过 `ssh <target>` 触发目标主机上的脚本。本脚本把“装客户端 / 生成密钥 /
# 写 known_hosts / 分发公钥 / 写别名 / 测试”整合成幂等流程。
#
# 运行位置:Docker 宿主机(即 docker compose 所在机器)。
#
# 用法:
# ./ds_ssh_setup.sh # 按下方 TARGETS 批量配置
# ./ds_ssh_setup.sh add --host IP [--port 22] [--user root] [--alias NAME] # 新增单台主机
# ./ds_ssh_setup.sh bake # 打印“烤进镜像”的 Dockerfile 片段
#
# 安全:脚本只做“新增/追加”,不删除任何文件或容器内容。公钥分发仅需目标机密码一次。
set -euo pipefail
WORKER_CONTAINER="${WORKER_CONTAINER:-docker-dolphinscheduler-worker-1}"
DEFAULT_USER="default"
# 内置目标列表(批量模式用): HOST:PORT:USER:ALIAS
# 新增一台主机:直接往这里加一行,或改用 `./ds_ssh_setup.sh add ...`
TARGETS=(
# "192.168.1.10:22:root:node10"
# "db.example.com:2222:researcher:dbhost"
)
# ---- 容器内执行助手 ----
dexec() { docker exec -u "$DEFAULT_USER" "$WORKER_CONTAINER" bash -c "$1"; }
dexec_t() { docker exec -it -u "$DEFAULT_USER" "$WORKER_CONTAINER" bash -c "$1"; }
dexec_root(){ docker exec -u 0 "$WORKER_CONTAINER" bash -c "$1"; }
log(){ printf '\033[36m[ds-ssh]\033[0m %s\n' "$*"; }
warn(){ printf '\033[33m[WARN]\033[0m %s\n' "$*"; }
# 1) 容器内 openssh-client + psmisc
ensure_client(){
log "检查容器内 SSH 客户端 ..."
if dexec 'command -v ssh >/dev/null 2>&1'; then
log "openssh-client 已存在"
else
log "安装 openssh-client ..."
dexec_root 'apt-get update -qq && apt-get install -y openssh-client'
fi
if dexec 'command -v pstree >/dev/null 2>&1'; then
log "psmisc(pstree) 已存在"
else
log "安装 psmisc(让 DS 能正常 kill 进程树,避免 137 清理失败)..."
dexec_root 'apt-get install -y psmisc'
fi
}
# 2) 生成 default 用户密钥(若缺失),确保属主为 default
ensure_key(){
log "确保 default 用户密钥 ..."
dexec 'mkdir -p ~/.ssh && chmod 700 ~/.ssh'
if dexec 'test -f ~/.ssh/id_ed25519'; then
log "密钥已存在"
else
log "生成 ed25519 密钥(无口令)..."
dexec 'ssh-keygen -t ed25519 -N "" -f ~/.ssh/id_ed25519'
# 用数字 ID,避免容器内 default 主组不叫 default 导致 chown 报错
dexec_root 'chown -R "$(id -u default):$(id -g default)" /home/default/.ssh'
fi
}
# 3) known_hosts(防 MITM,避免首次连接交互卡住)
add_known_host(){
local host="$1" port="$2"
log "把 $host 加入 known_hosts ..."
dexec "ssh-keyscan -p $port -H $host >> ~/.ssh/known_hosts 2>/dev/null; sort -u ~/.ssh/known_hosts -o ~/.ssh/known_hosts"
}
# 4) 部署公钥到目标主机 authorized_keys
deploy_key(){
local host="$1" port="$2" user="$3"
local pub
pub="$(dexec 'cat ~/.ssh/id_ed25519.pub')"
if dexec "ssh -o BatchMode=yes -p $port $user@$host 'true' 2>/dev/null"; then
log "$user@$host 已免密,跳过"
return 0
fi
log "部署公钥到 $user@$host ..."
# 需要目标机密码:优先用 ssh-copy-id(需交互 TTY);非交互环境则回退到手动命令
if [ -t 0 ] && dexec 'command -v ssh-copy-id >/dev/null 2>&1'; then
log "(需要该主机 $user 的密码,仅此一次)"
dexec_t "ssh-copy-id -p $port -o StrictHostKeyChecking=accept-new $user@$host"
else
warn "非交互环境或容器内无 ssh-copy-id,请手动在目标主机 $user@$host 执行:"
echo " mkdir -p ~/.ssh && chmod 700 ~/.ssh"
echo " echo '$pub' >> ~/.ssh/ authorized_keys && chmod 600 ~/.ssh/authorized_keys"
fi
}
# 5) 写 SSH config 别名(DS 任务可直接 ssh <alias>)
write_config(){
local alias="$1" host="$2" port="$3" user="$4"
local cfg="Host $alias\n HostName $host\n Port $port\n User $user\n IdentityFile ~/.ssh/id_ed25519\n StrictHostKeyChecking yes\n"
dexec "printf '$cfg' >> ~/.ssh/config; chmod 600 ~/.ssh/config"
log "已写入 SSH 别名:$alias -> $user@$host:$port"
}
# 6) 连通性测试
test_conn(){
local alias="$1" user="$2" host="$3" port="$4"
if dexec "ssh -o BatchMode=yes -p $port $user@$host 'echo CONNECTED-\$(whoami)@\$(hostname)' 2>/dev/null"; then
log "OK $alias 连通成功"
else
warn "FAIL $alias 仍无法免密,请检查目标 authorized_keys / 防火墙 / 端口"
fi
}
setup_one(){
local host="$1" port="${2:-22}" user="${3:-root}" alias="${4:-$host}"
add_known_host "$host" "$port"
deploy_key "$host" "$port" "$user"
write_config "$alias" "$host" "$port" "$user"
test_conn "$alias" "$user" "$host" "$port"
}
bake(){
cat <<'EOF'
# 把以下内容并入 worker 自定义 Dockerfile(基于官方镜像),避免重建容器后丢失:
# ---------------------------------------------------------------
# FROM apache/dolphinscheduler-worker-server:3.4.2
# USER root
# RUN apt-get update \
# && apt-get install -y --no-install-recommends openssh-client psmisc \
# && mkdir -p /home/default/.ssh \
# && ssh-keygen -t ed25519 -N '' -f /home/default/.ssh/id_ed25519 \
# && chown -R "$(id -u default):$(id -g default)" /home/default/.ssh \
# && chmod 700 /home/default/.ssh
# USER default
# ---------------------------------------------------------------
# 注意:密钥会打进镜像,属敏感信息;生产环境建议改用 secret / 挂载卷,或在镜像外分发。
EOF
}
main(){
[ -z "$(command -v docker)" ] && { echo "未找到 docker,请在 Docker 宿主机运行"; exit 1; }
case "${1:-}" in
bake)
bake; exit 0 ;;
esac
ensure_client
ensure_key
if [ "$#" -ge 1 ] && [ "$1" = "add" ]; then
shift
local_host="" local_port="22" local_user="root" local_alias=""
while [ "$#" -gt 0 ]; do
case "$1" in
--host|-h) local_host="$2"; shift 2 ;;
--port|-p) local_port="$2"; shift 2 ;;
--user|-u) local_user="$2"; shift 2 ;;
--alias|-a) local_alias="$2"; shift 2 ;;
*) echo "未知参数: $1(用法: add --host HOST [--port 22] [--user root] [--alias NAME])"; exit 2 ;;
esac
done
[ -z "$local_host" ] && { echo "用法: add --host HOST [--port 22] [--user root] [--alias NAME]"; exit 2; }
[[ "$local_port" =~ ^[0-9]+$ ]] || { echo "端口必须是数字: $local_port"; exit 2; }
setup_one "$local_host" "$local_port" "$local_user" "${local_alias:-$local_host}"
elif [ "$#" -ge 1 ]; then
echo "未知参数:$*"; exit 2
else
if [ "${#TARGETS[@]}" -eq 0 ]; then
warn "TARGETS 为空:请用 add 子命令,或编辑脚本里的 TARGETS 数组"
exit 0
fi
for t in "${TARGETS[@]}"; do
IFS=: read -r h p u a <<< "$t"
setup_one "$h" "$p" "$u" "$a"
done
fi
echo
echo "============================================================"
echo "⚠️ 容器是临时层:openssh-client / psmisc / 密钥 仅存在于运行容器,"
echo " docker compose 重建后会丢失。请用上面 bake 子命令烤进镜像,"
echo " 或把 /home/default/.ssh 挂成卷持久化。"
}
main "$@"
使用方法:
新增单台主机
bash ds_ssh_setup.sh add --host 10.0.0.5 --user user --port 22 --alias node5
批量修改
编辑脚本顶部的 TARGETS 数组,每行格式 HOST:PORT:USER:ALIAS,然后直接运行:
bash ds_ssh_setup.sh
长期优化
- 在目标机器上部署新worker(注册zookeeper)执行任务而非通过ssh调度
附录说明
dolphinscheduler伪集群模式组件明细
| 组件 | 进程/服务 | 伪集群实例数 | 默认端口 | 关键职责 | 依赖 | 该组件挂掉的故障影响 |
|---|---|---|---|---|---|---|
| ApiServer | api-server | 1(可多开做负载) | 12345 | 提供 UI/外部 REST API;工作流 CRUD、调度管理、权限、登录 | 元数据库 | 无法在页面操作/提交,但已在跑的任务不受影响 |
| MasterServer | master-server | ≥1(伪集群常 1,可多) | 5678 | DAG 切分、任务派发、容错重试、补数;通过 ZK 选主,仅 leader 干活 | ZK + DB | 新任务无法调度;运行中任务由 Worker 续跑,但 failover 无人接管 |
| WorkerServer | worker-server | ≥1(伪集群常 1,可多分组) | 1234 | 接收并执行具体任务(Shell/SQL/Spark…),回报状态与日志 | ZK + DB | 该 Worker 上的任务中断;Master 经 ZK 感知后 failover 到其他 Worker |
| AlertServer | alert-server | 1 | 50053 | 按告警规则发通知(邮件/钉钉/Webhook) | DB | 告警发不出去,但调度与任务执行不受影响 |
| ZooKeeper | zk 单节点 | 1 | 2181 | 服务注册发现、心跳、Master 选主、任务队列 | 无 | 全集群失联:Master/Worker 无法协调,调度瘫痪 |
| 元数据库 (Postgres/MySQL) | 单库 | 1 | 5432 | 存所有定义/实例/历史/用户 | 无 | 全集群瘫痪:各组件起不来或任务全失败(即你遇到的根因) |
| StandaloneServer(对照) | 单进程 | 1 | 12345 | 把上述全部揉进一个进程 | 内嵌 | 一停全停,不用于生产 |
- UI → ApiServer:用户提交/触发工作流 → ApiServer 把实例写入元数据库;
- Master 经 ZK 感知调度事件 → 从 DB 读实例、把 DAG 拆任务、按依赖 RPC 派给 Worker;
- Worker 执行任务、写日志、把状态回报 Master 并落 DB;
- 失败 → AlertServer 读 DB 告警规则发通知;Worker 挂 → Master 经 ZK 感知后 failover。
配置文件
dolphinscheduler 3.2.2
application-master.yaml
master:
exec-threads: 50 # 流程实例并发降一半
pre-exec-threads: 5
dispatch-task-number: 1 # 每次只派 1 个任务,降低瞬时压力
server-load-protection:
enabled: true
max-system-cpu-usage-percentage-thresholds: 0.85 # 临时放宽到 85%
max-system-memory-usage-percentage-thresholds: 0.85