使用 AWS CDK 部署可 SSH 访问的 EMR 集群:公有子网跳板机与私有子网集群实战指南

原创2026-10-14 18:12:001,356 阅读

使用 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):

ssh-into-emr-cluster 示例架构图:公有子网中的 EC2 跳板机通过 SSH 访问私有子网中的 EMR 主节点

简单来说,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(密钥对):

EC2 控制台左侧 Key Pairs 导航位置示意

然后创建名为 emr_test 的 RSA 密钥对,文件格式选择 .pem,并将生成的 pem 文件安全保存在本地电脑的固定位置,以备后续使用:

创建 RSA 密钥对 emr_test 并选择 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 控制台找到,参考下图:

在 EC2 控制台查找跳板机公网 IPv4 DNS 的位置示意

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 集群详情页中找到该值的位置可参考下图:

在 EMR 控制台查找主节点(master)公网 DNS 的位置示意

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 结果文件:

S3 桶 output 路径下的 Spark 作业 parquet 输出结果

恭喜!你已经成功 SSH 登录 EMR 集群,并从集群内部运行了一个 Spark 作业!

资源清理

测试完毕后,按以下顺序清理资源,避免持续产生费用:

  1. 在控制台终止 EMR 集群,并等待其完全终止后再继续;
  2. 在控制台清空 CDK 创建的 S3 桶;
  3. 在本地命令行(而非 EC2 shell 内)执行以下命令,删除除 VPC 及其关联资源以外的其余资源:
cdk destroy --profile <AWS_PROFILE_NAME>
  1. 若 VPC 尚未被删除,请在 VPC 控制台手动删除名为 emr_cdk_vpc 的 VPC。由于该 VPC 在 cdk destroy 时可能存在依赖问题,CDK 代码特意将其设为保留(RETAIN),需要事后手动删除;
  2. 最后,如果 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 示例 基础上开始自己的开发集群实践。

登录后查看全文
aws-cdk-examples