[INFRA] EMR集群内节点自动配置系统深入理解

请注意本文部分内容经过AI辅助生成,虽然经过笔者检查但是并不保证内容的正确性,请自行判断准确性,本文对相关后果不承担责任

EMR 集群的配置系统负责将用户通过 Configurations API 定义的参数(如 hdfs-site、yarn-site 等 classification)应用到每个节点的实际配置文件中。该系统在两个场景下工作:

  • 首次启动配置(Initial Provisioning):集群创建时,节点首次启动并应用配置
  • 运行时重配置(Reconfiguration):集群运行中,通过 ModifyInstanceGroups API 修改配置

两个场景共享同一套底层机制: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 中,该优化步骤已被禁用:

  1. cloud-init 的 write_files 配置中不包含 componentNames.csv,该文件不存在
  2. install-components 脚本中 start 函数的调用被注释掉:
# /usr/bin/install-components 末尾
  "$PROVISION_NODE_SCRIPT" --install --component-names "$component_names"
}

# start          ← 被注释,函数不会执行

exit 0
  1. 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 不是配置分发的瓶颈)
  • 节点故障不影响其他节点的配置过程
  • 新加入的节点自动获取最新版本的配置
posted @ 2026-03-24 13:32  zhaojie10  阅读(16)  评论(0)    收藏  举报