AWS EMR - IntelliJ 远程调试 Spark 应用程序
AWS EMR - IntelliJ Remote Debugging Spark Application
我想在 AWS EMR 集群上调试 运行 的 Spark 应用程序。如果我可以使用 IntelliJ 远程连接和调试它,那就太棒了。我搜索过,但发现很少。
是否可行,如果可行,有人可以为我指明正确的方向吗?
谢谢。
首先,我要提醒您,由于 AWS EMR 的大量错误和意外用例,您尝试做的事情基本上是不可能的。我强烈建议您购买最大的单个实例以 运行 您的工作(他们在负担得起的一端有 c4.8xlarge
,真正的疯子有 x1.32xlarge
!),并且只需安装 spark
在那个实例中 运行 完成你的工作。
先决条件
- 您的 VPC 必须正确配置以允许与外界的任何连接。这意味着您的 Internet 网关工作正常。您可以通过使用 EC2 密钥对启动集群、修改主服务器的安全组以允许从您的机器进行 SSH 连接(默认情况下它们自然不会这样做)并尝试从您的机器连接到主服务器来测试这一点。如果你做不到这一点,你将无法调试。在没有额外配置的情况下,我什至无法在新集群上满足此先决条件!
- 运行用于调试的 IntelliJ 机器必须可以从 Internet 访问。要对此进行测试,请修改主实例的安全组以允许在端口 5005 上出站连接到您的机器。然后,运行
nc -l 5005
在您的机器上。通过 SSH 连接到您的 master 并尝试 echo "test" | nc your_ip_address 5005
。在您的计算机终端上看到 test
之前,请不要继续。
IntelliJ 设置
创建一个新的远程配置。将调试器模式更改为侦听。命名配置并保存。当您点击调试时,它将等待连接。在那个 window 中,您会看到 "Command line arguments for running remote JVM",阅读内容如下:
-agentlib:jdwp=transport=dt_socket,server=n,address=localhost:5005,suspend=y
您可以像我一样删除 onthrow
和 oncaught
行。假设您的调试机器可以通过互联网访问 24.13.242.141
。假装它实际上是这样读的:
-agentlib:jdwp=transport=dt_socket,server=n,address=24.13.242.141:5005,suspend=y
我们将使用它来设置 Spark 进程的调试。
Spark 设置
有两个进程可以被调试:驱动进程(执行实例化你的SparkContext
的代码)和执行进程。最终,您会将这些 JVM 选项传递给 spark-submit
的特殊参数,以建立连接。要调试驱动程序,请使用
spark-submit --driver-java-options -agentlib:jdwp=transport=dt_socket,server=n,address=24.13.242.141:5005,suspend=y --class ...
要调试执行程序进程,您可以使用配置选项:
spark-submit --conf "spark.executor.extraJavaOptions=-agentlib:jdwp=transport=dt_socket,server=n,address=24.13.242.141:5005,suspend=y" --class ...
调试执行器非常棘手,因为会有多个进程。在 IntelliJ 中,您无法真正按照想象的方式调试多个进程。此外,您不能真正将 AWS EMR 中的执行程序数量限制为 1,即使他们声称可以。我相信如果其他执行者失败也没关系(他们会在无法连接到您的调试会话时失败)。但这一步未经测试。
综合起来
您可以使用 SDK 和 Web 控制台修改 spark-submit
的参数。请注意,在 SDK 中,您不应尝试自己连接 "args"——按照要求将它们作为数组项传递。
为了调试驱动程序,您需要从集群的初始阶段修改主节点的安全组(同样使用从节点的安全组来调试执行程序)。创建一个安全组,允许出站连接到您的 IP 地址和调试器端口(即 TCP 出站到 24.13.242.141:5005)。您应该使用该条目创建一个安全组,并使用 AWS SDK (.withAdditionalMasterSecurityGroups(...)
) 将其添加到 master/slave 作业流实例配置的安全组中。我不确定如何从 Web 控制台执行此操作。
一些常见问题
- 确保使用 Gradle 通过
classpath "com.github.jengelman.gradle.plugins:shadow:1.2.4"
插件生成影子 Jar。此外,启用 Zip64
。您将 :shadowJar
任务的结果上传到 S3 以在 AWS EMR 上实际执行。
buildscript {
repositories {
mavenCentral()
maven {
url "https://plugins.gradle.org/m2/"
}
}
dependencies {
classpath "com.github.jengelman.gradle.plugins:shadow:1.2.4"
}
}
apply plugin: "com.github.johnrengelman.shadow"
shadowJar {
zip64 true
}
- 确保使用
--deploy-mode cluster
和 --master yarn
(基本上未记录)启动您的 Spark 应用程序。
- 为了从 EMR 上的驱动程序或执行程序内部访问 S3,不要 执行修改
sc.hadoopConfiguration()
的繁琐操作(例如,configuration.set("fs.s3n.impl", "org.apache.hadoop.fs.s3native.NativeS3FileSystem");
) .根本不要配置这些属性! hadoop-aws
默认情况下在 EMR 环境中正常工作,并自动设置适当的属性。
- 将您的
log4j
日志记录选项设置为仅报告 WARN
及更高版本。在此 SDK 中,您可以使用:
.withConfigurations(new Configuration()
.withClassification("spark-log4j")
.addPropertiesEntry("log4j.rootCategory", "WARN, console"))
- 在调试之前检查您的
containers/applications_.../container.../stderr.gz
日志是否有错误!
- 如果您在容器日志中看到此错误 "WARN YarnClusterScheduler: Initial job has not accepted any resources; check your cluster UI to ensure that workers are registered and have sufficient resources",请确保为
spark
class 添加 maximizeResourceAllocation
配置 属性化.
new Configuration()
.withClassification("spark")
.addPropertiesEntry("maximizeResourceAllocation", "true"))
- 不要忘记在驱动程序结束时关闭上下文 (
sc.close()
)。否则,Yarn 永远不会启动。可笑的是没有记录。
- 影子 JAR 中的资源只能由与资源相同的 "JAR" 中的 class 加载。换句话说,不要使用
ClassLoader.getSystemClassLoader()
。如果 class A
通常在 a.jar
中想要访问 b.jar
中的资源,而 class B
是 b.jar
中的 class,请使用 B.class.getClassLoader().getResource...
.此外,使用相对路径(省略资源引用开头的正斜杠)。我建议捕获 NullPointerException
s 并尝试两者,这样无论 JAR 是如何打包的,它都可以正常工作。
- 如果您使用实现
Function
接口和类似接口的 classes,请确保创建一个无参数构造函数来执行您可能依赖的所有初始化。 Spark 对闭包和函数实例都使用 Kryo 序列化(而不是 Java 序列化),如果您忽略为应用程序特定的初始化代码(例如,从资源加载)提供无参数构造函数,您就赢了不要执行您期望的所有初始化。
我想在 AWS EMR 集群上调试 运行 的 Spark 应用程序。如果我可以使用 IntelliJ 远程连接和调试它,那就太棒了。我搜索过,但发现很少。
是否可行,如果可行,有人可以为我指明正确的方向吗?
谢谢。
首先,我要提醒您,由于 AWS EMR 的大量错误和意外用例,您尝试做的事情基本上是不可能的。我强烈建议您购买最大的单个实例以 运行 您的工作(他们在负担得起的一端有 c4.8xlarge
,真正的疯子有 x1.32xlarge
!),并且只需安装 spark
在那个实例中 运行 完成你的工作。
先决条件
- 您的 VPC 必须正确配置以允许与外界的任何连接。这意味着您的 Internet 网关工作正常。您可以通过使用 EC2 密钥对启动集群、修改主服务器的安全组以允许从您的机器进行 SSH 连接(默认情况下它们自然不会这样做)并尝试从您的机器连接到主服务器来测试这一点。如果你做不到这一点,你将无法调试。在没有额外配置的情况下,我什至无法在新集群上满足此先决条件!
- 运行用于调试的 IntelliJ 机器必须可以从 Internet 访问。要对此进行测试,请修改主实例的安全组以允许在端口 5005 上出站连接到您的机器。然后,运行
nc -l 5005
在您的机器上。通过 SSH 连接到您的 master 并尝试echo "test" | nc your_ip_address 5005
。在您的计算机终端上看到test
之前,请不要继续。
IntelliJ 设置
创建一个新的远程配置。将调试器模式更改为侦听。命名配置并保存。当您点击调试时,它将等待连接。在那个 window 中,您会看到 "Command line arguments for running remote JVM",阅读内容如下:
-agentlib:jdwp=transport=dt_socket,server=n,address=localhost:5005,suspend=y
您可以像我一样删除 onthrow
和 oncaught
行。假设您的调试机器可以通过互联网访问 24.13.242.141
。假装它实际上是这样读的:
-agentlib:jdwp=transport=dt_socket,server=n,address=24.13.242.141:5005,suspend=y
我们将使用它来设置 Spark 进程的调试。
Spark 设置
有两个进程可以被调试:驱动进程(执行实例化你的SparkContext
的代码)和执行进程。最终,您会将这些 JVM 选项传递给 spark-submit
的特殊参数,以建立连接。要调试驱动程序,请使用
spark-submit --driver-java-options -agentlib:jdwp=transport=dt_socket,server=n,address=24.13.242.141:5005,suspend=y --class ...
要调试执行程序进程,您可以使用配置选项:
spark-submit --conf "spark.executor.extraJavaOptions=-agentlib:jdwp=transport=dt_socket,server=n,address=24.13.242.141:5005,suspend=y" --class ...
调试执行器非常棘手,因为会有多个进程。在 IntelliJ 中,您无法真正按照想象的方式调试多个进程。此外,您不能真正将 AWS EMR 中的执行程序数量限制为 1,即使他们声称可以。我相信如果其他执行者失败也没关系(他们会在无法连接到您的调试会话时失败)。但这一步未经测试。
综合起来
您可以使用 SDK 和 Web 控制台修改 spark-submit
的参数。请注意,在 SDK 中,您不应尝试自己连接 "args"——按照要求将它们作为数组项传递。
为了调试驱动程序,您需要从集群的初始阶段修改主节点的安全组(同样使用从节点的安全组来调试执行程序)。创建一个安全组,允许出站连接到您的 IP 地址和调试器端口(即 TCP 出站到 24.13.242.141:5005)。您应该使用该条目创建一个安全组,并使用 AWS SDK (.withAdditionalMasterSecurityGroups(...)
) 将其添加到 master/slave 作业流实例配置的安全组中。我不确定如何从 Web 控制台执行此操作。
一些常见问题
- 确保使用 Gradle 通过
classpath "com.github.jengelman.gradle.plugins:shadow:1.2.4"
插件生成影子 Jar。此外,启用Zip64
。您将:shadowJar
任务的结果上传到 S3 以在 AWS EMR 上实际执行。
buildscript {
repositories {
mavenCentral()
maven {
url "https://plugins.gradle.org/m2/"
}
}
dependencies {
classpath "com.github.jengelman.gradle.plugins:shadow:1.2.4"
}
}
apply plugin: "com.github.johnrengelman.shadow"
shadowJar {
zip64 true
}
- 确保使用
--deploy-mode cluster
和--master yarn
(基本上未记录)启动您的 Spark 应用程序。 - 为了从 EMR 上的驱动程序或执行程序内部访问 S3,不要 执行修改
sc.hadoopConfiguration()
的繁琐操作(例如,configuration.set("fs.s3n.impl", "org.apache.hadoop.fs.s3native.NativeS3FileSystem");
) .根本不要配置这些属性!hadoop-aws
默认情况下在 EMR 环境中正常工作,并自动设置适当的属性。 - 将您的
log4j
日志记录选项设置为仅报告WARN
及更高版本。在此 SDK 中,您可以使用:
.withConfigurations(new Configuration()
.withClassification("spark-log4j")
.addPropertiesEntry("log4j.rootCategory", "WARN, console"))
- 在调试之前检查您的
containers/applications_.../container.../stderr.gz
日志是否有错误! - 如果您在容器日志中看到此错误 "WARN YarnClusterScheduler: Initial job has not accepted any resources; check your cluster UI to ensure that workers are registered and have sufficient resources",请确保为
spark
class 添加maximizeResourceAllocation
配置 属性化.
new Configuration()
.withClassification("spark")
.addPropertiesEntry("maximizeResourceAllocation", "true"))
- 不要忘记在驱动程序结束时关闭上下文 (
sc.close()
)。否则,Yarn 永远不会启动。可笑的是没有记录。 - 影子 JAR 中的资源只能由与资源相同的 "JAR" 中的 class 加载。换句话说,不要使用
ClassLoader.getSystemClassLoader()
。如果class A
通常在a.jar
中想要访问b.jar
中的资源,而class B
是b.jar
中的 class,请使用B.class.getClassLoader().getResource...
.此外,使用相对路径(省略资源引用开头的正斜杠)。我建议捕获NullPointerException
s 并尝试两者,这样无论 JAR 是如何打包的,它都可以正常工作。 - 如果您使用实现
Function
接口和类似接口的 classes,请确保创建一个无参数构造函数来执行您可能依赖的所有初始化。 Spark 对闭包和函数实例都使用 Kryo 序列化(而不是 Java 序列化),如果您忽略为应用程序特定的初始化代码(例如,从资源加载)提供无参数构造函数,您就赢了不要执行您期望的所有初始化。