使用 AWS CDK 部署可 SSH 访问的 EMR 集群:公有子网跳板机与私有子网集群实战指南
使用 AWS CDK 部署可 SSH 访问的 EMR 集群:公有子网跳板机与私有子网集群实战指南
本指南基于 aws-cdk-examples 仓库中的 ssh-into-emr-cluster 示例,完整讲解如何用 AWS CDK 一键部署一套"可安全 SSH 登录"的开发用 EMR 集群:在公有子网放置一台 EC2 跳板机、在私有子网创建 EMR 集群,并借助 CDK 编排的 VPC、安全组与密钥对,实现「本机 → EC2 跳板机 → EMR 主节点」的二级跳转登录。读完本文,你将掌握该模式的完整部署流程、app.py 与堆栈源码的核心配置含义,并能用 spark-submit 快速验证集群可用性。
架构总览:一台跳板机、一个集群、一个 S3 桶
该示例的整体架构如下(图片来源:emr_cdk.png):
简单来说,CDK 在公有子网中部署一台 EC2 实例,在私有子网中部署一个 EMR 集群。借助 CDK 代码中编排好的网络与安全组规则,你可以先从自己的电脑安全地 SSH 登录 EC2 实例,再从该实例 SSH 登录位于私有子网内的 EMR 主节点(master node)。这套模式的设计目标,是提供一种易于创建且相对安全的 EMR 开发集群,同时代码本身也可以进一步扩展,用于生产级场景。
堆栈中的所有资源都由 emr_pattern_stack.py 中的 EmrPatternStack 定义,核心组件如下:
| 资源 | 定义位置 | 作用 |
|---|---|---|
VPC(emr_cdk_vpc) |
L31-L44 | 提供公有/私有子网与 NAT 网关,是整套网络的基础 |
| EC2 安全组 | L48-L61 | 控制跳板机入站 SSH(22 端口)来源 |
| EMR 主节点安全组 | L64-L75 | 仅允许来自跳板机安全组的 SSH 流量 |
| EC2 跳板机(t3.nano) | L84-L93 | 位于公有子网,充当进入私有网络的"门户" |
S3 桶(emr-cdk-pattern-<账号ID>) |
L96-L109 | 存放 PySpark 脚本、输入数据与作业日志、输出 |
EMR 集群(1 Master + 2 Core,m6g.xlarge) |
L168-L191 | 位于私有子网,运行 Spark,空闲 1 小时后自动终止 |
前置条件
开始之前,你需要:
- 拥有 AWS 控制台访问权限;
- 在同一账号下完成 AWS CLI 的配置(
aws configure或环境变量方式),使 CLI 能够以你的身份调用 AWS API。
本地环境准备:虚拟环境与依赖安装
在 MacOS 与 Linux 上,用以下命令手动创建 Python 虚拟环境:
$ python3 -m venv .venv
初始化完成后,激活虚拟环境(Linux/macOS):
$ source .venv/bin/activate
Windows 平台的激活命令则为:
% .venv\Scripts\activate.bat
激活后安装依赖:
$ pip install -r requirements.txt
示例的 requirements.txt 只声明了两个核心依赖,版本区间如下:
aws-cdk-lib>=2.262.0,<3.0.0:AWS CDK 官方库,提供 EC2、EMR、IAM、S3 等全部构造;constructs>=10.5.0,<11.0.0:构造(Construct)基础库,CDK 应用的依赖项。
依赖安装完成后,可先执行 cdk synth 生成 CloudFormation 模板进行校验(可选),再进入下一步。
创建 RSA 密钥对并下载 PEM 文件
在 EC2 控制台左侧边栏的 Network & Security(网络与安全) 下进入 Key Pairs(密钥对):
然后创建名为 emr_test 的 RSA 密钥对,文件格式选择 .pem,并将生成的 pem 文件安全保存在本地电脑的固定位置,以备后续使用:
注意记录密钥的保存路径! 这份密钥将被使用两次:先用于 SSH 登录 EC2 跳板机,随后还会被拷贝到跳板机上,用于继续 SSH 登录 EMR 集群。
编辑 app.py:填写账号 ID、区域与可选 IP 白名单
示例的入口文件是 app.py,它创建了 EmrPatternStack 并调用 app.synth() 完成模板合成。在 app.py 中填入你的 AWS 账号 ID 与目标部署区域(保存修改):
EmrPatternStack(
app,
"EmrPatternStack",
env=cdk.Environment(
account="<YOUR-AWS-ACCOUNT-ID>",
region="<DESIRED-AWS-REGION-FOR-DEPLOYMENT>"
),
ssh_origin_ip=None # 保持 None,或传入字符串形式的 IP:如 "12.34.56.78"
)
可选:为跳板机设置 SSH 来源 IP 白名单
app.py 中提供了一个可选参数 ssh_origin_ip,用于向堆栈传入你的公网 IP;不指定则保持 None:
- 传入 IP 时:创建的 EC2 实例只允许来自该 IP 的 SSH 连接。你可以通过搜索引擎搜索 "my ip" 来获取当前出口公网 IP。
- 不传(
None)时:EC2 实例接受来自任意 IP 的 SSH 连接(当然,仍必须持有正确的密钥对才能登录)。
VPN 提示:如果你正在使用 VPN,要么在获取 IP 前先关闭 VPN,要么干脆保持
None。因为 VPN 可能改变你的出口 IP,导致无法登录那些只允许单一 IP 的实例。
例如,若你的公网 IP 是 12.34.56.78,则应填写:
EmrPatternStack(
app,
"EmrPatternStack",
env=cdk.Environment(
account='222222222222',
region='us-east-1'
),
ssh_origin_ip="12.34.56.78"
)
这一参数在源码中的实际作用,体现在安全组入站规则的编写逻辑上(emr_pattern_stack.py L55-L61):
# 若在 app.py 中提供了你的 IP,则收紧 EC2 安全组:仅允许该 IP 的 SSH
if ssh_origin_ip:
ec2_security_group.add_ingress_rule(ec2.Peer.ipv4(f"{ssh_origin_ip}/32"), ec2.Port.tcp(22), 'allow SSH from only one IP')
# 未指定 IP 时,允许来自任意 IPv4 的 SSH(仍相对安全,因为需要密钥)
else:
ec2_security_group.add_ingress_rule(ec2.Peer.any_ipv4(), ec2.Port.tcp(22), 'allow SSH from anywhere')
Bootstrap CDK(若你的账号/区域尚未初始化)
如果你此前从未在 app.py 指定的账号/区域中使用过 CDK,需要先执行下面的命令完成环境引导(bootstrap)。请务必传入你配置好的 AWS CLI profile 名称:
cdk bootstrap --profile <AWS_PROFILE_NAME>
这是一次性的引导操作,大约需要几分钟。请等待 CloudFormation 中的引导堆栈完成后再继续。
部署 CDK 堆栈
执行部署命令(同样记得传入 AWS CLI profile 名称):
cdk deploy --profile <AWS_PROFILE_NAME>
整套资源从零拉起大约需要 20 分钟,其中最后一个完成创建的资源是 EMR 集群本身——等待期间可以喝杯咖啡。你可以通过 CloudFormation 控制台或命令行监控堆栈状态。
堆栈部署成功后,进入下一节。需要特别留意的是:
EMR 集群在空闲 1 小时后会自动终止(由
auto_termination_policy控制,见下文源码解析),请务必在集群存活的时间窗口内完成后续 SSH 与测试步骤。
SSH 三步曲:从本机登录 EC2 跳板机
第 1 步:本地修改密钥文件权限(Windows 可跳过)
将下面命令中的路径替换为你本地保存 emr_test.pem 的路径,例如 chmod 400 /Users/user/Documents/keypairs/emr_test.pem:
chmod 400 <LOCAL_PATH_TO_PEM_FILE>/emr_test.pem
第 2 步:把密钥拷贝到 EC2 实例
这一步的目的是让密钥对在跳板机上可用,以便后续从跳板机 SSH 登录 EMR 主节点。命令中需要替换两处内容:
<LOCAL_PATH_TO_PEM_FILE>:本地密钥所在路径;<REPLACE_WITH_EC2_PUBLIC_IPV4_DNS>:跳板机的公网 IPv4 DNS。该实例在堆栈中的逻辑名为EmrPatternStack/ec2_instance。
注意: 切勿使用 EMR 集群中 core/main 节点(核心/主节点)对应 EC2 实例的 IPv4 地址!跳板机的公网 IPv4 DNS 可以在 EC2 控制台找到,参考下图:
scp -i <LOCAL_PATH_TO_PEM_FILE>/emr_test.pem <LOCAL_PATH_TO_PEM_FILE>/emr_test.pem ec2-user@<REPLACE_WITH_EC2_PUBLIC_IPV4_DNS>:/home/ec2-user/emr_test.pem
第 3 步:SSH 登录公有子网中的 EC2 实例
ssh -i <LOCAL_PATH_TO_PEM_FILE>/emr_test.pem ec2-user@<REPLACE_WITH_EC2_PUBLIC_IPV4_DNS>
至此,你应该已经"进入"了 EC2 实例,可以继续 SSH 登录位于私有子网中的 EMR 集群了!
从 EC2 跳板机 SSH 登录 EMR 主节点并验证集群
第 1 步:在 EC2 实例上修改密钥权限
chmod 400 emr_test.pem
第 2 步:SSH 登录 EMR 集群
从 EMR 控制台获取 master/main 实例的 DNS,替换下面命令中的 <REPLACE_WITH_EMR_MASTER_PUBLIC_DNS>。在 EMR 集群详情页中找到该值的位置可参考下图:
ssh -i ~/emr_test.pem hadoop@<REPLACE_WITH_EMR_MASTER_PUBLIC_DNS>
EMR 集群默认的 SSH 登录用户是
hadoop,而亚马逊 Linux 跳板机默认用户是ec2-user,两个用户名不要混淆。
登录成功后,你就进入了 EMR 主节点内部,可以开始用 PySpark 脚本测试集群了。
用 spark-submit 验证集群(可选)
下面这条 spark-submit 命令会运行 CDK 已上传到 S3 桶中的 PySpark 脚本,命令中需要在两个位置替换为 CDK 创建的 S3 桶名(桶名形如 emr-cdk-pattern-AWS_ACCOUNT_ID-RANDOM_STRING,实际由源码中的 f"emr-cdk-pattern-{Aws.ACCOUNT_ID}" 生成):
spark-submit --master yarn --deploy-mode cluster s3://<REPLACE_WITH_CDK_CREATED_BUCKET_NAME>/pyspark_test/glue_script.py <REPLACE_WITH_CDK_CREATED_BUCKET_NAME>
这条命令会执行仓库中 glue_script.py 所对应的脚本(该脚本位于 emr_pattern/src 目录,已被 CDK 通过 BucketDeployment 上传到 S3 桶的 pyspark_test/ 前缀下)。脚本逻辑非常简洁:
from pyspark.sql import SparkSession
import sys
spark = SparkSession.builder.appName('emr_cdk_pattern_app').getOrCreate()
bucket_name = sys.argv[1]
df = spark.read.csv(f's3://{bucket_name}/pyspark_test/test_data.csv')
df.write.parquet(f's3://{bucket_name}/output/')
它从 S3 桶加载 CSV 文件(test_data.csv,葡萄酒质量评测数据集),再以 parquet 格式写回同一个桶的 output/ 路径。作业只需几分钟即可运行完成。成功后,在 S3 桶的 output/ 路径下可以看到类似下图的 parquet 结果文件:
恭喜!你已经成功 SSH 登录 EMR 集群,并从集群内部运行了一个 Spark 作业!
资源清理
测试完毕后,按以下顺序清理资源,避免持续产生费用:
- 在控制台终止 EMR 集群,并等待其完全终止后再继续;
- 在控制台清空 CDK 创建的 S3 桶;
- 在本地命令行(而非 EC2 shell 内)执行以下命令,删除除 VPC 及其关联资源以外的其余资源:
cdk destroy --profile <AWS_PROFILE_NAME>
- 若 VPC 尚未被删除,请在 VPC 控制台手动删除名为
emr_cdk_vpc的 VPC。由于该 VPC 在cdk destroy时可能存在依赖问题,CDK 代码特意将其设为保留(RETAIN),需要事后手动删除; - 最后,如果
cdk destroy未能删除 CDK 创建的 S3 桶,请手动将其删除。
从源码看关键设计:安全、成本与可靠性
最后,结合 emr_pattern_stack.py 源码,提炼这套示例中值得借鉴的设计要点:
1. 安全组链式授权,最小化暴露面
EMR 主节点安全组(L71-L75)的 SSH 入站规则不是开放给任意 IP,而是仅允许来自跳板机安全组的流量:
emr_main_node_security_group.add_ingress_rule(
ec2.Peer.security_group_id(ec2_security_group.security_group_id),
ec2.Port.tcp(22),
'allow traffic to emr main node from EC2 SG to allow for SSH'
)
这保证了即使 EMR 集群处于私有子网,也只有通过跳板机这一条受控通道可以 SSH 进入,攻击面被显著收窄。
2. VPC 保留策略与手动清理
VPC 上显式调用了 vpc.apply_removal_policy(RemovalPolicy.RETAIN)(L46),注释也写明了原因:防止 cdk destroy 时 VPC 因依赖关系报错。这也是 README 清理步骤第 4 步要求手动删除 emr_cdk_vpc 的原因。
3. 成本控制的三重手段
- 跳板机选用 t3.nano 这一最便宜的通用型实例(L86);
- EMR 集群的 master 与 core 节点均为 m6g.xlarge 按需实例,且通过
auto_termination_policy={"IdleTimeout": 60*60}(L188)设置空闲 1 小时自动终止,避免开发集群长期空转产生费用; - VPC 配置了 S3 Gateway Endpoint(L35-L39),集群访问 S3 不走 NAT 网关,既节省流量费用又提升访问稳定性。
4. 数据与传输加密
S3 桶开启 S3_MANAGED 托管加密(L100),EMR 侧通过 CfnSecurityConfiguration 启用了静态数据加密(SSE-S3)(L114-L123),并设置了独立的 service_role 与 job_flow_role 两个 IAM 角色(L135-L165)承担集群服务与计算节点的权限职责。
5. 易于扩展的应用列表
EMR 集群通过 applications 参数声明应用(L187),示例仅启用了 Spark。源码注释明确指出可以在此列表中继续追加 HBase、Hive 等应用,或按需修改实例规格、Core 节点数量,以适配更大规模或更多引擎的集群需求。
至此,你已经掌握了「CDK 部署 + 密钥对 + 二级跳板 SSH + Spark 作业验证 + 清理」的完整闭环,可以直接在 aws-cdk-examples 仓库的 ssh-into-emr-cluster 示例 基础上开始自己的开发集群实践。





