【长文 Spark/Sedona】从零开始搭建Spark(Sedona) on Yarn的Docker集群,以及可能遇到的坑合集,2024-05-16

【长文 Spark/Sedona】从零开始搭建Spark(Sedona) on Yarn的Docker集群,以及可能遇到的坑合集,2024-05-16基于 docker 搭建 sparkonyarn 集群 sedonaspark

大家好,欢迎来到IT知识分享网。

从零开始搭建Spark(Sedona) on Yarn的Docker集群,以及可能遇到的坑合集,2024-05-16

引言

​ 最近在研究分布式计算,手上只有几台家用主机魔改的服务器,配置也参差不齐,如果不用虚拟化技术而直接在主机上搭建的话,就会出现 Hadoop 集群节点配置不对齐的问题,可能会影响 yarn 调度,于是便想到使用 Docker 来均分几台主机的配置;以下搭建过程中我使用的部分方案对你可能不适用,请注意甄别。

* 声明:请注意,以下教程是基于 Docker 搭建 Spark on Yarn 集群,所以不是每一步都给出了具体的代码,因为代码都写在 Dockerfile 中了,这个教程只是一个思路,如果你不是基于 Docker 搭建集群,那么需要自行鉴别每一步都在干什么

当然,我也提供了其他搭建方式,例如 Spark on Kubernetes ,使用 k8s 调度的方法,比本文中的搭建方要高明,自然也要难一点,链接放这,点击前往

* 防喷声明:以下内容为原创,也就是说都是我想出来的,肯定有不对的地方,请指出。此外这篇博客也是写给我自己的,以后方便查找内容,会不定期更新,所以有错的地方我会及时改正。
勿喷,Pace And Love

一、分布式计算简介

HadoopSpark 就不过多介绍了,网上关于这个的资料很多,简单说说 Sedona,这是一个 Spark 的扩展,不需要额外安装,只需要将扩展的Jar包下载下来放到对应目录即可;它是一个 Apache 的开源项目,主要是在 Spark 的基础上拓展对矢量数据的分布式计算支持,比如它支持空间分析:相交、擦除、联合…等等,就是我们在 PostGISArcGIS 中使用到的那些空间分析功能;

以往如果要对大量数据进行分析,计算量将是不可想象的。举个例子,对两个图层进行相交分析,如果这两个图层各有一千万条要素feature,假如不做优化处理的话,这两个图层的计算量是两个图层数量的笛卡尔积,1千万 * 1千万 次相交计算,还不算常用的嵌套分析—— 如相交后擦除、联结等等,如果是嵌套分析,那么计算量将会是天文数字,单台计算机运行这么多次计算会非常耗时间。Sedona 做的就是将这些计算从一台计算机转移到多台计算机上来做—— 一个矢量数据的分布式计算系统。
其实在矢量数据计算方面也有其他产品的,例如 Citus,开源免费的,它也可以做到我说的这些计算,但是只能做数据库相关的事,其他的计算就不支持了,假如某天你要干点其他事例如处理日志文本文件等计算呢,它就不方便了。

你可以将分布式计算系统中每台节点计算机看作单台计算中的一个线程,多个节点就像一台计算中的多线程,这些节点组合起来组成一个分布式计算系统,分布式计算系统 —> 多个计算机节点 —> 每个计算机节点上又有多个线程可用。其实就是之前的单台计算机的线程不够用了,去使用其他计算机上的线程资源,然后 yarn 就像一个分布在每台节点计算机上的操作系统,有一个总的 yarn 协调每个节点上的 yarn 完成任务 —— ResourceManager 。同样的 hdfs 就是每台节点上的文件管理系统,有一个总的 hdfs 来协调分布式文件系统 —— NameNode

* 小畅想

我一直在想既然 yarn 可以认为是一个分布式操作系统,为什么没人从真系统底层去支持分布式计算呢?例如 Linux ,如果 Linux 从底层就支持分布式计算,运行在分布式系统上的程序就像运行在单个计算机上一样简单,那还有 Hadoop 啥事啊。我感觉改天可以去和 Linus 聊聊(滑稽)。

二、步骤总览

要想搭建 Sedona on Yarn 集群,首先你得先搭建一个 Hadoop 集群,然后再使用 Spark 替代 MapReduce 计算,再添加开启 Sedona 插件支持;也就是:

搭建一个Hadoop集群 —> 使用Spark代替MapReduce计算 —> 添加Sedona扩展、

好了,废话少说,直接开始

三、搭建 Hadoop 集群

一、更新源以及安装必要的软件

openssh-server 是必需的,其他可选

sudo apt update && apt upgrade -y && \ sudo apt install -y unzip vim openssh-server iproute2 iputils-ping netcat-openbsd rsync nginx cron 
二、Ubuntu使用普通用户

创建一个普通用户,不能用root,用了root的话,集群启动不起来,因为Hadoop官方为了安全考虑不推荐使用root账户

useradd -m -s /bin/bash commonuser # 创建一个普通用户 usermod -aG sudo commonuser # 用户加入sudo组 echo 'commonuser:123' | chpasswd && echo 'root:456' | chpasswd # 修改用户密码和root密码 
三、安装Docker

这一步必须统一Docker版本,如果不统一版本,可能在后续会有其他问题,安装Docker要安装多个包,可以使用官方提供的安装脚本来一键安装

curl -fsSL https://get.docker.com -o get-docker.sh #下载官方脚本 sudo sh get-docker.sh # 执行脚本,如果报错记得给执行权限 sudo chmod -X get-docker.sh 
四、使用Docker Swarmoverlay 网络(可选,慎选)

做这一步的原因是我的主节点上安装了太多其他服务了,端口有点不够用,所以给这些节点分配一个内部网络。可是有个缺点,就是你无法直接访问到内部网络中的其他节点。我的解决办法是在主节点上用Nginx做反向代理,有点麻烦,如果你的端口够用,别用这个方法;

占用的端口都有(默认无修改情况下):
yarn web端口 —— 8088
yarn 调度器端口 —— 8030
yarn 管理端口 —— 8031
yarn IPC端口 —— 8032
NodeManager web端口 —— 8042
NodeManager 本地化端口 —— 8040
spark 的很多端口是动态的,不指定的话一般都是一万多 1xxxx 端口,可以提交任务时或者在配置文件中指定
spark web端口 —— 4040
spark 历史服务器 —— 18080

在主节点上创建一个 Swarm

docker swarm init 

你会看到一个结果类似:

docker swarm join --token SWMTKN-1-34y20tfgw9rj4wkmc5cloe8klfe 192.168.2.201:2377 

在其他节点上运行这段命令加入主节点创建的 Swarm,首要前提是节点之间是能够连通的

在主节点上创建一个overlay网络:

docker network create --driver overlay --attachable --subnet 6.6.6.0/24 --gateway 6.6.6.1 sedona-net 

必须得加上 --attachable--driver overlayattachable加了这个参数容器才能连进这个网络,driver指定使用overlay创建网络,其他都是可选的。

创建完成后其他主机就可以像在自己主机上一样连接进这个网络了,测试:

docker run -it --rm --network sedona-net --ip 6.6.6.10 ubuntu:latest 

容器跑起来后看看IP地址是不是 6.6.6.10

五、配置 /etc/hosts/etc/hostname 文件(重要)

先配置 hosts 文件,这一步可以省去我们很多事,配置hosts文件后我们不必再手输IP地址了,将IP地址和主机名的关系直接对应起来,vim /etc/hosts

6.6.6.10 master 6.6.6.11 slave01 6.6.6.11 slave02 6.6.6.11 slave03 6.6.6.11 slave04 # ... 如果有新的节点加进来,这里要更新 

再配置 hostname 文件:

echo "master(或者你想取的主机名)" | sudo tee /etc/hostname # tee 相当于 > tee -a 相当于 >> 

这里不能直接追加文件,因为操作的是系统文件,所以要用 sudo 命令,
又因为 sudo echo "..." > xxxx文件 sudo 的范围只有前面的 echo ,也就是说 sudo 只给 echo 提升了权限,而没有对后面的文件提升权限,所以用 管道 + tee 的方式;
echo "..." | tee xxx文件 相当于 echo "..." > xxx文件,清空原本的内容添加
echo "..." | tee -a xxx文件 相当于 echo "..." >> xxx文件,在原本的内容末尾追加

其实这里的原因很简单,就是以后对IP地址的操作都可以直接用主机名代替了,比如说你配置了master和slave01,那么你在master上对slave01的任何操作都可以直接:ping slave01 使用主机名;配置这两个文件的目的,就是告诉系统使用主机名的时候,这个主机名对应的IP地址到底是多少,同样的你要想让别人知道你的主机名是什么,也得配一个 hostname

六、安装JDK1.8

这里就不详细赘述了,常规操作,下载二进制包,解压,~/.bashrc 中配置环境变量 JAVA_HOMECLASSPATHPATH,也就是去用户的环境变量文件中配置,注意,JAVA_HOME 必须得配置:

export JAVA_HOME=/... 你的安装路径 export CLASSPATH=$CLASSPATH:$JAVA_HOME/lib:$JAVA_HOME/jre/lib export PATH=$PATH:$JAVA_HOME/bin 

别忘了 source ~/.bashrc

七、下载 Hadoop 二进制文件

贴一个官网地址,自己去下 官方下载地址 ,每个版本都有,我选的是最新的 3.4.0 ,下载慢的话可以考虑科学上网。

八、安装 Hadoop

解压,配置环境变量(可选):

tar -zxvf hadoop-3.4.0.tar.gz -C 指定一个安装目录 # -C 可选,不加解压到当前目录 

~/.bashrc 中配置环境变量(可选),配置两个,bin下是可执行文件,sbin下是启动脚本:

export HADOOP_HOME=/... 安装位置 export PATH=$PATH:$JAVA_HOME/bin:$HADOOP_HOME/bin:$HADOOP_HOME/sbin 
九、配置 Hadoop (重要)

共配置五个文件:hadoop-env.shcore-site.xmlhdfs-site.xmlyarn-site.xmlworkers

1、配置 hadoop-env.sh

在这个文件末尾添加一个环境变量 JAVA_HOME,没错,我们已经在 ~/.bashrc中配置过了,但是意外万一再配置一次,建议还是配置一次,不然可能要出问题:

export JAVA_HOME=/... 和之前一样的Java路径 

2、配置 core-site.xml (可选)

这个文件可以配置,也可以不管使用默认的,我的配置如下:

<configuration> <!-- 指定hdfs节点之间通信的端口以及指定谁是hdfs的主节点--> <property> <name>fs.defaultFS</name> <value>hdfs://master:9101</value> </property> <!-- 指定hdfs的缓存文件目录--> <property> <name>hadoop.tmp.dir</name> <value>file:/my_app_data</value> </property> <!-- 缓冲区大小 --> <property> <name>io.file.buffer.size</name> <value></value> </property> <!-- 回收站保留时间 --> <property> <name>fs.trash.interval</name> <value>1440</value> <!-- 24小时 --> </property> </configuration> 

3、配置 hdfs-site.xml

贴出我的配置,但不是最优的,因为是根据机器来看的,最优的配置你们自己去研究吧:

<configuration> <!-- 指定hdfs web页面的端口以及哪个IP来运行这个web服务--> <property> <name>dfs.http.address</name> <value>0.0.0.0:11110</value> </property> <!-- 开启RestFulApi --> <property> <name>dfs.webhdfs.enabled</name> <value>true</value> </property> <!-- 第二NameNode的地址(用来还原主节点的),不配置的话默认在主节点上再开启一个服务来跑 --> <property> <name>dfs.namenode.secondary.http-address</name> <value>slave01:11111</value> </property> <!-- 文件副本数 --> <property> <name>dfs.replication</name> <value>3</value> </property> <!-- 文件元数据都放在哪 --> <property> <name>dfs.namenode.name.dir</name> <value>file:/my_app_data/data/hdfs/meta_data</value> </property> <!-- 文件分片数据都放在哪 --> <property> <name>dfs.datanode.data.dir</name> <value>file:/my_app_data/data/hdfs/datas</value> </property> <!-- 块大小 --> <property> <name>dfs.blocksize</name> <value></value> <!-- 128 MB --> </property> </configuration> 

4、配置 yarn-site.xml

这里主要是配置 yarn 性能的,你们根据自己的机器情况来配置;简单说一下,yarn.nodemanager.resource.memory-mb 是指定每个 yarn 节点最大能用多少内存,如果你的节点能用16GB,那么这里建议配置14GB,因为要给Linux留点;
下面的注释中 CPU核心数分配 也是一个意思

<configuration> <!-- 配置谁是yarn主节点 --> <property> <name>yarn.resourcemanager.hostname</name> <value>master</value> </property> <!-- 这两个是配置saprk的shuffle,可选 --> <property> <name>yarn.nodemanager.aux-services</name> <value>spark_shuffle</value> </property> <property> <name>yarn.nodemanager.aux-services.spark_shuffle.class</name> <value>org.apache.spark.network.yarn.YarnShuffleService</value> </property> <!-- 禁用虚拟内存检查,避免内存溢出 --> <property> <name>yarn.nodemanager.vmem-check-enabled</name> <value>false</value> </property> <property> <name>yarn.nodemanager.pmem-check-enabled</name> <value>true</value> <!-- 启用物理内存限制检查 --> </property> <!-- 配置yarn web页面端口和在哪个IP地址运行 --> <property> <name>yarn.resourcemanager.webapp.address</name> <value>0.0.0.0:11120</value> </property> <!-- 设置每个NodeManager可用的最大内存量(以MB为单位) --> <property> <name>yarn.nodemanager.resource.memory-mb</name> <value>14336</value> <!-- 14 GB --> </property> <!-- 设置每个NodeManager节点上可用的虚拟CPU核心数 --> <property> <name>yarn.nodemanager.resource.cpu-vcores</name> <value>10</value> </property> <!-- 设置YARN调度器为任何容器请求分配的最小(大)内存量(以MB为单位) --> <property> <name>yarn.scheduler.minimum-allocation-mb</name> <value>1024</value> <!-- 1 GB --> </property> <property> <name>yarn.scheduler.maximum-allocation-mb</name> <value>14336</value> <!-- 14 GB --> </property> <!-- 每个节点的CPU核心数分配 --> <property> <name>yarn.scheduler.minimum-allocation-vcores</name> <value>1</value> </property> <property> <name>yarn.scheduler.maximum-allocation-vcores</name> <value>10</value> </property> </configuration> 

5、配置 worker (重要)

这是配置集群中启动几个节点的,还记得我们上面配置的 /etc/hosts 文件中的主机名和IP地址对应关系吗,这里直接填主机名就像,一个主机名一行,下面这种情况就会启动三个节点用于存储文件

master slave01 slave02 

四、安装配置 Spark

一、下载 Spark

同样的,贴出地址,官方下载地址 ,每个版本都有,下载慢科学上网,我下的是最新版3.5.1,注意不要选错了

在这里插入图片描述

二、安装 Spark

一样的,解压,配置环境变量(可选)

tar -zxvf spark-3.5.1-bin-hadoop3-scala2.13.tgz -C 安装地址 

编辑~/.bashrc 添加 binsbinPATH(可选):

export SPARK_HOME=/... 安装地址 export PATH=$PATH:$JAVA_HOME/bin:$HADOOP_HOME/bin:$HADOOP_HOME/sbin:$SPARK_HOME/bin:$SPARK_HOME/sbin 
三、配置 Spark (重要)

共配置三个文件:spark-env.shspark-defaults.confworkers
刚安装这三个文件是没有的,只有模板文件,所以我们复制模板:

cp spark-defaults.conf.template spark-defaults.conf cp spark-env.sh.template spark-env.sh cp workers.template workers 

1、配置 spark-env.sh

这里也要添加 JAVA_HOME 以防万一。
(重要)要将Hadoop和Yarn的配置文件地址放进去,Spark才能找到Hadoop
我的配置如下:

# 指定 Java Home export JAVA_HOME=/... Java的安装地址 #设置环境变量来帮助Spark找到YARN和Hadoop的配置文件 export HADOOP_CONF_DIR=/hadoop的安装地址/etc/hadoop export YARN_CONF_DIR=/hadoop的安装地址/etc/hadoop 

2、配置 spark-defaults.conf (重要)

Spark 的大部分配置都在这个文件中,我的配置如下,你们的要根据自己的机器性能配置:

spark.master yarn # 日志相关 spark.eventLog.enabled true # 日志存在哪里,这里的9101是之前我们配置的hdfs节点之间通信的端口 spark.eventLog.dir hdfs://master:9101/system/logs/spark spark.eventLog.compress true # 历史服务器端口 spark.history.ui.port 11130 spark.history.retainedApplications 3 # 设置 Spark 的序列化方式 spark.serializer org.apache.spark.serializer.KryoSerializer # UI 显示的端口 spark.ui.port 11131 # -------------- 性能相关 -------------- spark.executor.memory 2g spark.executor.cores 2 spark.yarn.am.memory 4g spark.executor.memoryOverhead 1g spark.dynamicAllocation.enabled true spark.dynamicAllocation.minExecutors 1 spark.dynamicAllocation.initialExecutors 8 spark.dynamicAllocation.maxExecutors 25 spark.shuffle.service.enabled true # 调整垃圾回收策略和参数 spark.executor.extraJavaOptions -XX:+UseG1GC spark.driver.extraJavaOptions -XX:+UseG1GC 

没有注释的几个配置解释:
1、spark.executor.memory 单个执行器的内存,Spark任务会在节点上启动执行器执行任务,你可以将它想象为一个线程
2、spark.executor.cores 单个执行器用几个CPU核心
3、spark.yarn.am.memory Spark任务启动时会在众多节点中随机选一个比较空闲的节点来运行 ApplicationManger ,也就是应用管理器,由这个管理器来统一分配任务给其他节点,我们得给它配置一个内存,它好收集结果,注意,这个管理器并不是任务开始时选一个后续就不变了,它后续会变,根据节点的空闲情况会调整,也就是说运行着运行着管理器可能就不在这个节点上跑到其他节点上去了
4、spark.dynamicAllocation.enabled 开启动态分配执行器,你可以理解为系统会自己考虑要多少个线程来完成任务
5、spark.dynamicAllocation.minExecutorsspark.dynamicAllocation.initialExecutorsspark.dynamicAllocation.maxExecutors 分别是:整个任务最少用几个执行器、任务运行时初始化几个执行器、整个任务最多用多少个执行器
其他都有注释,看注释吧

3、配置 workers

这里和 Hadoopworkers 是一个意思,只不过这里不是启动几个节点存储文件,是启动几个节点来执行 Spark 任务,将我们的节点放进去:

master slave01 slave02 

如果你不想主节点也参加计算任务的话,可以将它剔除

五、安装配置 Sedona

一、下载 Sedona

贴出下载地址:官方下载地址;同样的什么版本都有,我用的是 1.5.1,注意别下错了,选.bin-tar.gz 结尾的

二、开启 Sedona 扩展(注意细节)

解压文件

tar -zxvf apache-sedona-1.5.1-bin.tar.gz 

进去目录后你会发现一堆Jar包,看清楚了,看仔细了,别选错,选择你的 Spark 版本的那个Jar包,我们安装的是 Spark3.5.1并且用的是 Scala2.13,所以选择

在这里插入图片描述

将这个包拷贝到 Spark 安装目录下的 /jars 目录中去:

cp sedona-spark-shaded-3.5_2.13-1.5.1.jar $SPARK_HOME/jars/ 

完成这步后成功开启 Sedona 扩展

三、拷贝 spark_shuffle 相关Jar包(根据上文的配置可选)

这个问题有点打脑壳,我当时出问题了(报错详情下面报错合集中贴出来),我在 yarn-site.xml 中配置了:

<property> <name>yarn.nodemanager.aux-services</name> <value>spark_shuffle</value> </property> <property> <name>yarn.nodemanager.aux-services.spark_shuffle.class</name> <value>org.apache.spark.network.yarn.YarnShuffleService</value> </property> 

还有在 spark-defaults.conf 中配置了:

spark.shuffle.service.enabled true 

出的问题,如果这两个你都没配置,那么可以跳过这节;

解决办法:
复制 Spark 目录下的 yarn/spark-版本-yarn-shuffle.jarHadoop 目录下的 share/hadoop/yarn/下去:

cp $SPARK_HOME/yarn/spark-3.5.1-yarn-shuffle.jar $HADOOP_HOME/share/hadoop/yarn/ 

即可解决,具体错误情况看下面错误合集

六、免密登录(重要)

免密登录必须得配置,还记得我们一开始安装的 openssh-server 吗,这是一个 ssh 服务端和客户端软件,我们必须得安装 openssh-server 并且开机自启动它,因为 masterslave 之间的通信用的是 ssh 隧道;就比如启动集群,只需要在 master 上运行一个脚本,master 就会通过 ssh 隧道去访问其他节点并在其他节点上运行命令启动节点;然而 ssh 我们都知道需要密码,每次在使用 ssh 主机 的时候都需要我们手动输入一个密码,很麻烦,所以要在 master 主节点上生成一个 ssh密钥,并将这个密钥添加到 slave 工作节点中去。

主节点生成密钥:

ssh-keygen -t rsa 

会出现三次让你输入的操作,不知道是什么的话直接回车;
回车三次后会在 ~/.ssh 目录下生成一个类似 id_rsa.pub 的公钥文件,将这个文件好生保存,后续每个节点都会用到这个文件;

先将自己添加进自己的免密登录:

cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys 

这步是将免密登录的公钥添加进本机的公钥信任列表中去,让生成这个公钥的主机能够不用密码都能登录本机;

bash shell 脚本中的写法:

ssh-keygen -t rsa -f 公钥放的目录/id_rsa不要加.pub后缀 -N "" 

以后每添加一个节点,就要将这个文件拷贝到节点中,并且添加进 authorized_keys 文件

七、其他小工具

一、文件同步工具

我们每次改配置,或者文件,想把这些配置或文件同步到其他节点上很麻烦,如果只有几个节点那还好说,如果节点多了的话复制文件将会是一个很痛苦的事,所以我们便有了一个文件同步小工具—— 基于 rsync 的文件同步脚本;

首先你得安装一个 rsync:

sudo apt install -y rsync 

然后再写这个脚本,请注意,这个脚本的前置要求是:已经配置了 /etc/hosts 文件中主机与IP的对应关系
编辑一个脚本 vim xsync ,如果你不想使用的时候 xsync.sh ... 的话,不加.sh 后缀:

#!/bin/bash # 严格模式,报错就停止脚本 set -e # 辅助函数 error() { 
    echo -e "\033[31mERROR: $1\033[0m" >&2 } success() { 
    echo -e "\033[32mSUCCESS: $1\033[0m" } warning() { 
    echo -e "\033[33mWARNING: $1\033[0m" } if [ -z "$1" ]; then error "======================== 参数不能为空 ========================" exit 1 fi success "======================== 同步开始 ========================" if [ -z "$2" ]; then # 读取 /etc/hosts 文件 while IFS= read -r line; do # 使用 awk 打印每一行的第一个字段(通常是 IP 地址) ip=$(echo "$line" | awk '{print $1}') # 使用 awk 打印每一行的主机名字段 host_name=$(echo "$line" | awk '{print $2}') # 如果你的主节点不叫 master 那么改这个 master,这里是跳过本机不向本机同步文件 if [ "$host_name" == "master" ]; then continue fi # 同步文件到集群中所有机器 warning "======================== 准备同步 $1$ip ========================" rsync -av --rsync-path="sudo rsync" --inplace "$1" "$ip:$1" warning "======================== 同步完成 ========================" done < /etc/hosts else warning "======================== 准备同步 $1$2 ========================" rsync -av --rsync-path="sudo rsync" --inplace "$1" "$2:$1" warning "======================== 同步完成 ========================" fi success "======================== 同步完成 ========================" 

给这个脚本执行权限,并将这个脚本所在的路径放到 PATH 中去,那么你就可以在任何目录下都能使用这个脚本了,假设这个脚本在 /my_app/script 下:

chmod -X xsync # 给执行权限 # 将路径追加到 ~/.bashrc 中去 echo "export PATH=$PATH:/my_app/script" >> ~/.bashrc && source ~/.bashrc 

然后这个脚本我们就能使用了,格式是 xsync 要同步的文件/文件夹 主机名/IP地址(可选) ,比如我们要同步一个文件 /dir/xxx.txt,那么可以使用 xsync /dir/xxx.txt,不加主机或IP地址的话,脚本会去 /etc/hosts 文件中找主机以及IP地址,将文件全部同步到这些主机的 /dir 目录下去,如果指定了主机名/IP地址,那么只会同步到指定主机的对应目录中去

八、Dockerfile 以及我的完整搭建过程

声明:不建议用我的这个方式,因为不好用也不完善,只是我的一个实验品,这里贴出来仅供参考

一、完整的 Dockerfile

直接贴出代码:

# docker run 时需要做的事有: # 1、如果是以master启动: # # -- 那么必须指定hadoop和spark的配置文件,使用挂载将hadoop、spark的配置文件放到目录config/hadoop、config/spark下, # 挂载到/my_app/volum中,即 -v dir/:/my_app/volum/;要求dir下至少有config、config/hadoop、config/spark # 三个文件夹,并且两者的配置文件都在对应文件夹下。 # -- 其次必须指定ip --> --ip; # -- 还有网络 --> --newwork=xxx; # # 2、如果是以slave的方式启动: # # -- 必须指定主机名 --> -e HOST_NAME=xxx; # -- 必须指定master的地址 --> -e MASTER_URL=xxx # -- 必须要将master产生的公钥挂载到/my_app/volum/pubkey/中 # -- 容器的IP地址会自动获取,并且注册到master中,建议在容器启动时使用 --ip 固定 # -- 容器的hadoop、spark配置会自动从master同步 # # # 3、根据配置,需要映射端口出来,因为要使用前端管理页面,不需要可以不映射: # # 端口 #  # hdfs前端 9100 # yarn前端 9200 # spark历史服务器 9301 FROM ubuntu:latest LABEL maintainer="hyz <@.com>" # 定义容器启动时要用到的数据 # 容器启动方式,分为 master、slave方式,默认master方式 ENV BOOT_MODE=master # 配置容器的主机名,必须配置 ENV HOST_NAME=null # 如果是以slave的方式启动,则必须配置master的地址,因为要与master通信 ENV MASTER_URL=null # 容器的IP地址会自动获取,建议在容器启动时使用 --ip 固定 # 如果是以salve方式启动,则必须要将master产生的公钥挂载到/my_app/pubkey/中 # 添加用户,修改密码,创建文件夹 RUN apt update && apt upgrade -y && apt install -y unzip vim openssh-server \ sudo iproute2 iputils-ping netcat-openbsd rsync nginx cron && \ echo "@reboot /my_app/scripts/system_reboot_script.sh" > /etc/cron.d/mycron && \ crontab /etc/cron.d/mycron && rm /etc/cron.d/mycron && \ update-rc.d nginx defaults && useradd -m -s /bin/bash commonuser && \ usermod -aG sudo commonuser && \echo 'commonuser:bigdata' | chpasswd && \ echo 'root:bigdata' | chpasswd && mkdir -p /run/sshd /my_app/packages \ /my_app/apps /my_app/scripts /my_app/scripts/logs /my_app/volum/pubkey/ \ /my_app/data/hdfs/meta_data /my_app/data/hdfs/datas /tmp/spark-events # 将dockerfile同级目录下的packages中的文件拷贝 COPY ./packages/* /my_app/packages/ # 将dockerfile同级目录下的脚本文件全部拷贝到容器中去 COPY ./scripts/* /my_app/scripts/ ENV MY_SCRPTS=/my_app/scripts/ # 换文件时替换文件名,难得写自动匹配了 RUN cd /my_app/packages && tar -zxvf jdk-8u411-linux-x64.tar.gz -C /my_app/apps/ && \ mv /my_app/apps/jdk1.8.0_411 /my_app/apps/jdk8 && \ tar -zxvf hadoop-3.4.0.tar.gz -C /my_app/apps/ && mv /my_app/apps/hadoop-3.4.0/ \ /my_app/apps/hadoop340 && mkdir -p /my_app/apps/hadoop340/tmp && \ tar -zxvf spark-3.5.1-bin-hadoop3-scala2.13.tgz -C /my_app/apps/ && \ mv /my_app/apps/spark-3.5.1-bin-hadoop3-scala2.13 /my_app/apps/spark351 && \ tar -zxvf apache-sedona-1.5.1-bin.tar.gz -C /my_app/apps/ && \ mv /my_app/apps/apache-sedona-1.5.1-bin/sedona-spark-shaded-3.5_2.13-1.5.1.jar \ /my_app/apps/spark351/jars/ && rm -rf /my_app/packages/* && \ chown -R commonuser /my_app && echo "commonuser ALL=(ALL) NOPASSWD: ALL" >> \ /etc/sudoers && cp /my_app/apps/spark351/yarn/spark-3.5.1-yarn-shuffle.jar \ /my_app/apps/hadoop340/share/hadoop/yarn/ && chmod -R 777 /my_app/scripts/* ENV JAVA_HOME=/my_app/apps/jdk8 ENV JRE_HOME=$JAVA_HOME/jre ENV CLASSPATH=$JAVA_HOME/lib:$JRE_HOME/lib ENV HADOOP_HOME=/my_app/apps/hadoop340 ENV SPARK_HOME=/my_app/apps/spark351 # 将 JAVA_HOME、HADOOP_HOME、SPARK_HOME 添加到 PATH 中 ENV PATH=$PATH:$JAVA_HOME/bin:$HADOOP_HOME/bin:$HADOOP_HOME/sbin:$SPARK_HOME/bin:$SPARK_HOME/sbin:$MY_SCRPTS USER commonuser ENTRYPOINT ["/my_app/scripts/start.sh"] 
二、要用到的脚本

Dockerfile 中拷贝了 scripts 目录下的所有脚本,我都贴出来吧:

1、start.sh

#!/bin/bash # 辅助函数 error() { 
    echo -e "\033[31mERROR: $1\033[0m" >&2 } success() { 
    echo -e "\033[32mSUCCESS: $1\033[0m" } warning() { 
    echo -e "\033[33mWARNING: $1\033[0m" } # 启动ssh服务 success "=============== 启动ssh服务 ===============" sudo sshd success "=============== ssh服务启动完成 ===============" # 判断我们打的标记文件是否存在,看本容器是否已经配置过,如果没有配置过,则开始配置并且单机启动 if [ -e /my_app/_CONFIGURED ]; then # 标记文件存在,这个容器什么都不用管,master会来启动它的 if [ "$BOOT_MODE" == "master" ]; then system_reboot_script.sh fi else success "=============== 容器自动配置中 ===============" # 判断用户有没有传入我们必须的参数 if [ "$BOOT_MODE" == "slave" ]; then success "=============== 容器以salve模式启动 ===============" # 判断主机名 if [ "$HOST_NAME" == "null" ]; then error "=============== 容器初始化错误,主机名环境变量'HOST_NAME'未配置 ===============" exit 1 fi # 判断主机IP地址 #if [ "$HOST_IP" == "null" ]; then #error "=============== 容器初始化错误,主机IP地址变量' HOST_IP '未配置 ===============" #exit 1 #fi # 判断master的公钥是否提供了 if ! [ -e /my_app/volum/pubkey/id_rsa.pub ]; then error "=============== 容器初始化错误,master公钥环境变量'PUBKRY_PATH'未配置 ===============" exit 1 fi # 判断master的IP地址是否指定 if [ "$MASTER_URL" == "null" ]; then error "=============== 容器初始化错误,master的IP地址环境变量'MASTER_URL'未配置 ===============" exit 1 fi # 修改主机名 echo "$HOST_NAME" | sudo tee -a /etc/hostname # 将master生成的公钥添加进系统中 mkdir -p ~/.ssh touch ~/.ssh/authorized_keys cat /my_app/volum/pubkey/id_rsa.pub >> ~/.ssh/authorized_keys # 将自己注册到master中去,格式是主机名在前,IP地址在后 self_ip=$(ip addr show eth0 | grep inet | awk '{print $2}' | cut -d/ -f1) success "---------------- 准备向${HOST_NAME}发送同步请求,本身IP ${self_ip},master的IP ${MASTER_URL}" echo "$HOST_NAME $self_ip" | nc -q 0 "$MASTER_URL" 55180 # 工作做完了,在这里我们不配置hadoop、不配置spark,这些都统统从master同步,我们在一开始判断的那个目录下添加一个已配置的标记文件 touch /my_app/_CONFIGURED else # master 启动时要做的事 success "=============== 容器以master模式启动 ===============" # 修改主机名 echo "master" | sudo tee -a /etc/hostname #将自己的IP地址添加进hosts self_ip=$(ip addr show eth0 | grep inet | awk '{print $2}' | cut -d/ -f1) sudo touch /etc/hosts_temp echo "$self_ip master" | sudo tee /etc/hosts_temp sudo cat /etc/hosts_temp | sudo tee /etc/hosts # 生成 ssh 密钥给集群中的其他主机使用,并放到docker挂载盘下 ssh-keygen -t rsa -f ~/.ssh/id_rsa -N "" cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys sudo cp ~/.ssh/id_rsa.pub /my_app/volum/pubkey/ # 复制挂载进容器的hadoop配置文件到hadoop目录下 if [ "$(find /my_app/volum/config/hadoop -mindepth 1 -type f -print -quit)" ]; then sudo rm -rf /my_app/apps/hadoop340/etc/hadoop/* sudo cp -r /my_app/volum/config/hadoop/* /my_app/apps/hadoop340/etc/hadoop/ else error "--------- hadoop的配置文件并未挂载到容器,请使用 [ -v 所有配置文件的父路径:/my_app/volum/config/hadoop ] 将配置文件挂载到容器中" exit 1 fi # 复制挂载进容器的spark配置文件到spark配置目录下 if [ "$(find /my_app/volum/config/spark -mindepth 1 -type f -print -quit)" ]; then sudo rm -rf /my_app/apps/spark351/conf/* sudo cp -r /my_app/volum/config/spark/* /my_app/apps/spark351/conf/ else error "--------- spark的配置文件并未挂载到容器,请使用 [ -v 所有配置文件的父路径:/my_app/volum/config/spark ] 将配置文件挂载到容器中" exit 1 fi #再运行一遍所有权命令夺回用户所有权 sudo chown -R commonuser /my_app # 初始化 HDFS /my_app/apps/hadoop340/bin/hdfs namenode -format # Hadoop集群启动 /my_app/apps/hadoop340/sbin/start-dfs.sh /my_app/apps/hadoop340/sbin/start-yarn.sh # 为 Spark 创建 HDFS 中的日志目录 /my_app/apps/hadoop340/bin/hdfs dfs -mkdir -p /system/logs/spark # spark历史服务器启动 /my_app/apps/spark351/sbin/start-history-server.sh # 启动集群子节点新增监听并后台执行 touch /my_app/scripts/logs/listen_slave.log listen_slave.sh > /my_app/scripts/logs/listen_slave.log 2>&1 & touch /my_app/_CONFIGURED fi success "=============== 容器自动配置完成 ===============" fi # 保证容器不停止 tail -f /dev/null 

2、system_reboot_script.sh

#!/bin/bash # 辅助函数 error() { 
    echo -e "\033[31mERROR: $1\033[0m" >&2 } success() { 
    echo -e "\033[32mSUCCESS: $1\033[0m" } warning() { 
    echo -e "\033[33mWARNING: $1\033[0m" } if [ -e /my_app/_CONFIGURED ]; then # 系统重启时执行的任务 success "----------- 系统启动,执行自动任务 -----------" # 启动nginx success "----------- 启动nginx -----------" sudo service nginx start success "----------- nginx启动完成 -----------" # 将/etc/hosts文件替换 success "----------- 准备替换hosts文件 -----------" sudo cat /etc/hosts_temp | sudo tee /etc/hosts success "----------- hosts文件替换完成 -----------" #分发hosts文件 success "----------- 准备分发hosts文件 -----------" xsync /etc/hosts success "----------- hosts文件分发完成 -----------" # 分发hadoop配置文件 success "----------- 准备分发hadoop配置文件 -----------" xsync /my_app/apps/hadoop340/etc/hadoop/ success "----------- hadoop配置文件分发完成 -----------" # 分发spark配置文件 success "----------- 准备分发spark配置文件 -----------" xsync /my_app/apps/spark351/conf/ success "----------- spark配置文件分发完成 -----------" #启动集群 success "----------- 准备启动hadoop集群 -----------" /my_app/apps/hadoop340/sbin/start-dfs.sh /my_app/apps/hadoop340/sbin/start-yarn.sh success "----------- hadoop集群启动完成 -----------" else warning "----------- 系统未配置,自执行脚本无法执行 -----------" fi 

3、listen_slave.sh

#!/bin/bash while true; do # 监听 55180 端口,读取数据放到hosts中 nc -l -p 55180 -q 1 | while read -r name ip do # 读取数据放到hosts中 echo "" echo "======================= 新主机${name}开始注册 =======================" echo "将主机 ${name}${ip} 放到hosts文件中" sudo echo "$ip $name" | sudo tee -a /etc/hosts_temp sudo cat /etc/hosts_temp | sudo tee /etc/hosts # 读取主机名放到hadoop的workers中 echo "将主机 ${name} 放到hadoop/workers文件中" sudo echo "$name" | sudo tee -a /my_app/apps/hadoop340/etc/hadoop/workers # 读取主机名放到spark的workers中 echo "将主机 ${name} 放到spark351/conf/workers文件中" sudo echo "$name" | sudo tee -a /my_app/apps/spark351/conf/workers # 同步数据到每个集群主机中 echo "同步hosts文件" xsync /etc/hosts # 同步hadoop和spark配置到来注册的这个主机中 echo "同步hadoop配置文件" xsync /my_app/apps/hadoop340/etc/hadoop/ "$name" echo "同步spark配置文件" xsync /my_app/apps/spark351/conf/ "$name" echo "去主机 ${ip} 上启动dfs与yarn" sh commonuser@"$ip" '/my_app/apps/hadoop340/sbin/start-dfs.sh && /my_app/apps/hadoop340/sbin/start-yarn.sh' echo "======================= 主机${name}注册完成 =======================" done done 

4、xsync

这个在上面有了,目录第 节的第 个就是

三、HadoopSpark 配置文件

这个我在上面基本上把主要的配置文件代码都贴出来了,去上面看就行

四、详细解释

首先再次不推荐使用我的这个方法,这里贴出来是仅供参考,有很多地方都没有完善,bug很多

1、Dockerfile 解释

Dockerfile 一开始指了几个环境变量,作用是启动容器时动态指定一些参数,好让脚本能做一些自动化的操作
紧接着就是安装各种依赖、创建用户、创建文件夹等操作
拷贝所需的包,所以要将需要的包放到对应目录
拷贝脚本,注意脚本全部都需要可执行权限,不然容器启动时脚本执行不了
解压各种包,改名、移动、给权限等常规操作
配置环境变量
使用我们创建的普通用户登录容器
配置容器启动入口点

这个 Dockerfile就是一些常规操作,主要还是在为后面脚本的执行提供环境以及搭建基础环境

2、start.sh脚本解释

这个脚本是容器启动时自动执行的脚本,核心内容其实就是:*修改自己的主机名、*修改hosts文件、*生成 / 使用ssh密钥*复制容器外挂载进容器内部的配置文件到hadoop spark的配置目录下、*启动hdfs*启动yarn*启动历史服务器、*运行自己写的监听脚本监听子节点的加入行为 / 运行脚本将自己注册到主节点中去

3、systen_reboot_script.sh脚本解释

这个脚本其实就是开机自动启动集群。
然后因为我发现 docker 容器在重新启动后 hosts 文件会被覆写,所以将主机名信息都存到一个缓存文件中去,容器启动时将缓存文件写入hosts文件再分发给集群所有子节点。
还有因为我使用了nginx反向代理了hdfsyarn的前端页面,所以还启动了一个ngxin

4、listen_slave.sh脚本解释

这个脚本其实是我为了解决新增子节点,主节点无法发现它的问题。假如集群中又新增了一个节点,最好的办法是这个节点主动去找主节点告知自己的信息,例如IP地址、主机名等,当然,这里有其他更好的解决办法,但是我不想再麻烦安装其他软件、运行大服务了,所以写了一个脚本,开启一个端口来监听请求,有点像一个超级简易的http服务,然后收到请求后再分发配置、启动节点等操作

5、xsync上面有解释就不说了

五、执行流程

原本写了执行流程的,但是太多了又突然不想写了,写到这里都已经两万多字了,根本没人能看到这里,就算是我自己也看不到这里,所以索性都删了不写了,想知道流程的留言吧

九、错误合集

一、ERROR: Cannot set priority of namenode process xxx

1、问题解释

这个问题有点打脑壳,它出现在启动hdfs的时候:sbin/start-hdfs.sh
字面意思是无法设置namenode的进程优先级。网上给的大多数解决办法都是 kill -9 xxx,直接杀死这个进程再重启,如果你这样做了以后没问题了,那么恭喜你遇到的问题比较简单,只是单纯的占用问题;

但是你要是执行了 kill 不管用的话那么遇到的可能就是集群污染问题了(我自己取的名字,哈哈哈),就是说每一个集群在初始化时(hdfs namenode -format),都会有一个唯一的集群ID,这个集群ID标识了集群的唯一性,集群的节点之间的分片数据是一样的,namenode中存储的元数据和节点中存储的实际数据是能够对应得上的;
当你的集群中某个节点的数据,也就是你在hdfs-site.xml中配置的dfs.datanode.data.dir这个选项的目录的数据如果与其他节点不同的话,就会出现节点之间数据不统一的问题导致hdfs没能启动起来,就是说某个节点上的数据被污染了,

2、解决办法

我现在探索到两种解决办法,一种是直接重头再来,重新弄节点,但是比较憨,而且工作量很大,会丢数据。
另一种是 删除每个节点,记住,是每个节点中的hadoop日志目录和数据目录中的所有文件,不删除的话你移走也可以,数据目录是你在hdfs-site.xml中配置的dfs.datanode.data.dir,日志我忘了,反正要删除这两个,全部删除完后再去主节点中重新初始化集群:

hdfs namenode -format 
二、xxx: ERROR: Cannot set priority of nodemanager process xxx

这个看起来和上面的很像,但是不一样,这个是 nodemanager不能设置优先级,说白了这个是 yarn 的问题,上面那个是hdfs的问题;

这个问题出现在启动 yarn 的时候 —— sbin/start-yarn.sh;这个问题网上大多数人说的是配置格式写错了,你可以检查一下,如果确实是配置格式有问题,那么改一下,如果不是,听我慢慢道来;

出现这个问题一般是某个节点报错了,假如你的是 slave01: ERROR: Cannot set priority of nodemanager process 779,那你可以去这台节点上找日志文件,日志目录下有很多个文件,看 hadoop-gdyh-nodemanager-xxxxxxxx.log这个文件就行,直接看最后,绝对有报错,再根据报错解决问题,可能有很多个报错,我这里无法一一列举出来

三、org.apache.hadoop.yarn.exceptions.InvalidAuxServiceException: The auxService:spark_shuffle does not exist

如果你看了我上面的教程的话,你根本不会出现这个问题,这个问题的根本原因是包找不到。网上可能会教你在 yarn-site.xml 中添加:

property> <name>yarn.nodemanager.aux-services</name> <value>spark_shuffle</value> </property> <property> <name>yarn.nodemanager.aux-services.spark_shuffle.class</name> <value>org.apache.spark.network.yarn.YarnShuffleService</value> </property> 

可能会让你:

property> <name>yarn.nodemanager.aux-services</name> <value>spark2_shuffle</value> </property> <property> <name>yarn.nodemanager.aux-services.spark2_shuffle.class</name> <value>org.apache.spark.network.yarn.YarnShuffleService</value> </property> 

我不评价这么做对不对哈,只是我没成功,我解决的办法在上面,目录第 节的第 小节

四、Exception in thread "main" java.lang.NoClassDefFoundError: scala/collection/convert/DecorateAsJava

这个问题出现在你使用 spark-shell 命令的时候,字面意思是找不到类,如果你加上类:

spark-shell --driver-class-path $SCALA_HOME/lib/scala-library.jar 

就成功了的话,那么可以考虑是你自己安装的 scala 的问题,要不是版本不兼容,要不就是哪里的环境变量出了问题,如果 scala 不是必需的话,建议卸载掉 scala 问题就能解决

五、org.apache.spark.deploy.history.HistoryServer .... java.io.FileNotFoundException: File file:/xxx/spark-events does not exist

这个问题出现在启动 spark历史服务器 的时候,一般是它提示的这个目录不存在或者权限不够,创建目录或者添加权限:

mkdir -p /xxx chown -R 用户 /xxx 

十、最后的话

我想得起来的问题大概就这么多,两万五千字了wok,第一次写这么多字的博客,希望以上的内容对你能有所帮助,后续我能再想起点其他什么的,会回来更新这篇博客的内容;

都看到这里了,点个赞再走吧,码字不易

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/149395.html

(0)
上一篇 2025-03-24 14:05
下一篇 2025-03-24 14:10

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信