ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

阿里云ECS搭建Hadoop集群实战:配置、避坑与排查指南

阿里云ECS搭建Hadoop集群实战:配置、避坑与排查指南

先把话撂在前面:这篇文章不是让你按着一篇官方文档从头敲到尾,而是想告诉你,面对"阿里云ESC搭建hadoop集群"这种需求时,实际动手之前该想清楚什么、配置的时候每一步在干什么、出了问题往哪几个方向排查。标题里的"ESC"应该是"ECS",也就是弹性计算服务器,玩云服务器的人一眼就能看懂,但这不影响我们接下来要做的这件事——在云上把一套真正的多节点Hadoop集群从零跑起来。

我默认你至少碰过Linux,会解压压缩包、会用vim改文件,但不需要你之前搭过分布式系统。如果你正准备用云服务器做实验、跑课程设计或者验证一套数据处理的Demo,这篇内容基本可以照抄作业。文章里所有路径、配置、参数都以常见的Hadoop 3.x版本为例,SDK和内核版本不同会有细枝末节的差异,但搭建思路是共通的。

1. 整体设计:为什么选多节点ECS而不是一台物理机

1.1 单机伪分布式和真实集群的差距在哪

很多人一开始图省事,在一台机器上把Hadoop的伪分布式模式跑通就觉得自己会了。实际情况是,伪分布式只是把NameNode、DataNode、SecondaryNameNode都压在同一台进程里,它确实能帮你理解HDFS的基本读写流程,但完全暴露不出网络通信、节点间免密、端口开放、数据副本放置策略这些真正在生产环境才会遇到的问题。

我见过不止一个人,伪分布式玩得很溜,一上真实集群就傻眼:DataNode起不来、NameNode连不上、任务提交一个串一个报错。原因很简单,伪分布式里不存在跨节点通信,所有进程都监听在localhost上,而真实集群里面最核心的问题就是"节点之间怎么互相找到、怎么互相认证、怎么传输数据"。如果你打算以后做离线计算相关的工作,这一步迟早要迈过去。

用三台ECS搭一个最小集群,是我认为性价比最高的练习方式。三台机器的角色分配也很标准:一台做NameNode和ResourceManager,两台做DataNode和NodeManager。这个布局既覆盖了HDFS的元数据管理节点,也覆盖了YARN的资源调度节点,还保留了扩展空间,以后要加第四、第五台机器,只需要把新机器配置好然后启动DataNode就行。

1.2 ECS选型和费用控制方案

阿里云的ECS实例类型很多,做Hadoop集群不需要追求高算力,因为Hadoop本身吃的是磁盘I/O和网络带宽,CPU在数据量小的实验场景下几乎不是瓶颈。我自己的习惯是选2核4G的入门型实例,系统盘给40G,数据盘可以暂时不挂,等数据量大了再说。

这里有个很多人容易忽略的地方:按量付费的ECS在关机状态下,如果是普通关机(不是节省模式),还是会计收一定的资源占用费。所以如果是放在那里跑课后作业或者夜间测试,建议把实例设置为"节省停机模式",这样能省不少钱。但要注意,节省停机模式在部分实例类型上不支持,下单之前看一下页面提示。

内网IP的安排也要提前规划。同一地域、同一VPC下的ECS实例之间走的是内网流量,不占用公网带宽,而且速度远快于公网。我们的Hadoop集群内部通信必须走内网IP,否则不仅慢,还会在安全组配置上麻烦很多。三台机器最好放在同一个安全组里,这样内部端口互相访问可以设成白名单全放通,而公网只保留一个22端口供你SSH登录管理。

1.3 安全组规则怎么配才不踩坑

安全组是ECS的第一道防火墙,它控制谁可以访问你的机器。很多人集群搭不起来,不是配置文件有问题,而是安全组把端口挡了。

在Hadoop集群场景下,最需要关心的端口有这些:

  • 22端口:SSH登录,必须对公网开放
  • 9870端口:NameNode的Web管理界面,Hadoop 3.x版本默认是这个端口,老版本是50070
  • 8088端口:YARN的ResourceManager Web界面
  • 8020或9000端口:NameNode RPC端口,这个是集群内部通信用的
  • 9864端口:DataNode的数据传输端口
  • 9866端口:DataNode的RPC端口
  • 9868端口:DataNode的Web端口
  • 8042端口:NodeManager的Web端口

内部通信的端口其实不用一条条去安全组里精确控制,更好的做法是把这个集群的三台机器放在同一个安全组,然后在安全组里加一条规则:允许这个安全组内的所有IP互访所有端口。用安全组ID作为来源,比一个个填IP省事得多,而且以后扩容实例也不需要改规则。

对外Web界面则可以单独放行,比如只允许你的办公网IP访问9870和8088,或者图省事直接对公网开放(实验环境下可以接受,生产环境不建议)。端口映射搞明白之后,集群搭建的拦路虎就先消掉了一个。

2. 环境准备:三台机器的基础配置

2.1 主机名、hosts映射和免密登录

我习惯把三台机器命名为hadoop01、hadoop02、hadoop03,其中hadoop01承担NameNode角色。拿到机器后第一件事就是改主机名,不然你会看到一系列ip-172-31-24-123这种毫无辨识度的名字,排查问题的时候脑袋疼。

改主机名的方式很简单,以CentOS或Alibaba Cloud Linux为例:

hostnamectl set-hostname hadoop01

然后编辑/etc/hosts,把三台机器的内网IP和主机名对应关系写进去:

172.16.0.10 hadoop01 172.16.0.11 hadoop02 172.16.0.12 hadoop03

这里有个关键点:hosts文件里最好把本机的IP也写进去,不要写127.0.0.1。因为Hadoop的节点启动后,NameNode会根据hostname去解析DataNode的地址,如果解析到127.0.0.1,就会陷入"DataNode明明活着,NameNode却一直认不出它"的尴尬情况。这个坑我亲眼见人踩过,网上搜到的报错信息五花八门,最后定位原因时居然只是hosts文件少写了一行本机IP。

免密登录是启动集群的硬前提。NameNode启动的时候要通过SSH到各个DataNode去执行启动脚本,如果你每次都要输密码,脚本根本跑不起来。配置方式很简单,在hadoop01上执行:

ssh-keygen -t rsa -b 4096 -N ""

然后把公钥分发到所有机器(包括自己):

ssh-copy-id hadoop01 ssh-copy-id hadoop02 ssh-copy-id hadoop03

分发完成后务必要做一个验证,从hadoop01分别ssh到三台机器,能直接进去不用输密码,才算通过。我建议连本机的免密也做了,因为部分命令会在NameNode本机执行,不做的话后面格式化、启动时会多出很多密码输入,非常耗耐心。

2.2 JDK版本选择和安装位置约定

Hadoop 3.3.x版本已经要求Java 8以上,建议直接装Java 8,也就是1.8版本,因为它和Hadoop各个组件之间的兼容性被验证得最多。OpenJDK完全够用,不需要装Oracle JDK,没必要给自己增加许可方面的麻烦。

JDK的安装路径要统一。我的习惯是放在/usr/local/java下,然后通过/etc/profile配置环境变量:

export JAVA_HOME=/usr/local/java export PATH=$PATH:$JAVA_HOME/bin

这里有个小细节:不要只配置PATH而不配置JAVA_HOME,因为Hadoop的hadoop-env.sh脚本在启动时会显式读取JAVA_HOME。你光把java命令加到PATH里,启动Hadoop的时候它会报"JAVA_HOME is not set"。

另外,三台机器上的JDK版本必须保持一致。如果hadoop01是1.8.0_202,hadoop02是1.8.0_312,表面上看都是Java 8,但二进制兼容性在分布式场景下偶尔会冒出一些诡异问题。最简单的办法是装好一台之后,把JDK目录整个拷贝到其他机器,性能没损失,反而最省事。

2.3 时钟同步为什么会影响分布式任务

分布式系统对时间一致性有要求,这个要求不是秒级,而是毫秒级。如果节点之间的系统时间差得太多,HDFS的租约管理、YARN的资源心跳都会出现异常。表现的症状往往是:任务莫名其妙失败,日志里全是超时,但网络和磁盘都正常。

同步方式很简单,用chrony或者ntp都行。我在实验环境里直接选择让所有节点和阿里云的内网时间源同步,配置好后执行:

chronyc sources -v

确认状态是^*,说明已经同步成功。这一步别跳过,我见过有人的集群跑了两天突然出问题,查了一晚上发现是其中一台机器系统时间偏了好几分钟。

3. Hadoop核心配置拆解:每个参数为什么这么写

3.1 解压和目录规范

到官网下载Hadoop二进制包后,我一般统一解压到/opt/module下,这是很多教程喜欢用的目录结构,后续升级和换版本也比较方便。解压命令:

tar -zxvf hadoop-3.3.6.tar.gz -C /opt/module

很多人喜欢把Hadoop解压到/root或者/usr/local,也不是不行,但建议权限规划要清晰。我习惯创建一个专门用户hadoop来跑集群,而不是用root。用root跑Hadoop也不是完全不行,但会带来一些权限过大的安全问题,而且如果你以后进公司,公司集群大概率也不允许你用root跑,提前养成习惯比较好。

创建用户并授权目录:

useradd hadoop passwd hadoop chown -R hadoop:hadoop /opt/module/hadoop-3.3.6

之后所有跟Hadoop相关的操作都用hadoop用户执行,配置文件也都在这个用户的目录下修改。

3.2 核心配置文件逐个拆解

Hadoop的配置集中在$HADOOP_HOME/etc/hadoop/目录下,我们需要修改的主要是七个文件:hadoop-env.sh、core-site.xml、hdfs-site.xml、yarn-site.xml、mapred-site.xml、workers(在旧版本里叫slaves)、以及一个可选的log4j.properties。

先看hadoop-env.sh,里面最关键的就是JAVA_HOME。虽然前面设置了系统环境变量,但Hadoop的启动脚本为了可控性,会优先读取这个文件里的配置。我建议直接取消注释并写成绝对路径:

export JAVA_HOME=/usr/local/java export HDFS_NAMENODE_USER=hadoop export HDFS_DATANODE_USER=hadoop export HDFS_SECONDARYNAMENODE_USER=hadoop export YARN_RESOURCEMANAGER_USER=hadoop export YARN_NODEMANAGER_USER=hadoop

后面几行用户配置是为了避免Hadoop 3.x版本在非root用户下启动时报"Permission denied"问题,这是一开始踩坑最容易遇到的地方。

然后是core-site.xml,它是全局核心配置。我的配置如下:

<configuration> <property> <name>fs.defaultFS</name> <value>hdfs://hadoop01:8020</value> </property> <property> <name>hadoop.tmp.dir</name> <value>/opt/module/hadoop-3.3.6/tmp</value> </property> <property> <name>hadoop.proxyuser.hadoop.hosts</name> <value>*</value> </property> <property> <name>hadoop.proxyuser.hadoop.groups</name> <value>*</value> </property> </configuration>

fs.defaultFS是客户端的默认文件系统地址,如果用hdfs://前缀,说明走的是HDFS协议,客户端提交任务时不需要显式指定地址就能连上NameNode。hadoop.tmp.dir这个配置很容易被忽略,它的默认值是/tmp,这意味着如果你不改它,NameNode的元数据、DataNode的数据块都会存在系统临时目录里,系统一清理就全没了。这个坑非常经典,改了它之后最好把三元组的格式记一下,后面hdfs-site.xml里还会引用。

hdfs-site.xml是分布式存储的核心配置:

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

dfs.replication设成2是因为我们三台机器里只有两台DataNode,如果设成3,会一直有副本缺失告警,因为第三份根本放不下。实验环境设2合理,生产环境一般至少3。

SecondaryNameNode不是备用的NameNode,不承担故障转移任务,它只是定期合并NameNode的编辑日志。在Hadoop 3.x里,它的Web端口变成了9868。让hadoop03来跑这个角色,可以分担NameNode的I/O压力。

然后是yarn-site.xml,资源调度层的配置:

<configuration> <property> <name>yarn.nodemanager.aux-services</name> <value>mapreduce_shuffle</value> </property> <property> <name>yarn.resourcemanager.hostname</name> <value>hadoop01</value> </property> <property> <name>yarn.nodemanager.env-whitelist</name> <value>JAVA_HOME,HADOOP_COMMON_HOME,HADOOP_HDFS_HOME,HADOOP_CONF_DIR,CLASSPATH_PREPEND_DISTRIBUTE</value> </property> </configuration>

yarn.nodemanager.aux-services这个配置必须写成mapreduce_shuffle,否则MapReduce任务在shuffle阶段会直接报找不到服务。yarn.resourcemanager.hostname指定ResourceManager的位置,剩下两台机器通过心跳自动感知并作为NodeManager注册。

最后是mapred-site.xml,指定MapReduce跑在YARN上:

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

不配置这个的话,MapReduce任务默认在本地跑,不会提交到YARN集群。

配置文件改完之后,有一个必须执行的步骤:把配置同步到其他机器。不推荐用scp一个个文件拷贝,比较费劲。更好的方式是用rsync写一行命令,把hadoop-env.sh和所有xml文件一次性分发过去:

rsync -av /opt/module/hadoop-3.3.6/etc/hadoop/ hadoop02:/opt/module/hadoop-3.3.6/etc/hadoop/ rsync -av /opt/module/hadoop-3.3.6/etc/hadoop/ hadoop03:/opt/module/hadoop-3.3.6/etc/hadoop/

分发完以后,要到每台机器上检查一下环境变量是不是生效了。最容易出现的状况是,你改了hadoop01的/etc/profile,但hadoop02和hadoop03的/etc/profile根本没动,导致那边找不到Java。

3.3 workers文件、格式化NameNode与启动顺序

workers文件(旧版本叫slaves)里列出了所有充当DataNode的主机名。它决定NameNode通过start-dfs.sh脚本去唤醒哪些节点的DataNode进程:

hadoop02 hadoop03

注意不要在这里写hadoop01,因为hadoop01是纯NameNode。如果你想让它自己当DataNode也可以,但实验环境下我倾向于把角色分开,方便观察各角色的资源占用情况。

配置完成后的第一次启动有一个必须强调的细节:格式化NameNode这个操作,一辈子只做一次。

hdfs namenode -format

格式化会生成集群唯一的ID,并把元数据写入dfs.namenode.name.dir指定的目录。如果你之后改了配置文件,又重新格式化,会导致NameNode和DataNode的集群ID不一致,DataNode启动时会一直报错握手失败。这种情况在官方文档里讲得不多,但实际遇到的人非常多。判断是否需要重新格式化的标准是:你之前已经跑通过集群,现在只想改配置,那绝对不要格式化;如果集群是全新的,启动前格式化一次就行。

启动顺序是先启HDFS,再启YARN:

start-dfs.sh start-yarn.sh

启动完后可以用jps命令查看各节点的Java进程。hadoop01上应该有NameNode和ResourceManager,hadoop02和hadoop03上应该有DataNode和NodeManager。

验证集群是否正常,最直接的方法是打开Web界面:

  • http://hadoop01的IP:9870 看HDFS状态
  • http://hadoop01的IP:8088 看YARN资源情况

如果看到Datanodes列表里有两个节点都是Live状态,ResourceManager的Active Nodes有2个,基本就说明集群搭起来了。

4. 常见问题排查和调优心得

4.1 DataNode起不来或者一直在重试

这是新手最容易碰到的状况。NameNode起来了,DataNode却在很短时间内反复重启。最常见的两个原因:一是hosts解析问题,DataNode把自己的IP解析成了127.0.0.1,向NameNode注册的时候地址不对;二是集群ID不一致,也就是前面说的重复格式化造成的问题。

排查手段很简单,先看DataNode日志:

tail -100 /opt/module/hadoop-3.3.6/logs/hadoop-hadoop-datanode-hadoop02.log

如果看到类似Storage reported corrupt or missing block或者Incompatible clusterIDs的报错,基本可以确定是集群ID不匹配。解决办法不是重新格式化NameNode,而是把DataNode本地的VERSION文件删掉,让DataNode重启后自动从NameNode获取集群ID。VERSION文件位置在dfs.datanode.data.dir对应的目录下的current目录里。

如果看到的是连接NameNode超时,那就是网络问题,优先确认安全组规则、内网IP是不是配错、防火墙selinux是不是开着。

4.2 端口通但Web界面打不开

很多人遇到过这样一个现象:安全组配了9870端口,防火墙也关了,但浏览器就是打不开NameNode的Web页面。这种问题多半出在ECS实例的安全组入口方向,而不是操作系统防火墙。

阿里云安全组的规则分为入方向和出方向。入方向如果只放行了22端口,那就只允许SSH,其他端口一律不通。你需要明确加一条入方向规则,允许来源IP访问9870端口。来源IP可以写成你的本地公网IP,也可以写成0.0.0.0/0,前者更安全,后者实验图省事也可以。

还有一个小概率但曾经真实困扰过我的问题:部分阿里云地域的ECS默认开启了防火墙服务,但它的默认策略是拒绝所有外部访问。检查命令:

systemctl status firewalld

如果开着,建议直接关闭,然后确认用iptables检查有没有额外规则。实验环境不需要那么较真,关了省心。

4.3 集群能启动但跑任务很慢,资源分配不够

集群起来之后,跑一个WordCount测试任务,发现耗时远超预期。别急着怀疑机器性能,先看YARN的Web界面,确认Capacity Scheduler给每个任务分配了多少资源。默认配置下,2核4G的机器,NodeManager可能只给容器分配了很小的内存,任务跑得慢是完全正常的。

可以调一下yarn-site.xml里的几个参数:

<property> <name>yarn.nodemanager.resource.memory-mb</name> <value>3072</value> </property> <property> <name>yarn.scheduler.maximum-allocation-mb</name> <value>2048</value> </property> <property> <name>yarn.scheduler.minimum-allocation-mb</name> <value>512</value> </property>

注意预留一部分内存给操作系统和DataNode进程,不要把4G全部分给YARN。否则极端情况下系统内存耗尽,会触发OOM进而影响集群稳定性。调完后需要重启YARN:

stop-yarn.sh && start-yarn.sh

如果是跑MapReduce任务,还可以在mapred-site.xml里调mapreduce.map.memory.mb和mapreduce.reduce.memory.mb,让容器拿到更大的内存。不过这一步不用太激进,先跑通,再慢慢调,理解变化比单纯堆参数值更实际。

4.4 常见问题速查表

现象可能原因解决办法
NameNode启动失败格式化目录权限异常检查dfs.namenode.name.dir对应目录属主是否为hadoop用户
DataNode反复重启集群ID不一致删除DataNode目录下VERSION文件后重启
ResourceManager界面看不到节点NodeManager和RM版本不一致统一三台Hadoop版本
提交任务卡住安全组阻挡心跳端口放行集群内网全端口互通
Web界面中文乱码浏览器字符集问题不理会,生产环境影响不大
YARN任务OOM容器内存分配过大调低yarn.scheduler.maximum-allocation-mb
ssh执行脚本卡密码免密没配全ssh-copy-id到所有节点
SecondaryNameNode不启动没有配置http-address补全dfs.namenode.secondary.http-address
磁盘空间告警数据目录建在系统盘挂载数据盘并将目录软链接过去

4.5 磁盘和数据目录的一点建议

ECS默认系统盘空间不大,40G的系统盘装完Hadoop加上操作系统本身,已经所剩无几。而HDFS一旦开始写入数据,每个副本都占真实空间,磁盘告警是迟早的事。我建议一开始就挂载一块数据盘,比如100G的ESSD,然后把HDFS的数据目录指过去。

挂载并格式化数据盘之后,把数据目录建好:

mkfs.ext4 /dev/vdb mkdir -p /data/hdfs mount /dev/vdb /data/hdfs

再把hdfs-site.xml里的dfs.datanode.data.dir改成/data/hdfs/datanode,这样数据块就存在独立的数据盘上,哪怕系统盘坏了,数据盘还能保留着重新挂载恢复。这个操作在搭建阶段就做掉,比数据写满之后再迁移省心得多。

4.6 关于配置同步和版本管理的一些实操心得

配置同步这步看起来很机械,但恰恰是最容易出问题的环节。每台机器上的Hadoop版本要和所有机器一致,以及JDK版本一致,否则不同版本之间的RPC协议会出现兼容性差异,表现就是节点之间日志显示握手失败,而Web界面看起来又一切正常。

我习惯把整个/opt/module目录纳入版本管理,每次修改配置后用rsync分发,分发完跑一遍配置校验:

hadoop checknative

它会把Hadoop的本地库检查一遍,比如压缩编解码器是否可用,如果显示openssl为false,也不影响基础功能,但看到false心里有数就行。这个命令不能当作集群配置是否正确的主判断,只是用来扫一眼环境。

5. 集群优化和后续扩展方向

5.1 端口规划和多实例管理技巧

如果以后你想在同一批ECS上跑多个Hadoop生态组件,比如Hive、HBase,端口规划要尽早统一。HDFS的8020端口、YARN的8088端口、JobHistory Server的10020端口,这些在Hive或Spark连接的时候都会被引用。端口冲突是最让人抓狂的问题之一,很多组件装好之后互相抢端口,报错日志又不直接说端口被占,排查起来非常耗时。

一个建议是养成维护端口清单的习惯。比如我的实验环境就是一份简单的表格记录,每个组件占用了哪些端口,改动配置时先查表再动手。虽然听着很土,但在多组件环境下能省下很多救命时间。

5.2 从三节点扩展到更多节点

三台机器组成的集群是最小可用架构,但如果以后数据量涨了,扩容思路很简单:新买一台ECS,配好JDK、配置好环境变量、把hosts同步过去、把Hadoop安装目录拷贝过去,然后在workers文件里把新机器名字加进去,重启DataNode和NodeManager进程即可。

注意分布式扩容不需要重新格式化NameNode,DataNode进程会自动向NameNode注册。我见过有人扩容的时候手贱重新格式化,导致整个集群的数据元信息全部丢失,教训极其惨重。格式化这个命令,在集群搭建成功以后,就应该当作不存在。

5.3 监控工具的引入时机

集群刚搭好时靠着Web界面看状态就够了。但当跑的作业变多,你会需要历史服务器来查看已完成任务的日志,也就是MapReduce JobHistory Server。它默认不随start-dfs.sh启动,需要单独运行:

mapred --daemon start historyserver

然后访问10020端口查看任务的Map和Reduce阶段分别耗费了多少时间、有没有Shuffle异常。这个阶段看的是运行效率,排查慢任务时比Web界面好用得多。

如果以后集群规模超过5个节点,再考虑引入监控平台,把所有节点的CPU、内存、磁盘和网络指标集中展示。实验阶段不用过早部署,那是给自己加戏。

个人经验是,在云上搭集群最大的好处不是性能多强,而是你能快速制造一整套和真实生产环境几乎一致的网络拓扑和配置结构。在这里踩过的每一个坑,都会让你在以后面对真实集群时更有底气。毕竟分布式系统那些问题,不会因为你换了一家公司或者换了一套云平台就消失,它们只是以不同形式重新出现而已。

RELATED READING

延伸阅读

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