[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 之前

该脚本解决的是一个重启时的循环依赖问题:

  1. 首次启动时,setup-devices 将 /tmp 迁移到 /mnt/tmp,创建符号链接 /tmp → /mnt/tmp
  2. 节点重启时,/mnt 尚未挂载(需要 setup-devices 来挂载),但 /tmp 已经是指向 /mnt/tmp 的符号链接
  3. 网络配置(dhclient-script)比 setup-devices 先执行,需要往 /tmp 写文件
  4. 此时 /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 服务的桥梁,按顺序执行以下操作:

  1. setup_ssh_keys:从 IMDS 获取 SSH 公钥,安装到 /home/hadoop/.ssh/authorized_keys
  2. setup-dns:调用 /usr/bin/setup-dns 配置 DNS
  3. 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
  1. 创建排除文件:dfs.hosts.exclude、yarn.nodes.exclude.xml
  2. 创建符号链接:建立 legacy 路径兼容
  3. 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 等)
posted @ 2026-03-24 00:45  zhaojie10  阅读(27)  评论(0)    收藏  举报