EMR Core 节点部署 Flink Client 实战:Bootstrap Action 一次打包多次复用,解决调度系统提交任务的痛点
在 EMR 上跑 Flink 的同学应该都碰到过一个问题:Flink Client 只装在 Master 节点上,Core 节点默认没有。
这在大多数场景下没问题------反正任务从 Master 提交就行了。但如果你在 Core 节点上跑 DolphinScheduler、Airflow 这类调度系统的 Worker 组件,问题就来了:Worker 想直接调用 flink、flink-sql-client、yarn-session.sh 提交任务,发现命令不存在。
只能绕路 SSH 到 Master 节点执行,架构复杂度和维护成本都上去了。
这篇文章记录我怎么用 Bootstrap Action 解决这个问题:从 Master 打包 Flink 二进制 → 上传 S3 → 新集群启动时自动分发到 Core/Task 节点。整个方案遵循"一次打包、多次复用"的原则,只要 EMR 版本不变,打包产物可以反复用。
环境说明
- Amazon EMR 7.10.0
- Flink 1.20.0-amzn-4(AWS 基于开源版本的定制构建,包含安全补丁和服务集成优化)
- EMR 中的 Flink 版本与发行版绑定,选
emr-7.10.0自动获得Flink 1.20.0-amzn-4
第一步:创建打包用集群
先起一个临时集群用来打包 Flink 二进制文件:
bash
aws emr create-cluster \
--release-label emr-7.10.0 \
--applications Name=Flink Name=Hadoop \
--instance-type m5.xlarge \
--instance-count 3 \
--service-role EMR_DefaultRole \
--ec2-attributes InstanceProfile=EMR_EC2_DefaultRole \
--name "Flink-Source-Cluster"
等集群进入 WAITING 状态(约 5-10 分钟),记下 ClusterId。
第二步:打包 Flink Client
SSH 或 SSM 连接到 Master 节点:
bash
# 验证 Flink 版本
flink --version
# 验证 Flink 在 YARN 上能跑
sudo -u hadoop /usr/lib/flink/bin/yarn-session.sh -d -jm 1024 -tm 2048
yarn application -list
踩坑提醒:在 Master 节点上必须用完整路径 /usr/lib/flink/bin/yarn-session.sh,否则会因 $bin 路径解析问题报错。同时需要用 hadoop 用户才有 HDFS 写权限。
打包并上传 S3:
bash
# 打包二进制文件(bin、lib、opt、plugins、examples)
tar -czf flink-client-1.20.0-amzn-4.tar.gz -C /usr/lib flink
# 打包配置文件
tar -czf flink-conf.tar.gz -C /etc flink
# 上传到 S3
aws s3 cp flink-client-1.20.0-amzn-4.tar.gz s3://<your-bucket>/emr/bootstrap/
aws s3 cp flink-conf.tar.gz s3://<your-bucket>/emr/bootstrap/
这两个文件就是以后反复用的产物,只要 EMR 版本不变就不用重新打包。
第三步:编写 Bootstrap Action 脚本
这是核心部分。脚本会检测节点类型,只在 Core/Task 节点上执行安装:
bash
#!/bin/bash
# install_flink_client.sh
set -e
S3_BUCKET="<your-bucket>"
FLINK_TARBALL="flink-client-1.20.0-amzn-4.tar.gz"
FLINK_CONF_TARBALL="flink-conf.tar.gz"
S3_PREFIX="s3://${S3_BUCKET}/emr/bootstrap"
IS_MASTER=$(cat /emr/instance-controller/lib/info/instance.json | jq -r '.isMaster')
if [ "$IS_MASTER" = "false" ]; then
echo "Core/Task node detected, installing Flink client..."
# 1. 下载并解压 Flink 二进制
aws s3 cp "${S3_PREFIX}/${FLINK_TARBALL}" /tmp/
sudo tar -xzf "/tmp/${FLINK_TARBALL}" -C /usr/lib/
# 2. 下载并解压配置文件
aws s3 cp "${S3_PREFIX}/${FLINK_CONF_TARBALL}" /tmp/
sudo tar -xzf "/tmp/${FLINK_CONF_TARBALL}" -C /etc/
# 3. 修复 /etc/flink/conf 的 broken symlink
if [ -L "/etc/flink/conf" ] && [ ! -e "/etc/flink/conf" ]; then
echo "Fixing broken symlink /etc/flink/conf..."
sudo rm -f /etc/flink/conf
if [ -d "/etc/flink/conf.dist" ]; then
sudo cp -r /etc/flink/conf.dist /etc/flink/conf
else
sudo mkdir -p /etc/flink/conf
fi
fi
# 4. 修复 /usr/lib/flink/conf 的 broken symlink
if [ -L "/usr/lib/flink/conf" ] && [ ! -e "/usr/lib/flink/conf" ]; then
sudo rm -f /usr/lib/flink/conf
sudo ln -sf /etc/flink/conf /usr/lib/flink/conf
fi
# 5. 创建 wrapper scripts(不用 symlink!)
sudo tee /usr/bin/flink > /dev/null << 'WRAPPER'
#!/bin/bash
exec /usr/lib/flink/bin/flink "$@"
WRAPPER
sudo chmod +x /usr/bin/flink
sudo tee /usr/bin/flink-sql-client > /dev/null << 'WRAPPER'
#!/bin/bash
exec /usr/lib/flink/bin/sql-client.sh "$@"
WRAPPER
sudo chmod +x /usr/bin/flink-sql-client
sudo tee /usr/bin/yarn-session.sh > /dev/null << 'WRAPPER'
#!/bin/bash
exec /usr/lib/flink/bin/yarn-session.sh "$@"
WRAPPER
sudo chmod +x /usr/bin/yarn-session.sh
# 6. 环境变量
cat << 'ENVEOF' | sudo tee /etc/profile.d/flink.sh
export FLINK_HOME=/usr/lib/flink
export PATH=$FLINK_HOME/bin:$PATH
ENVEOF
# 7. 清理
rm -f "/tmp/${FLINK_TARBALL}" "/tmp/${FLINK_CONF_TARBALL}"
echo "Flink client installation completed."
else
echo "Master node detected, skipping."
fi
上传脚本:
bash
aws s3 cp install_flink_client.sh s3://<your-bucket>/emr/bootstrap/
为什么用 wrapper scripts 而不是 symlink
这是个踩过的坑。Flink 的 bin 目录下的脚本内部会用 . "$bin"/config.sh 这样的相对路径引用。如果用 symlink(ln -s /usr/lib/flink/bin/flink /usr/bin/flink),$bin 会解析为 /usr/bin/ 而不是 /usr/lib/flink/bin/,导致 config.sh 找不到。
用 wrapper scripts 的 exec 命令则不会有这个问题------它直接调用原始路径的脚本,$bin 自然解析为 /usr/lib/flink/bin/。
为什么要修复 broken symlink
EMR 在 Master 节点上会创建 /etc/flink/conf → /etc/alternatives/flink-conf 这个 symlink,但 Core 节点上 /etc/alternatives/flink-conf 不存在。打包配置文件后解压到 Core,这个 symlink 就是 broken 的。
脚本的处理逻辑:如果 /etc/flink/conf.dist 存在就复制一份,否则创建空目录。
第四步:创建正式集群
bash
aws emr create-cluster \
--release-label emr-7.10.0 \
--applications Name=Flink Name=Hadoop \
--bootstrap-actions Path=s3://<your-bucket>/emr/bootstrap/install_flink_client.sh,Name="Install Flink Client on Core Nodes" \
--instance-type m5.xlarge \
--instance-count 3 \
--service-role EMR_DefaultRole \
--ec2-attributes InstanceProfile=EMR_EC2_DefaultRole \
--name "Flink-Production-Cluster"
第五步:验证
Core 节点没有公网 IP,需要通过 Master 跳转。SSH 到 Master 后跳到任意 Core 节点:
bash
flink --version
flink-sql-client --help
yarn-session.sh --help
排查问题看 Bootstrap Action 日志:
bash
sudo cat /var/log/bootstrap-actions/1/stdout
sudo cat /var/log/bootstrap-actions/1/stderr
方案总结
| 维度 | 说明 |
|---|---|
| 适用版本 | EMR 7.10.0 + Flink 1.20.0-amzn-4 |
| 复用性 | 同版本 EMR 可直接复用 S3 上的打包文件 |
| 执行时机 | 集群启动时自动执行,无需手动干预 |
| 影响范围 | 仅 Core/Task 节点,Master 节点跳过 |
| 维护成本 | EMR 升级时需重新打包 |
整个方案的核心思路:把 Master 节点上已有的东西打包搬运到 Core 节点,而不是从头装一遍。简单直接,没有魔法。
生产环境建议:上线前通过 Bootstrap Action 日志和节点级命令验证确认安装结果,避免调度系统在生产中因环境缺失而静默失败。
本文参考亚马逊云科技官方博客:在 AWS EMR Core 节点部署 Flink Client 的实战指南