# hadoop-cluster **Repository Path**: ytmcaptain/hadoop-cluster ## Basic Information - **Project Name**: hadoop-cluster - **Description**: 用docker-compose搭建hadoop集群 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-22 - **Last Updated**: 2026-09-22 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Hadoop 安全集群(HDFS + YARN + Kerberos)— Docker / Podman Compose 一键部署 基于 **bde2020/hadoop 3.2.1 + Java 8** 镜像构建,附带一个自建 **Kerberos KDC** 容器, 用一份 `docker-compose.yml` 拉起整套开启 Kerberos 认证的 HDFS + YARN 集群。 Docker 与 Podman 通用(compose 规范编写,未使用任何厂商私有字段)。 --- ## 1. 节点规划 | 服务 | 容器名 | 主机名 | 角色 | Kerberos 主体 | |---|---|---|---|---| | kdc | hadoop-kdc | hadoop-kdc | krb5kdc + kadmind,分发 keytab/票据 | — | | namenode | hadoop-nn | hadoop-nn | NameNode(Kerberos 认证 + SPNEGO Web) | `nn/hadoop-nn`、`HTTP/hadoop-nn` | | datanode1 | hadoop-dn1 | hadoop-dn1 | DataNode + NodeManager | `dn/hadoop-dn1`、`nm/hadoop-dn1`、`HTTP/hadoop-dn1` | | datanode2 | hadoop-dn2 | hadoop-dn2 | DataNode + NodeManager | `dn/hadoop-dn2`、`nm/hadoop-dn2`、`HTTP/hadoop-dn2` | | resourcemanager | hadoop-rm | hadoop-rm | ResourceManager + JobHistory | `rm/hadoop-rm`、`jhs/hadoop-rm`、`HTTP/hadoop-rm` | | client | hadoop-client | hadoop-client | 命令行/作业提交(已带 hduser 票据) | `hduser` | 领域(REALM):**HADOOP.LOCAL** 普通用户:**hduser** 管理员:**admin/admin**(密码 `admin123`) > `hduser` 同时是 DataNode/NodeManager 容器内的 OS 账号:NodeManager 以它(而不是 root)运行, > 使 map 输出文件的 POSIX 属主与 Kerberos 作业用户名一致,否则 reduce 阶段 shuffle 必失败(详见 §5.9)。 ### 端口 | 端口 | 说明 | |---|---| | 9870 | NameNode Web UI(HTTP + SPNEGO) | | 8088 | ResourceManager Web UI(HTTP + SPNEGO) | | 19888 | JobHistory Web UI(HTTP + SPNEGO) | | 9864 / 9865 | DataNode1 / DataNode2 Web UI(**HTTPS** + SPNEGO,自签名证书) | | 8042 / 8043 | NodeManager1 / NodeManager2 Web UI(HTTP + SPNEGO) | --- ## 2. 前置要求 - Docker + `docker compose`(v2)**或** Podman + `podman compose` - 建议内存 ≥ 8 GB、CPU ≥ 4 核(虚拟机/桌面引擎内存可在 docker/podman 设置里调整) - 能拉取镜像(默认已配置为「渡渡鸟」国内镜像源,见 `.env`) --- ## 3. 一键部署 ```bash cd /path/to/hadoop-docker # Docker docker compose up -d --build # Podman(等价) podman compose up -d --build ``` 首轮会依次完成:构建 KDC 镜像 → 构建 Hadoop 镜像 → 启动 KDC 生成 keytab/票据 → 格式化并启动 NameNode → 启动 2 个 DataNode/NodeManager → 启动 ResourceManager/JobHistory → 启动客户端。 启动顺序由容器内启动脚本自行等待,无需人工干预,约 2~4 分钟。 查看状态: ```bash docker compose ps docker compose logs -f namenode # 或 kdc / datanode1 / resourcemanager ``` 验证: ```bash bash scripts/verify.sh ``` 预期输出包含 `Live datanodes (2)`、`hello-kerberos`、`Estimated value of Pi is ...`。 > **刚 `up -d` 完不要立刻写 HDFS**:容器 `healthy` 只代表端口通了。已有数据时 NameNode 启动后会先进入 > safe mode(等 DataNode 汇报块,之后还要再等 `dfs.namenode.safemode.extension` 默认 30s 才退出), > 这一阶段 HDFS **只读**,会报 `put: Cannot create file/xxx. Name node is in safe mode.`。 > `verify.sh` 的第 0 步会自动等待「6 容器 healthy + HDFS 可写」再往下走(最长约 5 分钟),所以直接跑它即可。 --- ## 4. 常用操作 ```bash # 进入客户端容器(已带 hduser@HADOOP.LOCAL 票据) docker compose exec client bash # 客户端内可直接使用(无需再 kinit) hdfs dfs -ls / hdfs dfs -mkdir -p /user/hduser/demo echo hello | hdfs dfs -put - /user/hduser/demo/a.txt yarn application -list # 跑一个 MapReduce 示例 hadoop jar /opt/hadoop-3.2.1/share/hadoop/mapreduce/hadoop-mapreduce-examples-3.2.1.jar pi 2 100 # 票据过期(默认 24h)后刷新 bash scripts/renew-ticket.sh # 停止 / 删除 docker compose stop docker compose down # 保留数据卷 docker compose down -v # 连 HDFS 数据一起清掉,下次 up 会重新格式化 ``` KDC 容器内置完整 Kerberos 客户端工具,可直接管理主体: ```bash docker compose exec kdc bash kadmin.local -q "listprincs" # 列出所有主体 kadmin.local -q "addprinc -pw 123456 tom" # 新增用户 kadmin.local -q "ktadd -k /keytabs/tom.keytab tom" # 导出 keytab 到共享卷 kadmin -p admin/admin -w admin123 -q "listprincs" # 远程 kadmin klist -c /keytabs/hduser.ccache # 查看票据 ``` --- ## 5. Kerberos 实现要点(排障时看这里) 1. **票据分发方式**:Hadoop 官方镜像(Debian 基底)里没有 `kinit`,因此由 KDC 容器在首次启动时 预生成 keytab 与票据缓存(ccache)写入共享卷 `keytabs`: - `/keytabs/*.keytab` — 各服务主体 keytab - `/keytabs/nn.ccache` — `nn/hadoop-nn` 的票据(NameNode 用于初始化 HDFS superuser 目录) - `/keytabs/hduser.ccache` — `hduser` 的票据(客户端容器通过 `KRB5CCNAME` 直接使用) 2. **配置注入**:Hadoop 镜像的 entrypoint 会把 `CORE_CONF_*` / `HDFS_CONF_*` / `YARN_CONF_*` / `MAPRED_CONF_*` 环境变量写进对应的 `*-site.xml`(规则见 `conf/common.env` 头部注释),因此集群配置全部集中在 `conf/*.env`。 3. **安全 DataNode 的两个硬性要求**(Hadoop 源码 `DataNode#checkSecureConfig`): - `dfs.data.transfer.protection=authentication`(SASL 数据传输) - Web 端必须 `dfs.http.policy=HTTPS_ONLY` → DN 容器启动时用 `make-certs.sh` 生成自签名 keystore 与 `ssl-server.xml` 放入 `/etc/hadoop` 4. **DataNode 以主机名注册通信**:`dfs.client.use.datanode.hostname=true` + `dfs.datanode.use.datanode.hostname=true`,否则客户端拿到 IP 后主体 `dn/hadoop-dn1` 匹配不上。 5. **格式化时机**:`hdfs namenode -format` 在 Kerberos 打开后无法登录 UGI,所以 NameNode 启动脚本会在 未格式化时用临时配置目录(把 `hadoop.security.authentication` 临时改为 `simple`)完成格式化。 6. **服务端主体通配校验(容器环境必备,`conf/common.env` 已配好)**:Hadoop 客户端在未配置 `<主体键>.pattern` 时,会用「连接地址的 IP」反解主机名来推算服务端主体 (`SaslRpcClient#getServerPrincipal` → `SecurityUtil.getServerPrincipal(conf.get(键), serverAddr.getAddress())`)。 容器重建后 IP 会变,而 NM→RM 之间是**长连接**(客户端连接缓存里可能仍是 RM 的旧 IP), 此时旧 IP 反解失败,Java 会退化成 IP 字面量,于是抛: `Server has invalid Kerberos principal: rm/hadoop-rm@HADOOP.LOCAL, expecting: rm/10.89.2.172@HADOOP.LOCAL` → NodeManager 注册失败 → MapReduce 作业直接 FAILED。 因此显式配置了 pattern,客户端改为只做通配校验,不再依赖 IP 反解: ```properties dfs.namenode.kerberos.principal.pattern = nn/*@HADOOP.LOCAL dfs.datanode.kerberos.principal.pattern = dn/*@HADOOP.LOCAL yarn.resourcemanager.principal.pattern = rm/*@HADOOP.LOCAL yarn.nodemanager.principal.pattern = nm/*@HADOOP.LOCAL mapreduce.jobhistory.principal.pattern = jhs/*@HADOOP.LOCAL ``` 7. **重启时的 safe mode(容易踩)**:**已有数据**时 NameNode 启动后会先进入 safe mode(等 DataNode 汇报块), 这一阶段 HDFS 拒绝 `mkdir/chmod/chown`,会报 `chmod: Cannot set permission for ... Name node is in safe mode.`。 启动脚本因此先轮询 `hdfs dfsadmin -safemode get` 等到 `Safe mode is OFF` 再做目录初始化, 并且这段初始化**故意不启用 `set -e`**——否则初始化失败会让 NameNode 容器反复重启 (实际观察到 RestartCount 19),集群永远起不来。 > 注:全新部署(`down -v` 后)因为刚格式化、无数据,NameNode 不会进 safe mode,掩盖了这个问题。 8. **KDC 重启与 keytab 一致性(最隐蔽的一个坑)**:`kadmin` 的 `ktadd` 是**追加**语义、且每次调用都会生成新密钥(密钥轮换), 而 KDC 数据库在容器内、keytab 在共享卷里。若直接沿用旧 keytab 文件,就会出现 「同一个 kvno 对应两把不同密钥」,Java 客户端取到旧密钥即报: `kinit: Password incorrect` / `KrbException: Checksum failed` / `KerberosAuthException: failure to login: for principal: nn/hadoop-nn@HADOOP.LOCAL from keytab` → NameNode 启动即退出、作业全失败。现在的做法: - Kerberos 数据库挂在 `kdc-data` 卷上持久化 → 重建 KDC 容器不必重建 Realm,也不反复换密钥; - 导出 keytab 前先 `rm -f` 旧文件,写到 `/keytabs/.tmp` 后**原子改名**(其它容器不会读到半成品); - 只有「DB 刚重建」或「就绪标记丢失」时才导出,其余情况跳过; - 全部完成后写就绪标记 `/keytabs/.ready`,各节点启动脚本都等这个标记出现再取 keytab/票据; - KDC 脚本里 `kinit`、`krb5kdc`、`kadmind` 失败只告警不退出(`set -e` 退出会让容器重启并再次轮换密钥)。 需要强制重新导出(新增节点、主动轮换密钥)时,**先 KDC 再其余**: ```bash docker compose up -d --force-recreate kdc # 重新导出 keytab 并重置 .ready docker compose up -d --force-recreate # 再重建其余容器(它们会等待新的 .ready) ``` 9. **NodeManager 必须以 hduser(而非 root)运行(shuffle 属主校验)**: reduce 抓取 map 输出时,常驻在 NodeManager 进程里的 **ShuffleHandler** 会调用 `SecureIOUtils.checkStat()`,把「map 输出文件的 POSIX 属主」与「Kerberos 作业用户名」逐字符比对。 `DefaultContainerExecutor`(默认容器执行器)下任务 JVM 继承 NM 的运行用户,所以 **NM 以 root 跑 → `file.out` / `file.out.index` 属主是 root,而作业用户叫 `hduser`**,于是: ``` Owner 'root' for path /tmp/hadoop-root/nm-local-dir/usercache/hduser/appcache/application_xxx/ output/attempt_xxx_m_000000_0/file.out.index did not match expected owner 'hduser' error in shuffle in fetcher#3 ... Exceeded MAX_FAILED_UNIQUE_FETCHES ``` 每个 reduce 都抓不到 map 输出,作业必定 FAILED(这一步发生在 map 全部成功之后,日志很容易被误读成网络问题)。 现在的做法: - 镜像里建好与 Kerberos 作业用户同名的 OS 账号:`useradd -m -U -s /bin/bash hduser`(见 `hadoop/Dockerfile`); - `hadoop/scripts/start-dn-nm.sh` 用 `runuser -u hduser -p -- yarn nodemanager` 启动 NM (`runuser` 会直接 exec 目标程序,PID 1 仍是 NM 的 JVM,容器停止时能收到 SIGTERM 优雅退出; `-p` 保留环境变量与 PATH);DataNode 仍以 root 运行(HDFS 有自己的权限模型,root 即 HDFS superuser); - 本地目录与日志目录固定为 `/tmp/hadoop-nm/{nm-local-dir,log}` 并归属 hduser (`hadoop.tmp.dir`、`yarn.nodemanager.local-dirs`、`yarn.nodemanager.log-dirs`, 避免路径随 `${user.name}` 漂移导致 NM / 任务 / ShuffleHandler 三方看到不同目录)。 > 这也是 Hadoop 官方对多用户安全集群的要求:YARN 守护进程与作业用户必须是同一个**非 root** 账号。 > 若你的环境只能以 root 运行 NM,替代方案是把作业用户也改成 root(即 `kinit root`), > 让「文件属主」与「Kerberos 用户」重新一致——但不推荐在生产中这么做。 --- ## 6. Web UI 说明 所有 Web UI 都开启了 SPNEGO(Kerberos 认证),浏览器需要先配置 Kerberos 才能直接访问。 配置较麻烦时,推荐用命令行方式查看(客户端容器内已带票据): ```bash docker compose exec client bash curl -s --negotiate -u : http://hadoop-nn:9870/jmx?qry=Hadoop:service=NameNode,name=NameNodeStatus | head # DataNode 的 Web 是 HTTPS + 自签名证书 curl -sk --negotiate -u : https://hadoop-dn1:9864/jmx?qry=Hadoop:service=DataNode,name=DataNodeInfo | head ``` Windows 浏览器若要直连,需要:把 `krb5.conf` 内容写入 `C:\ProgramData\MIT\Kerberos5\krb5.ini`、 用 MIT Kerberos 客户端 `kinit hduser`,并把 `localhost` 加进浏览器 Kerberos 白名单 (Chrome/Edge:`--auth-server-allowlist="*"`)。DN 的 HTTPS 还需信任自签名证书。 --- ## 7. 参数调整 ### 换镜像源 / 镜像标签 编辑 `.env`(无需改 `docker-compose.yml`): ```dotenv # 渡渡鸟镜像站(国内直连);镜像检索:https://docker.aityp.com HADOOP_BASE_IMAGE=swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/bde2020/hadoop-namenode:latest CENTOS_IMAGE=swr.cn-north-4.myhuaweicloud.com/ddn-k8s/quay.io/centos/centos:stream9 CENTOS_MIRROR=mirrors.tuna.tsinghua.edu.cn/centos-stream # 构建 KDC 用的 yum 源 ``` 能直连官方仓库时,把地址换成 `docker.io/bde2020/hadoop-namenode:latest`、`quay.io/centos/centos:stream9` 即可。 镜像架构需与宿主机匹配(x86_64 / arm64),渡渡鸟站点上可查到对应地址。 ### 改集群配置 编辑 `conf/` 下的 env 文件后重建容器: ```bash docker compose up -d --force-recreate ``` - `conf/common.env`:core/hdfs/yarn/mapred 公共项(含 SASL、YARN 主机名、MR 内存等) - `conf/nn.env` / `conf/dn1.env` / `conf/dn2.env` / `conf/rm.env` / `conf/client.env`:各角色私有项 - 增加 DataNode:复制 `datanode2` 服务块,改 `container_name`/`hostname`(`hadoop-dn3`)、 端口、`conf/dn3.env`,并在 `kdc-setup.sh` 里追加 `dn/hadoop-dn3`、`nm/hadoop-dn3`、`HTTP/hadoop-dn3` 三个主体与对应 `kt` 导出行,然后按 §5.8 的方式「先 KDC 再其余」重建(强制重新导出 keytab) ### 改 REALM / 主机名 REALM 与主机名分布在 `kdc/kdc-setup.sh` 和 `conf/*.env` 中,改动需同步这几处, 然后 `docker compose down -v && docker compose up -d --build` 全新部署。 ### 数据落盘位置 HDFS 数据保存在命名卷中,默认由 docker/podman 管理: ```bash docker volume ls | grep hadoop-docker # 想改成宿主机目录,把 docker-compose.yml 里的 # nn-data:/data/nn → ./data/nn:/data/nn # dn1-data:/data/dn → ./data/dn1:/data/dn (dn2 同理) ``` --- ## 8. 目录结构 ``` hadoop-docker/ ├── docker-compose.yml # 一键部署编排 ├── .env # 镜像源等可调参数 ├── kdc/ │ ├── Dockerfile # CentOS Stream 9 + krb5-server/krb5-workstation │ └── kdc-setup.sh # 建 realm、建主体、导 keytab、预授权票据、起 KDC ├── hadoop/ │ ├── Dockerfile # 基于 bde2020/hadoop-namenode,固化启动脚本 │ └── scripts/ │ ├── start-nn.sh # 格式化(幂等) + NameNode + HDFS 目录初始化 │ ├── start-dn-nm.sh # 自签证书 + DataNode + NodeManager │ ├── start-rm-jhs.sh # ResourceManager + JobHistory │ ├── start-client.sh # 客户端常驻容器 │ └── make-certs.sh # 生成 DataNode HTTPS 证书与 ssl-*.xml ├── conf/ │ ├── common.env # 公共配置(含环境变量→xml 的命名规则说明) │ ├── nn.env dn1.env dn2.env rm.env client.env └── scripts/ ├── verify.sh # 一键验证(HDFS + YARN + Kerberos) └── renew-ticket.sh # 刷新 Kerberos 票据 ``` --- ## 9. 常见报错与排障 | 现象 | 原因 | 处理 | |---|---|---| | 作业 FAILED,reduce 日志报 `error in shuffle in fetcher#3` / `Exceeded MAX_FAILED_UNIQUE_FETCHES`,底层是 `Owner 'root' for path .../file.out.index did not match expected owner 'hduser'` | NodeManager 以 root 运行,map 输出文件属主是 root,而 Kerberos 作业用户是 hduser,ShuffleHandler 的 `SecureIOUtils.checkStat` 校验失败 | 已根治:NM 以 hduser 运行(见 §5.9)。改完需 `up -d --build` 重建镜像,仅 `--force-recreate` 不够 | | 作业 FAILED,NodeManager 日志报 `Server has invalid Kerberos principal: rm/hadoop-rm@HADOOP.LOCAL, expecting: rm/10.89.2.172@HADOOP.LOCAL` | 容器重建后 IP 变化,客户端连接缓存里还是旧 IP,旧 IP 反解主机名失败后退化成 IP 字面量 | 已由 `*.principal.pattern` 根治(见 §5.6)。如需自建集群,把这 5 行 pattern 配置补上即可 | | 容器反复重启、退出码 137 | 启动脚本里有会「接管主进程」的命令(如 `mapred --daemon start`),主进程退出即容器退出 | 本项目已改成:JHS 用 `... &` 后台启动,RM 用 `exec yarn resourcemanager` 当前台 1 号进程 | | DataNode 报 `Cannot start secure DataNode` | 安全模式下缺 SASL 或 Web 未用 HTTPS | 确认 `dfs.data.transfer.protection=authentication`、`dfs.http.policy=HTTPS_ONLY`;DN 已由 `make-certs.sh` 生成自签证书 | | `kinit`/`kadmin.local` 报 `unable to get default realm` | 容器内缺 `/etc/krb5.conf` | 启动脚本会从共享卷把 `/krb5conf/krb5.conf` 复制到 `/etc/krb5.conf`;KDC 脚本则先写配置文件再调 `kadmin.local` | | 改了 `conf/*.env` 后配置没生效 | entrypoint 是在**容器启动时**把环境变量写入 `*-site.xml` 的 | `docker compose up -d --force-recreate`(重启即可,无需重建镜像) | | 只想重来一遍 | — | `docker compose down -v && docker compose up -d --build`(`-v` 会清空 HDFS 数据,NN 会自动重新格式化) | | 刚启动就 `hdfs dfs -put` 报 `Name node is in safe mode` | NameNode 启动后要先收齐 DataNode 的块汇报(再叠加默认 30s 的 `dfs.namenode.safemode.extension`)才会退出 safe mode,此期间 HDFS 只读 | 等一会儿即可:`docker compose exec client sh -c 'KRB5CCNAME=FILE:/keytabs/nn.ccache hdfs dfsadmin -safemode get'` 看到 `Safe mode is OFF` 再操作;`verify.sh` 已内置该等待 | 常用排查命令: ```bash docker compose logs -f namenode # 跟踪某节点日志(也可 kdc / datanode1 / datanode2 / resourcemanager / client) docker compose exec client bash # 进客户端查 HDFS/YARN docker compose exec resourcemanager bash -c 'tail -50 /var/log/jobhistory.log' # JobHistory 日志 docker compose exec datanode1 bash -c 'jps' # 看容器内进程(DataNode / NodeManager) ```