[INFRA] EMR集群节点初始化和启动流程深度解析
请注意本文部分内容经过AI辅助生成,虽然经过笔者检查但是并不保证内容的正确性,请自行判断准确性,本文对相关后果不承担责任
EMR 集群中每个节点(Master / Core / Task)从 EC2 实例启动到集群服务就绪,会经历一系列有序的启动阶段。这些阶段由 systemd 服务依赖链编排,确保磁盘、网络、目录结构等基础设施就绪后,再启动上层 EMR 组件。所有节点类型的启动阶段和服务完全一致,区别仅在于 provision-node 阶段 Puppet 配置的具体应用组件不同(Master 配置 NameNode/ResourceManager 等,Core 配置 DataNode/NodeManager 等)。
完整的启动阶段如下:
mk-tmp-on-root # 解决重启时 /tmp 断链问题
↓
setup-devices # 磁盘分区/挂载/目录迁移 (~10s)
↓
cloud-init-local → cloud-init → cloud-config # 标准 cloud-init 流程
↓ ↓
install-components libinstance-controller-java
(提前安装组件RPM) (SSH密钥 + setup-dns + 目录创建)
↓
┌──────────────┼──────────────┐
↓ ↓ ↓
instance-controller logpusher apppusher / metricscollector
↓
system-actions (EMR 内部系统脚本)
↓
bootstrap-actions (用户自定义脚本)
↓ ← lss:BA_COMPLETED
provision-node (Puppet 安装配置应用组件)
↓ ← lss:COMPLETED
节点上线 (provision: SUCCESSFUL)
以下是 systemd-analyze blame 的实际输出,可以看到各阶段的耗时:
9.797s setup-devices.service
5.159s apppusher.service
2.059s cloud-init.service
1.993s instance-controller.service
1.308s metricscollector.service
644ms libinstance-controller-java.service
239ms logpusher.service
166ms systemd-tmpfiles-setup.service
81ms cloud-init-local.service
4ms install-components.service
mk-tmp-on-root:解决重启时的 /tmp 断链问题
| systemd 服务 | mk-tmp-on-root.service |
|---|---|
| 脚本 | /usr/bin/mk-tmp-on-root |
| 执行时机 | 最早期,在 network.service 之前 |
该脚本解决的是一个重启时的循环依赖问题:
- 首次启动时,
setup-devices将/tmp迁移到/mnt/tmp,创建符号链接/tmp → /mnt/tmp - 节点重启时,
/mnt尚未挂载(需要setup-devices来挂载),但/tmp已经是指向/mnt/tmp的符号链接 - 网络配置(dhclient-script)比
setup-devices先执行,需要往/tmp写文件 - 此时
/tmp → /mnt/tmp指向的目标不存在,dhclient 写入失败,网络配置中断
因此 mk-tmp-on-root 在重启时检测到 /tmp 是断裂的符号链接后,删掉它并在 root 卷上重建真实的 /tmp 目录,让 dhclient 能正常工作。之后 setup-devices 挂载好 /mnt 后,会再把 /tmp 重新链接回 /mnt/tmp。
首次启动时 /tmp 还不是符号链接,脚本不做任何操作:
# /var/log/mk-tmp-on-root/mk-tmp-on-root.log
+ [[ ! -L /tmp ]]
+ echo '/tmp is not symlink yet, do nothing'
/tmp is not symlink yet, do nothing
+ return
setup-devices:磁盘初始化
| 项目 | 说明 |
|---|---|
| systemd 服务 | setup-devices.service |
| 脚本 | /usr/bin/setup-devices |
| 依赖 | After: network.service, mk-tmp-on-root.service;Before: cloud-init.service |
| 耗时 | ~10s(启动阶段中耗时最长) |
| 幂等性 | 通过 /var/setup-devices/completed 标记区分首次启动和重启 |
| 日志 | /emr/setup-devices/ |
进程信息
# 服务状态
sudo systemctl status setup-devices.service
# 服务定义
cat /etc/systemd/system/setup-devices.service
# [Unit]
# Description=Setup devices
# Before=cloud-init.service chronyd.service log4j-cve-2021-44228-hotpatch.service
# After=network.service mk-tmp-on-root.service
# [Service]
# Type=oneshot
# ExecStart=/usr/bin/setup-devices
关键文件
| 文件路径 | 说明 |
|---|---|
/usr/bin/setup-devices |
主脚本 |
/var/setup-devices/device-names.json |
设备名映射和正则配置 |
/var/setup-devices/mount-map |
设备 UUID → 挂载点映射(重启时使用) |
/var/setup-devices/completed |
首次启动完成标记 |
/etc/fstab |
挂载表(由脚本自动维护) |
日志文件
所有日志位于 /emr/setup-devices/:
/emr/setup-devices/
├── setup_drives.<timestamp>.log # 磁盘发现、分区、格式化、挂载
├── setup_tmp_dir.<timestamp>.log # /tmp 迁移
├── setup_var_log_dir.<timestamp>.log # /var/log 迁移
├── setup_var_lib_dir.<timestamp>.log # /var/lib 迁移
├── setup_var_tmp_dir.<timestamp>.log # /var/tmp 迁移
├── setup_var_cache_dir.<timestamp>.log # /var/cache 迁移
├── setup_emr_metrics.<timestamp>.log # metrics 目录创建
├── DiskEncryptor.log # 磁盘加密日志
└── udev.<timestamp>.log # udev 设备事件监控
执行流程图示
首次启动流程
start()
├── setup_drives() # 磁盘发现、分区、加密、格式化、挂载
│ ├── 读取 device-names.json # 设备名映射配置
│ ├── 枚举块设备 (lsblk) # 发现所有 NVMe/EBS 设备
│ ├── 排除 root/swap 设备
│ ├── 第一个设备: sgdisk 分两区 # 5GB → /emr, 剩余 → /mnt
│ ├── DiskEncryptorMain # LUKS 加密(如启用)
│ ├── make_xfs_parallel() # 并行 mkfs.xfs 格式化
│ └── create_devices_mount_map() # 创建挂载映射并写入 fstab
│
├── setup_tmp_dir() # /tmp → /mnt/tmp
├── stop_amazon_ssm_agent() # 临时停止 SSM Agent
├── setup_var_log_dir() # /var/log → /mnt/var/log
├── setup_var_lib_dir() # /var/lib → /mnt/var/lib
├── setup_var_tmp_dir() # /var/tmp → /mnt/var/tmp
├── setup_var_cache_dir() # /var/cache → /mnt/var/cache
├── setup_emr_metrics_dirs() # 创建 /mnt/var/em/raw
├── start_amazon_ssm_agent() # 恢复 SSM Agent
└── touch completed # 标记首次启动完成
重启流程
start()
├── setup_drives() # 根据 mount-map 重新挂载
│ ├── 读取 /var/setup-devices/mount-map
│ ├── DiskEncryptorMain # 重新打开加密设备(如启用)
│ └── create_drive() # 按 UUID 重新挂载
│
├── setup_tmp_dir() # 检查 /tmp 链接是否正确
├── restart auditd # 重启持有旧文件句柄的进程
└── restart rsyslog
主要职责为:
- 发现并分区块设备(NVMe 等),排除 root 和 swap 设备
- 第一个设备分两个分区:5GB →
/emr,剩余 →/mnt - 其他设备依次挂载为
/mnt1、/mnt2...(为 Hadoop 生态组件提供多磁盘并行 IO,详见下方说明) - 支持 LUKS 磁盘加密(通过
DiskEncryptorMain) - 格式化为 XFS 文件系统,写入 fstab 时使用
noauto选项(由脚本手动挂载,非系统自动挂载) - 将关键目录迁移到临时盘(保护 root 卷不被日志和缓存撑满,临时盘空间远大于 root 卷):
/tmp→/mnt/tmp、/var/log→/mnt/var/log、/var/lib→/mnt/var/lib、/var/tmp→/mnt/var/tmp、/var/cache→/mnt/var/cache - 创建 EMR metrics 目录(
/mnt/var/em/raw) - 重启时根据
/var/setup-devices/mount-map重新挂载
磁盘分区与挂载
setup-devices 是整个启动链中耗时最长的阶段,cloud-init 依赖其完成后才启动(通过 Before=cloud-init.service 声明)。它负责发现所有块设备(NVMe/EBS),排除 root 和 swap 设备后,按以下方案分区:
- 第一个可用设备分两个分区:5GB →
/emr(EMR 核心组件运行数据),剩余 →/mnt(主临时存储) - 其他设备依次整盘挂载为
/mnt1、/mnt2...
# /emr/setup-devices/setup_drives.*.log
2026-03-23 14:22:46.152 + setup_drives
2026-03-23 14:22:46.160 Drives setup started at: Mon Mar 23 14:22:46 UTC 2026
2026-03-23 14:22:46.174 + local 'block_devices=/dev/nvme0n1
2026-03-23 14:22:46.176 /dev/nvme1n1
2026-03-23 14:22:46.177 /dev/nvme2n1'
...
# 最终挂载结果
2026-03-23 14:22:52.243 /dev/nvme1n1p1 5177344 69368 5107976 2% /emr
2026-03-23 14:22:52.244 /dev/nvme1n1p2 28244972 230756 28014216 1% /mnt
2026-03-23 14:22:52.246 /dev/nvme2n1 33488896 267356 33221540 1% /mnt1
所有分区格式化为 XFS
# /emr 分区挂载选项
mount -o inode64,nodev -t xfs UUID=${deviceUUID} /emr
# /mnt 及其他分区挂载选项(启用配额)
mount -o inode64,nodev,usrquota,grpquota,prjquota -t xfs UUID=${deviceUUID} /mnt
写入 fstab 时使用 noauto 选项(由脚本手动挂载,非系统自动挂载)。这也是 mk-tmp-on-root 存在的原因——重启时这些盘还没被 setup-devices 挂载,/tmp → /mnt/tmp 的符号链接是断的。
UUID=2ea3fd6a-... /emr xfs defaults,noauto,comment=emr-setup-devices 0 0
UUID=bc68e724-... /mnt xfs defaults,noauto,comment=emr-setup-devices 0 0
UUID=0f013fb6-... /mnt1 xfs defaults,noauto,comment=emr-setup-devices 0 0
当集群启用磁盘加密时,DiskEncryptorMain 会在格式化前对设备进行 LUKS 加密。
# 加密器 Java 进程
java -cp $DISK_ENCRYPTOR_CP \
-Dlog4j2.configurationFile=file:/etc/instance-controller/disk-encryptor-log4j2.xml \
aws157.instancecontroller.encryptor.DiskEncryptorMain ${active_devices[*]}
# 输入: /dev/nvme1n1p1 /dev/nvme1n1p2 /dev/nvme2n1
# 输出: /dev/mapper/nvme1n1p1 /dev/mapper/nvme1n1p2 /dev/mapper/nvme2n1 (加密成功)
# 或: /dev/nvme1n1p1 /dev/nvme1n1p2 /dev/nvme2n1 (未启用加密)
未启用时日志/emr/setup-devices/DiskEncryptor.log如下:
# /emr/setup-devices/DiskEncryptor.log
2026-03-23 14:22:51,371 INFO main: Cannot find disk encryption configuration in user data. do nothing.
多磁盘挂载点的作用
EMR 会自动将所有 /mnt* 挂载点加入 Hadoop 组件的数据目录配置:
<!-- HDFS DataNode 数据分散到多盘,IO 并行 -->
dfs.datanode.data.dir = file:///mnt/hdfs, file:///mnt1/hdfs, file:///mnt2/hdfs ...
<!-- NameNode 元数据跨盘冗余,一块盘坏了另一块还有 -->
dfs.namenode.name.dir = file:///mnt/namenode, file:///mnt1/namenode ...
<!-- YARN shuffle 数据分散到多盘 -->
yarn.nodemanager.local-dirs = /mnt/yarn, /mnt1/yarn ...
/mnt1、/mnt2 等额外挂载点的核心价值在于为 Hadoop 生态组件提供多磁盘并行 IO。EMR 会自动将所有 /mnt* 挂载点加入 HDFS 和 YARN 的数据目录配置:
dfs.datanode.data.dir = file:///mnt/hdfs, file:///mnt1/hdfs, file:///mnt2/hdfs ...
dfs.namenode.name.dir = file:///mnt/namenode, file:///mnt1/namenode ...
yarn.nodemanager.local-dirs = /mnt/yarn, /mnt1/yarn ...
磁盘越多,IO 吞吐越高、存储容量越大,NameNode 元数据还能跨盘冗余(一块盘坏了另一块还有)。
目录迁移
EMR 的 root EBS 卷空间有限(本集群 30GB),而 Hadoop/YARN/Spark 等组件的日志、缓存、状态数据增长很快。如果都写在 root 卷上会很快撑满根分区导致系统不可用。因此 setup-devices 将以下目录迁移到大容量的临时盘,通过符号链接保持路径透明,所有进程无需改配置即可继续正常写入
迁移原理
move_to_ephemeral() 函数将系统目录从 root 卷迁移到临时盘(/mnt),通过符号链接保持路径兼容:
# 以 /var/log 为例
mkdir -p /mnt/var/log # 在临时盘创建目标目录
mv /var/log /var/log.old # 重命名原目录
ln -nsf /mnt/var/log -T /var/log # 创建符号链接(先于 rsync,避免写入丢失)
rsync -a /var/log.old/ /mnt/var/log # 同步数据
rm -Rf /var/log.old # 清理旧目录
实际日志(/emr/setup-devices/setup_var_log_dir.*.log):
2026-03-23 14:22:52.536 + setup_var_log_dir
2026-03-23 14:22:52.539 + target_dir=/var/log
2026-03-23 14:22:52.540 + move_to_ephemeral /var/log
2026-03-23 14:22:52.541 Moving directory to ephemeral: /var/log
2026-03-23 14:22:52.542 + ls -l /var/log
2026-03-23 14:22:52.553 total 4
2026-03-23 14:22:52.556 drwxr-xr-x. 2 root root 6 Mar 11 23:44 amazon
2026-03-23 14:22:52.557 drwx------. 2 root root 23 Mar 23 14:22 audit
2026-03-23 14:22:52.560 drwxrwxr-x. 2 hdfs hadoop 6 Feb 20 23:17 hadoop-hdfs
2026-03-23 14:22:52.561 drwxrwxr-x. 2 httpfs httpfs 6 Feb 20 23:17 hadoop-httpfs
...
迁移目录清单
| 原路径 | 迁移后实际路径 |
|---|---|
/tmp |
/mnt/tmp |
/var/log |
/mnt/var/log |
/var/lib |
/mnt/var/lib |
/var/tmp |
/mnt/var/tmp |
/var/cache |
/mnt/var/cache |
迁移过程中会先创建符号链接再 rsync 数据,避免其他进程在迁移期间写入丢失:
# /emr/setup-devices/setup_var_log_dir.*.log
2026-03-23 14:22:52.536 + setup_var_log_dir
2026-03-23 14:22:52.539 + target_dir=/var/log
2026-03-23 14:22:52.540 + move_to_ephemeral /var/log
2026-03-23 14:22:52.541 Moving directory to ephemeral: /var/log
迁移完成后,持有旧文件句柄的进程(auditd、rsyslog、dhclient)需要重启,否则日志会写入已删除的旧目录。迁移后进程重启
# auditd - 审计日志守护进程
systemctl start auditd
# rsyslog - 系统日志守护进程
systemctl restart rsyslog
# dhclient - DHCP 客户端(/var/lib 迁移后)
service network restart
重启流程
重启时 setup-devices 不再分区格式化,而是根据 /var/setup-devices/mount-map 中保存的 UUID → 挂载点映射重新挂载设备,然后重启 auditd 和 rsyslog。
cloud-init 与 install-components
cloud-init
cloud-init 依赖 setup-devices 完成后才启动(After=setup-devices.service),负责标准的 cloud-init 流程,写入 userData 和 /var/aws/emr/componentNames.csv 等配置。
install-components(无效服务)
读取 /var/aws/emr/componentNames.csv 中的组件列表,这似乎是个历史遗留文件,已经不被用到了,历史上似乎是用来预热安装组件用的。
注意:实际上EMR 7.12没有这个文件 。实际的组件安装是由 instance-controller 在 BA_COMPLETED ,直接 fork 一个进程执行 provision-node,不依赖 componentNames.csv,也不依赖 install-components.service。
libinstance-controller-java: 实例初始化
| 项目 | 说明 |
|---|---|
| systemd 服务 | libinstance-controller-java.service |
| 脚本 | /usr/bin/libinstance-controller-java |
| 依赖 | After: install-components.service |
| 耗时 | ~644ms |
服务定义
# /etc/systemd/system/libinstance-controller-java.service
[Unit]
Description=Install security keys from instance metadata and start instance controller
After=install-components.service
[Service]
Type=oneshot
ExecStart=/usr/bin/libinstance-controller-java
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
执行流程
实际执行日志(/emr/instance-state/libinstance-controller.log-*):
+ setup_ssh_keys
Fetch ssh key ...
+ query_imds meta-data/public-keys/0/openssh-key
Installing ssh key ...
+ cat /tmp/root-public-key.pem
+ chmod 600 /home/hadoop/.ssh/authorized_keys
+ chown -R hadoop.hadoop /home/hadoop/.ssh
+ /usr/bin/setup-dns
+ make_directories
+ mkdir -p /mnt/var/lib/hadoop /mnt/var/run/hadoop /mnt/var/log/hadoop
+ mkdir -p /emr/instance-controller/lib ... /emr/logpusher/lib ...
+ mkdir -p /emr/instance-state
+ touch /emr/instance-controller/lib/dfs.hosts.exclude
+ ln -sf /emr/instance-controller/lib/info /mnt/var/lib/
+ ln -sf /emr/instance-controller/log/bootstrap-actions /mnt/var/log/
+ change_permissions
+ chown hadoop:hadoop /var/aws/emr/userData.json
+ chmod 750 /emr/instance-controller
Making sure /mnt* is mounted and owned by hadoop
==> /emr
==> /mnt
==> /mnt1
(console) 2026-03-23 14:22:59 Service libinstance-controller-java installed security keys
本身是 bash 脚本,是连接底层系统初始化(setup-devices)和上层 EMR 服务的桥梁,按顺序执行以下操作:
- setup_ssh_keys:从 IMDS 获取 SSH 公钥,安装到
/home/hadoop/.ssh/authorized_keys - setup-dns:调用
/usr/bin/setup-dns配置 DNS - make_directories:创建 EMR 核心目录结构
注意:instance-controller 启动时会从 userData 中的预签名 URL 下载
extraInstanceData.json,该 URL 指向 S3 公网端点。详见 04_libinstance_controller_java.md。
/emr/instance-controller/{lib,run,log,db}/emr/logpusher/{lib,run,log,db}/emr/instance-state/mnt/var/{lib,run,log}/hadoop
- 创建排除文件:
dfs.hosts.exclude、yarn.nodes.exclude.xml - 创建符号链接:建立 legacy 路径兼容
- change_permissions:设置目录权限,确保 hadoop 用户拥有正确权限
# /emr/instance-state/libinstance-controller.log-2026-03-23-14-22
+ setup_ssh_keys
Fetch ssh key ...
+ query_imds meta-data/public-keys/0/openssh-key
Installing ssh key ...
+ /usr/bin/setup-dns
+ make_directories
+ mkdir -p /mnt/var/lib/hadoop /mnt/var/run/hadoop /mnt/var/log/hadoop
+ mkdir -p /emr/instance-controller/lib ... /emr/logpusher/lib ...
+ ln -sf /emr/instance-controller/lib/info /mnt/var/lib/
+ ln -sf /emr/instance-controller/log/bootstrap-actions /mnt/var/log/
+ change_permissions
+ chown hadoop:hadoop /var/aws/emr/userData.json
+ chmod 750 /emr/instance-controller
Making sure /mnt* is mounted and owned by hadoop
==> /emr
==> /mnt
==> /mnt1
(console) 2026-03-23 14:22:59 Service libinstance-controller-java installed security keys
instance-controller / logpusher / apppusher / metricscollector这四个服务都依赖 libinstance-controller-java.service 完成后并行启动
| 服务 | 耗时 | 说明 |
|---|---|---|
instance-controller.service |
~1.9s | EMR 核心守护进程,管理实例生命周期、执行 Steps |
apppusher.service |
~5.2s | 应用日志/指标收集 |
logpusher.service |
~259ms | 日志推送到 S3 |
metricscollector.service |
- | EMR 指标采集上报 |
setup-dns
| 项目 | 说明 |
|---|---|
| 脚本 | /usr/bin/setup-dns |
| 调用方式 | 由 libinstance-controller-java 调用 |
| 日志 | /mnt/var/log/setup-dns/setup-dns.log |
setup-dns 负责确保 EMR 节点的 DNS 解析正常工作。在 VPC 环境中,如果节点的主机名正向/反向解析不一致,会导致 Hadoop 生态组件(HDFS、YARN 等)通信异常。该脚本通过配置 dnsmasq 作为本地 DNS 缓存来解决此问题。
脚本首先检测网络管理方式(AL2 用 dhclient,AL2023 用 systemd-resolved),然后验证 hostname -f 的正向解析和反向解析是否一致:
# /mnt/var/log/setup-dns/setup-dns.log
+ systemctl is-active systemd-networkd
active
AMI is running systemd-networkd to manage networking.
...
'hostname -f' returns : ip-192-168-26-164.cn-north-1.compute.internal
+ check_reverse ip-192-168-26-164.cn-north-1.compute.internal
+ ip_addr=192.168.26.164
+ reverse='164.26.168.192.in-addr.arpa domain name pointer ip-192-168-26-164.cn-north-1.compute.internal.'
Reverse DNS works and matches.
...
Resolving hostname ip-192-168-26-164.cn-north-1.compute.internal successfully, do nothing and exit.
执行流程
标准模式(默认)
main()
├── 等待网络就绪 (nm-online)
├── 检查 resolv.conf 中是否有 search 域
├── show_dns_status("BeforeSetup") # 记录初始 DNS 状态
│
├── 判断是否在 VPC 中
│ ├── 非 VPC (us-east-1):
│ │ └── 确保 ec2.internal 在 search 域中
│ │
│ └── VPC:
│ ├── 检查 hostname -f 是否正常
│ ├── 检查反向 DNS 是否一致
│ │
│ ├── DNS 正常 → 不做任何操作
│ └── DNS 异常 → 启动 dnsmasq
│ ├── setup_emr_custom_domain_configs()
│ │ └── 为每个 VPC CIDR 生成 synth-domain 规则
│ └── run_dnsmasq()
│ ├── 停止已有 dnsmasq 进程
│ ├── systemctl start dnsmasq
│ └── 将 127.0.0.1 添加为首选 DNS
│
├── restart_network_services_if_needed()
└── show_dns_status("AfterSetup") # 验证最终 DNS 状态
HA Primary 域名模式
当 CloudWatch Agent 安装在 HA 集群上时,以参数 setup_emr_primary_internal_domain 调用:
main("setup_emr_primary_internal_domain")
├── setup_emr_primary_internal_domain_configs()
│ └── 为每个 Primary 节点 IP 生成 address 记录
│ 写入 /etc/dnsmasq.d/emr-metrics.conf
├── run_dnsmasq()
├── restart_network_services_if_needed()
└── check_forward_emr_primary_internal() # 验证域名解析
此模式配置 emr-primary.internal 域名解析到所有 Primary 节点 IP,供 CloudWatch Agent 使用。
如果 DNS 正常(如上),脚本不做任何修改直接退出。如果 DNS 异常,则配置 dnsmasq 作为本地 DNS 缓存,为 VPC CIDR 生成 synth-domain 规则(将 IP 自动映射为 ip-x-x-x-x.<domain> 格式),并将 127.0.0.1 添加为首选 DNS 服务器。
对于 HA 集群,还支持以 setup_emr_primary_internal_domain 参数调用,配置 emr-primary.internal 域名解析到所有 Primary 节点 IP,供 CloudWatch Agent 使用。
dnsmasq 配置
EMR 自定义域名配置/etc/dnsmasq.d/emr.conf
# 监听本地回环地址
listen-address=127.0.0.1
# 为 VPC CIDR 生成 synth-domain 规则
# 格式: synth-domain=<search-domain>,<vpc-cidr>,ip-
# 作用: 将 IP 地址自动映射为 ip-x-x-x-x.<domain> 格式的主机名
synth-domain=cn-north-1.compute.internal,192.168.0.0/16,ip-
HA 集群 Primary 节点配置/etc/dnsmasq.d/emr-metrics.conf
# 将 emr-primary.internal 解析到所有 Primary 节点
address=/emr-primary.internal/192.168.27.46
address=/emr-primary.internal/192.168.18.100
address=/emr-primary.internal/192.168.30.200
DNS 解析链路
应用程序 DNS 查询
↓
127.0.0.1 (dnsmasq)
├── 匹配 synth-domain 规则 → 直接返回合成结果
├── 匹配 address 规则 → 返回配置的 IP
└── 未匹配 → 转发到上游 DNS (VPC DNS: x.x.0.2)
DNS 正常时不启动 dnsmasq
当 VPC DNS 解析正常时,setup-dns 不做任何修改直接退出:
# /mnt/var/log/setup-dns/setup-dns.log(DNS 正常场景)
+ hostname=ip-192-168-26-164.cn-north-1.compute.internal
'hostname -f' returns : ip-192-168-26-164.cn-north-1.compute.internal
+ check_reverse ip-192-168-26-164.cn-north-1.compute.internal
+ ip_addr=192.168.26.164
+ reverse='164.26.168.192.in-addr.arpa domain name pointer ip-192-168-26-164.cn-north-1.compute.internal.'
Reverse DNS works and matches.
...
Resolving hostname ip-192-168-26-164.cn-north-1.compute.internal successfully, do nothing and exit.
网络管理适配
setup-dns 通过检测 systemd-networkd 是否运行来判断使用哪种网络管理方式:
# /mnt/var/log/setup-dns/setup-dns.log
+ systemctl is-active systemd-networkd
active
+ echo 'AMI is running systemd-networkd to manage networking.'
AMI is running systemd-networkd to manage networking.
+ uses_systemd_networkd=true
AL2(dhclient 模式)
# 添加 DNS 服务器到 dhclient 配置
echo 'prepend domain-name-servers 127.0.0.1;' >> /etc/dhcp/dhclient.conf
# 添加搜索域
echo 'prepend domain-name "ec2.internal ";' >> /etc/dhcp/dhclient.conf
# 重启网络生效
service network restart
AL2023(systemd-resolved 模式)
# 检测方式
systemctl is-active systemd-networkd
# 配置文件
mkdir -p /etc/systemd/resolved.conf.d
echo -e "[Resolve]\nDNS=127.0.0.1" > /etc/systemd/resolved.conf.d/emr.conf
# 重启 resolved 生效
service systemd-resolved restart
DNS 验证逻辑
脚本执行前后都会进行 DNS 验证:
正向解析检查
# 获取 FQDN
hostname -f
# 预期: ip-192-168-27-46.cn-north-1.compute.internal
# 正向解析
host ip-192-168-27-46.cn-north-1.compute.internal
# 预期: ip-192-168-27-46.cn-north-1.compute.internal has address 192.168.27.46
反向解析检查
# 反向解析
host 192.168.27.46
# 预期: 46.27.168.192.in-addr.arpa domain name pointer ip-192-168-27-46.cn-north-1.compute.internal.
# 验证正反向一致性
# 反向解析结果的主机名 == 正向解析的 FQDN
HA Primary 域名检查
# 验证 emr-primary.internal 解析到所有 Primary 节点 IP
host emr-primary.internal
# 预期: 返回所有 Primary 节点 IP,与 extraInstanceData.json 中的 IP 一致
instance-controller 启动与 extraInstanceData.json
libinstance-controller-java 完成后,instance-controller、logpusher、apppusher、metricscollector 四个服务并行启动。
公有子网模式下,instance-controller 启动时首先加载 extraInstanceData.json,这是其运行所需的核心元数据文件。数据来源链路如下:
EMR 控制面
↓ 生成集群配置并上传
S3 (aws157-instance-data-1-prod-<region>/<cluster-id>/<instance-group-id>.json)
↑ 预签名 URL(S3 公网端点: s3.<region>.amazonaws.com.cn)
EC2 userData.json 中的 extraInstanceDataPresignedUrls 字段
↓ instance-controller 启动时通过预签名 URL 下载
/emr/instance-controller/lib/info/extraInstanceData.json
该文件包含集群拓扑(Master/Core/Task 实例组)、实例角色、systemActions、安全证书、日志上传策略、HA 候选节点 IP 等元数据。
注意:以上场景仅限公有子网模式
预签名 URL 指向 S3 公网端点(如
aws157-instance-data-1-prod-cn-north-1.s3.cn-north-1.amazonaws.com.cn)。当前集群为 public 子网模式("isPrivateSubnetCluster": false),EC2 实例可直接通过公网访问 S3。
# /emr/instance-controller/log/instance-controller.log
2026-03-23 14:23:02,809 INFO main: Loading ExtraInstanceData from file
2026-03-23 14:23:02,828 INFO main: Read extraInstanceData, framework version: null
目录结构
/emr/
├── instance-controller/
│ ├── lib/
│ │ ├── info/ # job-flow.json, extraInstanceData.json
│ │ ├── dfs.hosts.exclude # HDFS 排除节点列表
│ │ └── yarn.nodes.exclude.xml # YARN 排除节点列表
│ ├── run/ # PID 文件
│ ├── log/
│ │ └── bootstrap-actions/ # Bootstrap Action 日志
│ └── db/ # HSQLDB 数据库
├── logpusher/
│ ├── lib/ run/ log/ db/
├── logpusher-user-overrides/ # 用户自定义 logpusher 配置
├── logpusher-system-overrides/ # 系统 logpusher 配置覆盖
└── instance-state/ # 实例状态日志
/mnt/var/
├── lib/
│ ├── hadoop/ # Hadoop 运行数据
│ ├── instance-controller/
│ │ └── extraInstanceData.json → (符号链接)
│ └── info → /emr/instance-controller/lib/info (符号链接)
├── run/hadoop/
└── log/
├── hadoop/
├── instance-state → /emr/instance-state (符号链接)
└── bootstrap-actions → /emr/instance-controller/log/bootstrap-actions (符号链接)
关键文件
| 文件路径 | 说明 |
|---|---|
/usr/bin/libinstance-controller-java |
主脚本 |
/etc/instance-controller/imds-utils |
IMDS 查询工具函数库 |
/emr/instance-state/libinstance-controller.log-* |
执行日志 |
/var/aws/emr/userData.json |
用户数据配置 |
/emr/instance-controller/lib/dfs.hosts.exclude |
HDFS 节点排除列表 |
/emr/instance-controller/lib/yarn.nodes.exclude.xml |
YARN 节点排除列表 |
InstanceConfiguratorDoConfigureThread
instance-controller 启动后,由 InstanceConfiguratorDoConfigureThread 线程依次执行三个阶段
以下是从 instance-controller 日志中提取的完整状态转换过程,清晰展示了各阶段的执行顺序和时间:
# /emr/instance-controller/log/instance-controller.log
# IC 启动,加载 extraInstanceData
14:23:02,809 INFO main: Loading ExtraInstanceData from file
14:23:02,828 INFO main: Read extraInstanceData, framework version: null
# 状态: STARTING
14:23:08,911 INFO main: LIS 0 lss:STARTING provision: PENDING
# 执行 system actions(本集群 0 个)和 bootstrap actions(本集群 0 个)
14:23:12,768 INFO InstanceConfiguratorDoConfigureThread: Running 0 system action(s)...
14:23:12,771 INFO InstanceConfiguratorDoConfigureThread: Running 0 bootstrap action(s)...
# 状态变为 BA_COMPLETED,开始执行 provision-node
14:23:12,780 INFO InstanceConfiguratorDoConfigureThread: Fetching file '/var/lib/aws/emr/provision-node'
14:23:12,781 INFO InstanceConfiguratorDoConfigureThread: startExec '/var/lib/aws/emr/provision-node --install --provision -d0/d55b32eb...'
14:23:12,996 INFO Poller: LIS 0 lss:BA_COMPLETED provision: PENDING
# provision-node 执行完成
14:23:14,787 INFO InstanceConfiguratorDoConfigureThread: total process run time: 2 seconds
14:23:14,787 INFO InstanceConfiguratorDoConfigureThread: Execution succeeded
# 状态变为 COMPLETED
14:23:15,041 INFO Poller: LIS 0 lss:COMPLETED provision: PENDING
# 最终 provision 成功,节点上线
14:27:13,413 INFO Poller: LIS 0 lss:COMPLETED provision: SUCCESSFUL
system-actions
EMR 控制面下发的内部预置操作(用户不可直接配置),通过 extraInstanceData.json 的 "systemActions" 字段定义。从 IC 的 Java 类可以看到支持以下类型:
InstallCertsSystemAction— 安装 TLS/SSL 证书(启用安全配置时)DiskEncryptionSystemAction— 磁盘加密相关操作DownloadSystemAction— 从 S3 下载文件
未启用相关安全功能时,该数组为空("systemActions": [])。
bootstrap-actions
用户在创建集群时定义的自定义初始化脚本,每个 bootstrap action 的执行结果以 controller、stderr、stdout 三个文件记录在 /emr/instance-controller/log/bootstrap-actions/ 目录中。
provision-node
在 bootstrap-actions 完成(BA_COMPLETED)之后执行。通过 Puppet 执行应用组件安装和配置,包含三个子集:
-
puppet_core_set.log:核心组件集(HDFS、YARN 等) -
puppet_aux_set_1.log:辅助组件集 1 -
puppet_aux_set_2.log:辅助组件集 2
日志目录结构:
/mnt/var/log/provision-node/
├── <uuid>/ # instance-controller 触发的 provision 日志
│ ├── controller # IC 执行记录
│ ├── stdout
│ └── stderr
├── apps-phase/
│ └── 0/<uuid>/ # Puppet 应用配置日志
│ ├── stdout
│ ├── stderr
│ ├── puppet_core_set.log
│ ├── puppet_aux_set_1.log
│ └── puppet_aux_set_2.log
└── reports/
└── 0/<uuid>/ # Puppet 报告
安装组件 RPM(yum install)
由于 componentNames.csv 不存在,它从 EMR 控制面(platform)获取组件列表:
No componentNames passed. Fetching componentNames from platform
Installing components: [hadoop-client, hive-server2, spark-on-yarn, hbase-hmaster, ...]
然后用 yum 安装这些组件的 RPM 包。从 stdout 可以看到大部分包已经预装在 AMI 里了(is already installed),只有少数几个需要额外安装(如 emr-notebook-env、hbase-master、emr-wal-cli 等)
获取配置并生成 Puppet Hiera 数据
从控制面拉取集群配置(hdfs-site、yarn-site 等 classification),加上节点本地信息(磁盘挂载点、hostname 等),生成 Puppet 的 hiera YAML 文件。
Applying Emr7 configuration overrides
contents of core_set_hiera.yaml: ...
contents of site.yaml: ...
执行 Puppet 配置应用组件
| Puppet 集合 | 耗时 | 内容 |
|---|---|---|
| core_set | ~82s | HDFS、YARN、ZooKeeper、HBase 等核心组件 |
| aux_set_1 | ~28s | Spark、Livy、Hudi 等辅助组件 |
| aux_set_2 | ~17s | Notebook、httpd 等辅助组件 |
core_set 先执行,完成后 aux_set_1 和 aux_set_2 并行执行。Puppet 负责:
- 写配置文件(如 hdfs-site.xml、yarn-site.xml)
- 创建目录和设置权限
- 启动服务(NameNode、ResourceManager、HiveServer2 等)

浙公网安备 33010602011771号