[INFRA] EMR集群内节点自动配置系统深入理解
请注意本文部分内容经过AI辅助生成,虽然经过笔者检查但是并不保证内容的正确性,请自行判断准确性,本文对相关后果不承担责任
EMR 集群的配置系统负责将用户通过 Configurations API 定义的参数(如 hdfs-site、yarn-site 等 classification)应用到每个节点的实际配置文件中。该系统在两个场景下工作:
- 首次启动配置(Initial Provisioning):集群创建时,节点首次启动并应用配置
- 运行时重配置(Reconfiguration):集群运行中,通过
ModifyInstanceGroupsAPI 修改配置
两个场景共享同一套底层机制:provision-node + Puppet,区别仅在于触发方式和状态转换路径。
核心组件
provision-node
| 项目 | 说明 |
|---|---|
| 入口脚本 | /var/lib/aws/emr/provision-node → /usr/share/aws/emr/node-provisioner/bin/provision-node |
| 实际程序 | Java 程序 com.amazonaws.emr.node.provisioner.Program |
| classpath | /usr/share/aws/emr/node-provisioner/lib/* |
| 日志路径 | /mnt/var/log/provision-node/apps-phase/<configurationVersion>/<uuid>/stderr |
入口脚本是一个简单的 bash wrapper:
#!/usr/bin/bash
set -ex
sudo /usr/share/aws/emr/node-provisioner/bin/provision-node "$@"
实际的 provision-node 脚本解析参数后,以 nohup 方式启动 Java 进程:
nohup -- java -classpath "$CLASSPATH" com.amazonaws.emr.node.provisioner.Program \
--yum-config-option skip_missing_names_on_install=False \
--no-repo-provision $args > $STDOUT_LOG 2> $STDERR_LOG 0</dev/null &
关键参数
| 参数 | 说明 |
|---|---|
--install |
安装组件 RPM 包 |
--provision |
执行 Puppet 配置并启动服务 |
--component-names |
指定组件列表(逗号分隔),不指定则从控制面获取 |
-d |
日志子目录,格式为 <configurationVersion>/<uuid> |
instance-controller (IC)
IC 是 provision-node 的调用方,负责:
- 首次启动时通过
InstanceConfiguratorDoConfigureThread线程调用 provision-node - 运行时通过
Poller线程的doRollingReconfiguration方法检测配置版本变化并触发重配
Puppet / Bigtop
provision-node 内部使用 Puppet(通过 Bigtop 框架)来实际生成配置文件和管理服务。Puppet 的 Hiera 数据层级决定了配置的优先级和合并方式。
首次启动配置流程
IC 启动后,InstanceConfiguratorDoConfigureThread 线程在 bootstrap actions 完成(BA_COMPLETED)后调用 provision-node:
# /emr/instance-controller/log/instance-controller.log
16:00:25,527 INFO InstanceConfiguratorDoConfigureThread: Script 221aa460-... - Fetching file '/var/lib/aws/emr/provision-node'
16:00:25,528 INFO InstanceConfiguratorDoConfigureThread: startExec '/var/lib/aws/emr/provision-node --install --provision -d0/221aa460-c789-4cb5-aad9-c053425c27ec'
16:00:27,542 INFO InstanceConfiguratorDoConfigureThread: waitProcessCompletion ended with exit code 0
注意参数 -d0/221aa460-...,其中 0 是 configurationVersion。
从控制面拉取配置
provision-node 启动后,PlatformConfigApplierFactory 向 EMR 控制面请求当前节点的配置:
# /mnt/var/log/provision-node/apps-phase/0/221aa460-c789-4cb5-aad9-c053425c27ec/stderr
16:00:28,662 INFO PlatformConfigApplierFactory: platform configuration response:
{
"configurations" : [ {
"classification" : "hdfs-site",
"properties" : {
"dfs.data.dir" : "/mnt/hdfs,/mnt1/hdfs",
"dfs.replication" : "1",
"dfs.namenode.handler.count" : "64",
...
}
}, {
"classification" : "yarn-site",
"properties" : {
"yarn.nodemanager.resource.memory-mb" : "6144",
"yarn.nodemanager.resource.cpu-vcores" : "4",
...
}
}, {
"classification" : "spark-defaults",
"properties" : {
"spark.executor.memory" : "4743M",
"spark.executor.cores" : "2",
...
}
},
... ],
"componentNames" : [ "hadoop-client", "hadoop-hdfs-namenode", "hive-server2", "spark-on-yarn", "hbase-hmaster", ... ],
"clusterId" : "j-NIRY0CET7ORZ",
"instanceId" : "i-0c55d20ec0a458ddf",
"instanceType" : "m5.xlarge",
"nodeType" : "master",
"releaseLabel" : "emr-7.12.0",
"mountedDirs" : [ "/mnt", "/mnt1" ],
"configurationVersion" : 0,
"namenodeFormatted" : false,
"isReprovision" : false,
"isReversion" : false,
"reconfigurationType" : "OVERWRITE"
}
响应中包含:
configurations:所有 classification 的配置参数(EMR 默认值 + 用户自定义值的合并结果)componentNames:该节点需要安装的组件列表configurationVersion:配置版本号(首次为0)nodeType:节点类型(master/core/task)mountedDirs:已挂载的磁盘目录(由 setup-devices 阶段创建)isReprovision:是否为重配置(首次为false)
安装组件 RPM
开始安装具体的components
16:00:28,692 INFO NodeProvisionerWorkflow: No componentNames passed. Fetching componentNames from platform
16:00:28,706 INFO NodeProvisionerWorkflow: Installing components: [hadoop-client, mariadb-server, delta, tez-on-yarn, hbase-thrift-server, emr-goodies, ...]
大部分 RPM 已预装在 AMI 中(is already installed),只有少数需要额外安装:
# /mnt/var/log/provision-node/apps-phase/0/221aa460-c789-4cb5-aad9-c053425c27ec/stdout
Package hadoop-client-3.4.1.amzn.4-1.amzn2023.x86_64 is already installed.
Package spark-core-3.5.6.amzn.1-1.amzn2023.x86_64 is already installed.
...
Installing:
aws-emr-notebook-agent noarch 1.23.0-1 emr_apps 14 M
emr-notebook-env x86_64 1.0.0-1 emr_apps 497 M
hbase-master noarch 2.6.2.amzn.3-1.amzn2023 emr_apps 8.0 k
...
16:03:22,026 INFO ComponentInstaller: Took 2 minutes, 53 seconds and 67 milliseconds to install packages
生成 Puppet Hiera 数据
Emr7ConfigApplier 将控制面返回的 JSON 配置转换为 Puppet Hiera YAML 格式:
16:03:22,304 INFO Emr7ConfigApplier: Applying Emr7 configuration overrides
16:03:22,351 INFO PuppetConfigurator: contents of core_set_hiera.yaml:
---
defaults:
datadir: /etc/puppetlabs/puppet/hieradata
data_hash: yaml_data
hierarchy:
- name: bigtop
paths:
- site.yaml # 最高优先级:用户配置 + EMR 计算值
- bigtop/%{hadoop_hiera_ha_path}.yaml # HA/非HA 配置
- bigtop/cluster.yaml # Bigtop 框架默认值
- generated.yaml # 自动生成的密码等
- core_set_context.yaml # 执行集上下文标识
version: 5
site.yaml 由 provision-node 在内存中动态生成(运行时注入 Puppet,不落盘为独立文件),包含了所有配置参数。以 hdfs-site 为例,用户设置的 dfs.replication: 1 会出现在:
# site.yaml 中的 hdfs-site 配置
'hadoop::common_hdfs::hdfs_site_overrides':
'dfs.replication': '1'
'dfs.namenode.handler.count': '64'
'dfs.data.dir': '/mnt/hdfs,/mnt1/hdfs'
...
Hiera 查找优先级:site.yaml > bigtop/noha.yaml > bigtop/cluster.yaml > generated.yaml
site.yaml 中的值会覆盖 cluster.yaml 中的默认值,这就是用户自定义配置生效的机制。
Puppet 分批应用配置
Puppet 分三批执行,core_set 先执行,完成后 aux_set_1 和 aux_set_2 并行执行:
16:03:22,489 INFO BigtopPuppeteer: setConfig reportdir: /var/log/provision-node/reports/0/221aa460-...
16:03:24,902 INFO BigtopPuppeteer: Set 1 puppet configs
16:04:46,748 INFO BigtopPuppeteer: Applied puppet core_set : Changes were successfully applied. # ~82s
16:05:02,846 INFO BigtopPuppeteer: Applied puppet aux_set_2 : Changes were successfully applied. # ~16s ┐ 并行
16:05:13,319 INFO BigtopPuppeteer: Applied puppet aux_set_1 : Changes were successfully applied. # ~27s ┘
Puppet 执行的具体操作:
- 根据 Hiera 数据生成 XML 配置文件(如
/etc/hadoop/conf/hdfs-site.xml) - 创建必要的目录结构并设置权限
- 启动对应的服务进程(NameNode、ResourceManager、HiveServer2 等)
Puppet 报告保存在:
/mnt/var/log/provision-node/reports/0/<uuid>/
├── core_set/<hostname>/<timestamp>.yaml
├── core_set/<hostname>/<timestamp>.puppetreport.json
├── aux_set_1/<hostname>/<timestamp>.yaml
└── aux_set_2/<hostname>/<timestamp>.yaml
IC 状态转换
首次启动时 IC 的 provision 状态转换链路:
# /emr/instance-controller/log/instance-controller.log
16:00:23,749 provision: PENDING # IC 启动,等待 provision-node 完成
... (provision-node 执行中,持续 PENDING 约 5 分钟)
16:05:13,613 provision: SUCCESSFUL # provision-node 完成,节点上线
运行时重配置流程
触发方式
IC 的 Poller 线程每 ~30 秒执行一次 doRollingReconfiguration,检查每个 Instance Group 的配置版本:
# 正常轮询(无配置变更)
16:48:27,984 INFO Poller: No instances require reconfiguration for IG: ig-98Z5LSYM6ADI with requested configuration version 0.
16:48:27,984 INFO Poller: No instances require reconfiguration for IG: ig-1Y2TWODR3B7IU with requested configuration version 0.
16:48:27,984 INFO Poller: No instances require reconfiguration for IG: ig-1XQ3QT6FP7PPX with requested configuration version 0.
16:48:27,984 INFO Poller: long poll - doRollingReconfiguration took 0ms
当用户通过 ModifyInstanceGroups API 提交新配置后,控制面的 configurationVersion 递增。Poller 检测到版本不匹配,触发重配:
# 检测到版本变化,触发重配
16:49:25,366 INFO InstanceConfiguratorDoConfigureThread: Reconfigure instance from version 0 to version 1.
16:49:25,380 INFO InstanceConfiguratorDoConfigureThread: startExec '/var/lib/aws/emr/provision-node --install --provision -d1/fb1f804e-5605-473e-976c-1b58d9aee049'
↑ configurationVersion = 1
provision-node 执行过程
重配时 provision-node 的执行过程与首次启动基本一致,但有几个关键差异。
控制面响应中的差异字段
# /mnt/var/log/provision-node/apps-phase/1/fb1f804e-.../stderr
16:49:26,408 INFO PlatformConfigApplierFactory: platform configuration response:
{
"configurations" : [ {
"classification" : "hdfs-site",
"properties" : {
"dfs.replication" : "2", ← 从 "1" 变为 "2"
...
}
}, ... ],
"configurationVersion" : 1, ← 从 0 变为 1
"isReprovision" : true, ← 从 false 变为 true
"isReversion" : false,
"reconfigurationType" : "OVERWRITE"
}
| 字段 | 首次启动 | 运行时重配 |
|---|---|---|
configurationVersion |
0 |
1(递增) |
isReprovision |
false |
true |
dfs.replication |
"1" |
"2"(本次修改) |
RPM 安装阶段
重配时所有 RPM 已安装,此阶段几乎瞬间完成,对比首次启动的 2 分 53 秒,差异显著。
16:49:27,584 INFO ComponentInstaller: Took 1 second and 14 milliseconds to install packages
Puppet 应用阶段
16:49:27,827 INFO Emr7ConfigApplier: Applying Emr7 configuration overrides
16:49:28,007 INFO PuppetConfigurator: contents of site.yaml:
...
'hadoop::common_hdfs::hdfs_site_overrides':
'dfs.replication': '2' ← 新值
...
'emr::is_reprovision': !!bool 'true' ← 标记为重配置
'hadoop::namenode::is_reprovision': !!bool 'false' ← NameNode 不重新格式化
...
Puppet 执行结果:
16:50:11,233 INFO BigtopPuppeteer: Applied puppet core_set : Changes were successfully applied. # ~43s
16:50:16,595 INFO BigtopPuppeteer: Applied puppet aux_set_2 : There were no changes to apply. # 无变更
16:50:21,842 INFO BigtopPuppeteer: Applied puppet aux_set_1 : Changes were successfully applied. # ~11s
注意 aux_set_2 报告 There were no changes to apply——因为本次只修改了 hdfs-site,与 aux_set_2 管理的组件(Notebook、httpd 等)无关,Puppet 智能跳过了无变更的部分。
IC 状态转换
运行时重配的状态转换链路:
16:49:23,357 provision: SUCCESSFUL # 重配前,节点正常运行
16:49:27,989 provision: RECONFIGURING # 检测到版本变化,开始重配
16:49:58,002 provision: RECONFIGURING # provision-node 执行中
16:50:24,471 provision: SUCCESSFUL # 重配完成,节点恢复正常
滚动重配
Master 节点的 IC 负责协调所有节点的重配,按 Instance Group 滚动执行:
# Master 自身先完成重配
16:49:25,366 INFO InstanceConfiguratorDoConfigureThread: Reconfigure instance from version 0 to version 1.
16:49:27,382 INFO InstanceConfiguratorDoConfigureThread: waitProcessCompletion ended with exit code 0
# 然后通知其他 IG 的节点重配
16:49:27,992 INFO Poller: Reconfiguring instances: [i-03dc982658b9b4309] for IG: ig-1Y2TWODR3B7IU with requested configuration version 1.
16:49:27,992 INFO Poller: Reconfiguring instances: [i-0b4648b9ef7704727] for IG: ig-1XQ3QT6FP7PPX with requested configuration version 1.
16:49:27,992 INFO Poller: No instances require reconfiguration for IG: ig-98Z5LSYM6ADI with requested configuration version 1.
↑ Master 自身已完成,无需再配
首次启动 vs 运行时重配 对比
| 对比项 | 首次启动 (Initial Provisioning) | 运行时重配 (Reconfiguration) |
|---|---|---|
| 触发方式 | IC 的 InstanceConfiguratorDoConfigureThread,在 BA_COMPLETED 后自动执行 |
IC 的 Poller.doRollingReconfiguration,每 ~30s 轮询检测版本变化 |
| 调用命令 | provision-node --install --provision -d0/<uuid> |
provision-node --install --provision -d1/<uuid> |
| configurationVersion | 0 |
递增(1, 2, ...) |
| isReprovision | false |
true |
| RPM 安装耗时 | ~2 分 53 秒(部分包需要下载安装) | ~1 秒(所有包已安装) |
| Puppet 执行 | 所有 set 都有变更需要应用 | 仅受影响的 set 有变更,其他报告 no changes |
| Puppet core_set 耗时 | ~82 秒 | ~43 秒 |
| IC 状态转换 | PENDING → SUCCESSFUL |
SUCCESSFUL → RECONFIGURING → SUCCESSFUL |
| 日志目录 | apps-phase/0/<uuid>/ |
apps-phase/1/<uuid>/ |
| 滚动策略 | 无(所有节点并行首次配置) | 有(按 IG 滚动,避免服务中断) |
| 配置来源 | 创建集群时的 Configurations 参数 | ModifyInstanceGroups API 提交的新配置 |
配置转换链路总览
用户 API (CreateCluster / ModifyInstanceGroups)
↓
EMR 控制面存储配置 (configurationVersion: N)
↓
每个节点的 IC 检测到版本变化
↓
IC 调用 provision-node --install --provision -dN/<uuid>
↓
provision-node (Java) 从控制面拉取配置 (PlatformConfigApplierFactory)
↓
返回 JSON: { configurations: [...], configurationVersion: N, ... }
↓
Emr7ConfigApplier 转换为 Puppet Hiera YAML (site.yaml)
↓
Puppet Hiera 层级合并:
site.yaml (最高优先级,用户配置 + EMR 计算值)
> bigtop/noha.yaml 或 bigtop/ha.yaml
> bigtop/cluster.yaml (Bigtop 框架默认值)
> generated.yaml (自动生成的密码等)
↓
Puppet 分批执行:
core_set → HDFS, YARN, ZooKeeper, HBase 等核心组件
aux_set_1 → Spark, Livy, Hudi 等 (与 core_set 完成后并行)
aux_set_2 → Notebook, httpd 等 (与 aux_set_1 并行)
↓
生成配置文件 (如 /etc/hadoop/conf/hdfs-site.xml) + 重启受影响的服务
↓
IC 状态变为 SUCCESSFUL,节点配置完成
日志路径汇总
| 日志 | 路径 | 说明 |
|---|---|---|
| IC 主日志 | /emr/instance-controller/log/instance-controller.log |
配置触发、状态转换、滚动重配协调 |
| provision-node stderr | /mnt/var/log/provision-node/apps-phase/<version>/<uuid>/stderr |
配置拉取、Hiera 生成、Puppet 执行 |
| provision-node stdout | /mnt/var/log/provision-node/apps-phase/<version>/<uuid>/stdout |
RPM 安装输出 |
| Puppet core_set 日志 | /mnt/var/log/provision-node/apps-phase/<version>/<uuid>/puppet_core_set.log |
核心组件 Puppet 详细日志 |
| Puppet aux_set_1 日志 | /mnt/var/log/provision-node/apps-phase/<version>/<uuid>/puppet_aux_set_1.log |
辅助组件集 1 Puppet 详细日志 |
| Puppet aux_set_2 日志 | /mnt/var/log/provision-node/apps-phase/<version>/<uuid>/puppet_aux_set_2.log |
辅助组件集 2 Puppet 详细日志 |
| Puppet 报告 | /mnt/var/log/provision-node/reports/<version>/<uuid>/<set>/<hostname>/ |
YAML 和 JSON 格式的 Puppet 执行报告 |
| IC 触发记录 | /mnt/var/log/provision-node/<uuid>/stdout 和 stderr |
IC 调用 provision-node 的输出 |
apps-phase/ 下的数字目录即 configurationVersion,每次重配会新增一个目录,可通过目录数量判断集群经历了多少次配置变更:
# 查看配置变更历史
ls /mnt/var/log/provision-node/apps-phase/
# 输出: 0 1
# 说明: 首次启动(0) + 一次重配(1)
关于 install-components 的说明
install-components.service 会读取 /var/aws/emr/componentNames.csv 并提前调用 provision-node --install --component-names 预安装 RPM。
在 EMR 7.12.0 中,该优化步骤已被禁用:
- cloud-init 的
write_files配置中不包含componentNames.csv,该文件不存在 install-components脚本中start函数的调用被注释掉:
# /usr/bin/install-components 末尾
"$PROVISION_NODE_SCRIPT" --install --component-names "$component_names"
}
# start ← 被注释,函数不会执行
exit 0
install-components.service虽然存在并正常启动,但实际不执行任何操作:
# journalctl -u install-components.service
install-components[3944]: + COMPONENT_NAMES_FILE_PATH=/var/aws/emr/componentNames.csv
install-components[3944]: + PROVISION_NODE_SCRIPT=/var/lib/aws/emr/provision-node
install-components[3944]: + exit 0 ← 直接退出
所有组件安装由 IC 触发的 provision-node --install --provision 一步完成,不再分为"预安装"和"配置"两个阶段。
Puppet Hiera 数据目录结构
/etc/puppetlabs/puppet/hieradata/
├── bigtop/
│ ├── cluster.yaml # Bigtop 框架默认配置模板
│ ├── ha.yaml # HA 模式配置
│ └── noha.yaml # 非 HA 模式配置
├── core_set_context.yaml # 内容: 'context::execution_set': 'core_set'
├── aux_set_1_context.yaml # 内容: 'context::execution_set': 'aux_set_1'
├── aux_set_2_context.yaml # 内容: 'context::execution_set': 'aux_set_2'
└── generated.yaml # 自动生成的数据库密码等
site.yaml 不在磁盘上,由 provision-node 在运行时动态注入 Puppet 进程。
配置拉取模型
EMR 采用拉模型(Pull Model)进行配置分发:
- 每个节点的 IC 独立轮询控制面,检测配置版本变化
- 每个节点的 provision-node 独立从控制面拉取配置
- 不存在 Master 向 Core/Task 推送配置的过程
这种设计的优势:
- 无论集群规模多大,都能并行完成配置
- 不存在单点瓶颈(Master 不是配置分发的瓶颈)
- 节点故障不影响其他节点的配置过程
- 新加入的节点自动获取最新版本的配置

浙公网安备 33010602011771号