☰
Ubuntu虚拟机搭建Hadoop分布式集群:避坑全攻略
2026/10/12 23:44:51 网站建设 项目流程

简介:面向大数据初学者与开发者的Hadoop分布式环境搭建指南,聚焦Ubuntu操作系统下的实战流程。教程从虚拟机创建与系统安装讲起,依次覆盖SSH免密登录、共享文件夹挂载、JDK环境配置、Hadoop核心参数调整以及系统环境变量设置,每一步都配有步骤说明与界面截图,适合零基础用户按图索骥,也能帮助有经验者快速定位环境问题。资源包共93个文件,总体积222.16MB,主要文件类型包括Word实验文档、Java/Class程序文件、XML配置文件、PNG操作截图、JAR依赖包及说明文本,目录结构清晰,可按章节或模块单独查阅。压缩包内另附一套云计算课程设计项目及多个经典Hadoop实验源码与数据,例如倒排索引、矩阵乘法、最短路径和二次排序,可直接导入开发环境运行验证,有助于理解分布式计算原理。目前已有112人学习使用,既能支撑课程实验与期末设计,也可作为搭建生产集群前的操作参考。

1. 在 Ubuntu 虚拟机上搭 Hadoop 分布式环境:先把这条链路上的坑都标出来

很多人第一次搭 Hadoop,习惯直奔安装包,结果卡在虚拟机网络、SSH 密钥、JDK 版本这些看似外围的环节上。真正让“基于 Ubuntu 系统的 Hadoop 分布式环境搭建”耗掉一整天的,不是 Hadoop 本身,而是虚拟机网络模式选错、SSH 免密登录不通、共享文件夹挂载不上、环境变量设置不生效这四件事。这篇笔记按我实际搭建的顺序写:从虚拟机安装 Ubuntu 开始,到 SSH 免密登录、共享文件夹挂载、JDK 环境配置,再到 Hadoop 安装与配置和环境变量设置,最后落到大数据处理平台的启动验证。适合两类人:想低成本在笔记本上复现分布式集群的初学者,以及要在多台虚拟机上快速搭可复现实验环境的一线工程师。每个参数我都会给出具体值,踩过的坑也会单独列一章。

2. 虚拟机安装 Ubuntu 与共享文件夹挂载:先打通宿主机到集群的数据通道

2.1 为什么选 VMware + Ubuntu 搭 Hadoop:网络与资源的取舍要先想清楚

搭建 Hadoop 分布式环境,最常见的载体就是 VMware 虚拟机里的 Ubuntu。原因很直接:Hadoop 的官方支持列表、社区教程、排错经验大部分都围绕 Linux 展开,Ubuntu 又恰好是包管理最省心的发行版。用虚拟机而不是物理机,是因为分布式集群最少要两个节点,普通笔记本没那么多闲置机器,VMware 克隆虚拟机、改主机名、配置静态 IP 都很快。另一个好处是快照:在格式化 NameNode 之前打一个快照,配置写崩了直接回滚,不用重装系统。

选 VMware 还是 VirtualBox,我的判断标准是“共享文件夹和拖拽复制哪边更省事”。VMware Workstation 的共享文件夹走 vmhgfs-fuse,配合 open-vm-tools,宿主机和虚拟机之间的数据交换非常直接;VirtualBox 的增强功能也能用,但在 Ubuntu 内核升级后偶尔要重编模块。所以下面都以 VMware Workstation + Ubuntu Desktop 为例,服务器版没有图形界面,共享文件夹和桌面工具链反而绕路。

资源分配上,我一般给每台虚拟机 4GB 内存、2 核 CPU、40GB 磁盘。这个配置跑 Hadoop 3.x 加 YARN 的 MapReduce 任务不会太局促。内存别小于 2GB,否则 JVM 一启动,机器就开始疯狂换页,DataNode 放到一半就断连。磁盘大小也别抠,HDFS 的副本机制会把数据复制三份,磁盘不够时看起来是“服务都在,Datanode 日志里全是写入失败”。

2.2 VMware 安装 Ubuntu 的系统参数与网络模式:NAT 还是桥接直接决定免密登录顺不顺

虚拟机创建阶段有三个关键参数:网络模式、虚拟机内存、磁盘类型。网络模式里,我强烈建议用 NAT。桥接模式会让虚拟机直接暴露在局域网里,IP 是路由器分配的,如果你办公室的 Wi-Fi 做了 AP 隔离,多台虚拟机之间互相 ping 不通,SSH 免密登录自然就失败。NAT 模式下 VMware 会在宿主机上模拟一个虚拟交换机,多台虚拟机之间不管宿主机有没有网都能互通,这对本机搭 Hadoop 集群是最稳的选择。

创建虚拟机时按下面这张表填参数即可,不需要额外装 VMware Tools,Ubuntu 桌面版装完后执行 apt install open-vm-tools open-vm-tools-desktop 就能实现窗口自适应和共享文件夹功能。

配置项推荐值说明
网络模式NAT保证虚拟机间互通,不受外部网络影响
内存4096 MBHadoop 各进程 JVM 默认堆内存加起来接近 3GB
处理器2 核不需要更多,学习环境 CPU 不是瓶颈
磁盘40 GB,单文件快照和克隆更稳定
Ubuntu 版本22.04 LTS 或 20.04 LTS内核新一点,open-vm-tools 兼容性更好

安装 Ubuntu 的流程里唯一要注意的是磁盘分区,选“Erase disk and install Ubuntu”即可,让安装器自动用 LVM。Hadoop 的数据目录放在哪个分区下面不重要,重要的是不要把 /tmp 单独分太小的区,因为我的教程里会把 Hadoop 存储目录放到 /usr/local/hadoop,而 /usr/local 在默认分区大小下装完 JDK 和 Hadoop 可能只剩几个 GB,保险起见把逻辑卷容量稍微加大。

装完第一台虚拟机后,先别急着装 Hadoop,立刻做两件事:设置静态 IP 和修改主机名。Ubuntu 18.04 之后网络配置走 netplan,我一般直接改 /etc/netplan/01-network-manager-all.yaml,把 DHCP 改成手动。各台虚拟机的 IP 我习惯按节点角色分配:master 用 192.168.30.100,slave1 用 192.168.30.101,slave2 用 192.168.30.102。修改好之后执行 sudo netplan apply,再用 ping 192.168.30.100 验证。主机名改到 /etc/hostname 和 /etc/hosts,后面 SSH 和 Hadoop 配置都会直接依赖主机名,这一步不做后面全是坑。

2.3 共享文件夹挂载:vmhgfs-fuse 挂载宿主机目录到 /mnt/hgfs

共享文件夹是这套环境里最实用的功能。我会在宿主机上建一个 ~/data 目录,把要导入 HDFS 的数据放进去,虚拟机里直接读取,不用每次都通过 scp 拷贝。VMware 里先在“虚拟机设置 – 选项 – 共享文件夹”里启用共享,添加宿主机目录,然后安装 open-vm-tools,用下面这组命令测试挂载:

# 查看 VMware 共享文件夹列表,确认宿主机目录已经传给虚拟机 vmware-hgfsclient # 创建挂载点 sudo mkdir -p /mnt/hgfs # 挂载共享目录,.host:/ 表示宿主机所有共享根目录 sudo vmhgfs-fuse .host:/ /mnt/hgfs -o allow_other -o uid=1000,gid=1000 # 检查是否挂载成功 ls /mnt/hgfs

vmware-hgfsclient 这条命令是给 open-vm-tools 用的,如果输出为空,说明共享文件夹没有开启,或者虚拟机还没加载到配置。挂载命令里 .host:/ 是固定写法,表示取宿主机共享根目录,也可以写成 .host:/data 直接挂载某个具体目录。allow_other 参数非常关键,不加它,只有 root 用户能访问挂载点,Hadoop 的普通用户读写共享目录时会出现 Permission denied。uid 和 gid 我直接写成当前用户的 id,避免用 root 挂载后普通用户往共享文件夹写入时报权限错误。

ls /mnt/hgfs能列出宿主机目录,说明挂载成功。但这个挂载是临时的,虚拟机重启后还会丢。要让共享文件夹开机自动挂载,我一般把挂载命令写进 /etc/fstab。fstab 里这一行容易写错,我先把完整格式放出来:

# 在 /etc/fstab 末尾追加这一行 .host:/ /mnt/hgfs fuse.vmhgfs-fuse allow_other,uid=1000,gid=1000,defaults 0 0

fstab 挂载 vmhgfs 有一个需要注意的点:系统启动时网络和 vmhgfs 模块的加载顺序不一定稳定,偶尔会挂载失败。更稳妥的做法是写一个 systemd 服务,在 multi-user.target 启动后执行挂载命令。我把这个方案放在后面的避坑章节里讲。共享文件夹本身只是数据通道,不要在挂载目录里直接跑 Hadoop 的临时文件,HDFS 目录和日志一定要独占磁盘,否则 vmhgfs 的 FUSE 延迟会让 NameNode 的读写超时。

3. SSH 免密登录与 JDK 环境配置:把节点间的信任关系和 Java 底座一次搞定

3.1 分布式集群为什么必须依赖 SSH 免密:HDFS 的远程执行机制

Hadoop 集群启动时,NameNode 需要远程启动所有 DataNode 进程,ResourceManager 需要远程启动 NodeManager。这里的“远程启动”本质上靠 SSH 执行命令。如果每次连接都要求输入密码,start-dfs.sh 会在每个节点前停下来等密码,分布式集群完全无法实现一键启动。这就是必须配 SSH 免密登录的原因。

SSH 免密登录的核心是密钥对:每台机器生成一对公钥和私钥,把公钥分发到需要被登录的机器的 ~/.ssh/authorized_keys 文件里。登录时客户端用自己的私钥签名,服务端用存放的公钥验签,验签通过就放行。整个过程不传输密码,也就避免了密码泄露的风险。集群规模不大时,我会采用最朴素的做法:master 生成一对密钥,把公钥分发给所有节点,包括 master 自己。这样从 master 上 ssh slave1、ssh slave2 都不需要密码。

3.2 生成密钥对并批量分发:从 Master 到 Slave 一条命令打通

我习惯在 master 节点上统一生成密钥对,不搞“每台机器各自生成再互发”的复杂流程。因为学习环境的信任模型很简单:master 是控制端点,从 master 能免密登录所有节点就够了。执行下面的命令:

# 在 master 节点执行;-t 指定算法,-N 表示空 passphrase,-f 指定输出路径 ssh-keygen -t rsa -b 4096 -N "" -f ~/.ssh/id_rsa # 把公钥追加到 master 自己的 authorized_keys,并设置正确权限 cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys chmod 700 ~/.ssh chmod 600 ~/.ssh/authorized_keys # 将公钥分发到 slave1 和 slave2,命令执行过程中输入一次密码即可 ssh-copy-id -i ~/.ssh/id_rsa.pub hadoop@192.168.30.101 ssh-copy-id -i ~/.ssh/id_rsa.pub hadoop@192.168.30.102

生成密钥时我把 passphrase 设为空,这是不得已的取舍。带 passphrase 的私钥安全性更好,但每次 SSH 连接都要输入私钥口令,start-dfs.sh 依然会被卡住。测试环境用空 passphrase 是主流做法,生产环境则应改用 SSH agent 或者更严格的安全机制。rsa 和 ed25519 都可以,我选 rsa 一是兼容性好,二是个别 Hadoop 相关脚本在特殊环境下对 ed25519 的识别会有问题。chmod 700 和 chmod 600 这两条权限设置不能跳过,sshd 对 authorized_keys 权限有严格校验,权限过宽会直接忽略这个文件。

批量分发完成后,用 ssh slave1 hostname 验证一下。第一次连接会出现 yes/no 指纹确认,输入 yes 回车,之后就不会再问。如果这一步报 Permission denied,最常见原因是分发公钥时写到了别的用户目录下,比如当前用户是 hadoop,ssh-copy-id 却连到了 root 用户,导致公钥落在 /root/.ssh/authorized_keys。后面避坑章节会专门讲权限问题。

3.3 JDK 安装:OpenJDK 8 还是 11,版本坑比你想的多

Hadoop 是 Java 写的,JDK 版本直接决定能不能启动。Hadoop 3.x 官方支持 Java 8 和 Java 11,社区里跑得最稳的组合是 OpenJDK 8。我遇到过有人装 JDK 17 然后 HDFS 启动直接抛 UnsupportedClassVersionError 的情况,这不是 Hadoop 不支持新特性,而是编译字节码版本超过了 JVM 能加载的版本。用 Ubuntu 的 apt 装 OpenJDK 11 是一个不折腾的选择:

# 更新软件源索引并安装 OpenJDK 11 sudo apt update sudo apt install -y openjdk-11-jdk # 查看安装路径,后续 JAVA_HOME 要用 which java sudo update-alternatives --config java

update-alternatives --config java 会列出所有已安装的 JDK 以及各自的路径。Ubuntu 上 OpenJDK 默认装在 /usr/lib/jvm/java-11-openjdk-amd64,这个路径待会要直接写进 hadoop-env.sh。安装完成后运行 java -version 确认版本,如果系统里同时装了 JRE 和多个 JDK,一定要检查默认 java 指向的是哪个,Hadoop 脚本认的是 JAVA_HOME 而不是 PATH 里的 java。

为什么不用 Oracle JDK?因为 Hadoop 学习环境用不上 Oracle JDK 的商用特性,OpenJDK 和它在 Hadoop 场景下几乎没有性能区别。装 Oracle JDK 反而要手工解压、配置软链接,多一堆麻烦。如果你所在的公司强制要求 Oracle JDK,至少选择 8u201 以上版本,避免 2019 年之前版本里的 Java 序列化漏洞影响 Hadoop IPC 通信。

3.4 环境变量设置与 PATH 生效:JAVA_HOME 别只写在终端里

JDK 装好后,要把 JAVA_HOME、PATH、HADOOP_HOME 写进 ~/.bashrc。有一个常见误区:只在当前终端 export JAVA_HOME,然后换一个终端就失效了。正确做法是写进用户的环境变量文件,让每个新开的 shell 都自动加载。我通常用下面这段:

# 编辑 ~/.bashrc,在文件末尾追加 cat >> ~/.bashrc << 'EOF' export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64 export PATH=$JAVA_HOME/bin:$PATH export HADOOP_HOME=/usr/local/hadoop export PATH=$HADOOP_HOME/bin:$HADOOP_HOME/sbin:$PATH export HADOOP_CONF_DIR=$HADOOP_HOME/etc/hadoop EOF # 使配置立即生效 source ~/.bashrc # 验证三个关键变量 echo $JAVA_HOME echo $HADOOP_HOME java -version

HADOOP_HOME 指向 Hadoop 解压目录,我统一放在 /usr/local/hadoop,避免不同用户目录下的路径把环境变量搞乱。PATH 里同时加 bin 和 sbin,bin 提供 hdfs、yarn 命令,sbin 提供 start-dfs.sh、stop-dfs.sh 等启动脚本。HADOOP_CONF_DIR 设成 etc/hadoop,后面写配置文件时,Hadoop 会优先读取这个环境变量指向的目录。

这里有个坑:source ~/.bashrc 只在当前 shell 生效。之后如果通过 SSH 免密远程执行启动命令,SSH 连接是非交互式 shell,默认不会加载 .bashrc。到时候从 master SSH 到 slave 去执行 hadoop-daemon.sh,可能提示 command not found。所以环境变量不仅要写进 ~/.bashrc,我还会确认 /etc/profile.d/ 下有一个全局的 hadoop.sh,或者直接把这些 export 写进 /etc/profile,保证 SSH 非交互登录也能加载。具体操作我会在 Hadoop 配置章节一并给出。

4. Hadoop 安装与配置:改好 5 个文件,启动真正的分布式集群

4.1 安装包解压与安装目录规划:用户、目录权限、版本一致性一次说清

Hadoop 的安装目录规划看起来简单,但目录权限问题经常会引发 DataNode 启动失败。我建议把所有 Hadoop 相关文件都放在 hadoop 用户下,避免用 root 启动 HDFS。HDFS 进程如果以 root 身份运行,DataNode 写入数据块目录时会把目录所有者变成 root,下次用普通用户启动就直接抛权限异常。新建一个专门的用户,然后解压安装包:

# 创建 hadoop 用户并设置密码,后面所有节点都用这个用户 sudo useradd -m hadoop -s /bin/bash sudo passwd hadoop # 解压 Hadoop 二进制包到 /usr/local,并统一目录名 sudo tar -zxf /mnt/hgfs/software/hadoop-*.tar.gz -C /usr/local sudo mv /usr/local/hadoop-* /usr/local/hadoop # 把 /usr/local/hadoop 的所有者改为 hadoop 用户 sudo chown -R hadoop:hadoop /usr/local/hadoop # 创建 HDFS 数据目录和临时目录 sudo mkdir -p /data/hdfs/namenode /data/hdfs/datanode /data/hdfs/tmp sudo chown -R hadoop:hadoop /data/hdfs

这里把 hadoop 的 tgz 包放在共享文件夹 /mnt/hgfs/software 下,正好用上了第二章挂载的共享目录。所有节点必须使用同一个 Hadoop 版本,版本不一致会让 DataNode 与 NameNode 之间的 RPC 协议不兼容,表现为 DataNode 注册失败。存储路径我不放在默认的 /tmp/hadoop-* 下,因为 /tmp 目录在系统重启后可能被清理,NameNode 的持久化文件一旦丢失,整个集群就失去元数据。

目录规划里,namenode 和 datanode 分目录存放,tmp 目录是 Hadoop 的默认临时目录。等会配置 core-site.xml 里的 hadoop.tmp.dir 时,我直接指向 /data/hdfs/tmp,NameNode 的格式化文件也会写到这个目录下面。

4.2 core-site.xml 与 hdfs-site.xml:NameNode 地址、数据目录与副本数

进入 /usr/local/hadoop/etc/hadoop,先改 core-site.xml。这个文件决定文件系统入口,也就是客户端连接 HDFS 时该找谁。分布式模式下,NameNode 的地址必须用 master 节点的主机名加 8020 端口。配置如下:

<!-- core-site.xml:核心文件系统参数 --> <configuration> <!-- NameNode 的 RPC 通信地址,hadoop-master 是主机名 --> <property> <name>fs.defaultFS</name> <value>hdfs://hadoop-master:8020</value> </property> <!-- Hadoop 运行时临时目录,必须提前创建好 --> <property> <name>hadoop.tmp.dir</name> <value>/data/hdfs/tmp</value> </property> <!-- 控制 HDFS 文件系统的缓冲区大小,默认 4KB 太小 --> <property> <name>io.file.buffer.size</name> <value>131072</value> </property> </configuration>

fs.defaultFS 这个值决定了你在任意一台节点上执行 hdfs dfs -ls / 访问的是哪套文件系统。所有节点都要配置成 hdfs://hadoop-master:8020,不能写成 localhost,否则 DataNode 向 localhost 注册会连到本机,集群就变成单点。hadoop.tmp.dir 直接影响 NameNode 的 fsimage 和 DataNode 的 data 目录位置,我用固定路径避免系统 /tmp 清理。

接着改 hdfs-site.xml,这里面重点是副本数和 NameNode/DataNode 数据目录:

<!-- hdfs-site.xml:HDFS 存储与副本策略 --> <configuration> <!-- 副本数设置为 2,三节点集群能容忍一台机器故障 --> <property> <name>dfs.replication</name> <value>2</value> </property> <!-- NameNode 元数据存储目录 --> <property> <name>dfs.namenode.name.dir</name> <value>/data/hdfs/namenode</value> </property> <!-- DataNode 数据块存储目录,多个目录用逗号分隔 --> <property> <name>dfs.datanode.data.dir</name> <value>/data/hdfs/datanode</value> </property> <!-- 关闭 HDFS 权限检查,学习环境省去大量排错 --> <property> <name>dfs.permissions.enabled</name> <value>false</value> </property> </configuration>

dfs.replication 默认是 3,集群一共三个节点,理论上可以设为 3。但我建议设成 2,因为副本数设 3 时,一个节点宕机后 HDFS 会把这个节点上的副本判定为丢失并立即开始复制补副本,在只有 3 个节点的小集群里容易触发集群进入安全模式的连锁反应。设成 2 反而更稳,既能覆盖单点故障,又不会让补副本机制在资源不足时反复挣扎。dfs.permissions.enabled 在生产环境不要关,但在学习环境关掉能少踩很多 ACL 的坑,后面避坑章节会提到。

4.3 yarn-site.xml 与 mapred-site.xml:资源调度和计算框架的绑定

YARN 负责资源调度,mapred-site.xml 负责 MapReduce 运行时框架。先看 yarn-site.xml,关键是把 ResourceManager 所在节点指到 master,并告诉 NodeManager 如何聚合日志:

<!-- yarn-site.xml:YARN 资源调度核心参数 --> <configuration> <!-- ResourceManager 所在节点主机名 --> <property> <name>yarn.resourcemanager.hostname</name> <value>hadoop-master</value> </property> <!-- NodeManager 依赖的辅助服务 --> <property> <name>yarn.nodemanager.aux-services</name> <value>mapreduce_shuffle</value> </property> <!-- shuffle 服务的类名,MapReduce 任务数据传输的关键 --> <property> <name>yarn.nodemanager.aux-services.mapreduce_shuffle.class</name> <value>org.apache.hadoop.mapred.ShuffleHandler</value> </property> <!-- 每个容器允许的最大内存,默认值在小内存虚拟机上容易 OOM --> <property> <name>yarn.nodemanager.resource.memory-mb</name> <value>3072</value> </property> </configuration>

yarn.resourcemanager.hostname 如果漏配,ResourceManager 会绑定到每个节点自己的主机名,从 slave 上提交作业时会发现连不上 ResourceManager。aux-services 这段是 MapReduce 跑在 YARN 上的关键,漏掉后作业提交会在 Running 状态卡住,日志报 ShuffleHandler 找不到。内存参数我给 3072MB,这是根据每台虚拟机 4GB 内存估出来的,留 1GB 给操作系统和 HDFS。如果机器只有 2GB 内存,这里要降到 2048,并同步调整下面的 mapred-site.xml。

mapred-site.xml 在 Hadoop 2.x 之后默认没有这个文件,只有一个 mapred-site.xml.template,需要先复制一份:

# 复制模板为正式配置文件 cp /usr/local/hadoop/etc/hadoop/mapred-site.xml.template \ /usr/local/hadoop/etc/hadoop/mapred-site.xml
<!-- mapred-site.xml:指定 MapReduce 运行在 YARN 之上 --> <configuration> <!-- 运行框架必须是 yarn,否则 MR 任务会跑在独立模式 --> <property> <name>mapreduce.framework.name</name> <value>yarn</value> </property> <!-- Map Task 最大堆内存,必须小于 YARN 容器内存 --> <property> <name>mapreduce.map.memory.mb</name> <value>1024</value> </property> <!-- Reduce Task 最大堆内存 --> <property> <name>mapreduce.reduce.memory.mb</name> <value>1024</value> </property> </configuration>

mapreduce.framework.name 不设成 yarn,作业提交后会尝试在本地跑 MapReduce,你会发现明明启动了三个节点,作业却只在提交的那台机器上执行。map 和 reduce 的内存分别设成 1024MB,两个任务并发时占用 2GB,加上 HDFS 和系统本身,4GB 虚拟机能跑但不能同时开太多作业。如果是学习环境经常报“Container is running beyond physical memory limits”,优先检查这里而不是 YARN 的总内存。

4.4 hadoop-env.sh 与环境变量:JAVA_HOME 必须写成绝对路径

/etc/hadoop/hadoop-env.sh 里有一个被无数人忽略的配置。文件开头的注释里写了 JAVA_HOME 的示例,默认是 export JAVA_HOME=${JAVA_HOME},这会把系统里 PATH 能找到的 java 当作 JAVA_HOME。问题是 SSH 非交互登录拿不到 ~/.bashrc 里的 export,就会变成空的,然后 Hadoop 启动时找不到 java。我把 JAVA_HOME 直接写成绝对路径:

# 编辑 $HADOOP_HOME/etc/hadoop/hadoop-env.sh,找到 JAVA_HOME 行改为: export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64

同时把上一章说的全局环境变量写进 /etc/profile.d/hadoop.sh,这样即使 SSH 远程执行启动脚本,也能找到 hadoop 和 yarn 命令:

# 创建 /etc/profile.d/hadoop.sh,所有用户登录时都会加载 sudo tee /etc/profile.d/hadoop.sh > /dev/null <<'EOF' export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64 export HADOOP_HOME=/usr/local/hadoop export PATH=$JAVA_HOME/bin:$HADOOP_HOME/bin:$HADOOP_HOME/sbin:$PATH export HADOOP_CONF_DIR=$HADOOP_HOME/etc/hadoop EOF # 切到 hadoop 用户后重新登录或执行 source source /etc/profile.d/hadoop.sh

hadoop-env.sh 里还可以顺手把 HDFS 的 JVM 参数加一点。默认情况下 HDFS 各进程的堆内存根据机器内存自动计算,但虚拟机的可用内存会被 YARN 吃掉,NameNode 经常拿不到足够堆内存。生产环境会单独配 HADOOP_NAMENODE_OPTS 和 HADOOP_DATANODE_OPTS,学习环境保持默认即可,遇到频繁 GC 导致 NameNode 进入安全模式再加。

4.5 格式化与首次启动:先启动 DFS,再启动 YARN

所有配置完成后,第一件不能省的事是格式化 NameNode。格式化会生成集群 ID 和文件系统元数据。但在多节点集群里,我必须提醒你:格式化只需要在 master 上执行一次,不要对已经跑过的集群反复格式化,否则 DataNode 的 clusterID 会和 NameNode 不一致,这是后面避坑章节第一个要讲的大坑。

# 在 master 节点格式化 NameNode hdfs namenode -format # 启动 HDFS start-dfs.sh # 启动 YARN start-yarn.sh # 在 master 上查看进程 jps

start-dfs.sh 会读取 /etc/hadoop/workers 文件,这个文件写每台从节点的主机名。Hadoop 3.x 里它叫 workers,Hadoop 2.x 叫 slaves。文件里要写全集群所有节点,包括 master 自己。写完后每行一个主机名,不要带多余空格和注释:

# 编辑 $HADOOP_HOME/etc/hadoop/workers hadoop-master hadoop-slave1 hadoop-slave2

jps 命令输出里,master 上应该有 NameNode、SecondaryNameNode、ResourceManager,slave 上应该有 DataNode 和 NodeManager。看到这些进程后,再执行 hdfs dfsadmin -report 检查 DataNode 是否都注册成功。如果 jps 里少了进程,第一件事是去看 $HADOOP_HOME/logs/ 下对应进程的日志,比如 hadoop-hadoop-datanode-hadoop-slave1.log。这个日志文件是排错的第一现场,比网上搜报错更直接。

5. 避坑:Ubuntu 下 Hadoop 环境搭建频率最高的 6 个故障点

5.1 SSH 免密失败:Permission denied (publickey,password)

现象:在 master 上执行 ssh hadoop-slave1,输入密码后能连上,但 ssh-copy-id 之后再用密钥连接仍然提示 Permission denied (publickey)。检查 ~/.ssh/authorized_keys 文件,公钥确实在里面。

原因:~/.ssh 目录或 authorized_keys 文件的权限过宽。sshd 要求用户主目录是 755、.ssh 是 700、authorized_keys 是 600,任何一项多了组用户和其他用户的可写权限,OpenSSH 出于安全考虑就会拒绝使用这个密钥文件。

解决:在每台被登录的节点上执行 chmod 700 ~/.ssh && chmod 600 ~/.ssh/authorized_keys。如果是 root 用户被拒绝,还要检查 /etc/ssh/sshd_config 里是否设置了 PermitRootLogin no。另外别忽略主机名解析因素:/etc/hosts 里必须把 hadoop-slave1 映射到正确 IP,否则 SSH 可能连到了同名但不同 IP 的机器。

5.2 NameNode 格式化失败:目录已存在且非空

现象:第二次执行 hdfs namenode -format 时报错,日志显示 NameNode is already formatted,或者是 namenode 目录下存在 current/VERSION 文件无法覆盖。

原因:第一次格式化已经把集群 ID 写入 /data/hdfs/namenode/current。Hadoop 3.x 会拒绝在非空目录里重复格式化,防止误操作把现有集群元数据清掉。很多人想通过重复格式化解决 DataNode 注册问题,结果越格式化越乱。

解决:在确定要重建集群时,手动删除所有 HDFS 数据目录再重新格式化,顺序是:stop-all.sh 停掉所有进程,删除 /data/hdfs/namenode 和 /data/hdfs/datanode 下的全部内容,然后在 master 上执行 hdfs namenode -format。不要用 -force 参数跳过检查,因为你真正要解决的不是 NameNode 目录,而是 NameNode 与 DataNode 之间 clusterID 不一致的问题。

5.3 DataNode 进程消失:Incompatible clusterID 与注册失败

现象:集群启动后 master 上的 jps 有 NameNode,slave 上的 jps 没有 DataNode,查看日志报 java.io.IOException: Incompatible clusterIDs,或者 DataNode 反复连接 NameNode 失败。

原因:DataNode 的 data 目录里保存了它第一次连接 NameNode 时拿到的 clusterID。如果 NameNode 被重新格式化过,clusterID 变了,旧 DataNode 就不认账。常见场景是:第一次格式化后启动过集群,后来因为某个配置错误重新格式化,却没有清掉 DataNode 的数据目录。

解决:停止所有服务后,登录每个 slave 节点,删除 /data/hdfs/datanode 下的所有文件和目录,保留空目录即可,然后重新启动 HDFS。DataNode 启动时会从 NameNode 获取新的 clusterID 并写入自己的数据目录。如果是学习环境,直接删除所有节点的 /data/hdfs 下内容是唯一最省事的办法。

5.4 JDK 版本不兼容:UnsupportedClassVersionError 和 GC 配置报错

现象:Hadoop 启动脚本执行到一半报 UnsupportedClassVersionError,或者 NameNode 进程启动后数十秒内就退出,日志里出现 Java HotSpot 无法识别的 VM 参数。

原因:Hadoop 3.3 用 Java 11 编译,如果系统默认 Java 是 8,版本低的一方无法读取高版本字节码;反过来 Hadoop 3.1 用 Java 8 编译,在 Java 17 上运行时又会出现某些反射或模块访问被拒的错误。日志里的 VM 参数报错,则是 Hadoop 脚本检查到 Java 版本后设置了不兼容的 GC 参数。

解决:统一集群所有节点的 JDK 版本,我自己的选择是 OpenJDK 11。在每一台节点上执行 java -version 确认默认版本,再确认 hadoop-env.sh 里 JAVA_HOME 是绝对路径。如果系统里同时有多个 JDK,用 update-alternatives --config java 把默认版本切到 11。这个故障最隐蔽的地方在于:有时 master 是 JDK 11,slave 是 JDK 8,start-dfs.sh 在 master 上能启动,但 slave 的 DataNode 就是起不来,一定要逐台检查。

5.5 共享文件夹挂载后目录为空或写入失败

现象:/mnt/hgfs 存在,但 ls 看不到任何共享目录;或者能看到目录,但创建文件时报 Operation not permitted。

原因:虚拟机开机后 vmhgfs-fuse 没有自动挂载,或者 VMware 共享文件夹设置没有被 open-vm-tools 识别。写入失败则多半是挂载时没加 allow_other 和 uid/gid 参数,导致挂载点归 root 所有,其他用户无法写入。

解决:先执行 vmware-hgfsclient 确认虚拟机确实能看到共享目录,再手动执行 vmhgfs-fuse .host:/ /mnt/hgfs -o allow_other,uid=1000,gid=1000。如果手动挂载成功,再考虑开机自动挂载。开机自动挂载我推荐 systemd 而不是 fstab,因为 vmhgfs-fuse 依赖 open-vm-tools 的服务,systemd 可以设置 After=open-vm-tools.service。写一个 /etc/systemd/system/mnt-hgfs.service,ExecStart 填完整的挂载命令,enable 后重启测试。不要直接把共享文件夹挂载为 HDFS 数据目录,FUSE 文件系统和本地 xfs/ext4 在写入语义上有差异,HDFS 在这种文件系统上跑容易出奇奇怪怪的延迟。

5.6 虚拟机重启后环境变量与服务全部丢失

现象:虚拟机重启后,手动执行 hadoop version 提示 command not found,start-dfs.sh 也找不到,之前明明配置过环境变量。

原因:环境变量写在了 ~/.bashrc 里,但 Hadoop 节点间远程执行启动命令走的是 SSH 非交互 shell,默认不加载 ~/.bashrc。另外如果使用图形界面重启,桌面进程继承了 gnome-session 的环境变量,而终端新开的 shell 又重新加载了一次 bashrc,造成“有的终端有命令,有的终端没有命令”的割裂感。

解决:把环境变量统一放到 /etc/profile.d/hadoop.sh,这个文件在登录 shell 和 SSH 非交互登录时都会被 /etc/profile 间接加载。然后确认各节点 /etc/hosts 里的主机名映射不依赖于 DHCP 分配的动态 IP。虚拟机重启后 IP 可能变化,而 Hadoop 配置里全是主机名,IP 一变,节点间通信就断。最稳的办法是把所有虚拟机网络改为 NAT 模式下的固定 DHCP 保留地址,或者在 netplan 里配置静态 IP。

6. 集群配好了先别急着提交作业:一套从进程到数据读写的验收清单

环境搭建完成不是“能启动就行”,我会强制自己跑完一整套验收流程,再判断环境是否真正可用。第一层是进程检查,用 jps 看每个节点进程是否齐全。我不只盯着 master 看,而是分别 ssh 到每台 slave 上执行 jps,确认 DataNode 和 NodeManager 都存在。这一步能顺手验证 SSH 免密登录在非交互 shell 下是否正常,如果哪台机器需要手动输入密码才能执行 jps,说明前面配置的 profile.d 环境变量或 SSH key 还没覆盖全。

第二层是 Web UI。HDFS 的 NameNode 页面在 http://hadoop-master:9870,YARN 的 ResourceManager 页面在 http://hadoop-master:8088。浏览器访问时,把地址里的主机名换成宿主机能解析到的 IP,192.168.30.100:9870。这两个页面打开后,重点看 Datanode 列表里的节点数和 Live Nodes 是否等于 workers 文件里的节点数,以及 YARN 页面的 Active Nodes 数。如果页面能打开但节点数少了,说明网络通信没问题,问题出在注册和端口上。

第三层是实际数据读写。我习惯先创建一个测试目录,再上传一个文件,然后下载回来比对 MD5。HDFS 的数据块分布在多个节点上,这一步能同时验证客户端、NameNode、DataNode 三方协作是否正常:

hdfs dfs -mkdir -p /test hdfs dfs -put /mnt/hgfs/sample_data.log /test/sample_data.log hdfs dfs -cat /test/sample_data.log | head -n 5 hdfs dfs -get /test/sample_data.log /tmp/sample_data_backup.log md5sum /mnt/hgfs/sample_data.log /tmp/sample_data_backup.log

MD5 不一致说明数据在写入或传输过程中出了问题,最常见的元凶是副本数配置和 DataNode 磁盘目录权限冲突。这一套跑完后,我还会提交一个 WordCount 作业,验证 MapReduce 的完整调度链路。WordCount 的 jar 在 Hadoop 安装包的 share/hadoop/mapreduce 目录下,提交命令参考官方示例,参数里指定输入输出路径。注意输出路径必须事先不存在,否则作业会直接报 FileAlreadyExistsException。第一次作业运行会从 HDFS 拉一堆依赖 jar,日志看似卡住,其实是在初始化,容器启动后自然加速。

最后一个技巧是打快照。在格式化 NameNode 之前,给每台虚拟机打一个干净快照。后续配置调坏、想重新拉集群,直接回滚到快照,比重新装系统省半个小时。我自己搭测试集群时也常用 Cloudera 或 Apache 发行版,但基于 Ubuntu 手工搭一遍能让你把配置文件的参数真正理解透。遇到玄学问题就比着日志一步步查,不要急着重装,Hadoop 的日志已经把答案写得很清楚了。希望这些步骤和踩坑记录对你有用,环境跑通了之后,你自然会开始琢磨副本摆放策略和参数调优,那时候这套环境就是你的实验场。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询