ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

ARM云主机Hadoop集群搭建:JAVA_HOME、SSH与对象存储排障

ARM云主机Hadoop集群搭建:JAVA_HOME、SSH与对象存储排障

简介:《鲲鹏云大数据实验docx》是一份面向大数据初学者与云计算实验教学场景的实操型报告,围绕华为云ECS、OBS与Hadoop集群的搭建配置展开,重点解决云主机准备、对象存储接入、节点互信及Hadoop运行环境调试等问题。压缩包仅含1个docx文档,大小约2.52MB,以图文步骤和命令记录为主,便于按实验流程查阅与复盘。资源已有893人学习,适合作为课程实验、实训任务或自主练习的参考资料。内容完整记录了从购买云服务器、创建OBS并行文件系统并获取AK/SK,到三节点Hadoop集群的目录规划、SSH免密、配置分发、NameNode初始化与HDFS启动等环节;对Java家目录配置错误导致的重复分发与启动失败也保留了排错过程。读者可借此理解鲲鹏云环境下大数据基础设施的部署顺序、配置要点与常见问题定位方法。

1. 集群环境里最容易卡住的一步

集群搭到一半,hdfs namenode -format突然报JAVA_HOME is not set,可java -version明明能跑;把 JAVA_HOME 补上,格式化过了,启动 HDFS 又报JAVA_HOME is incorrectly set,等你改完路径再敲hdfs dfs -ls,它第三次报错。Java 装好了却连续多次失败,是 ARM 架构云主机上搭 Hadoop 最典型的开局。这份实验记录里最折腾人的几处,根本原因几乎都在这一个环境变量上,剩下的问题分布在节点互信、hosts 解析和对象存储接入这三段。下面按实际场景逐个拆解,正卡在启动环节的人可以照着排查。

2. 格式化就失败:JAVA_HOME 指错目录引发的连续失败

2.1 三个报错其实来自同一个问题

第一次翻车通常出现在hdfs namenode -format。终端里不会给你任何“Java 路径不对”的提示,抛出来的东西五花八门,我按出现频率排一下:

# 形态一:环境变量压根没设 JAVA_HOME is not set and could not be found. # 形态二:设了,但指向的目录不存在 /usr/local/hadoop/bin/hdfs: line 320: /usr/java/default/bin/java: No such file or directory # 形态三:设了,目录也存在,但没有可执行权限或不是 JDK Error: JAVA_HOME is incorrectly set. Please update /home/modules/hadoop-2.8.3/etc/hadoop/hadoop-env.sh

形态二最容易误导人。line 320: /usr/java/default/bin/java: No such file or directory看起来像个 shell 脚本错误,实际上是 hadoop 启动脚本拿着$JAVA_HOME/bin/java去执行,发现文件不存在。形态三更隐蔽,目录明明在,ls也能列出来,但目录里只有jre/bin/java,没有bin/javac,脚本会判定这不是一个合法的 JDK 根目录。

这三种形态的共同点是:报错的模块和出问题的模块不是同一个。你在 NameNode 初始化的位置看到报错,问题却出在几百行之外的hadoop-env.sh或者/etc/profile。很多人第一反应是重装 Hadoop、重新解压安装包、甚至重买一台机器,做完一圈发现还是同样的报错,这就是没定位到根本原因的表现。

我一般会养成一个习惯:先别管报什么错,先在出问题的节点上手工把三件事验证一遍——echo $JAVA_HOME有没有值、这个值指向的目录存不存在、这个目录下有没有bin/javac。三个问题里只要有一个是“否”,后面看到的所有报错都是它的衍生症状,不用再往下查了。

2.2 为什么 ARM 机器上这个路径特别容易写错

x86 时代的教程里,JAVA_HOME 基本是两三个固定值:/usr/java/default、/usr/lib/jvm/java-1.8.0-openjdk.x86_64、/opt/jdk1.8.0_xxx。你照着抄,八成的机器能跑起来。换成 ARM 架构的云主机之后,这套照抄法就失效了,原因有三个。

第一个是目录名变长了。以常见的 openEuler 系镜像为例,装完java-1.8.0-openjdk-devel之后,真实目录是/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.oe1.aarch64这种形态。中间那一串构建号加发行版标识加架构后缀,随镜像版本变化,不同批次买的机器可能都不一样。教程里写的java-1.8.0-openjdk.x86_64在这类机器上根本就不存在。

第二个是只装了 JRE 没装 JDK。很多镜像为了省空间,预装的是java-1.8.0-openjdk-headless,java -version正常输出,which java也有结果,看起来一切正常。但 Hadoop 的脚本会去找javac,找不到就判定 JAVA_HOME 非法。这也是“Java 明明装好了却报错”最常见的真相。

第三个是软链接造成的假象。which java输出的是/usr/bin/java,而/usr/bin/java只是个软链接,指向/etc/alternatives/java,后者又指向真实的 JVM 目录。如果直接把/usr/bin当成 JAVA_HOME 写进去,Hadoop 会去/usr/bin/bin/java找可执行文件,自然找不到。

所以定位 JAVA_HOME 的正确做法不是猜,而是让系统告诉你:

# 一层层剥掉软链接,拿到 java 的真实绝对路径 readlink -f "$(which java)" # 典型输出:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.oe1.aarch64/jre/bin/java # 确认同目录下有没有 javac,没有就说明只装了 JRE ls -l "$(dirname "$(readlink -f "$(which java)")")/javac"

readlink -f的作用是递归解析软链接并输出最终的真实路径,比which可靠得多。拿到输出之后,把末尾的/jre/bin/java三段砍掉,剩下的部分就是 JAVA_HOME。上面那条典型输出的结果是/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.oe1.aarch64。

注意:如果ls那一步报 no such file,说明这台机器只装了运行时,先补装开发包(Debian 系是openjdk-8-jdk,RPM 系是java-1.8.0-openjdk-devel),再回来看 JAVA_HOME。这一步不补,后面所有配置都是白费。

2.3 改对 JAVA_HOME:两处都得写,一处都不能漏

路径找到了,接下来是写在哪。这里有个很典型的分歧:有人只改/etc/profile,有人只改hadoop-env.sh,两种做法都会在某些场景下失败。

/etc/profile是登录 shell 加载的,你手工ssh上去敲命令,它生效;但start-dfs.sh启动子节点进程时,是通过非交互式 SSH 远程执行的,很多发行版的非登录 shell 根本不读/etc/profile。表现出来就是:你在 node-0001 上手工java -version正常,start-dfs.sh一跑,node-0002 报JAVA_HOME is not set。

hadoop-env.sh是 Hadoop 自己的脚本里 source 的,任何启动方式都会读到,优先级也最高。但它只对 Hadoop 生效,你敲hdfs、yarn这些命令的时候用得上,别的组件不受影响。

稳妥的写法是两处都写、值保持一致:

# /etc/profile 末尾追加,供交互式终端和依赖环境变量的脚本使用 export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.oe1.aarch64 export HADOOP_HOME=/home/modules/hadoop-2.8.3 export PATH=$JAVA_HOME/bin:$HADOOP_HOME/bin:$HADOOP_HOME/sbin:$PATH # 立即生效 source /etc/profile
# $HADOOP_HOME/etc/hadoop/hadoop-env.sh 中,把原来的 export JAVA_HOME=... 换成真实路径 # 如果原文件里是注释掉的,取消注释再改;不要新增一行,否则后面的赋值会覆盖前面的 export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.oe1.aarch64

第一段的关键是PATH的顺序。$JAVA_HOME/bin要排在前面,否则系统里如果同时存在另一个 Java(比如某个软件自带一份),java命令解析到的可能不是你以为的那个。$HADOOP_HOME/sbin加进去之后,start-dfs.sh、start-yarn.sh就能直接敲,不用每次写全路径。

第二段的关键是别重复赋值。hadoop-env.sh是顺序执行的 shell 脚本,如果文件前面已经有一行export JAVA_HOME=/usr/java/default,你在末尾再追加一行正确的,最终生效的确实是你新写的那行;但如果你写在前面,就会被后面的覆盖掉。改完记得用grep -n JAVA_HOME $HADOOP_HOME/etc/hadoop/hadoop-env.sh确认整个文件里只有一处有效赋值。

验证不能只验一次,要验两种 shell:

# 登录 shell 下验证 bash -lc 'echo $JAVA_HOME; java -version; javac -version' # 模拟 Hadoop 远程启动时的非交互 shell ssh node-0002 "echo \$JAVA_HOME; java -version; javac -version"

第二条命令里的\$JAVA_HOME用反斜杠转义,是因为要让它在对端展开而不是本地展开。这条命令输出为空或者报 command not found,就说明对端的 profile 没生效或者根本没配置,那start-dfs.sh一定起不来。

2.4 改完之后必须重做的三件事

JAVA_HOME 改对不等于问题解决了。前面几轮失败的格式化、启动留下了不少残留,不清干净的话,你看到的下一个报错跟 Java 一点关系都没有。

第一件事是把改过的配置同步到所有节点。Hadoop 的start-dfs.sh只在 node-0001 上执行,但真正的 NameNode、DataNode 进程分布在各个节点上,每个节点都有自己的hadoop-env.sh。只改一台,另外两台起来照样报错。

# 在 node-0001 上把整份 Hadoop 目录同步到其余节点,包含改过的 hadoop-env.sh for h in node-0002 node-0003; do rsync -av /home/modules/hadoop-2.8.3/ "$h":/home/modules/hadoop-2.8.3/ done # 各节点分别执行,让 /etc/profile 的改动生效 ssh node-0002 'source /etc/profile && java -version' ssh node-0003 'source /etc/profile && java -version'

这里用rsync -av而不是scp -r,是因为前者只传改动过的文件,后面你调core-site.xml的时候会反复用到这个动作。注意别加--delete,一旦加了这个参数,对端比源端多出来的日志、临时文件会被清掉,排查到一半的现场就没了。

第二件事是清掉上次失败的格式化残留。NameNode 格式化会在dfs.namenode.name.dir指定的目录里生成current/VERSION和一堆edits文件,DataNode 在dfs.datanode.data.dir下也有自己的current/VERSION。上一次格式化如果中途因为 Java 报错中断,这些目录可能只写了一半。

# 清理 NameNode 与 DataNode 的数据目录,路径以 hdfs-site.xml 里配的为准 rm -rf /home/modules/data/buf/* rm -rf /home/nm/localdir/* # 清理临时目录与日志,避免旧日志干扰判断 rm -rf /home/modules/hadoop-2.8.3/logs/*

第三件事是重新格式化并逐节点检查进程。这里必须强调顺序:先清残留,再格式化,再启动,最后jps核对。

# 在 node-0001 上执行,只格式化一次,中间不要重复执行 hdfs namenode -format # 启动 HDFS start-dfs.sh # 每个节点都要看,确认进程数对得上 jps

jps是 JDK 自带的工具,能看到本机所有 JVM 进程。node-0001 上应该看到NameNode、SecondaryNameNode,其余节点应该看到DataNode。少一个都不算成功,别急着往下做实验。

注意:hdfs namenode -format在同一个集群上执行多次是有代价的。每格式化一次,NameNode 会生成新的 clusterID,而 DataNode 的VERSION文件里还记着旧的,启动时就会报Incompatible clusterIDs。这个问题在下一章会详细说。

3. 节点之间连不上:SSH 互信与 hosts 的排查顺序

3.1 三类报错,对应三种根因

配置节点互信这一段,报错信息比 Java 那段直白得多,但麻烦在于它会在不同阶段冒出来,有时是ssh手工连不上,有时是start-dfs.sh执行到一半停住。

# 形态一:公钥没有被对端接受 Permission denied (publickey,gssapi-keyex,gssapi-with-mic). # 形态二:对端主机指纹没被记录,或者变了 Host key verification failed. # 形态三:主机名解析不了 ssh: Could not resolve hostname node-0002: Name or service not known

形态一是最常见的。ssh-copy-id执行过一次,自以为配好了,实际上可能复制到了错误的用户目录下,或者authorized_keys的权限不对。SSH 对权限非常敏感,~/.ssh必须是 700,~/.ssh/authorized_keys必须是 600,多一个组写权限就会被拒绝,而且日志里只说“publickey”,不会告诉你权限有问题。

形态二通常是重装机器或者重建实例之后出现的。云主机的 IP 复用很常见,同一个 IP 上换了台新机器,本地~/.ssh/known_hosts里记的还是旧指纹,连接时直接拒绝。

形态三是 hosts 没配或者配错。这个问题最直接,但也最容易配漏——三台机器要各自维护一份/etc/hosts,漏一台就有一个方向不通。

我排查这一段固定按这个顺序走:先ping node-0002看解析,不通就先修 hosts;通了再ssh -v node-0002看卡在哪一步;-v的输出会把公钥尝试过程全部打出来,比猜快得多。

3.2 /etc/hosts 的几种典型错法

很多人觉得/etc/hosts就是三行 IP 加主机名,没什么可错的。实际排查下来,出问题的方式至少有四类。

第一类是只在 node-0001 上配了。start-dfs.sh在执行节点上是能解析对端名字的,所以脚本能跑起来,但它通过 ssh 到 node-0002 之后,node-0002 上的sshd启动DataNode时又要解析一次node-0001,这时候就对不上了。表现是 node-0001 上的NameNode起来了,其他节点的DataNode一个都没有。

第二类是把公网 IP 写进去了。云主机一般有内外两张网卡,集群内部通信应该走内网地址。如果 hosts 里写的是公网 IP,即使能通,流量也会绕一圈,还受安全组和带宽限制。更糟的情况是公网入方向没放行 22 端口,ssh直接超时。

第三类是主机名带下划线或者大小写不一致。Hadoop 的slaves文件、core-site.xml里的fs.defaultFS、/etc/hosts里的映射,三处的名字必须完全一致。node_0001和node-0001在 DNS 规则里不是一回事,Node0001和node0001也可能因为大小写敏感对不上。

第四类是把主机名映射到127.0.0.1。这种写法在单机上没问题,放到集群里就是麻烦:node-0002 上ping node-0002解析到的是 127.0.0.1,看似通了,实际上 DataNode 注册到 NameNode 的地址是本机回环地址,NameNode 永远找不到它。

正确写法是这样的,三台机器完全一致:

# /etc/hosts,三台机器内容必须完全相同,用内网地址,不要用公网地址 10.0.0.11 node-0001 10.0.0.12 node-0002 10.0.0.13 node-0003 # 本地回环保持在最后,且不要把主机名挂在 127.0.0.1 上 127.0.0.1 localhost localhost.localdomain

改完 hosts 还要顺手确认主机名本身是对的:

# 三台机器各自执行,确保 hostname 与 hosts 里写的名字一致 hostname hostnamectl set-hostname node-0002 # 校验解析结果是不是内网 IP getent hosts node-0002

getent hosts比ping更干净,它直接查名称服务,输出解析结果就退出,不会像ping那样还要等你按 Ctrl+C。三台机器上都跑一遍这三个名字,全部返回内网 IP 才算过关。

3.3 免密登录:一次配通的完整序列

节点互信的本质是三台机器互相之间都能免密ssh。常见的偷懒做法是只在 node-0001 上生成密钥、复制到其他两台,这样 node-0001 能免密连出去,反方向不行。平时跑start-dfs.sh勉强够用,一旦涉及yarn的ResourceManager重启或者跨节点调度,就会出现莫名其妙的权限错误。

我的做法是每台机器都生成自己的密钥对,然后把三份公钥汇总成一个authorized_keys,再分发回去。

# 三台机器各自执行:生成密钥,-N '' 表示不设密码,-f 指定路径避免覆盖已有密钥 ssh-keygen -t rsa -b 2048 -N '' -f /root/.ssh/id_rsa # 三台机器各自执行:把本机公钥追加到自己的 authorized_keys,实现免密登录本机 cat /root/.ssh/id_rsa.pub >> /root/.ssh/authorized_keys chmod 700 /root/.ssh chmod 600 /root/.ssh/authorized_keys chmod 600 /root/.ssh/id_rsa # 在 node-0001 上:把其他两台的公钥拉过来,汇总成一份完整清单 ssh node-0002 'cat /root/.ssh/id_rsa.pub' >> /tmp/all_keys ssh node-0003 'cat /root/.ssh/id_rsa.pub' >> /tmp/all_keys cat /root/.ssh/id_rsa.pub >> /tmp/all_keys sort -u /tmp/all_keys -o /tmp/all_keys # 分发回每台机器 for h in node-0001 node-0002 node-0003; do scp /tmp/all_keys "$h":/root/.ssh/authorized_keys ssh "$h" 'chmod 600 /root/.ssh/authorized_keys' done

ssh-keygen -N ''里的-N是 passphrase 参数,空字符串表示不加密私钥。集群内部自动拉起进程时没有人能交互输入密码,这一步必须做成空口令。-b 2048是密钥长度,RSA 2048 位是当前兼容性和安全性都够用的值,太短的老算法会被新版sshd拒绝。

sort -u用来去重,避免 node-0001 的公钥在文件里出现两次。虽然多一条不影响功能,但后面出问题看文件的时候容易犯迷糊。

分发完立刻验证,而且要把六个方向都验一遍:

# 六条命令全部无密码返回 hostname,才算互信成立 for src in node-0001 node-0002 node-0003; do for dst in node-0001 node-0002 node-0003; do echo -n "$src -> $dst : " ssh "$src" "ssh -o BatchMode=yes -o ConnectTimeout=5 $dst hostname" done done

-o BatchMode=yes是关键参数,它禁止任何交互式提问,包括密码输入和首次连接的主机指纹确认。如果某一对没配好,命令会直接失败而不是卡在那里等你敲yes。ConnectTimeout=5防止网络不通时一直挂着。

3.4 start-dfs.sh 只起了一半节点

互信配完了,start-dfs.sh还是可能出问题,表现是输出里有一部分节点报了Starting datanodes之后就没了下文,或者直接打印一串Permission denied。

第一种情况是首次连接没有预先把对端指纹写进known_hosts。上面的验证脚本用BatchMode=yes能发现这个问题,但如果验证时偷懒用了手工ssh,你可能已经点过yes,指纹记录只是存在于你当前那个会话对应的用户目录下。start-dfs.sh以另一个身份跑,或者用了不同的HOME,指纹记录就读不到。

第二种情况是slaves文件写错了。这个文件在$HADOOP_HOME/etc/hadoop/slaves,每行一个主机名,必须与/etc/hosts里完全一致,不能有多余的空格和空行,不能写 IP。

# $HADOOP_HOME/etc/hadoop/slaves # 只写主机名,不写 IP,不要留空行和行尾空格 node-0002 node-0003

提示:slaves文件里不包含 node-0001 本身。NameNode 和 ResourceManager 跑在 node-0001 上,DataNode 如果也想起在 node-0001 上,需要在slaves里显式加上,否则jps在 node-0001 上只会看到 NameNode。

第三种情况是sshd对 root 登录做了限制。有些镜像默认PermitRootLogin是prohibit-password或者no,公钥方式在部分配置下也会被拦。改/etc/ssh/sshd_config里的PermitRootLogin yes之后记得重启服务,光改文件不重启是不生效的。

4. 对象存储接不上:core-site.xml 里的 AK/SK 与 endpoint 怎么填

4.1 报错特征:先分清是认证问题还是网络问题

Hadoop 跑起来之后,把对象存储挂成文件系统是下一个容易卡住的位置。这一段的报错信息看起来都很像“连不上”,但根因差别很大,先看异常类名能省掉一半排查时间。

# 认证类:AK/SK 不对、没权限、桶策略没开 org.apache.hadoop.fs.obs.OBSIOException: ... 403 Forbidden NoAuthException: Anonymous access is forbidden # 配置类:协议实现类没配或者对应的 jar 包没加载 No FileSystem for scheme: obs java.lang.ClassNotFoundException: org.apache.hadoop.fs.obs.OBSFileSystem # 网络类:endpoint 写错、区域不对、出口不通 java.net.UnknownHostException: obs.<region>.example-cloud.com java.net.SocketTimeoutException: connect timed out

403 Forbidden说明网络是通的,请求已经到达服务端,被拒的是身份。No FileSystem for scheme说明 Hadoop 根本没找到处理obs://协议的实现类,通常是 jar 包没放进 classpath 或者配置项名字写错。UnknownHostException是域名解析失败,先nslookup或getent hosts验一下 endpoint 能不能解析,别在配置里反复改。

这三类里最坑的是UnknownHostException和SocketTimeoutException混着出。同一个 endpoint,在你本地电脑上能解析,在云主机上解析不了,因为云主机的 DNS 配置可能指向了内网解析器。这种问题改配置文件是解决不了的。

4.2 core-site.xml 关键参数逐项说明

对象存储的接入配置集中在core-site.xml,几个参数配错任何一个都会导致前面的报错。下面这份是我验证过的结构:

<configuration> <!-- 默认文件系统仍然指向 HDFS,对象存储作为额外的一种 scheme 使用 --> <property> <name>fs.defaultFS</name> <value>hdfs://node-0001:9000</value> </property> <!-- 声明 obs:// 协议由哪个类实现,名字写错就会报 No FileSystem for scheme --> <property> <name>fs.obs.impl</name> <value>org.apache.hadoop.fs.obs.OBSFileSystem</value> </property> <!-- Hadoop 的抽象文件系统层同样需要注册,缺了它部分命令会退化 --> <property> <name>fs.AbstractFileSystem.obs.impl</name> <value>org.apache.hadoop.fs.obs.OBS</value> </property> <!-- 认证信息,从控制台下载的凭证文件里取,注意首尾不要带空格和换行 --> <property> <name>fs.obs.access.key</name> <value>填写你自己的AK</value> </property> <property> <name>fs.obs.secret.key</name> <value>填写你自己的SK</value> </property> <!-- 终端节点,必须与桶所在区域一致,跨区会显著变慢甚至超时 --> <property> <name>fs.obs.endpoint</name> <value>obs.&lt;region&gt;.example-cloud.com</value> </property> <!-- 走 HTTPS,端口 443,公网环境下不要关掉 --> <property> <name>fs.obs.connection.ssl.enabled</name> <value>true</value> </property> </configuration>

fs.defaultFS保持指向 HDFS 是故意的。默认文件系统决定了hadoop fs -ls /这种不带 scheme 的命令落在哪里,让它落在 HDFS 上更符合实验预期,访问对象存储时显式写全obs://桶名/路径。

fs.obs.impl和fs.AbstractFileSystem.obs.impl两个都要配。前者是文件系统实现,后者是抽象层注册,Hadoop 2.x 里这两层是分开的。只配一个的时候,hadoop fs -ls obs://...可能能跑,但hadoop distcp这类依赖抽象层的工具会报错。

AK/SK 那两行最容易出问题。登录控制台创建一个访问密钥,下载下来的凭证文件里是 CSV 格式,字段顺序是用户名、Access Key Id、Secret Access Key。复制的时候别用鼠标划选,容易多带空格;用awk -F, '{print $2}'和{print $3}精确取值更稳。取出来的值写到配置里,前后不能有空格,<value>标签必须紧贴内容。

endpoint 那行注意 XML 里的<和>要转义。有些区域名字里带特殊字符,直接写会破坏 XML 结构,导致整个配置文件解析失败,Hadoop 启动时的报错会指向别的模块。

4.3 endpoint 选错区域与双栈地址的区别

对象存储的控制台一般会给两个终端节点:一个是普通的 IPv4 地址,一个是双栈地址。双栈那个能同时响应 IPv4 和 IPv6,看起来更“高级”,但用错了就是灾难。

云主机如果只有 IPv4 出口(大部分默认网络环境是这样),双栈域名解析时可能优先返回 AAAA 记录。客户端拿到 IPv6 地址去连,发现本机根本没有 IPv6 路由,连接请求发不出去,表现出来就是connect timed out,而且超时时间很长,看起来像服务端挂了。这种问题最难查,因为ping那个 IPv4 地址是通的,配置文件看起来也没问题。

我的判断方法是直接看解析结果:

# 分别看 A 记录和 AAAA 记录,确认客户端拿到的地址族 getent ahosts obs.<region>.example-cloud.com | head -5 # 如果第一行就是 IPv6 地址、而本机没有 IPv6 路由,就换成纯 IPv4 的 endpoint ip -6 route show default

第二条命令没有输出,说明本机没有 IPv6 默认路由,那就老老实实用 IPv4 那个 endpoint。别为了“看起来更先进”去配双栈,除非你确认过整条链路都支持。

区域一致性同样重要。桶建在哪个区域,endpoint 就必须用那个区域的。跨区访问不一定报错,但延迟会明显上升,传大文件的时候表现成间歇性超时,排查起来非常迷惑。控制台的桶详情页里能看到区域信息,抄下来直接对应。

4.4 用一条最小命令验证接入

配置改完不是直接跑业务,先做最小验证。这个顺序能帮你把“配置问题”和“数据问题”分开。

# 第一步:确认实现类加载成功,能列出桶根目录 # 这条命令不涉及具体数据,只验证认证和连通性 hadoop fs -ls obs://你的桶名/ # 第二步:写一个小文件上去,验证写权限 echo "hello obs" > /tmp/probe.txt hadoop fs -put /tmp/probe.txt obs://你的桶名/probe/ # 第三步:读回来比对内容,验证读权限 hadoop fs -cat obs://你的桶名/probe/probe.txt

第一步报403就回去查 AK/SK 和桶策略,报No FileSystem for scheme就回去查fs.obs.impl和 jar 包,报UnknownHostException就回去查 endpoint 和 DNS。三类错误对应三块配置,不要混着改。

第二步报权限错误但第一步能列目录,说明这个 AK 只有读权限没有写权限。桶的访问策略里可以分别控制读写,别想当然以为能读就能写。

第三步读回来的内容不对,那是传输或者编码问题,跟接入配置无关了。到这一步,接入就算通了,可以开始跑后面的实验步骤。

5. 踩坑清单:五个卡了大半天的现场

5.1 只改了一台机器的 hosts,集群起了一半

现象:start-dfs.sh输出里 node-0002 显示Starting datanodes,但等几秒后jps在 node-0002 上什么都没有;node-0003 正常。NameNode 起来了,hdfs dfsadmin -report显示只有一台 DataNode。

原因:node-0002 的/etc/hosts里只配了自己和 node-0001,解析不到 node-0003,也解析不到自己在集群里的角色名。DataNode 启动时要向 NameNode 注册,注册信息里带上自己的主机名,NameNode 反向解析失败就把这个节点丢弃了。

解决:把三台机器的 hosts 做成同一份文件,用scp统一下发,改完用getent hosts在每台机器上把三个名字都验一遍。以后任何涉及主机名的改动,都按“改一份、发三份、验三遍”走。

5.2 重新分发时漏了 hadoop-env.sh,配置回滚

现象:node-0001 上java -version正常,JAVA_HOME 也改对了,start-dfs.sh一到 node-0003 就报JAVA_HOME is not set。去 node-0003 上看hadoop-env.sh,里面还是老路径。

原因:改配置的时候只改了 node-0001,分发的时候用了rsync但目标路径写成了$HADOOP_HOME/share,etc目录整个没同步过去。这种漏同步在目录层级多的时候特别容易发生。

解决:分发只做整目录同步,不做局部。命令固定成rsync -av $HADOOP_HOME/ node-000X:$HADOOP_HOME/,源和目标的结尾斜杠都带上,保证是“目录内容到目录内容”的语义。同步完用md5sum对比一下关键配置文件,确认三台一致。

5.3 重复格式化,DataNode 起不来

现象:HDFS 启动后,jps在 node-0002 上能看到DataNode进程一闪而过,日志里刷Incompatible clusterIDs in /home/nm/localdir/current: namenode clusterID = CID-xxxx; datanode clusterID = CID-yyyy。

原因:hdfs namenode -format执行过不止一次。每格式化一次,NameNode 生成新的 clusterID,而 DataNode 目录里的VERSION文件还记着旧值,两边对不上,DataNode 拒绝启动。

解决:最干净的办法是清掉所有节点的数据目录,重新格式化一次,之后别再执行 format。如果目录里有你不想丢的数据,可以手工把 DataNodeVERSION文件里的clusterID改成与 NameNode 一致,但这是补救手段,实验环境直接清空更省事。

注意:format 命令只应该在集群首次搭建时执行一次。任何时候想“重新来一遍”,都要先确认数据目录已经清空,否则就是在给自己制造 clusterID 冲突。

5.4 数据目录没提前建,启动时报 Mkdirs failed

现象:start-dfs.sh过程中报java.io.IOException: Mkdirs failed to create /home/nm/localdir,或者 YARN 那边报类似local-dirs are bad。

原因:hdfs-site.xml和yarn-site.xml里配的目录路径,Hadoop 不会自动帮你逐级创建。父目录/home/nm不存在的时候,mkdirs只能建最后一级,前面的建不出来。

解决:在所有节点上提前把这些目录建好,并且把属主设对。这一步要在启动集群之前做,不是出了错再补。

# 所有节点执行,-p 保证父目录一起创建,重复执行不报错 mkdir -p /home/modules/data/buf mkdir -p /home/nm/localdir mkdir -p /home/modules/hadoop-2.8.3/logs # 确认目录属主与运行 Hadoop 的用户一致 ls -ld /home/nm/localdir

5.5 进程都在,网页打不开

现象:三台机器jps输出齐全,hdfs dfs -ls /也能正常执行,但浏览器访问http://node-0001:9870一直转圈,最后超时。

原因:云主机的安全组默认只放行 22 端口,9870、8088、9000、8020 这些全都没开。命令行能通是因为走的是内网地址,浏览器走的是公网入口,两条路径的放行规则不一样。

解决:在控制台的安全组里按需放行端口,并且限定来源 IP,别图省事开成 0.0.0.0/0。先用ss -lntp确认进程确实在监听对应端口,再去查安全组,能省掉一半来回。

# 在 node-0001 上确认监听状态,有 LISTEN 才说明是网络层的问题 ss -lntp | grep -E '9870|9000|8088'

这个坑之所以记下来,是因为它太像“玄学”了:所有命令都正常,日志里什么错都没有,就是打不开。搞不清楚状况的时候,人会开始怀疑镜像、怀疑版本,其实只是云平台那一层没放行。

6. 快速定位:先看哪一行,再敲哪条命令

排查这套集群问题,我最后收敛成一个三步走的习惯,比翻文档快得多。

第一步永远是确认“我以为的环境”和“实际的环境”是不是同一个。很多人报错之后的第一反应是改配置,但配置改动生效需要重启进程,重启又依赖互信,互信又依赖 hosts。这条链上任何一环断了,你改的所有东西都不会生效。所以先跑这三条:

# 三条命令分别确认:Java 环境、主机名解析、免密登录 bash -lc 'echo $JAVA_HOME && readlink -f $(which java) && javac -version' getent hosts node-0001 node-0002 node-0003 ssh -o BatchMode=yes -o ConnectTimeout=5 node-0002 hostname

三条全过,说明基础环境没问题,问题在 Hadoop 配置或者服务进程上;任何一条不过,先修它,别往下走。

第二步是按“报错位置”反推“配置位置”。下面这张表是我贴在备忘里的对应关系,遇到报错先查表,别从零开始想:

报错或现象优先检查具体看什么
JAVA_HOME is not sethadoop-env.sh是否只有一处有效export JAVA_HOME,路径下有没有bin/javac
只有部分节点起不来/etc/hosts、slaves三台 hosts 是否一致,slaves是否只有主机名
Permission denied (publickey)~/.ssh权限目录 700、authorized_keys600,公钥是否在对端
Incompatible clusterIDs各节点数据目录current/VERSION里的 clusterID 是否一致
No FileSystem for scheme: obscore-site.xml、jar 包fs.obs.impl是否拼错,jar 是否在 classpath
命令能跑但网页打不开安全组、ss -lntp端口是否监听、来源是否放行

第三步是看日志,而且只看第一处异常。Hadoop 的日志会级联,一个 Java 环境问题能刷出几百行ERROR,你从后往前看会看到一堆衍生错误。正确姿势是grep -n "Caused by" 日志文件 | head -3,找到最内层那个异常,它通常才是根因。

# 按时间取最近的 NameNode 日志,只揪最内层异常 ls -t $HADOOP_HOME/logs/hadoop-root-namenode-*.log | head -1 grep -n -A3 "Caused by" $(ls -t $HADOOP_HOME/logs/hadoop-root-namenode-*.log | head -1) | head -20

-A3会连带打出异常后面三行,栈信息的第一层通常就写清楚了是哪个文件、哪一行出的问题。这比把整个日志文件从尾到头翻一遍高效得多。

从那以后,我每次重建集群都会强制走一遍这几个确认步骤:先验 Java 真实路径,再验三台 hosts 一致,再验六向免密,最后才动format和start-dfs.sh。前面这几步每步只要十几秒,却能省掉后面几个小时反复重启和自我怀疑。前面这几个步骤通过了,还有个固定习惯是改任何配置都先rsync全量同步一次再重启,不做“只改一台试试看”的操作,集群环境里从来没有“只改一台”这回事。希望帮到你。

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

RELATED READING

延伸阅读

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