Docker Spark 远程连接 Master 失败?一文解决!
2025-05-05 18:34:06
解决本地 Docker Spark 连接远程 Docker Spark Master 难题
咱们在用 Spark 做开发的时候,有时候会遇到这么个情况:本地的 Spark Docker 容器,想连到远端虚拟机上的 Spark Master Docker 容器,结果就连不上。这感觉就像打电话,明明拨了号,对方就是不接。今天咱就来捋捋这事儿,看看咋回事,怎么解决。
问题在哪儿?
直接点说,就是我本地起了个 Bitnami Spark Docker 容器,想把 PySpark 脚本用 cluster
模式提交到远程虚拟机(比如 IP 是 11.119.9.1
)上的 Spark Master Docker(端口 7077
)。我本地机器跟远程机器之间通着 VPN。
执行 spark-submit
后,去看 Spark Master Web UI 的日志,冒出来这么个错:
java.io.IOException: Failed to connect to spark-master/11.119.8.1:35369
这错误瞅着像是 Worker 节点连不上 Master(或者更准确地说,是连不上 Driver)。
日志里还有这么一段,是 Executor 启动命令和后续的报错信息:
Spark Executor Command: "/opt/bitnami/java/bin/java" "-cp" "/opt/bitnami/spark/conf/:/opt/bitnami/spark/jars/*"
"-Xmx1024M" "-Dspark.driver.port=35369" ...
"org.apache.spark.executor.CoarseGrainedExecutorBackend"
"--driver-url" "spark://CoarseGrainedScheduler@spark-master:35369" "--executor-id" "17" "--hostname" "11.119.8.3"
...
Caused by: java.io.IOException: Failed to connect to spark-master/11.119.8.1:35369
at org.apache.spark.network.client.TransportClientFactory.createClient(TransportClientFactory.java:294)
...
Caused by: io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: spark-master/11.119.8.1:35369
从错误日志看,11.119.8.1
是我本地机器通过 VPN 在远程网络里的 IP 地址,35369
是 Spark Driver 用的一个端口。问题就是,远程的 Spark Executor (运行在比如 11.119.8.3
的 Worker 上) 要反向连接我本地 Docker 容器里跑的 Driver 程序,但是连不上。
虽然用 telnet 11.119.8.1 7077
从远程 Master 容器测试到我本地机器是通的(注意,这里测试的是 7077,不是出问题的 35369 端口),而且远程 Master 和 Worker 之间通信也没问题(同事在 Master Docker 里直接 spark-submit
是好的),但这并不能说明我本地 Docker 里的 Driver 就能被远程 Worker 上的 Executor 顺畅访问。
为啥会连不上?
这事儿吧,主要跟 Spark 的通信机制和 Docker 的网络环境有关系,尤其是跨了 VPN 这种网络。
-
Spark 通信流 :
当你从本地提交一个 Spark 应用(尤其是用类似client
模式,或者cluster
模式但 Driver 仍然被告知在提交端)时,Driver 程序可能就在你本地机器的 Docker 容器里跑。Spark Master 收到任务后,会通知 Worker 节点启动 Executor。这些 Executor 启动后,需要反向连接到 Driver 程序来接收任务、汇报状态。 -
IP 地址和端口问题 :
- Driver 地址通告 :你本地 Docker 容器里的 Driver 启动时,会告诉 Spark Master 自己的 IP 地址和端口。如果它通告的是一个 Worker 节点无法访问的地址(比如 Docker 内部的私有 IP,或者
localhost
),那 Worker 上的 Executor 自然就懵了,找不到组织。 - 端口暴露 :即使 Driver 通告了正确的 IP(比如你本地机器在 VPN 网络中的 IP
11.119.8.1
),它监听的端口(比如35369
)也必须从 Docker 容器映射到宿主机上,并且这个宿主机的 IP:端口 组合必须能被远程 Worker 访问。
- Driver 地址通告 :你本地 Docker 容器里的 Driver 启动时,会告诉 Spark Master 自己的 IP 地址和端口。如果它通告的是一个 Worker 节点无法访问的地址(比如 Docker 内部的私有 IP,或者
-
Docker 网络隔离 :
Docker 容器默认有自己的网络空间。容器内部看到的localhost
和 IP 地址,跟宿主机以及其他远程机器看到的可能完全不一样。 -
VPN 和防火墙 :
VPN 虽然打通了网络,但中间的防火墙规则或者 VPN 本身的策略也可能阻止特定端口的通信。
看错误日志 Failed to connect to spark-master/11.119.8.1:35369
,这里的 spark-master
可能是 Driver 节点注册到 Master 时用的名字,被解析成了 11.119.8.1
。关键在于,Worker 节点上的 Executor 拿到了这个 11.119.8.1:35369
地址,然后尝试连接,结果被拒(Connection refused
)。这通常意味着:
- 要么
11.119.8.1
这个 IP 地址虽然是你的 VPN IP,但 Worker 节点就是连不上这个 IP 的35369
端口。 - 要么
35369
端口在你本地 Docker 容器里是监听了,但没有正确地从容器暴露到你本地宿主机的11.119.8.1
这个 IP 上。
咋整?来试试这几招
针对上面的原因,咱们一步步来排查和配置。
一、 指定 Driver 的可访问地址和端口
当你的 spark-submit
是从本地 Docker 容器发出,并且 Driver 程序也在这个容器里运行时,你需要明确告诉 Spark Driver,它应该通告哪个 IP 地址给 Master 和 Worker。
原理和作用
spark.driver.host
: 这个配置项用来设定 Driver 监听以及对外通告的 IP 地址或主机名。这个地址必须是远程 Worker 节点可以访问到的。spark.driver.port
: 这个配置项用来固定 Driver 监听的 RPC 端口。默认情况下,这个端口是随机的。固定下来方便我们做 Docker 端口映射和防火墙配置。spark.driver.bindAddress
: Driver 实际绑定的网络接口地址。通常,设置了spark.driver.host
就不太需要单独设置这个,除非你有更复杂的网络需求。spark.driver.host
更常用,因为它同时影响绑定和通告。
操作步骤
-
确定本地 Docker 客户端的 VPN IP :就是错误日志里提到的
11.119.8.1
。这个 IP 必须是远程 Spark Worker 节点可以路由到的。 -
修改
spark-submit
命令 :
在你的spark-submit
命令里加入以下配置:# 在你的本地 Docker 容器内执行 $SPARK_HOME/bin/spark-submit \ --master spark://11.119.9.1:7077 \ --deploy-mode client \ # 即使你提到 cluster 模式,错误日志表明 Driver 行为像 client 模式 --conf spark.driver.host=11.119.8.1 \ --conf spark.driver.port=35369 \ # 使用日志中出现的端口,或自定义一个未被占用的 # 可能还需要配置 BlockManager 端口,如果它也随机且导致问题 --conf spark.driver.blockManager.port=36000 \ # 举个例子,选择一个端口 # ... 其他你的应用参数 ... your_pyspark_script.py
注意 :用户提到的是
cluster
模式,但日志行为非常像client
模式的 Driver-Executor 通信(Executor 连接回提交任务的客户端)。如果严格要求 Driver 在远端集群(Master/Worker 所在的网络)启动,则配置会有所不同,通常不需要本地 IP。但基于当前日志,我们先按 Driver 在本地运行来解决。如果确实需要 Driver 在远端 Worker 上启动(真正的cluster
模式),那本地的spark.driver.host
配置就不重要了,关键是 Master 和 Worker 间的配置。但这里的错误信息是 Executor 连11.119.8.1
(本地VPN IP),说明 Driver 还是在本地。 -
启动本地 Docker 容器时暴露端口 :
当你启动运行spark-submit
的那个本地 Docker 容器时,必须把 Driver 要用的端口(包括spark.driver.port
和spark.driver.blockManager.port
以及 Spark UI 端口4040
)映射到宿主机上,并且绑定到 VPN 的 IP 地址。# 假设你在本地机器(不是容器内)启动这个用于提交任务的 Spark Docker 容器 docker run -it --rm \ -p 11.119.8.1:35369:35369 \ # Driver RPC 端口 -p 11.119.8.1:36000:36000 \ # Driver BlockManager 端口 -p 11.119.8.1:4040:4040 \ # Spark Driver UI 端口 # 其他必要的卷挂载、环境变量等,比如 Spark 配置、脚本 -v /path/to/your/scripts:/scripts \ bitnami/spark /bin/bash # 进入容器后,再执行上面的 spark-submit 命令
如果 VPN IP
11.119.8.1
就是你本地宿主机的一个网络接口 IP,可以省略 IP 地址部分,Docker 会默认绑定到所有接口 (0.0.0.0
):docker run -it --rm \ -p 35369:35369 \ -p 36000:36000 \ -p 4040:4040 \ ... bitnami/spark /bin/bash
安全建议
- 确保你本地机器的防火墙(比如
ufw
,firewalld
, 或 Windows Firewall)允许来自远程 Spark Worker IP 地址段对这些暴露端口(35369
,36000
,4040
)的入站连接。 - VPN 配置本身也可能有限制,需要检查。
二、 确认 Spark Master 的可访问地址
远程 Spark Master 也需要正确配置其对外服务的地址,以便你的本地 spark-submit
客户端和远程 Worker 都能找到它。
原理和作用
Bitnami Spark Docker 镜像通常使用环境变量 SPARK_MASTER_HOST
来设置 Master 绑定的主机名或 IP。如果 Master 在 Docker 容器内,这个地址也需要是从容器外部(包括本地客户端通过 VPN,以及其他 Worker 容器)可以访问的。
操作步骤
在启动远程 Spark Master Docker 容器时,设置 SPARK_MASTER_HOST
为其所在虚拟机的 IP 地址 11.119.9.1
。
# 在远程虚拟机 (11.119.9.1) 上启动 Master 容器
docker run -d --name spark-master \
-p 7077:7077 \ # Master RPC 端口
-p 8080:8080 \ # Master Web UI 端口 (Bitnami 默认可能是 8080)
-e SPARK_MODE=master \
-e SPARK_MASTER_HOST=11.119.9.1 \
# Bitnami 可能用 SPARK_RPC_BIND_ADDRESS,查阅具体镜像文档
# -e SPARK_RPC_BIND_ADDRESS=0.0.0.0 \ # 确保绑定所有接口
bitnami/spark
如果使用的是 docker-compose.yml
:
version: '3.8'
services:
spark-master:
image: bitnami/spark
container_name: spark-master
ports:
- "7077:7077" # Master RPC
- "8080:8080" # Master Web UI
environment:
- SPARK_MODE=master
- SPARK_MASTER_HOST=11.119.9.1 # 必须是Worker和其他客户端能访问的Master IP
# - SPARK_RPC_BIND_ADDRESS=0.0.0.0 # 确保监听所有接口
三、 确认 Spark Worker 的可访问地址
同理,远程 Spark Worker 节点也需要告诉 Master 自己在哪儿,这个地址也应该是 Master 和 Driver(如果 Driver 在本地)能访问到的。
原理和作用
Bitnami Spark Worker 容器使用 SPARK_WORKER_HOST
来指定自己的可访问主机名/IP,以及 SPARK_MASTER_URL
来连接 Master。
操作步骤
在启动远程 Spark Worker Docker 容器时:
# 在远程 Worker 虚拟机 (如 11.119.9.2, 11.119.9.3) 上启动 Worker 容器
# Worker 1 (在 11.119.9.2 上)
docker run -d --name spark-worker-1 \
-p 8081:8081 \ # Worker Web UI (每个 Worker UI 端口应不同,或只暴露一个用于检查)
-e SPARK_MODE=worker \
-e SPARK_MASTER_URL=spark://11.119.9.1:7077 \
-e SPARK_WORKER_HOST=11.119.9.2 \ # Worker 自身的虚拟机 IP
# -e SPARK_RPC_BIND_ADDRESS=0.0.0.0 \
# Worker 也需要暴露它监听 Executor 通信的端口,但这个端口是动态的。
# 通常 Worker 会选择一个可用端口,Master 会把它告诉 Driver。
# SPARK_WORKER_WEBUI_PORT 可以用来改Worker的UI端口
-e SPARK_WORKER_WEBUI_PORT=8081 \
bitnami/spark
# Worker 2 (在 11.119.9.3 上,也就是日志中hostname 11.119.8.3 可能解析到的那台机器的另一个IP)
# 假设11.119.8.3是VPN地址,实际虚拟机可能是11.119.9.3
docker run -d --name spark-worker-2 \
-p 8082:8081 \ # 将容器内8081映射到宿主机的8082
-e SPARK_MODE=worker \
-e SPARK_MASTER_URL=spark://11.119.9.1:7077 \
-e SPARK_WORKER_HOST=11.119.9.3 \ # Worker 自身的虚拟机 IP
# -e SPARK_RPC_BIND_ADDRESS=0.0.0.0 \
-e SPARK_WORKER_WEBUI_PORT=8081 \ # 容器内还是8081,只是映射出去不同
bitnami/spark
如果使用 docker-compose.yml
在每个 Worker 节点上部署(或用 Swarm/K8s 管理):
# worker-compose.yml (在 11.119.9.2 上)
version: '3.8'
services:
spark-worker:
image: bitnami/spark
container_name: spark-worker-1
ports:
- "8081:8081" # Worker Web UI
environment:
- SPARK_MODE=worker
- SPARK_MASTER_URL=spark://11.119.9.1:7077
- SPARK_WORKER_HOST=11.119.9.2 # 本机IP
- SPARK_WORKER_WEBUI_PORT=8081
# - SPARK_RPC_BIND_ADDRESS=0.0.0.0
Worker Executor 还会使用一些随机端口与 Driver 通信,这一般不需要在 docker run
时特别指定映射,因为它们是出站连接或由 Spark 内部机制协商。关键是 Worker 本身和 Driver 能够被对方访问。
四、检查 Docker 网络模式 (本地客户端)
如果以上配置还不行,可以考虑本地提交任务的 Docker 客户端容器的网络模式。
- Bridge Mode (默认) : 如上所述,需要正确使用
-p
参数映射端口。这是推荐的方式。 - Host Mode (
--network host
) : 本地客户端 Docker 容器直接使用宿主机的网络栈。这样容器内应用监听的端口就直接是宿主机上的端口,IP 也是宿主机的 IP。这能简化很多网络问题,但牺牲了容器的网络隔离性,且在 Mac/Windows 上的 Docker Desktop 中行为可能不同。
如果使用# 在本地机器启动用于提交任务的 Spark Docker 容器 (使用 host 网络) docker run -it --rm --network host \ -v /path/to/your/scripts:/scripts \ bitnami/spark /bin/bash
--network host
,那么在spark-submit
时,spark.driver.host
仍然应该设置为你的 VPN IP (11.119.8.1
)。就不再需要-p
来映射端口了,因为容器直接用宿主机的端口。
安全建议
--network host
模式会暴露容器内所有监听的端口到宿主机网络,安全性较低,请谨慎使用。
五、VPN 和防火墙详细排查
即便 IP 和端口都配置对了,VPN 或防火墙也可能从中作梗。
- 本地防火墙 :确保本地机器的防火墙允许从 VPN 另一端的 Worker IP (例如
11.119.9.2
,11.119.9.3
, 或者报错日志里看到的11.119.8.3
这个 Executor 运行的 IP) 访问11.119.8.1
上的35369
,36000
,4040
端口。 - 远程虚拟机防火墙 :虽然 Worker 是主动连 Driver,但也检查下远程虚拟机防火墙是否有非常严格的出站规则(可能性较小)。更可能是远程网络的网关或 VPN 设备上的防火墙规则。
- VPN 策略 :有些 VPN 配置可能会限制特定端口或协议的流量。咨询你的网络管理员。
进一步调试方法 :
- 在遇到问题的远程 Worker 容器内部 ,尝试
telnet 11.119.8.1 35369
(或其他 Driver 配置的端口)。
如果这里不通,说明 Worker 容器到你本地 Docker 容器的 Driver 端口这条路确实不通。原因可能是:# 假设你可以在远程 Worker 容器上执行命令 docker exec -it <worker_container_name_or_id> bash # apt-get update && apt-get install -y telnet (如果容器内没有 telnet) telnet 11.119.8.1 35369
- 本地 Docker 没正确发布端口。
- 本地宿主机防火墙拦截。
- VPN 或网络中间设备拦截。
进阶使用技巧:一致性和日志
- Spark 版本一致 :确保你本地 Spark Docker 客户端、远程 Master、远程 Worker 使用的 Spark 版本(特别是主版本号)最好一致,避免兼容性问题。
- 详细日志 :增加 Spark 日志级别 (
conf/log4j2.properties
中设置rootLogger.level = DEBUG
),能提供更多线索。检查 Master、Worker 以及 Driver (本地应用) 的完整日志。
总结一下,处理这类跨 Docker、跨 VPN 的 Spark 连接问题,核心思路是保证 Spark 各组件(Driver, Master, Executor/Worker)通告的 IP 和端口是对方真正可以访问到的,并且网络路径(包括 Docker 端口映射、宿主机防火墙、VPN 通道)是畅通的。细心配置 spark.driver.host
,spark.driver.port
,并在本地 Docker 容器启动时正确暴露这些端口,通常能解决问题。