ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

从零搭建Hadoop完全分布式集群:Linux最小化安装实战指南

从零搭建Hadoop完全分布式集群:Linux最小化安装实战指南

在我负责的某套大数据平台初期建设里,Hadoop完全分布式集群的规划与落地是最基础、也最容易出问题的环节。当时用的正是Linux最小安装版本,没有图形界面、没有额外服务,所有基础依赖都要靠手工补齐,整个过程踩了不少坑,也积累了相当多一手经验。这篇文章就把整套搭建流程从头梳理一遍,围绕“最小安装”这个前提,把网络配置、JDK环境、SSH免密、Hadoop核心配置、集群启动与验证、故障排查这些环节完整呈现出来。如果你正准备在洁净的Linux环境里手工搭一套多节点Hadoop集群,或者搭过但总在某些环节卡住,这篇文章可以直接作为操作手册参考。

1. 整体设计思路:为什么用最小安装,以及如何规划完全分布式集群

1.1 最小安装模式的实际价值

很多人在学习Hadoop时习惯用带桌面环境的Linux发行版,或者干脆在物理机上装双系统。但到了真实的机房部署或虚拟机集群环境,你会发现服务器供应商给的镜像几乎都是最小化安装,没有X Window、没有自带JDK、连网络工具都要手动补。使用最小安装系统搭建Hadoop集群,最大的意义在于逼着你理解集群环境的每一层依赖,而不是依赖图形界面点选,掩盖了底层的配置细节。

最小安装模式下,系统只保留最基础的运行库和命令行工具,这意味着:

  • 不会有桌面包等无用进程占用宝贵的内存。Hadoop的NameNode和DataNode都吃内存,尤其在起步阶段没有大内存可用,装桌面版会白白浪费资源。
  • 系统攻击面小、更稳定。集群跑在机房内,没有图形界面意味着少了很多潜在漏洞和崩溃入口。
  • 便于标准化复制。最小系统上安装什么、改什么完全由你掌控,后续扩缩容节点时能够保持环境一致,不会出现“那台机器多装了个库”这类环境不一致问题。

但代价也很明显:很多开发中习惯的命令,比如ifconfig、vim编辑器,在最小安装下都没有,DNS解析也可能是空的。这些问题本身不难解决,只是新手会在一开始被挫败感劝退。本篇就是在告诉你,不用慌,按步骤一步步来。

1.2 完全分布式架构的角色分配

Hadoop完全分布式和伪分布式的本质区别在于:伪分布式把所有进程塞在一台机器上模拟,而完全分布式则是每个角色进程分布在真实的主机节点上。

一个标准的最小集群通常包含三台节点,我习惯这样规划:

节点角色主机名部署进程资源侧重
主节点masterNameNode、ResourceManager、SecondaryNameNodeCPU、内存要求高
工作节点1slave1DataNode、NodeManager磁盘容量要求高
工作节点2slave2DataNode、NodeManager磁盘容量要求高

这个1主2从的规模足够了。NameNode负责管理文件系统的命名空间和元数据,ResourceManager负责资源和任务调度,两者都是集群的“大脑”,不适合放在廉价节点上;DataNode和NodeManager是真正干活的人,负责存储数据块和运行计算任务,需要大存储。

如果你的场景里未来数据量明显上涨,可以先按3个DataNode规划,后续往slave3、slave4方向横向扩展,但基础结构不变。在动手之前,最好把节点分工画在一张表里发给相关同事确认,确认后再分配IP和主机名,避免中途返工。

2. 节点环境准备:从零构建集群“地基”

2.1 修改主机名并固化IP映射

拿到三台最小化Linux系统后,第一件事不是装Java,而是把网络和主机名规划好。我见过不少同学在配置阶段用IP通信没问题,一改用主机名就各种失败,原因就是hosts文件没有提前统一维护。

主机名的配置因发行版而异。在大多数基于systemd的发行版上,命令是:

hostnamectl set-hostname master

执行后可以通过以下命令确认:

hostnamectl

如果当前系统没有hostnamectl命令,可能是极简安装缺少了相关包,可以直接修改/etc/hostname文件,然后执行reboot或systemctl restart systemd-hostnamed使其生效。

接下来是关键的一步,在三台机器上都维护一份统一的/etc/hosts映射。以我实际用的网段为例:

cat >> /etc/hosts << EOF 192.168.10.10 master 192.168.10.11 slave1 192.168.10.12 slave2 EOF

这里有个易忽略的点:不要写127.0.1.1 master这种映射,否则NameNode启动后会发现自己的hostname与IP的解析关系不稳定,偶尔出现节点失联。要去掉或注释掉系统自带的、与hostname同名的本地回环条目,这一条在最小化安装的默认配置里很常见,很多人因此卡住了两天。

然后关闭防火墙或放行对应端口。不同发行版的防火墙管理工具不一样,常见的是firewalld和iptables。实验环境下最简单的是禁用防火墙,生产环境可以只放行Hadoop必要的8020、9870、8088等端口。我建议在你的内部测试环境直接:

systemctl stop firewalld systemctl disable firewalld

同时检查SELinux,如果开启需要临时放行或设置为宽松模式:

setenforce 0

这里值得解释一下为什么Hadoop对防火墙这么敏感。分布式系统内部要频繁进行RPC通信、心跳上报,如果每发一个数据包都过一遍防火墙规则过滤,延迟会显著上升,而且一旦规则不当直接导致集群内部通信断裂。在测试和学习环境,优先保证通信畅通,生产环境则要精确放行端口,这个取舍要清楚。

2.2 统一用户、目录和Shell环境

集群中各节点最好使用同一个用户运行Hadoop进程,推荐创建一个专用用户,不要用root。用root启动Hadoop会引发安全警告,而且HDFS上文件的所有者权限会很混乱。我通常这样操作:

useradd -m -s /bin/bash hadoop passwd hadoop

只是在三台机器上都要执行一遍,包括master、slave1、slave2。

目录规划也很重要。推荐单独设置一个数据目录,系统盘和数据盘分离:

mkdir -p /data/hadoop/tmp mkdir -p /data/hadoop/namenode mkdir -p /data/hadoop/datanode chown -R hadoop:hadoop /data/hadoop

把NameNode的元数据目录、DataNode的数据块目录、运行时临时目录分开,这样后续排查问题、备份恢复时会有清晰边界,不会把所有东西混在/tmp下导致重启后数据丢失。

还需要提一下操作系统层面的一个参数,打开文件数限制。Hadoop集群运行时会有大量文件句柄操作,默认的1024很容易不够用,在/etc/security/limits.conf中加入:

hadoop soft nofile 65535 hadoop hard nofile 65535 hadoop soft nproc 65535 hadoop hard nproc 65535

2.3 SSH免密登录与密钥分发

完全分布式集群启动时,master节点要通过SSH远程启动slave1和slave2上的DataNode进程,如果每次连接都要求输密码,自动化根本跑不起来。所以免密登录是搭建过程中必须配置的一环。

步骤并不复杂。首先在master上生成密钥对:

ssh-keygen -t rsa -b 4096 -P '' -f ~/.ssh/id_rsa

-P ''表示空口令,-f指定生成路径。生成之后把公钥分发到包括自己在内的所有节点:

ssh-copy-id hadoop@master ssh-copy-id hadoop@slave1 ssh-copy-id hadoop@slave2

执行过程中会要求输入对方用户的密码,输入一次即可。完成后验证:

ssh master 'hostname' ssh slave1 'hostname' ssh slave2 'hostname'

如果你发现还是提示输入密码,常见的原因是~/.ssh目录权限不对。在公钥分发完成后,把master上~/.ssh/authorized_keys的权限改成600,~/.ssh目录改成700。这个细节最容易忽略,因为SSH对权限极其严格,权限过宽会直接拒绝信任。

还有个经验点:建议在master上把私钥也同步到slave节点,这样将来在任意节点上执行集群巡检脚本都能免密连接其他节点,省去来回跳转。

scp -r ~/.ssh hadoop@slave1:/home/hadoop/ scp -r ~/.ssh hadoop@slave2:/home/hadoop/

不过要注意,这样分发后slave节点也能免密登录master,某些安全要求高的场景不建议这么做,自己权衡。

2.4 JDK安装:版本选择和环境变量

Hadoop本身就是Java写的,运行环境缺少JDK寸步难行。很多教程直接用openjdk,但我在实践中更推荐使用特定版本,因为Hadoop官方对Java版本有明确支持范围。以Hadoop 3.3.x为例,Java 8和Java 11都在支持范围内,我倾向于选择Java 8,生态兼容性好,踩坑概率低。

最小安装系统通常没有Java环境,安装包可以通过包管理器直接拉取,或从离线介质手动安装。在线方式:

yum install -y java-1.8.0-openjdk java-1.8.0-openjdk-devel

安装完成后确认路径。openjdk一般会装到/usr/lib/jvm/java-1.8.0-openjdk.x86_64这个位置。如果你使用Oracle JDK的tar包,则解压到/opt/java目录后手动配置环境变量。

以解压方式为例,在/etc/profile.d/hadoop.sh中写入:

export JAVA_HOME=/opt/java/jdk1.8.0_202 export PATH=$JAVA_HOME/bin:$PATH export HADOOP_HOME=/opt/hadoop export PATH=$HADOOP_HOME/bin:$HADOOP_HOME/sbin:$PATH

写入后执行source /etc/profile,然后用java -version验证。这里有一个常见的坑:当你在本机明明能执行java命令,但脚本启动Hadoop时报“JAVA_HOME not found”,多半是环境变量没有写进Hadoop加载的配置文件。

Hadoop启动脚本不会去读用户的.bashrc,而是要看$HADOOP_HOME/etc/hadoop/hadoop-env.sh里是否明确了JAVA_HOME。因此最稳妥的做法是在hadoop-env.sh中硬编码:

export JAVA_HOME=/opt/java/jdk1.8.0_202

即使你的.bashrc里配错了,Hadoop脚本依然能找到Java。这一点在协作环境中尤其重要,因为不同人登录时的Shell环境可能不一样。

3. 目录文件级配置:Hadoop的配置文件逐项吃透

3.1 下载、解压与目录规划

Hadoop的二进制发行包可以从官方Apache软件基金会站点下载。版本选择上,我建议优先选择稳定的3.3.x系列,不建议一上来就追最新版本,因为生态系统如Hive、Spark对Hadoop版本的兼容往往滞后。

下载并解压:

wget https://archive.apache.org/dist/hadoop/common/hadoop-3.3.6/hadoop-3.3.6.tar.gz tar -zxvf hadoop-3.3.6.tar.gz -C /opt/ mv /opt/hadoop-3.3.6 /opt/hadoop chown -R hadoop:hadoop /opt/hadoop

解压完成后,把整个hadoop目录分发到另外两台机器:

scp -r /opt/hadoop hadoop@slave1:/opt/ scp -r /opt/hadoop hadoop@slave2:/opt/

如果带宽有限,也可以在slave节点分别下载解压,效果相同。只是后一种方式要记得在每台机器上都执行chown,避免文件属主混乱。

3.2 hadoop-env.sh:JVM与守护进程参数

除了前面说的JAVA_HOME,hadoop-env.sh还承载着每个守护进程的JVM参数配置。测试环境下默认参数够用,但生产环境建议单独设置NameNode和ResourceManager的堆大小:

export HDFS_NAMENODE_OPTS="-Xms4g -Xmx4g" export HDFS_DATANODE_OPTS="-Xms2g -Xmx2g" export YARN_RESOURCEMANAGER_OPTS="-Xms2g -Xmx2g"

对内存不大充裕的虚拟机环境,可以保守一点:

export HDFS_NAMENODE_OPTS="-Xms512m -Xmx512m"

这个配置属于调试过程必然要调整的参数。但我建议不要在第一次搭建时就搞复杂的调优,先把集群跑起来,观察内存使用,再迭代调整。

3.3 core-site.xml:默认文件系统与临时目录

core-site.xml是整个集群的“总入口”,里面配置所有通用属性。我每次搭建时必须改的三个属性:

<configuration> <property> <name>fs.defaultFS</name> <value>hdfs://master:8020</value> </property> <property> <name>hadoop.tmp.dir</name> <value>/data/hadoop/tmp</value> </property> <property> <name>ha.zookeeper.quorum</name> <value>master:2181,slave1:2181,slave2:2181</value> </property> </configuration>

fs.defaultFS指定了默认文件系统,客户端访问路径为hdfs://master:8020/xxx时,才会把请求交给分布式存储。hadoop.tmp.dir是文件系统的基础临时目录,NameNode元数据和DataNode数据块如果没单独指定目录,就会默认存在这个tmp目录下面。这也是为什么前面要单独建目录的原因,否则系统重启后/var/tmp被清理,可能导致元数据丢失。

想确认NameNode元数据实际存放位置,可以在core-site.xml的基础上,结合hdfs-site.xml的判断逻辑,最终以dfs.namenode.name.dir的绝对路径为准。所以更严谨的作法是在hdfs-site.xml显式指定name目录和data目录,避免Hadoop把元数据放在tmp目录里。

3.4 hdfs-site.xml:副本数、NameNode目录与DataNode目录

这一份文件决定了HDFS的存储策略。最关键的配置:

<configuration> <property> <name>dfs.namenode.name.dir</name> <value>file:///data/hadoop/namenode</value> </property> <property> <name>dfs.datanode.data.dir</name> <value>file:///data/hadoop/datanode</value> </property> <property> <name>dfs.replication</name> <value>2</value> </property> <property> <name>dfs.namenode.secondary.http-address</name> <value>master:9868</value> </property> </configuration>

副本数dfs.replication是很多初学者会忽略的地方。默认是3,如果你只有2个DataNode节点,并且把副本数设为3,那么第三份副本始终无法满足,会不断触发复制告警。所以我建议一开始设成2,既保证数据冗余,又能和节点数匹配。当然如果你坚持采用1主3从结构,默认3副本才是标准生产配置。

这里需要解释一下SecondaryNameNode的作用。它不是NameNode的热备,而是定期拉取NameNode的元数据镜像和编辑日志,合并后推回去,帮助NameNode减轻内存压力、缩短重启恢复时间。所以secondary.http-address要指向master节点上运行SecondaryNameNode进程的那台机器,一般是和NameNode同机部署。

3.5 yarn-site.xml与mapred-site.xml:计算资源管理

YARN是Hadoop 2.0之后引入的资源管理框架,负责集群中所有计算任务的内存和CPU调度。在yarn-site.xml中,需要配置ResourceManager所在的机器以及每个节点可用的资源上限:

<configuration> <property> <name>yarn.resourcemanager.hostname</name> <value>master</value> </property> <property> <name>yarn.nodemanager.aux-services</name> <value>mapreduce_shuffle</value> </property> <property> <name>yarn.nodemanager.resource.memory-mb</name> <value>8192</value> </property> <property> <name>yarn.scheduler.maximum-allocation-mb</name> <value>8192</value> </property> </configuration>

nodemanager.aux-services必须配置为mapreduce_shuffle,否则运行MapReduce任务时,中间数据无法在不同任务之间传递,这是最容易踩的一个坑。容器内存参数根据机器实际内存调整。

mapred-site.xml原本不存在,需要从模板复制一份:

cp $HADOOP_HOME/etc/hadoop/mapred-site.xml.template $HADOOP_HOME/etc/hadoop/mapred-site.xml

并确认采用YARN框架:

<configuration> <property> <name>mapreduce.framework.name</name> <value>yarn</value> </property> </configuration>

如果这个配置忘了,作业提交后只会以本地模式运行,根本没走集群资源调度。

3.6 workers文件与配置文件同步

在Hadoop 3.x版本中,以前写slaves文件的习惯被替换成了workers文件,位于$HADOOP_HOME/etc/hadoop/workers,里面列出所有DataNode主机名:

cat > $HADOOP_HOME/etc/hadoop/workers << EOF slave1 slave2 EOF

workers文件的意义在于,执行start-dfs.sh时,脚本会读取这个文件,通过SSH去逐个节点启动DataNode进程。如果这里填错主机名,节点数量会不对,集群状态一眼就看出来异常。

确保所有配置文件修改完毕后,把它们同步到所有节点:

scp -r $HADOOP_HOME/etc/hadoop/* hadoop@slave1:$HADOOP_HOME/etc/hadoop/ scp -r $HADOOP_HOME/etc/hadoop/* hadoop@slave2:$HADOOP_HOME/etc/hadoop/

需要特别提醒:不要在master上改完配置就忘了分发,否则会出现NameNode在master上、DataNode在slave上的配置不统一的现象,表现为datanode进程启动后连接不上NameNode或者端口匹配不一致,极其浪费时间。

4. 集群首次启动、初始化与验证

4.1 NameNode格式化:只有第一次能做的事

启动集群前,必须在master上执行一次格式化操作:

hdfs namenode -format

格式化做的事情是初始化NameNode的元数据目录,生成fsimage和edits文件。这一步必须在所有配置分发完毕、hdfs-site.xml指向正确的namenode目录之后做。

格式化有一个强制性注意事项:不能重复执行。如果因为配置调整需要重新格式化,必须先删除所有节点的数据目录和临时目录,否则NameNode自己格式化了新集群,但DataNode还保留着旧集群的DataNode ID,启动时就会报版本不匹配或namespaceID不一致的错误。

保险一点的流程:

rm -rf /data/hadoop/tmp/* rm -rf /data/hadoop/namenode/* rm -rf /data/hadoop/datanode/*

在三台机器上都执行一遍,然后回到master重新hdfs namenode -format。有人会问为什么不能直接再format一次,非得清空DataNode目录?原因是DataNode会把自己的ID注册到NameNode,第二次格式化相当于换了集群ID,旧的DataNode注册信息自然失效了。你要么清空重建,要么手动改DataNode的VERSION文件里的clusterID,显然后者更麻烦。

4.2 启动HDFS和YARN

执行格式化后,开始启动HDFS:

start-dfs.sh

启动脚本会自动到master上启动NameNode、SecondaryNameNode,并读取workers文件到slave1、slave2上启动DataNode。启动过程中如果SSH免密没配好,会卡在输密码这一步,所以前面反复强调免密配置的重要性。

然后启动YARN:

start-yarn.sh

同样,ResourceManager在master上启动,NodeManager在worker节点上启动。

有经验的工程师会把这两个脚本合在一起:

start-all.sh

但我不建议新手这么做。分开启动的好处在于,如果某一部分启动失败,能更快定位是HDFS的问题还是YARN的问题。

启动后用jps查看各节点的Java进程。在master上应该看到:

  • NameNode
  • SecondaryNameNode
  • ResourceManager

在slave节点应该看到:

  • DataNode
  • NodeManager

jps命令是JDK自带的,如果提示找不到,确认PATH里是否包含$JAVA_HOME/bin。

4.3 通过Web界面和命令行验证集群

集群启动后,我一般会做三重验证。第一重是看进程,刚才已经通过jps确认了;第二重是看Web界面:

  • NameNode的Web管理界面:http://master:9870,在Hadoop 3.x中默认端口是9870,2.x则常用50070。
  • ResourceManager的Web界面:http://master:8088。

在9870页面里,可以确认当前集群的活跃节点数量、总容量、已用容量、DataNode列表状态。在8088页面里,可以查看正在运行的Application和可用队列资源。

切记:如果你访问不了Web界面,第一件事不是查Hadoop,而是检查防火墙是否放行了这些端口。这个问题在最小化安装系统中尤其容易出现。

第三重验证是命令行操作。在HDFS上建个目录验证读写:

hdfs dfs -mkdir -p /test hdfs dfs -put /etc/hostname /test/ hdfs dfs -cat /test/hostname

如果文件能够正常写入和读取,说明HDFS的基本链路是通的。

跑一个简单的MapReduce作业验证计算链路:

hadoop jar $HADOOP_HOME/share/hadoop/mapreduce/hadoop-mapreduce-examples-3.3.6.jar pi 4 1000

如果长时间没有输出或者卡住,可以用yarn logs -applicationId查看容器日志,定位问题。这一条非常重要,因为很多初学者作业提交失败后直接看控制台报错,但真正的异常堆栈在容器日志里。

4.4 停止集群的正确方式

停止集群的顺序和启动相反,先停YARN再停HDFS:

stop-yarn.sh stop-dfs.sh

为什么必须按这个顺序?ResourceManager是调度者,它会在作业运行过程中向NodeManager下发任务。先停掉YARN,确保没有计算任务还在占用资源;再停HDFS,避免NameNode还在处理元数据时底层DataNode突然退出导致数据块状态不完整。虽然顺序反了大概率也不会造成数据损坏,但业内约定俗成,养成好习惯能避免极端场景下的不一致问题。

还有一个经验:如果某次启动后状态异常,而你又不想全部停止,可以针对个别进程操作。比如只重启DataNode:

hdfs --daemon stop datanode hdfs --daemon start datanode

这种方式比stop-all再start-all快得多,故障排查时很实用。

5. 搭建过程中最常见的坑与排查手段

5.1 DataNode始终不能注册到NameNode

这种现象很典型:master上的NameNode已经启动,但slave节点上的DataNode进程反复退出,或者jps能看到进程,但Web界面上就是永远只有1个活跃节点。

排查思路依次如下:

首先看DataNode日志。日志位置在$HADOOP_HOME/logs/hadoop-hadoop-datanode-主机名.log。常见报错是Incompatible clusterIDs,原因就是之前说的重复格式化导致集群ID不一致。方法也只能清空数据目录重新格式化。

还有一种情况是java.io.IOException: File /data/hadoop/datanode can only be replicated to 0 nodes。这通常是DataNode无法访问master的8020端口,或master无法反向连接DataNode的9866端口。检查两端防火墙和hosts映射。

5.2 NodeManager启动失败,ResourceManager健康状态异常

NodeManager启动失败常见的报错是错误提示无法创建本地目录,对应配置是yarn.nodemanager.local-dirs,默认在/tmp下。最小安装系统的/tmp空间往往很小,或者被清理过,进程起不来。

建议显式指定:

<property> <name>yarn.nodemanager.local-dirs</name> <value>/data/hadoop/yarn/local</value> </property> <property> <name>yarn.nodemanager.log-dirs</name> <value>/data/hadoop/yarn/log</value> </property>

然后创建对应目录和属主:

mkdir -p /data/hadoop/yarn/local mkdir -p /data/hadoop/yarn/log chown -R hadoop:hadoop /data/hadoop/yarn

5.3 远程执行start-dfs.sh卡在密码输入

另一种常见是SSH免密失效,尤其是当你配置了多个密钥时。SSH会优先尝试默认密钥,不符合sshd的权限设置就跳过。建议调试时用ssh -v显式输出连接过程:

ssh -v slave1

观察输出内容。如果看到bad permissions,说明.ssh目录或者authorized_keys权限有问题。修复后一般就正常了。

5.4 作业执行失败,ResourceManager日志无异常

这种情况最让人头大,因为整体集群正常,但一提交作业任务就失败。我遇到过最典型的原因是Java版本不一致。master上用的JDK 8,slave上自动更新到了JDK 11,字节码版本不兼容,Container启动就被杀。

处理方法是保证三台节点JDK版本完全一致,并关闭自动升级:

yum remove -y *openjdk*

然后统一用同一tar包重新安装。

此外还可能是操作系统DNS配置干扰了节点间通信,建议使用IP或确认/etc/hosts的一致性。

最后再说两句实际操作中积累的经验

整个流程走下来,我自己最深刻的体会是:Hadoop集群搭建的大部分时间其实不是在写配置,而是在“纠偏”——配置项遗漏、环境不一致、权限问题、端口不通。如果你能把每个组件的职责理解清楚,比如NameNode管元数据、DataNode管数据、ResourceManager管资源调动、NodeManager管容器执行,遇到问题就从角色职责出发去推导可能原因,会比死记硬背配置有效得多。

还有一个小技巧想分享:在调试阶段,不要急着把三台节点一起启动,可以先在master单独启动NameNode进程观察是否正常,再逐台启动DataNode测试连接。集群不是越早启动就越快成功的,从一开始把环境差异抹平,后面你会省下大把排查时间。

这套环境搭好之后,你可以继续在这个基础之上部署Hive、Spark、Flink等组件,把数据仓库链路打通。作为大数据底层设施的起点,亲手搭一遍比任何教程都更让人安心,希望这篇文章能帮你在搭建过程中少走我当年走过的弯路。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进