返回

Docker Spark 远程连接 Master 失败?一文解决!

python

解决本地 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 这种网络。

  1. Spark 通信流
    当你从本地提交一个 Spark 应用(尤其是用类似 client 模式,或者 cluster 模式但 Driver 仍然被告知在提交端)时,Driver 程序可能就在你本地机器的 Docker 容器里跑。Spark Master 收到任务后,会通知 Worker 节点启动 Executor。这些 Executor 启动后,需要反向连接到 Driver 程序来接收任务、汇报状态。

  2. 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 访问。
  3. Docker 网络隔离
    Docker 容器默认有自己的网络空间。容器内部看到的 localhost 和 IP 地址,跟宿主机以及其他远程机器看到的可能完全不一样。

  4. 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 更常用,因为它同时影响绑定和通告。

操作步骤

  1. 确定本地 Docker 客户端的 VPN IP :就是错误日志里提到的 11.119.8.1。这个 IP 必须是远程 Spark Worker 节点可以路由到的。

  2. 修改 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 还是在本地。

  3. 启动本地 Docker 容器时暴露端口
    当你启动运行 spark-submit 的那个本地 Docker 容器时,必须把 Driver 要用的端口(包括 spark.driver.portspark.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 exec -it <worker_container_name_or_id> bash
    # apt-get update && apt-get install -y telnet (如果容器内没有 telnet)
    telnet 11.119.8.1 35369
    
    如果这里不通,说明 Worker 容器到你本地 Docker 容器的 Driver 端口这条路确实不通。原因可能是:
    1. 本地 Docker 没正确发布端口。
    2. 本地宿主机防火墙拦截。
    3. 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.hostspark.driver.port,并在本地 Docker 容器启动时正确暴露这些端口,通常能解决问题。