用Docker从零搭建Hadoop完全分布式集群实战指南
2026/9/16 9:12:25 网站建设 项目流程

这几年我在不同电脑上反复搭过 Hadoop 环境,最大的痛点就是“一模一样的过程,换个机器就翻车”:要么 JDK 版本不对,要么 SSH 免密登录失效,要么文件权限搞错,排查一个下午才发现是环境不一致。后来我干脆把所有东西都塞进 Docker,一套镜像走天下,到任何一台机器上只要装了 Docker Desktop,拉起来就是完好的集群。这篇文章就是把我这一整套“零基础也能跑起来”的 Docker 化 Hadoop 集群搭建过程完整拆给你看。

这个项目本质上解决的是“一台电脑怎么低成本模拟出多台服务器的分布式效果”的问题。传统做法是用虚拟机开三台 CentOS,内存光吃 6GB 起步,启动还要等几分钟,配置 IP、配免密、改 hosts 一套流程走下来,新手至少折腾半天。而用 Docker 之后,3 个容器加起来内存可以控制在 4GB 左右,启动只需要十几秒,所有配置通过文件管理,想删掉重建集群也只是一条命令的事。适合完全没有 Docker 经验、但又想快速上手 Hadoop 的大数据初学者,也适合准备面试前想在本地搭一套环境练手的人。

整套搭建过程我分成了五个部分:先说整体设计和思路,再说环境准备,然后是核心的镜像构建和配置文件编写,接着是启动验证和提交 WordCount 任务,最后是高频问题和排查技巧。你只要照着顺序做,基本能一次跑通。

1. 项目概述:为什么零基础首选 Docker 搭 Hadoop

1.1 核心需求解析:一台电脑搭出“三台机器”

Hadoop 完全分布式集群和伪分布式的差别,很多人一开始没搞清楚。伪分布式只是把 NameNode、DataNode、ResourceManager、NodeManager 这些角色全部塞进同一个 JVM 进程,看起来是集群,实际上根本没有网络通信和节点间协作。而完全分布式要求每个角色跑在不同节点上,节点之间要通过 SSH 互相免密登录,NameNode 要能远程启动 DataNode 上的进程,这对环境一致性要求极高。

如果用物理机,你得有三台电脑,这在学习阶段不现实。用虚拟机,三台 CentOS 光安装配置就要半天,而且虚拟机快照、克隆之后经常出现 MAC 地址冲突、hostname 不对等幺蛾子。用 Docker 的好处在于:容器本身就是轻量级的“微型服务器”,每个容器有独立的 IP、hostname、文件系统,和真实服务器几乎一样的隔离效果,但底層共享宿主机内核,资源开销极小。

我见过很多新手在博客上抄了一套安装步骤,结果每个博客写的目录路径、版本号、配置项都不一样,抄完根本跑不起来。而 Docker 方案最爽的一点是:所有环境信息都被固化在镜像和配置文件中,只要你用和我相同的基础镜像版本、相同的 Hadoop 版本,结果就是确定性的。这也是我为什么强烈推荐零基础的人直接从 Docker 方案入手,而不是一上来就折腾虚拟机。

1.2 方案选型:为什么不用虚拟机,选 Docker

先别急着写配置,得把方案选的逻辑讲清楚。很多人觉得用虚拟机更“贴近生产环境”,这话没错,但对于学习和实验来说,Docker 的优势是压倒性的。

第一,资源开销差距明显。三台 CentOS 虚拟机至少要分配 6GB 以上内存才跑得动,而 Docker 容器共享宿主机内核,三个容器加起来预留 4GB 就非常宽裕。你在 Docker Desktop 的 Settings 里给虚拟机分配 4GB 内存,三台 Hadoop 节点加操作系统,还能剩下一半内存给 IDE 和浏览器。

第二,环境一致性。虚拟机最容易踩坑的地方在于:你在第一台机器上装好了 JDK,第二台漏了一步,第三台环境变量没配好,结果 NameNode 启动成功、DataNode 启动失败,排查的时候那叫一个痛苦。Docker 我们把所有依赖通过一份 Dockerfile 构建成镜像,三个容器共用同一个镜像,等于从源头保证了三台机器初始状态完全一致。

第三,可重复性和清理成本。虚拟机玩坏了要么回滚快照,要么重新安装。Docker 直接docker-compose down把容器全删掉,再docker-compose up -d用同样的配置文件重新拉起来,整个过程不超过一分钟。这种“随时推倒重来”的能力对新手来说特别友好,容错率极高。

当然 Docker 方案也有局限:容器是进程级隔离,和真实物理机的网络、磁盘 IO 表现肯定有差距,如果你要研究 Hadoop 的机架感知、磁盘故障恢复等底层机制,还是得回归物理机或虚拟机。但作为学习 Hadoop 组件用法、跑 MapReduce 任务、练习调参,Docker 完全够用。

1.3 集群架构设计:一主两从,各节点角色分配

我采用的是最经典的一主两从结构,总共三个节点,每个节点是一个独立的容器。

节点分配如下:

容器名角色运行进程外部端口映射
namenode主节点NameNode、ResourceManager9870、8088
datanode1从节点DataNode、NodeManager
datanode2从节点DataNode、NodeManager

NameNode 负责管理 HDFS 的元数据,ResourceManager 负责 YARN 的资源调度,这两个角色放在同一台机器上,和 most 教材里的经典部署一致。两个 DataNode 存储实际数据块,同时各自跑一个 NodeManager 接收 MapReduce 任务。副本因子我配置为 2,这样两个 DataNode 各存一份,既能看到副本机制的效果,又不会因为副本数不足导致 HDFS 进入安全模式。

加粗一点讲:为什么选择一主两从而不是一主一从?因为 Hadoop 默认副本因子是 3,如果只有一个 DataNode,副本数永远配不齐,NameNode 会一直提示有 block 处于 under-replicated 状态,新手看到这种情况容易胡思乱想,以为是搭建出问题了。配两个 DataNode,设成副本数为 2,状态就干干净净。

容器的网络我采用 Docker Compose 创建的自定义 bridge 网络,容器之间通过 service 名直接互通,不需要像传统虚拟机那样手动配置 IP 和 hosts 映射。这一点等下在配置文件里详细展开。

2. 环境准备:先把容器化基础打好

2.1 安装 Docker Desktop:最容易卡住的一步

别小看这一步,我见过至少一半的新手卡在 Docker Desktop 起不来。网上问得最多的一个报错就是:Docker Desktop failed to start because virtualisation support wasn't detected

这个报错的核心原因是 Windows 的虚拟化功能没开。Docker Desktop 在 Windows 上运行,底层依赖两种虚拟化方案中的一种:一种是 WSL2,一种是 Hyper-V。无论你用哪一种,前提都是 CPU 的虚拟化指令已经在 BIOS 中开启。检查步骤如下:

  1. 打开任务管理器,切到“性能”选项卡,点 CPU,看右下角“虚拟化”这一项的状态。如果显示“已启用”,说明 BIOS 层面没问题;如果显示“已禁用”,需要重启电脑进 BIOS,找到 Intel Virtualization Technology(AMD 对应 SVM Mode),设为 Enabled。
  2. 开启虚拟化后,进入“控制面板 - 程序 - 启用或关闭 Windows 功能”,确保“适用于 Linux 的 Windows 子系统”和“虚拟机平台”这两项被勾选。如果你用的是 Windows 11,一般默认已经开启。
  3. 如果你的电脑是 Windows 10 家庭版,没有 Hyper-V 组件,那走 WSL2 方案就行。在 PowerShell(管理员权限)里执行wsl --install安装默认的 Linux 发行版,装完重启电脑。
  4. 安装 Docker Desktop 安装包,一路下一步。安装完成后,在 Settings -> General 中确保Use the WSL 2 based engine被勾选。

安装完成后打开终端,输入docker version,能看到 Client 和 Server 两段信息说明 Docker 已经正常工作了。注意:如果只显示 Client 信息而 Server 报错,说明 Docker 引擎没启动起来,回到上面的步骤排查。

2.2 配置镜像加速和确认环境可用

镜像加速这一步很多新手会忽略,但如果你不配置,下载 Hadoop 基础镜像时可能会很慢甚至超时。打开 Docker Desktop 的 Settings -> Docker Engine,在 JSON 配置里加入镜像加速地址。

{ "registry-mirrors": [ "https://docker.m.daocloud.io" ] }

加完之后点击 Apply & Restart 让配置生效。常用的加速地址有多个,我这边实测下来主要用 daocloud 的,稳定性还可以。配置完成后拉一个测试镜像验证一下:

docker pull ubuntu:20.04

如果镜像能拉下来,说明网络和加速配置都没问题。这里再提前做一件事:创建项目目录。我在本地统一用D:\hadoop-cluster,你在自己的电脑上找一个路径,后面所有 Dockerfile、配置文件都放到这个目录下面,方便整个集群的配置统一管理。

2.3 基础镜像与软件版本选型

版本选型这件事看起来不起眼,实际上决定了你后面能不能顺利跑通。我给这套方案选定的是:Ubuntu 20.04 作为基础镜像、OpenJDK 8、Hadoop 3.3.6。

为什么是这三个版本?

  • Ubuntu 20.04:Docker Hub 上最稳定的 LTS 版本之一,apt 源默认可用,安装 OpenJDK 直接一条命令。
  • OpenJDK 8:Hadoop 官方长时间只保证对 Java 8 的支持,Java 11 虽然也能跑但偶尔有兼容性问题。零基础就别给自己找麻烦,老老实实用 Java 8。
  • Hadoop 3.3.6:3.x 系列是目前主流,和网上大部分教程、参考文档匹配。3.2.x 和 3.3.x 配置方法基本一样,但 3.3.x 的下载包和 bug 修复更完善。

Hadoop 安装包需要提前下载好。你去 Apache 官网或者清华镜像站下载hadoop-3.3.6.tar.gz,然后把安装包放到项目目录下,Dockerfile 构建时会直接使用这个本地包。

另外提醒一下:国内用户如果直接从官网下载 Hadoop 安装包,速度可能特别慢,用清华镜像源下载会快很多。这个在 Hadoop 下载页面上搜索“清华镜像”就能找到,不做过多展开。

3. 核心实现:从镜像到集群的完整搭建

3.1 第一步:准备 JDK 和 Hadoop 安装包

在项目目录下创建build子目录,把 Hadoop 安装包放进去。整个项目目录结构是这样的:

hadoop-cluster/ ├── build/ │ ├── Dockerfile │ ├── hadoop-3.3.6.tar.gz │ └── ssh_config ├── docker-compose.yml ├── config/ │ ├── core-site.xml │ ├── hdfs-site.xml │ ├── yarn-site.xml │ ├── mapred-site.xml │ └── workers

这里单独解释一下ssh_config文件是干什么的。Hadoop 的 start-dfs.sh 脚本通过 SSH 远程到每个 DataNode 启动进程,如果 SSH 连接时有任何交互式确认,脚本就会卡住。所以我们需要在配置里加上StrictHostKeyChecking noUserKnownHostsFile /dev/null,跳过首次连接时的 host key 确认,避免集群启动脚本卡死在确认环节。

3.2 第二步:编写 Dockerfile 构建基础镜像

Dockerfile 是整个集群的地基,它的作用是构建出一个“已经装好了 JDK、SSH、Hadoop 环境变量”的镜像。这个镜像本身不启动任何 Hadoop 服务,只是准备好环境。真正的服务启动动作,是容器启动后在启动脚本里手动执行的。

直接贴我这份 Dockerfile:

FROM ubuntu:20.04 # 避免 apt 安装时出现交互式提示 ENV DEBIAN_FRONTEND=noninteractive # 安装基础工具、SSH、JDK8 RUN apt-get update && apt-get install -y --no-install-recommends \ openssh-server \ openssh-client \ vim \ net-tools \ iputils-ping \ openjdk-8-jdk \ rsync \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* # 创建 hadoop 用户,后续所有操作以该用户身份执行 RUN useradd -m -s /bin/bash hadoop # 配置 SSH 免密登录:生成密钥,并把公钥写入 authorized_keys RUN mkdir -p /home/hadoop/.ssh \ && ssh-keygen -t rsa -P '' -f /home/hadoop/.ssh/id_rsa \ && cat /home/hadoop/.ssh/id_rsa.pub >> /home/hadoop/.ssh/authorized_keys \ && chown -R hadoop:hadoop /home/hadoop/.ssh \ && chmod 700 /home/hadoop/.ssh \ && chmod 600 /home/hadoop/.ssh/authorized_keys # 设置 Hadoop 目录 RUN mkdir -p /opt/hadoop WORKDIR /opt/hadoop # 拷贝本地 Hadoop 安装包到镜像中并解压 COPY hadoop-3.3.6.tar.gz /tmp/ RUN tar -xzf /tmp/hadoop-3.3.6.tar.gz -C /opt/hadoop \ && mv /opt/hadoop/hadoop-3.3.6/* /opt/hadoop/ \ && rm -rf /opt/hadoop/hadoop-3.3.6 /tmp/hadoop-3.3.6.tar.gz # 创建 HDFS 数据目录 RUN mkdir -p /opt/hadoop/data/namenode \ && mkdir -p /opt/hadoop/data/datanode \ && chown -R hadoop:hadoop /opt/hadoop # 设置环境变量 ENV JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64 ENV HADOOP_HOME=/opt/hadoop ENV PATH=$PATH:$HADOOP_HOME/bin:$HADOOP_HOME/sbin # 复制 ssh_config,跳过首次连接时的 host key 确认 COPY ssh_config /home/hadoop/.ssh/config RUN chown hadoop:hadoop /home/hadoop/.ssh/config # 声明端口:SSH、NameNode Web UI、ResourceManager Web UI、DataNode Web UI EXPOSE 22 9870 8088 9864 # 启动 SSH 服务并保持前台运行 CMD ["/usr/sbin/sshd", "-D"]

有几个细节我强调一下。

第一,生成 SSH 密钥时-P ''表示空密码,这个必须指定,否则 ssh-keygen 会交互式让你输入密码,构建直接卡住。第二,所有 Hadoop 相关的目录权限最后都要chown给 hadoop 用户,因为我们在后面所有操作都用 hadoop 用户执行,权限不对会导致 DataNode 启动失败。第三,容器启动后直接以 SSH 守护进程作为第一个进程,这就保证了集群脚本可以远程操作每个节点。

ssh_config文件的内容如下:

Host * StrictHostKeyChecking no UserKnownHostsFile /dev/null LogLevel ERROR

3.3 第三步:编写 docker-compose.yml

docker-compose.yml 负责把三个容器拉起来、组成网络。我用的 Compose V2 语法,直接在项目目录下创建文件:

version: '3.8' services: namenode: image: hadoop-cluster:3.3.6 container_name: namenode hostname: namenode restart: always ports: - "9870:9870" - "8088:8088" volumes: - ./config:/opt/hadoop/etc/hadoop networks: - hadoop-net command: ["/usr/sbin/sshd", "-D"] datanode1: image: hadoop-cluster:3.3.6 container_name: datanode1 hostname: datanode1 restart: always volumes: - ./config:/opt/hadoop/etc/hadoop networks: - hadoop-net command: ["/usr/sbin/sshd", "-D"] datanode2: image: hadoop-cluster:3.3.6 container_name: datanode2 hostname: datanode2 restart: always volumes: - ./config:/opt/hadoop/etc/hadoop networks: - hadoop-net command: ["/usr/sbin/sshd", "-D"] networks: hadoop-net: driver: bridge

重点看这里:我把宿主机上的./config目录挂载到了容器里的/opt/hadoop/etc/hadoop,也就是 Hadoop 的配置目录。这样做的意思是,所有 XML 配置文件我在宿主机上改好后直接生效,不需要重新构建镜像。以后你想调整参数,改完配置文件重启容器就行,效率比传统虚拟机方案高了很多。

hostname配置决定了容器内部的主机名,同时 Docker Compose 会自动将服务名注册到自定义 DNS 中。也就是说在 namenode 容器里直接执行ping datanode1,能解析到对应的容器 IP,完全不需要手动配 hosts。这比虚拟机方案里手动改 /etc/hosts 省心一万倍。

关于端口映射,这里是第一个新手容易犯迷糊的地方:"9870:9870"前面是宿主机端口,后面是容器端口。外部访问用宿主机端口,容器之间通信用容器端口。如果 8088 端口在宿主机上被其他程序占了,可以改成"18088:8088",访问时就用 localhost:18088。

3.4 第四步:编写 Hadoop 核心配置

这是整个搭建过程中最关键的一步。Hadoop 的配置分散在多个 XML 文件中,每个文件管一类组件。我直接给你一个能跑通的完整配置,每个文件都会解释核心参数。

第一个是core-site.xml

<?xml version="1.0" encoding="UTF-8"?> <?xml-stylesheet type="text/xsl" href="configuration.xsl"?> <configuration> <property> <name>fs.defaultFS</name> <value>hdfs://namenode:9000</value> </property> <property> <name>hadoop.tmp.dir</name> <value>/opt/hadoop/tmp</value> </property> </configuration>

fs.defaultFS是整个 HDFS 的门面地址,所有客户端要访问 HDFS 都是通过这个地址。注意这里我用的是namenode而不是 IP 或 localhost,因为容器之间通过 hostname 互通,这个 hostname 在 compose 文件里已经定义好了。

hadoop.tmp.dir是 Hadoop 的临时目录,NameNode 的元数据和 DataNode 的数据块默认都在这个目录下面,所以必须保证这个目录有写权限。

第二个是hdfs-site.xml

<?xml version="1.0" encoding="UTF-8"?> <?xml-stylesheet type="text/xsl" href="configuration.xsl"?> <configuration> <property> <name>dfs.namenode.name.dir</name> <value>file:///opt/hadoop/data/namenode</value> </property> <property> <name>dfs.datanode.data.dir</name> <value>file:///opt/hadoop/data/datanode</value> </property> <property> <name>dfs.replication</name> <value>2</value> </property> <property> <name>dfs.permissions.enabled</name> <value>false</value> </property> </configuration>

dfs.namenode.name.dirdfs.datanode.data.dir分别指定 NameNode 和 DataNode 的数据存储位置。这两个目录我在 Dockerfile 里已经创建好了,并且权限已经给到了 hadoop 用户。

dfs.replication=2前面说过了,两个 DataNode 各存一份。如果你的集群有三台 DataNode,就设成 3。

dfs.permissions.enabled=false这个是我在实验环境里刻意关掉的 HDFS 权限检查。生产环境绝对不能关,但本地学习环境开着会导致上传文件时经常遇到 permission denied,新手排查起来心态容易崩。

第三个是yarn-site.xml

<?xml version="1.0" encoding="UTF-8"?> <?xml-stylesheet type="text/xsl" href="configuration.xsl"?> <configuration> <property> <name>yarn.resourcemanager.hostname</name> <value>namenode</value> </property> <property> <name>yarn.nodemanager.aux-services</name> <value>mapreduce_shuffle</value> </property> <property> <name>yarn.nodemanager.resource.memory-mb</name> <value>2048</value> </property> </configuration>

yarn.resourcemanager.hostname告诉所有 NodeManager 去哪里找 ResourceManager,这里同样用 hostname 而不是 IP。mapreduce_shuffle是 MapReduce 任务在 YARN 上运行时的辅助服务,少了它任务会直接失败。

yarn.nodemanager.resource.memory-mb是每个 NodeManager 能使用的物理内存上限。我给的是 2048MB,如果你宿主机内存只有 8GB,建议减到 1536,不然三个容器加起来的内存开销可能会拖垮电脑。

第四个是mapred-site.xml

<?xml version="1.0" encoding="UTF-8"?> <?xml-stylesheet type="text/xsl" href="configuration.xsl"?> <configuration> <property> <name>mapreduce.framework.name</name> <value>yarn</value> </property> <property> <name>mapreduce.application.classpath</name> <value>$HADOOP_HOME/share/hadoop/mapreduce/*:$HADOOP_HOME/share/hadoop/mapreduce/lib/*:$HADOOP_HOME/share/hadoop/common/*:$HADOOP_HOME/share/hadoop/common/lib/*:$HADOOP_HOME/share/hadoop/hdfs/*:$HADOOP_HOME/share/hadoop/hdfs/lib/*:$HADOOP_HOME/share/hadoop/yarn/*:$HADOOP_HOME/share/hadoop/yarn/lib/*</value> </property> </configuration>

mapreduce.framework.name=yarn表示 MapReduce 任务提交到 YARN 上运行。mapreduce.application.classpath是 Hadoop 3.x 中必须显式配置的,因为 YARN 的 ApplicationClassLoader 默认不会自动加载 Hadoop 的相关 jar 包,不配置的话运行 WordCount 时会报ClassNotFoundException,这是 Hadoop 3.x 最容易踩的坑之一。

第五个是workers文件:

datanode1 datanode2

workers文件列出了所有 DataNode 节点的 hostname,start-dfs.sh 脚本就是通过读取这个文件来逐个启动 DataNode 进程的。注意 Hadoop 2.x 里这个文件名是slaves,到了 Hadoop 3.x 改成了workers,网上老教程经常写混,你要确保自己用的是和 Hadoop 版本匹配的文件名。

3.5 第五步:初始化 NameNode 并启动集群

配置文件全部放好之后,先构建基础镜像:

cd D:\hadoop-cluster docker build -t hadoop-cluster:3.3.6 ./build

构建过程会执行 Dockerfile 里的 apt 安装和 Hadoop 解压,根据网络情况可能需要几分钟到十几分钟不等。构建完成后,先用以下命令启动集群:

docker compose up -d

三个容器启动后,用docker ps确认所有容器都在运行状态。然后进入 NameNode 节点执行格式化操作,这一步是把 HDFS 的元数据初始化好,相当于给一块新硬盘做分区:

docker exec -it namenode bash su - hadoop hdfs namenode -format

格式化完成之后,启动 HDFS 和 YARN 服务:

start-dfs.sh start-yarn.sh

脚本执行过程中你会看到输出信息显示 NameNode、DataNode、ResourceManager、NodeManager 依次启动,并且会通过 SSH 远程到 datanode1 和 datanode2 上启动进程。这里有个心理准备:第一次执行时因为是 root 用户切换可能环境变量不完整,所以强调一下要用su - hadoop而不是直接在 root 下跑脚本。我刚开始搭的时候直接在 root 下执行,结果环境变量缺失导致 datanode 起不来,后来发现是HADOOP_HOME没带上。

4. 集群验证:跑通第一个 MapReduce 任务

4.1 验证服务进程和 Web UI

集群启动完,第一件事是在 namenode 节点上执行jps命令看进程状态。jps是 JDK 自带的一个工具,会列出当前用户启动的所有 Java 进程。

正常的话,namenode 节点上应该看到:

NameNode ResourceManager

datanode1 和 datanode2 节点上应该看到:

DataNode NodeManager

如果某个节点的进程缺失,不要急着重启整个集群,先去看对应日志。Hadoop 日志在/opt/hadoop/logs目录下,DataNode 起不来大概率是两个原因:一个是数据目录权限不对,另一个是格式化信息不一致。这部分我在第 5 章详细展开。

进程验证通过后,在浏览器上访问两个 Web 界面。NameNode 的 Web UI 是http://localhost:9870,YARN 的 ResourceManager Web UI 是http://localhost:8088。在 NameNode 的页面上,你可以看到集群活跃节点数量、HDFS 容量和文件系统状态;在 YARN 页面上,可以看到每个节点的资源信息和正在运行的任务列表。

这里有一个观察点很有用:打开 NameNode 页面后,看 Datanodes 标签页,正常情况下两个 DataNode 的 Last Contact 应该显示当前时间,如果有节点长期不更新,说明这个 DataNode 和 NameNode 之间存在心跳问题。

4.2 验证 HDFS 基本操作

Web 界面只能看状态,真正能证明集群可用的是 HDFS 文件操作。在 namenode 容器里执行以下命令:

hdfs dfs -mkdir /input hdfs dfs -put /opt/hadoop/README.txt /input/ hdfs dfs -ls /input

如果能看到 README.txt 文件成功上传,说明 HDFS 的写入链路是通的。再执行:

hdfs fsck /input/README.txt -files -blocks -locations

这个命令会显示文件被切分成了几个 block、每个 block 存放在哪些节点上。默认 block 大小是 128MB,README.txt 很小,所以只有一个 block,但你应该能看到它有两个副本,分别存放在 datanode1 和 datanode2 上,这就直接证明了副本机制在工作。

顺便验证一下 HDFS 的容错特性:你可以直接把某个 DataNode 容器停掉,然后重新上传一个文件,再用 fsck 查看副本分布。但这里不建议零基础一上来就做破坏性实验,先把基本流程跑通再说。

4.3 提交 WordCount 测试任务

WordCount 就是 Hadoop 世界里的 Hello World,虽然简单,但能完整验证 HDFS、YARN、MapReduce 三层链路是否通畅。

Hadoop 自带的示例 jar 包里有 WordCount 程序,直接拿来用就行:

hadoop jar /opt/hadoop/share/hadoop/mapreduce/hadoop-mapreduce-examples-3.3.6.jar wordcount /input /output

任务提交后,YARN 会为这个 job 分配容器资源,你会看到控制台输出一波类似下面的日志:

INFO mapreduce.Job: Running job: job_xxxx_0001 INFO mapreduce.Job: map 0% reduce 0% ... INFO mapreduce.Job: map 100% reduce 100%

当你看到map 100% reduce 100%,并且最后一行显示Job completed successfully,恭喜你,整个集群已经验证通过了。查看结果:

hdfs dfs -cat /output/part-r-00000

输出结果就是 README.txt 里每个单词的出现次数。

需要特别注意的是:如果之前/output目录已经存在,Hadoop 会报错说输出目录已存在。所以每次重新跑任务前要先删掉上一次的输出目录:

hdfs dfs -rm -r /output

或者换个输出路径,比如/output2

5. 常见问题与排查技巧

5.1 进程起不来类问题

这类问题出现的频率最高,我按常见程度排个序。

首当其冲的是DataNode 起不来,日志报 clusterID 不一致。这种情况基本都发生在你格式化过两次 NameNode 之后。第一次格式化 NameNode 会在数据目录生成一个 clusterID,DataNode 启动后也会注册自己的 clusterID。而如果你第二次格式化 NameNode,NameNode 生成了新的 clusterID,但 DataNode 的数据目录里还是旧 clusterID,两边对不上,DataNode 就会拒绝注册。

解决方案分两种情况:如果集群里还没存重要数据,直接清空所有数据目录然后重新格式化。具体操作是在三个容器里分别删掉/opt/hadoop/data/datanode下的内容,NameNode 上删掉/opt/hadoop/data/namenode下的内容,然后重新执行hdfs namenode -format。如果集群里已经有数据,那就需要手动修改 clusterID 让它和 NameNode 保持一致,但零基础阶段我建议直接清空重来,省时省力,这也是容器化方案的优势。

第二个高频问题是NameNode 启动时一直处于 SafeMode。Hadoop 刚启动时 NameNode 会进入安全模式,在这个模式下 HDFS 是只读的,不能写数据。正常情况下安全模式会随着 DataNode 上报的数据块达到阈值而自动退出。如果一直不退,通常是因为dfs.replication配置值大于实际 DataNode 数量,导致副本永远无法齐。比如你有 2 个 DataNode 却配了副本因子 3,那安全模式永远退不出来。解法就是把副本因子改成 2,或者加一台 DataNode。

第三个问题是ResourceManager 或 NodeManager 内存不足启动失败。这通常发生在宿主机内存偏小、容器内存配额不足的情况下。解决方法是调低yarn.nodemanager.resource.memory-mb的数值,同时在 Docker Desktop 的 Settings 里把内存配额调大,建议至少 4GB。

5.2 SSH 免密登录失效

集群启动脚本 start-dfs.sh 依赖 SSH,如果免密登录配得不对,你会在执行脚本看到它卡在输入密码的交互界面。

这里有一个我在 Docker 环境里特有的注意点:镜像构建时生成的 SSH 密钥是在 root 用户下执行的,但我们实际运行 Hadoop 的是 hadoop 用户,或者你在容器里切换到了 root。确保执行 start-dfs.sh 的那个用户,它的~/.ssh/id_rsa私钥和其他节点上/home/hadoop/.ssh/authorized_keys中的公钥是互相匹配的。

我的做法是在 Dockerfile 里直接用 hadoop 用户生成密钥,这样三个容器共用同一个镜像,每个人的公钥都是同一把,天然互信。如果你在构建过程中遇到 SSH 权限问题,检查一下.ssh目录权限:chmod 700 ~/.sshchmod 600 ~/.ssh/authorized_keys。权限太宽松会让 SSH 拒绝使用这个文件。

另外,由于我配置了StrictHostKeyChecking no,正常情况不会出现 host key 确认的卡顿。如果你手动执行 SSH 时提示 host key 冲突,删掉~/.ssh/known_hosts即可。

5.3 端口冲突与资源不足

端口冲突是最直观的报错:执行docker compose up -d时提示端口已被占用。最常见的是 8088 端口被你本地的某个 Java 应用占了,或者是之前跑过一次容器但忘了停止。

先排查哪个程序占用了端口。Windows 上用:

netstat -ano | findstr 8088

找到占用进程的 PID 后,在任务管理器里确认是什么程序,如果是你自己起的东西就关掉,如果是系统服务就别动,去修改 docker-compose.yml 里的端口映射,比如把"8088:8088"改成"18088:8088",然后用浏览器访问http://localhost:18088

资源不足的问题表现为容器卡死、任务一直 pending 或者执行命令时无响应。Docker Desktop 在 Windows 上运行在虚拟机里,如果你只分配了 2GB 内存,三个 Hadoop 节点跑起来会非常吃力。打开 Docker Desktop 的 Settings -> Resources,把内存调到 4GB 以上,CPU 至少给到 4 核。

5.4 问题排查速查表

我整理了一张常见问题的排查速查表,遇到问题时先对着表格排查,能省不少时间:

症状可能原因排查方法解决方案
Docker Desktop 启动失败虚拟化没开启任务管理器检查虚拟化状态BIOS 开启 VT-x/AMD-V,启用 WSL2
拉取镜像超时镜像源不通查看 Docker 日志配置镜像加速地址
start-dfs.sh 卡在密码输入SSH 免密失效手动 ssh datanode1 测试重新配置密钥,检查 .ssh 权限
DataNode 起不来,报 clusterID 不一致多次格式化 NameNode查看 datanode 日志清空数据目录,重新格式化
NameNode 一直安全模式副本因子大于 DataNode 数检查 dfs.replication改副本因子为 2,或加节点
WordCount 报 ClassNotFoundExceptionmapreduce.application.classpath 未配置查看 job 日志在 mapred-site.xml 补 classpath
Web UI 访问不了端口映射错误或容器未启动docker ps 检查容器状态检查 compose 端口映射
容器内 ping 不通其他节点网络配置错误docker network inspect 查看确认用同一自定义 network

这张表我实际踩坑过程中反复对照,现在搭集群基本能一次过。

整套流程我前前后后搭了无数次,体会最深的一点是:大数据学习最大的门槛不是概念有多深奥,而是环境搭建太容易让人劝退。Docker 这个方案最大的价值在于,它把环境搭建从“易碎品”变成了“可复制品”——你不需要理解每一个底层细节就能跑起一个真正的分布式集群,等集群跑起来之后再去研究 NameNode 和 DataNode 之间怎么通信、数据块怎么分布,学习曲线会平缓很多。最后再分享一个小建议:第一次搭建的时候,别着急做任何优化,先用这套默认配置把流程完整走通一遍,把“搭建-验证-跑任务”这个闭环打通,后面再慢慢调内存参数、加数据节点、整合 Hive 和 Kafka 这些生态组件,会顺利得多。

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

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

立即咨询