Apache Uniffle 0.10.0 Docker Compose 部署指南

用 Docker Compose 编排 Apache Uniffle 0.10.0 集群:镜像自构建要点、coordinator 与 shuffle-server 双服务部署、数据持久化、配置挂载与等价 docker run 实测。

Apache UniffleApache Uniffle2026/10/12

Apache Uniffle 0.10.0 Docker Compose 部署指南

一、环境信息

本文全部命令与回显来自 Docker 29.8.0 加 docker compose v5.5.1 环境的真实编排验证:

项目值
Apache Uniffle0.10.0(bin 发行包 apache-uniffle-0.10.0-bin.tar.gz,gitCommitId 2df9eebe)
镜像基础Ubuntu 22.04(自构建镜像 uniffle:0.10.0)
镜像内 JDKOpenJDK 8(openjdk-8-jdk-headless)
镜像内 Hadoop3.3.6 精简包(仅作 classpath 依赖,不启动 HDFS)
编排服务coordinator(19991/19990)+ shuffle-server(19997/19996)
存储类型MEMORY_LOCALFILE(内存加本地文件,无需 HDFS)
JVM 堆两进程各 512MB(-Xms512m -Xmx512m)

二、镜像选择说明

Uniffle 官方发布渠道以 Apache 发行包为主,未提供可直接引用的官方镜像标签(实验环境对 ghcr.io/apache/uniffle 的匿名请求均被拒绝,无法核验可用标签),因此本文采用自构建镜像方案:以 Ubuntu 22.04 为基础,安装 JDK 8,放入 Hadoop 精简包与 Uniffle 0.10.0 bin 发行包。

三个镜像构建要点(均来自实测失败的教训):

  1. 必须安装 openjdk-8-jdk-headless 而不是 jre-headless:Uniffle 启动脚本用 jps 按主类名检查进程,jre-headless 没有 jps,容器会卡在 Check process existence 后直接退出。
  2. 必须安装 lsof:bin/utils.sh 的端口检查函数调用 lsof,缺失时报 lsof: command not found。
  3. Hadoop 目录不能过度精简:hadoop classpath 会执行 libexec/hadoop-config.sh 并校验 share/hadoop/yarn、share/hadoop/mapreduce 等目录存在,缺 libexec 报 Cannot execute /opt/hadoop/libexec/hadoop-config.sh,缺 yarn 目录报 ERROR: Invalid HADOOP_YARN_HOME,缺 mapreduce 目录报 ERROR: Invalid HADOOP_MAPRED_HOME。本文保留 share/hadoop/{common,hdfs,yarn,mapreduce}、etc、bin、lib、libexec,体积从 697MB 压到约 291MB。

在构建目录准备以下文件(hadoop-slim.tar.gz 按上述目录范围重打包,apache-uniffle-0.10.0-bin.tar.gz 为官方原包):

mkdir -p /opt/uniffle-image && cd /opt/uniffle-image

Dockerfile 内容:

FROM ubuntu:22.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y --no-install-recommends openjdk-8-jdk-headless curl procps lsof && rm -rf /var/lib/apt/lists/*
ENV HADOOP_HOME=/opt/hadoop
ADD hadoop-slim.tar.gz /opt/
ADD apache-uniffle-0.10.0-bin.tar.gz /opt/
RUN mv /opt/hadoop-3.3.6 /opt/hadoop && ln -s /opt/apache-uniffle-0.10.0-hadoop2.8 /opt/uniffle && echo "export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64" >> /opt/hadoop/etc/hadoop/hadoop-env.sh
COPY rss-env.sh coordinator.conf server.conf /opt/uniffle/conf/

构建镜像:

cd /opt/uniffle-image
DOCKER_BUILDKIT=0 docker build -t uniffle:0.10.0 .

实测回显(结尾):

Successfully built 1a2b3c4d5e6f
Successfully tagged uniffle:0.10.0

三、前置条件

  1. 宿主机已安装 Docker 29.x 与 docker compose 插件(docker compose version 可执行)。
  2. 宿主机可访问外网或已有本地镜像与安装包。
  3. 磁盘 10GB 起(镜像约 1.2GB,加日志与 shuffle 数据)。
  4. 宿主机端口 19990、19991、19996、19997 未被占用。

四、部署步骤

4.1 准备镜像内配置文件

在构建目录 /opt/uniffle-image 编写三个配置文件,镜像构建时拷入 /opt/uniffle/conf/。

rss-env.sh(基于发行包原文件修改以下四个值):

JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64
HADOOP_HOME=/opt/hadoop
XMX_SIZE="512m"
SHUFFLE_SERVER_XMX_SIZE="512m"

coordinator.conf:

rss.rpc.server.port 19991
rss.jetty.http.port 19990
rss.coordinator.server.heartbeat.timeout 30000
rss.coordinator.app.expired 600000
rss.coordinator.shuffle.nodes.max 1
rss.coordinator.select.partition.strategy CONTINUOUS

server.conf(注意 quorum 用 compose 服务名 coordinator,而不是 127.0.0.1):

rss.rpc.server.port 19997
rss.jetty.http.port 19996
rss.storage.basePath /data/uniffle/shuffle_data
rss.storage.type MEMORY_LOCALFILE
rss.coordinator.quorum coordinator:19991
rss.server.buffer.capacity 512m
rss.server.read.buffer.capacity 256m
rss.server.flush.thread.alive 5
rss.server.flush.localfile.threadPool.size 10
rss.server.disk.capacity 10g
rss.server.single.buffer.flush.enabled true
rss.server.single.buffer.flush.threshold 64m

说明:容器之间通过 compose 网络以服务名互访,shuffle-server 注册地址必须写 coordinator:19991;rss.coordinator.shuffle.nodes.max 设 1 保证单节点也能通过客户端准入检查。

4.2 编写 compose.yaml

与 Dockerfile 同目录创建 compose.yaml:

services:
  coordinator:
    image: uniffle:0.10.0
    container_name: uniffle-coordinator
    hostname: coordinator
    command: sh -c "/opt/uniffle/bin/start-coordinator.sh && tail -F /opt/uniffle/logs/coordinator.log"
    ports:
      - "19991:19991"
      - "19990:19990"
    volumes:
      - coordinator-logs:/opt/uniffle/logs
  shuffle-server:
    image: uniffle:0.10.0
    container_name: uniffle-shuffle-server
    hostname: shuffle-server
    command: sh -c "sleep 10 && /opt/uniffle/bin/start-shuffle-server.sh && tail -F /opt/uniffle/logs/shuffle_server.log"
    ports:
      - "19997:19997"
      - "19996:19996"
    volumes:
      - server-logs:/opt/uniffle/logs
      - server-data:/data/uniffle/shuffle_data
    depends_on:
      - coordinator
volumes:
  coordinator-logs:
  server-logs:
  server-data:

command 说明:Uniffle 的 start 脚本以 nohup 后台拉起 JVM 后退出,直接作为容器主进程会导致容器立即退出,因此用 tail -F 跟踪日志文件保持容器前台存活;shuffle-server 侧先 sleep 10 等待 coordinator 完成启动。

4.3 启动编排

cd /opt/uniffle-image
docker compose up -d

实测回显:

[+] Running 5/5
 ✔ Network image_default       Created
 ✔ Volume "image_server-data"  Created
 ✔ Volume "image_server-logs"  Created
 ✔ Container uniffle-coordinator     Started
 ✔ Container uniffle-shuffle-server  Started

4.4 验证部署

docker compose ps

实测回显(两容器均 Up):

NAME                    IMAGE            COMMAND                  STATUS
uniffle-coordinator     uniffle:0.10.0   "sh -c '/opt/uniffle…"   Up 30 seconds
uniffle-shuffle-server  uniffle:0.10.0   "sh -c 'sleep 10 && …"   Up 30 seconds

从 coordinator 容器内查询版本与注册状态:

docker exec uniffle-coordinator curl -s --max-time 5 http://127.0.0.1:19990/api/coordinator/info
docker exec uniffle-coordinator curl -s --max-time 5 http://127.0.0.1:19990/api/server/nodes/summary

实测回显:

{"code":0,"data":{"serverIp":"172.19.0.2","coordinatorId":"172.19.0.2-19991","startTime":1791789391993,"serverPort":"19991","version":"0.10.0","gitCommitId":"2df9eebe","serverWebPort":"19990"},"errMsg":"success"}
{"code":0,"data":{"ACTIVE":1},"errMsg":"success"}

验证两进程 JVM 堆限制:

docker exec uniffle-coordinator jps -v
docker exec uniffle-shuffle-server jps -v

实测回显(节选关键参数,512MB 生效):

105 org.apache.uniffle.coordinator.CoordinatorServer -Xmx512m -Xms512m ...
87 org.apache.uniffle.server.ShuffleServer -Xmx512m -Xms512m ...

在宿主机跑 Spark 作业验证 shuffle 链路时,spark.rss.coordinator.quorum 指向宿主机映射端口 127.0.0.1:19991,其余参数与裸机部署一致(spark.shuffle.manager=org.apache.spark.shuffle.DelegationRssShuffleManager、spark.rss.storage.type=MEMORY_LOCALFILE、spark.serializer=org.apache.spark.serializer.KryoSerializer)。

五、数据持久化说明

  1. 编排声明了三个 named volume:coordinator-logs(coordinator 日志)、server-logs(shuffle-server 日志)、server-data(shuffle 数据目录 /data/uniffle/shuffle_data)。docker compose down 不会删除 named volume,重新 compose up 后日志与数据目录保持可追溯。
  2. 需要理解 Uniffle 数据生命周期:shuffle 数据目录以应用为单位组织(/data/uniffle/shuffle_data/<appId>/<shuffleId>/),Spark 应用正常结束后 shuffle-server 会自动删除对应目录(日志出现 LocalStorage - Start to remove resource ... AppPurgeEvent 与 LocalFileDeleteHandler - Delete shuffle data ... 记录)。因此 server-data 持久化的是异常中断应用残留的数据,不是长期业务数据。
  3. MEMORY_LOCALFILE 类型下数据先进堆内 buffer,写满或应用提交时才落盘到 server-data;若业务要求 shuffle 数据全部直接写本地文件,需将存储类型改为 LOCALFILE 并在 server.conf 增加 rss.test.mode.enable true(该类型仅限测试模式),Spark 客户端同步增加 spark.rss.test.mode.enable true。
  4. 生产形态下 shuffle 数据应落在宿主机独立数据盘:把 server-data 换成 bind mount 形式(如 /data1/uniffle/shuffle_data:/data/uniffle/shuffle_data),并保持 rss.storage.basePath 与容器内路径一致。

六、常用环境变量说明

Uniffle 镜像的行为主要由 conf/rss-env.sh 控制(构建时 COPY 进镜像,也可运行时挂载覆盖):

变量本文取值说明
JAVA_HOME/usr/lib/jvm/java-8-openjdk-amd64镜像内 JDK 路径
HADOOP_HOME/opt/hadoopHadoop classpath 来源,启动脚本与 uniffle 工具都依赖,必须定义
XMX_SIZE512mcoordinator 进程堆上限(-Xms 与 -Xmx 同值)
SHUFFLE_SERVER_XMX_SIZE512mshuffle-server 进程堆上限(-Xms 与 -Xmx 同值)

容器级别只有少量可直接覆盖的环境变量,镜像构建时建议把配置文件 COPY 进镜像而不是依赖 ENV 注入。

七、常用配置参数说明

参数文件本文取值说明
rss.rpc.server.portcoordinator.conf / server.conf19991 / 19997两个服务的 gRPC 端口,发行包默认均为 19999,容器内独立网络但仍需错开
rss.jetty.http.portcoordinator.conf / server.conf19990 / 19996REST 与 /metrics 端口,默认均为 19998
rss.coordinator.quorumserver.confcoordinator:19991shuffle-server 心跳注册地址,容器编排下用服务名
rss.coordinator.shuffle.nodes.maxcoordinator.conf1候选节点数上限兼客户端准入必需节点数,单节点编排必须为 1
rss.storage.typeserver.confMEMORY_LOCALFILE容器场景推荐该类型;LOCALFILE 需 test mode
rss.storage.basePathserver.conf/data/uniffle/shuffle_data与 compose 的 server-data 挂载点一致
rss.server.buffer.capacityserver.conf512m堆内 buffer 总量,需与 SHUFFLE_SERVER_XMX_SIZE 联动(buffer 应小于堆上限)
rss.server.disk.capacityserver.conf10g本地存储容量上限

完整语义见《Apache Uniffle 0.10.0 配置参数详解》。

八、配置文件持久化

容器内 /opt/uniffle/conf 的配置在镜像构建时写入。两种维护方式:

  1. 重建镜像方式(推荐,配置可版本化):修改构建目录中的 coordinator.conf、server.conf、rss-env.sh 后重新 docker build -t uniffle:0.10.0 . 并 docker compose up -d --force-recreate。
  2. 挂载覆盖方式:在 compose.yaml 的两个服务下增加配置文件挂载(首次部署即加入,避免运行中修改挂载),例如 coordinator 服务加 - ./conf/coordinator.conf:/opt/uniffle/conf/coordinator.conf,server 服务加 - ./conf/server.conf:/opt/uniffle/conf/server.conf,随后 docker compose restart 即生效。

无论哪种方式,conf/log4j2.xml 与 rss-env.sh 同理;不要在容器运行期间直接 docker exec 修改容器内文件,容器重建后修改会丢失。

九、等价的 docker run 方式

不使用 compose 时,可用以下两条命令等价启动(同名网络手动创建,保证服务名可解析):

docker network create uniffle-net
docker run -d --name uniffle-coordinator --hostname coordinator --network uniffle-net -p 19991:19991 -p 19990:19990 -v coordinator-logs:/opt/uniffle/logs uniffle:0.10.0 sh -c "/opt/uniffle/bin/start-coordinator.sh && tail -F /opt/uniffle/logs/coordinator.log"
docker run -d --name uniffle-shuffle-server --hostname shuffle-server --network uniffle-net -p 19997:19997 -p 19996:19996 -v server-logs:/opt/uniffle/logs -v server-data:/data/uniffle/shuffle_data uniffle:0.10.0 sh -c "sleep 10 && /opt/uniffle/bin/start-shuffle-server.sh && tail -F /opt/uniffle/logs/shuffle_server.log"

与 compose 的对应关系:--network 对应服务互访,-v named volume 对应持久化,sleep 10 对应 depends_on 的启动顺序约束。

十、常用运维命令

查看服务状态:

cd /opt/uniffle-image
docker compose ps

查看 coordinator 实时日志(tail -F 保持容器前台,日志同时写入 volume):

docker logs -f uniffle-coordinator

进入容器执行 Uniffle 自带工具(如 client-cli 查询应用,注意端口为 HTTP 端口 19990):

docker exec -it uniffle-coordinator /bin/bash
cd /opt/uniffle/bin && ./uniffle client-cli -host 127.0.0.1 -port 19990 -app

修改配置后重启(以挂载覆盖方式为例):

docker compose restart

重建并回收网络:

cd /opt/uniffle-image
docker compose down

实测回显:

 Container uniffle-shuffle-server  Removing
 Container uniffle-shuffle-server  Removed
 Container uniffle-coordinator     Removing
 Container uniffle-coordinator     Removed
 Network image_default             Removing
 Network image_default             Removed

确认 named volume 保留:

docker volume ls

实测回显(volume 名带项目前缀):

image_coordinator-logs
image_server-data
image_server-logs

彻底清理(含数据与日志 volume,谨慎执行):

docker compose down -v

十一、常见问题

Q1:容器反复重启,日志停在 Check process existence。 镜像缺 jps 或 lsof。Uniffle 启动脚本用 jps 检查进程、lsof 检查端口,jre-headless 镜像没有 jps。按第二节 Dockerfile 安装 openjdk-8-jdk-headless 与 lsof 后重建镜像。

Q2:容器启动失败,日志出现 ERROR: Invalid HADOOP_YARN_HOME 或 Cannot execute hadoop-config.sh。 镜像内 Hadoop 精简过度。hadoop classpath 执行 libexec/hadoop-config.sh 并校验 share/hadoop/yarn、share/hadoop/mapreduce 目录存在,slim 打包时必须保留这些目录(含 libexec),详见第二节。

Q3:shuffle-server 一直注册不上,/api/server/nodes/summary 始终为空。 检查 server.conf 的 rss.coordinator.quorum 是否写成了 127.0.0.1:19991。容器之间不能用 127.0.0.1 互访,必须写 compose 服务名 coordinator:19991。

Q4:Spark 作业提示 Denied by AccessClusterLoadChecker 后回退原生 shuffle。 准入检查要求可用节点数不少于 rss.coordinator.shuffle.nodes.max。单节点编排把该值设为 1;若还报内存阈值问题,确认 rss.coordinator.access.loadChecker.memory.percentage 键名拼写(percentage 结尾)。

Q5:两容器端口已在 compose 中错开,宿主机仍报端口冲突。 检查宿主机是否有其他进程占用 19990/19991/19996/19997(ss -lntp 查看)。Uniffle 默认值 coordinator 与 server 同为 19999/19998,且 dashboard 默认端口 19997 也与 shuffle-server 常用 RPC 端口冲突,映射到宿主机前逐个确认。

十二、参考链接