用Docker部署Hadoop说难不难但网上教程十个里有八个是互相抄的照着做经常卡在同一个地方镜像拉下来、容器也起了DataNode就是不见注册。这个系列写到第03篇前面既然已经把Hadoop组成和HDFS原理聊得不少了这篇不再堆概念直接给你一条我从零跑通的部署路线。如果你已经装了Docker只是想快速有一个能用的Hadoop环境做练习、跑MapReduce、跟别的中间件联调这篇文章会比较适合你。即便是刚接触Docker的人跟着每一步走也能把环境搭起来。我的环境是Windows 11加Docker DesktopLinux服务器上的差异点我也会标出来。1. 为什么要把Hadoop环境塞进Docker里1.1 裸机安装到底恶心在哪我早期装Hadoop是在一台云服务器上步骤看起来不多解压、配hostname、写ssh免密、改四个xml文件、格式化NameNode、启动。但真操作起来每一步都有暗坑。JDK版本先卡一遍。Hadoop 3.x要求Java 8或11系统里如果还留着其他项目的JDK 17class文件版本直接不认。接着是SSH免密有些云服务器默认没有openssh-server还得先补装。再往下是/etc/hosts里的hostname映射不同教程写法还不一样改完重启一下机器主机名又可能被云平台重置回去。最难受的是配置文件。Hadoop配置项多但大多数教程只盯着core-site.xml、hdfs-site.xml、yarn-site.xml、mapred-site.xml这几个。真出问题的时候日志里报的却是“目录权限不对”“临时目录创建失败”这些又牵扯到宿主机的权限体系。好不容易跑通一个wordcount可能因为某个进程占了端口又失败。等环境终于好了你也不敢随便动它因为下一次重装整套流程又得从头走一遍。这些痛点的本质是Hadoop环境依赖的东西远远不只是“Hadoop本身”。它依赖JDK、SSH、特定网络配置、端口可用、目录权限。这些状态全部落在你的机器上任何一个跟你日常开发环境冲突都是灾难。1.2 Docker如何把状态固化下来Docker解决这个问题的思路很直接把“一个能运行Hadoop的完整状态”打包成镜像。JDK版本、系统库、用户配置、启动脚本全部固定在一个只读层里你只需要在运行容器时暴露端口和数据目录。带来的第一个好处是可重复。同一份镜像在这台机器上能跑换一台机器照样能跑不会因为你机器上装了别的软件就出幺蛾子。第二个好处是低成本销毁重建。Hadoop集群配置改坏了或者DataNode元数据状态乱了删容器重来就行不需要在宿主机上一点一点清理残留。第三个好处是并行环境。同一台机器上可以同时存在Hadoop 2.x的容器和Hadoop 3.x的容器互不干扰这在裸机上几乎是不可想象的事。我在实操里的体会是Docker特别适合三类场景学原理、跑联调测试、做课程设计和Demo。它把“环境搭建”从两天的体力活压缩到几分钟你才有精力去关注Hadoop本身的行为。1.3 先把边界说清楚哪些场景别硬上我不想把Docker包装成万能方案。有些场景用它跑Hadoop是给自己找麻烦。第一是生产环境。虽然网上有各种容器化跑大数据的方案但通常会配合K8s、分布式存储、资源调度器一起做。单纯用docker run拉几个Hadoop容器当生产集群数据安全性和性能都没有保障。HDFS本身需要多节点冗余容器生命周期一断直接影响数据。第二是数据量很大的场景。Docker容器的存储默认在宿主机某个目录或卷里I/O路径多了一层性能跟裸盘比有损耗。测试几百MB没问题真要跑几TB的分析任务建议老老实实用物理机或云主机。第三是深度调优场景。容器把环境隔离了也把内核参数、网络栈这些调优入口隔离了很多底层参数在容器里改不到。把边界想清楚再动手后面所有操作都会踏实很多。2. 镜像选型、端口规划和网络准备2.1 社区镜像怎么选确定用Docker之后第一个问题就是镜像从哪来。Docker Hub上搜hadoop结果非常杂很多镜像几年不更新拉下来Hadoop还是2.7的Web UI端口也还是50070跟现在主流的3.x教程对不上。我梳理过自己试过的几类来源镜像来源优点缺点我的评价Apache相关镜像相对规范适合CI场景更新分散完整可跑集群需要自己组合学习阶段不推荐直接用bde2020/hadoop覆盖Hadoop 3.x有基础镜像和节点镜像默认配置要自己改老版本依赖多我用来快速起步sequenceiq/hadoop-docker经典老镜像网上教程多Hadoop 2.x时间久远适合特定课程要求自己基于openjdk构建完全可控版本随意需要写Dockerfile一开始麻烦最终推荐的方式我自己的做法是学习阶段先拿一个能用的社区镜像把流程跑通跑通之后再基于openjdk 8把Hadoop 3.3.6打一个自己的镜像。这样既拿到快速路径又确定了可控的基础。这里有一个重要经验拉镜像时一定看tag和更新时间不要默认latest。Hadoop版本跨度大配置文件写法变化不大但Web UI端口、部分命令行为差别很大。我当前环境是Hadoop 3.3.x下面所有示例都以这个版本为准。2.2 端口、内存、存储三件事提前规划启动Hadoop之前先看一张端口表。Hadoop 3.x的常用端口如下服务端口说明NameNode Web UI9870HDFS管理界面NameNode RPC9000客户端读写HDFS的通信端口YARN ResourceManager Web UI8088YARN管理界面MapReduce JobHistory Web UI19888任务历史界面SecondaryNameNode Web UI9868检查点状态DataNode Web UI9864单个节点状态伪分布式如果只用一个容器这几个端口在docker run时按需映射出来即可。集群模式里9870和9000只需映射NameNode容器8088映射ResourceManager所在容器DataNode的端口大多在容器内网使用不必全部暴露到宿主机。内存规划是我最想提醒的。Hadoop的Java进程默认按物理内存的一定比例申请堆内存容器里如果不配置NameNode和DataNode很可能把容器内存吃满然后被OOM杀掉。我单机伪分布式至少给容器2GBcompose跑一个NameNode加两个DataNode时每个容器建议不低于1.5GB。存储规划分两类数据不需要长期保留的话直接放在容器里就行测试完删掉如果希望重启容器后数据还在用命名数据卷或绑定挂载目录把Hadoop的data目录和日志目录挂出来。我习惯在compose里这样写volumes: - ./hadoop-data:/opt/hadoop/data - ./hadoop-logs:/opt/hadoop/logs这样即使删掉容器重建HDFS里的文件还在。不过要注意NameNode格式化信息和DataNode元数据都在这些目录里换镜像或改名的时候容易引发clusterID不一致这个坑放到第7章详细说。2.3 网络模式容器名就是hostnameDocker容器的网络有几种模式。单容器部署直接用默认bridge就行端口映射到宿主机就能访问。麻烦的是多节点集群——Java进程之间需要按hostname互相访问。在Docker里同一个自定义bridge网络下的容器可以用容器名解析到对应IP。这正好可以解决Hadoop最头疼的hostname配置core-site.xml里写hdfs://namenode:9000让NameNode容器在网络里的名字就叫namenode节点之间的访问就能自动走通。这里有个细节很容易忽略Docker默认bridge网络不支持容器名解析只有自定义bridge网络会把容器名注册进DNS。所以你如果以后打算从单容器扩成集群从一开始就建立自定义网络是省事的选择docker network create hadoop-net后面docker run时加--network hadoop-netcompose里也可以直接用network配置。这样单机和集群的切换成本都很低。3. 单机伪分布式部署全流程3.1 拿到容器后的第一件事理清目录启动容器很简单关键是进入容器后知道自己在哪里、要改什么。我以自己构建的Hadoop 3.3.6镜像为例Hadoop安装目录在/opt/hadoop配置目录是/opt/hadoop/etc/hadoop环境变量已经写在/etc/profile里进入bash就可以直接用hdfs命令。第一次进入容器建议先执行docker exec -it hadoop-node bash在容器内确认三件事java -version、hadoop version、echo $HADOOP_HOME。如果这三个没问题说明镜像和JDK部分正常后续只需要改配置。这也是判断“是环境问题还是配置文件问题”的标准流程。如果你用的镜像结构不同先执行echo $HADOOP_CONF_DIR看看配置目录到底在哪不要想当然地去找。容器因为镜像不同有的配置放在/etc/hadoop/conf有的放在/opt/hadoop/etc/hadoop路径找错了后面改再久都是白改。3.2 四个核心配置文件怎么改伪分布式只需要改四个文件都在配置目录下。core-site.xml设置默认文件系统和临时目录configuration property namefs.defaultFS/name valuehdfs://localhost:9000/value /property property namehadoop.tmp.dir/name value/opt/hadoop/tmp/value /property /configurationhdfs-site.xml设置副本数和NameNode/DataNode数据目录configuration property namedfs.replication/name value1/value /property property namedfs.namenode.name.dir/name value/opt/hadoop/data/name/value /property property namedfs.datanode.data.dir/name value/opt/hadoop/data/data/value /property /configurationyarn-site.xml设置shuffle服务MapReduce任务依赖它configuration property nameyarn.nodemanager.aux-services/name valuemapreduce_shuffle/value /property property nameyarn.nodemanager.aux-services.mapreduce_shuffle.class/name valueorg.apache.hadoop.mapred.ShuffleHandler/value /property /configurationmapred-site.xml指定计算框架跑在YARN上configuration property namemapreduce.framework.name/name valueyarn/value /property /configuration不要只抄配置要理解每个property在说什么。core-site决定HDFS入口hdfs-site决定数据存哪、副本几份yarn-site和mapred-site决定计算层如何调度。这样报错的时候你才知道往哪个方向排查。3.3 SSH免密和格式化NameNodeHadoop脚本启动进程时会通过ssh在各个节点执行命令所以容器里必须装ssh并配置免密登录localhost。我在容器内执行ssh-keygen -t rsa -P -f ~/.ssh/id_rsa cat ~/.ssh/id_rsa.pub ~/.ssh/authorized_keys chmod 600 ~/.ssh/authorized_keys ssh localhost hostname这里的原理是start-dfs.sh会尝试远程执行配置好免密整个过程就不会卡在密码输入。很多人裸机装Hadoop卡住十有八九是这一步没做对。容器里反而简单因为镜像一般自带openssh-server。然后格式化NameNode。这一步必须在配置都改好之后做而且只做一次别重复hdfs namenode -format格式化会写入NameNode的元数据目录里面包含一个clusterID。这个ID很关键——如果DataNode之前已经按旧clusterID启动过新的NameNode格式化后clusterID变了DataNode注册时会报文件系统ID不一致表现为Datanode状态一直是Dead。伪分布式里因为是同一次初始化一般不会踩但后面集群重装数据卷的时候会很难受。3.4 启动服务并确认进程格式化完成后依次启动start-dfs.sh start-yarn.sh jpsjps的输出应该能看到NameNode、DataNode、SecondaryNameNode、ResourceManager、NodeManager这几个Java进程。这一步能通过伪分布式就算起来了。这里有个细节很多教程让你用start-all.sh这个脚本在新版本里仍然存在但已经不建议使用。我习惯分开执行start-dfs.sh和start-yarn.sh这样如果HDFS起不来至少能判断计算层是否独立可用排错范围缩小一半。启动完之后访问宿主机上的http://localhost:9870能看到NameNode界面Live Nodes显示1说明HDFS起来了。这一步别跳过Web UI能打开说明端口映射和Java进程都是正常的。4. 用docker-compose搭起多节点集群4.1 为什么不用连续docker run如果你只想伪分布式第3章已经结束了。但大多数人的实际目标是集群哪怕只是为了项目演示也需要一个像样的多节点环境。连续docker run三个容器再自己配网络不是不行但每次重建集群都要重复一遍还容易在参数上出错。docker-compose的价值在于把容器定义、网络、挂载、依赖关系写成一个YAML文件一条docker compose up -d就能启动整个集群。学习和演示场景下compose是性价比最高的方案。另外要说明的是compose只负责容器生命周期。Hadoop内部的hostname、配置仍然要自己规划好。编排解决了启动顺序和管理问题节点间的分布式配置逻辑还得按HDFS机制来。4.2 编写docker-compose.yml我给的示例是一个NameNode加两个DataNode。假设你已经构建好自己的镜像tag是my-hadoop:3.3.6。version: 3 services: namenode: image: my-hadoop:3.3.6 container_name: namenode hostname: namenode networks: - hadoop-net ports: - 9870:9870 - 9000:9000 - 8088:8088 volumes: - ./config/core-site.xml:/opt/hadoop/etc/hadoop/core-site.xml - ./config/hdfs-site.xml:/opt/hadoop/etc/hadoop/hdfs-site.xml - ./config/yarn-site.xml:/opt/hadoop/etc/hadoop/yarn-site.xml - ./config/mapred-site.xml:/opt/hadoop/etc/hadoop/mapred-site.xml - ./namenode-data:/opt/hadoop/data environment: - HADOOP_HEAPSIZE1024 command: bash -c service ssh start if [ ! -d /opt/hadoop/data/name/current ]; then hdfs namenode -format; fi hdfs --daemon start namenode yarn --daemon start resourcemanager tail -F /dev/null datanode1: image: my-hadoop:3.3.6 container_name: datanode1 hostname: datanode1 networks: - hadoop-net ports: - 9864:9864 volumes: - ./config/core-site.xml:/opt/hadoop/etc/hadoop/core-site.xml - ./config/hdfs-site.xml:/opt/hadoop/etc/hadoop/hdfs-site.xml - ./config/yarn-site.xml:/opt/hadoop/etc/hadoop/yarn-site.xml - ./config/mapred-site.xml:/opt/hadoop/etc/hadoop/mapred-site.xml - ./datanode1-data:/opt/hadoop/data environment: - HADOOP_HEAPSIZE1024 command: bash -c service ssh start hdfs --daemon start datanode yarn --daemon start nodemanager tail -F /dev/null datanode2: image: my-hadoop:3.3.6 container_name: datanode2 hostname: datanode2 networks: - hadoop-net ports: - 9865:9864 volumes: - ./config/core-site.xml:/opt/hadoop/etc/hadoop/core-site.xml - ./config/hdfs-site.xml:/opt/hadoop/etc/hadoop/hdfs-site.xml - ./config/yarn-site.xml:/opt/hadoop/etc/hadoop/yarn-site.xml - ./config/mapred-site.xml:/opt/hadoop/etc/hadoop/mapred-site.xml - ./datanode2-data:/opt/hadoop/data environment: - HADOOP_HEAPSIZE1024 command: bash -c service ssh start hdfs --daemon start datanode yarn --daemon start nodemanager tail -F /dev/null networks: hadoop-net:这里有几个逻辑要讲清楚core-site.xml里的fs.defaultFS要改成hdfs://namenode:9000而不是localhost。DataNode容器里的客户端访问NameNode必须经过容器hostname。NameNode这边的command里先判断有没有格式化过。第一次启动时目录不存在就格式化以后重启不会重复格式化避免了clusterID被反复重置。我没有让DataNode执行start-dfs.sh而是用hdfs --daemon start datanode单独拉起守护进程。这样不需要在节点之间配置SSH免密也不需要维护workers列表容器场景下清爽很多。4.3 集群启动顺序和状态检查先启动docker compose up -d然后逐个确认日志。NameNode日志最重要看格式化是否成功、9870端口是否监听。等十几秒后用jps和页面状态双重验证。这里值得说一个点YARN的ResourceManager我放在NameNode容器上NodeManager放在DataNode容器上。yarn-site.xml里需要单独配置ResourceManager地址property nameyarn.resourcemanager.hostname/name valuenamenode/value /property不配置这一项NodeManager会默认向自己所在容器找ResourceManager然后出现一长串连接超时日志。检查命令docker compose logs -f datanode1日志里如果出现成功注册的信息说明节点之间打通了。之后打开9870页面Live Nodes应该显示2。到这一步一个最小HDFS集群就跑起来了。5. 端口映射与Web UI访问5.1 访问之前先分清版本端口端口这块说多了都是泪。Hadoop 2.x和3.x端口变化很大网上老教程里50070、8088、9000混在一起新手很容易懵。3.x的常用端口在表格里已经列过这里再强调一个关键差异NameNode界面从50070改成了9870。如果你页面打不开第一个怀疑对象应该是版本和端口对应关系错了而不是去怀疑防火墙。YARN的8088端口在2.x和3.x保持一致。但不同镜像可能在yarn-site.xml里自定义了端口所以访问8088之前先确认ResourceManager容器里的yarn-site.xml没有改成别的值。另外9868是SecondaryNameNode的Web端口9864是DataNode的Web端口。要看某个DataNode具体状态就把对应容器的9864映射出来。5.2 容器内外访问的正确姿势访问分三层容器内互访、宿主机访问、外部机器访问。容器内互访靠hostname。同一个自定义网络下直接写namenode、datanode1这样的名字即可不需要关心IPDocker内置DNS会自动解析。这是最省心的一层。宿主机访问Web UI靠端口映射。docker run -p 9870:9870或者compose里的ports就是把容器内9870绑定到宿主机。Docker Desktop在Windows和macOS上其实是跑在一个虚拟机里端口映射做好后浏览器直接访问localhost:9870即可。外部机器访问需要把端口绑定到具体网卡并检查防火墙。localhost:9870能访问不代表局域网里其他机器通过你的IP:9870也能访问。排查时先分清是Docker映射问题还是防火墙问题。Windows上可以用netstat -ano | findstr 9870确认端口在监听再检查防火墙放行。5.3 改了配置不生效怎么办Hadoop环境里最让人崩溃的是改完配置重启结果一点变化没有。认真排查下来通常不是“没变化”而是改了文件但进程没重新加载或者改错文件。排查三步走一确认挂载进去的文件确实覆盖了容器里的文件在容器内执行cat /opt/hadoop/etc/hadoop/core-site.xml查看内容二确认所有相关进程都重启了特别是NameNode和DataNode都要重启三翻日志确认进程加载配置时的实际值很多启动日志会把关键配置项打出来。我自己遇到最多的情况是宿主机挂载目录里的xml文件改了容器里的文件也变了但Java进程早就启动了根本没读新配置。这种时候没有捷径就是停掉全部进程、确认进程没了、再启动。Hadoop基本不支持热加载配置改动必须完整重启。6. 上传文件、跑通WordCount才算部署完成6.1 先检查HDFS状态配置启动都正常还不算完。我不会说部署完成除非能跑通一次完整的HDFS读写和一个MapReduce任务。先检查HDFS整体状态hdfs dfsadmin -report正常输出应该能看到NameNode地址、Live Nodes数量、每台DataNode的容量和状态。如果显示Zero datanodes先别急着跑任务回到第7章对照一遍。然后看根目录能不能正常操作hdfs dfs -ls /这个命令能跑通说明客户端到NameNode的RPC链路是通的。如果命令卡住多半是安全模式执行hdfs dfsadmin -safemode leave手动离开或者等HDFS自动退出安全模式。6.2 上传文件到HDFS先在HDFS创建目录hdfs dfs -mkdir -p /input然后上传一个本地测试文件echo docker hadoop hdfs yarn wordcount test test.txt hdfs dfs -put test.txt /input/上传是验证DataNode写入链路的好方法。NameNode只负责记录元数据实际数据块是往DataNode写的。如果put命令能成功说明至少有一个DataNode正常参与了写入。再用hdfs dfs -cat /input/test.txt确认内容一致。小文件只有一两个block读出来顺序不会乱如果发现内容缺失或报错就要注意副本和block健康状态了。6.3 启动一个WordCount任务文件上传成功接下来跑官方自带的wordcount程序hadoop jar /opt/hadoop/share/hadoop/mapreduce/hadoop-mapreduce-examples-*.jar wordcount /input /output跑任务时盯两件事任务状态有没有从RUNNING变成SUCCEEDED以及8088界面里是否有container被调度。如果任务停在ACCEPTED或者ResourceManager根本没反应那是YARN配置的问题跟HDFS没关系回yarn-site.xml检查ResourceManager地址。跑完后查看结果hdfs dfs -ls /output hdfs dfs -cat /output/part-r-00000这里能看到每个单词的计数结果。到这一步HDFS读写、YARN调度、MapReduce执行链路全部验证通过我才会说这个Hadoop环境是可用的。之后要接别的项目比如把自己写的MapReduce打包成jar提交上来验证流程和这个一模一样。7. 部署过程踩过的坑完整排查链路7.1 DataNode一直Dead先查clusterID现象很经典9870页面能开Live Nodes却是0jps里DataNode进程存在但日志里反复报Incompatible clusterIDs。我在Docker环境里遇到这个坑几乎都是格式化顺序和目录残留引起的。第一次用容器A格式化NameNode并启动后来删了容器A数据卷里的NameNode元数据保留了clusterID-A再用数据卷启动新的容器B格式化时没清理旧目录clusterID变成B而DataNode数据目录还是A两边对不上。排查链路是到NameNode和DataNode的版本目录下查看VERSION文件对比clusterID。NameNode的位置在/opt/hadoop/data/name/current/VERSIONDataNode在/opt/hadoop/data/data/current/VERSION。如果不一致最干净的办法是把两边数据目录全部清掉重新格式化rm -rf /opt/hadoop/data/name/current /opt/hadoop/data/data/current hdfs namenode -format注意清之前确认这个集群里没有重要数据。测试环境我一般直接清有数据的话把VERSION里的clusterID改成一致再重启也能救但操作风险高。compose场景下最稳妥的做法是第一次搭建集群时先把挂载的数据卷目录清干净再让NameNode格式化脚本里加入“目录不存在才格式化”的判断从根上避免反复格式化导致的clusterID错乱。7.2 进程启动后崩掉内存不足第二种高发问题是start-dfs.sh看起来成功了jps也能看到Java进程但过一会儿进程消失甚至容器重启退出。原因多半是给容器的内存太小而Hadoop的Java堆默认值太高。我刚开始用docker run --memory 1g跑NameNode默认JVM堆申请比较多容器内存被吃满Docker的OOM killer介入进程直接被杀掉。解决方法是显式设置堆大小。compose的environment里写HADOOP_HEAPSIZE1024也可以去hadoop-env.sh改export HADOOP_HEAPSIZE1024另外宿主机内存紧张时容器总内存要算好。我建议伪分布式整体给2GB集群模式NameNode 1.5GB、每个DataNode 1GB整个compose环境大概4GB实测比较稳。排查内存问题时docker stats可以实时看容器内存很好用。7.3 DataNode状态为Dead且页面打不开这个现象比“不注册”更隐蔽Live Nodes有数量但某台节点状态是Dead对应9864端口页面也打不开。我在多节点Demo环境里遇过最后定位为端口映射冲突或容器网络异常。Docker Desktop偶尔在网络重载后端口映射没恢复容器日志里出现Error starting DataXceiver。排查时先进入该容器确认9864端口在监听ss -lnt | grep 9864容器内正常再看宿主机netstat确认对外映射存在。两边都正常就访问一下该节点的URL如果只有这个容器不通重建该容器通常能解决。还有一种情况是防火墙。Windows下Docker Desktop对外端口可能被系统防火墙拦截局域网访问时尤其常见。先在本机访问验证再去外部机器就能把问题范围一步步缩小。7.4 Docker Desktop本身的问题Windows上装Docker Desktop启动时报virtualization support not detected这其实和Hadoop没有任何关系是宿主机虚拟化环境的问题。解决办法是到BIOS里开启虚拟化并在Windows功能里启用Virtual Machine Platform相关选项。如果报failed to connect to the docker api at npipe:////./pipe/dockerDesktopLinuxEngine一般是Docker引擎还在启动中或者Docker服务被手动停止。确认Docker图标变成稳定状态再执行命令必要时重开Docker Desktop而不是反复去改Hadoop配置。这类问题在Linux服务器上几乎不会出现。我个人建议如果单纯想学Hadoop装个Linux虚拟机其实比Windows上的Docker Desktop更省心如果办公电脑受限于环境Docker Desktop也完全够用只是遇到这类坑时要知道锅不在Hadoop。最后分享一个我自己的小习惯我会把常用命令写成一个init-hadoop.sh放在项目目录里里面包含进入容器、检查版本、格式化、启动、上传测试文件的完整流程。每次环境被搞坏直接执行这个脚本几分钟就恢复省掉了大量重复劳动。这个项目目录本身还可以跟着镜像和compose文件一起备份或分享换一台电脑照样复现。Docker加Hadoop的组合本质上是把Hadoop从系统级配置问题降级成了应用级启动问题。越早接受“环境被搞坏很正常推倒重来就好”的心态你学Hadoop的速度会越快。这篇写的每条命令我都自己跑过如果按顺序操作还有卡住的地方对照文中排错链路逐条查日志会比重新搜教程靠谱很多。
