Skip to content

[SPARK-27900][K8s] Add jvm oom flag #25229

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed

Conversation

skonto
Copy link
Contributor

@skonto skonto commented Jul 22, 2019

What changes were proposed in this pull request?

  • Adds a flag to make the driver exit in case of an oom error in the entrypoint script.
  • Adds integration tests for supporting de-activation of the flag if the user wishes.
  • adds verbose flag support within the driver's container.
    Note: This follows the discussion here: [SPARK-27900][CORE] Add uncaught exception handler to the driver #24796. Without this pods on K8s will keep running although Spark has failed. In addition current behavior creates a problem to the Spark Operator and any other operator as it cannot detect failure at the K8s level.

How was this patch tested?

Manually by launching SparkPi with a large number 100000000 which leads to an oom due to the large number of tasks allocated.

 $kubectl logs spark-pi-a59f4d6c44b525a9-driver -n spark

19/07/30 21:07:08 INFO BlockManagerMaster: Registered BlockManager BlockManagerId(driver, spark-pi-a59f4d6c44b525a9-driver-svc.spark.svc, 7079, None)
19/07/30 21:07:08 INFO BlockManager: Initialized BlockManager: BlockManagerId(driver, spark-pi-a59f4d6c44b525a9-driver-svc.spark.svc, 7079, None)
19/07/30 21:07:14 INFO KubernetesClusterSchedulerBackend$KubernetesDriverEndpoint: Registered executor NettyRpcEndpointRef(spark-client://Executor) (172.17.0.5:55032) with ID 1
19/07/30 21:07:14 INFO BlockManagerMasterEndpoint: Registering block manager 172.17.0.5:43067 with 413.9 MiB RAM, BlockManagerId(1, 172.17.0.5, 43067, None)
19/07/30 21:07:15 INFO KubernetesClusterSchedulerBackend$KubernetesDriverEndpoint: Registered executor NettyRpcEndpointRef(spark-client://Executor) (172.17.0.6:37344) with ID 2
19/07/30 21:07:15 INFO KubernetesClusterSchedulerBackend: SchedulerBackend is ready for scheduling beginning after reached minRegisteredResourcesRatio: 0.8
19/07/30 21:07:16 INFO BlockManagerMasterEndpoint: Registering block manager 172.17.0.6:46213 with 413.9 MiB RAM, BlockManagerId(2, 172.17.0.6, 46213, None)
#
# java.lang.OutOfMemoryError: Java heap space
# -XX:OnOutOfMemoryError="kill -9 %p"
#   Executing /bin/sh -c "kill -9 19"...
$ kubectl get pods spark-pi-8387506c19ad344d-driver  -n spark -o yaml
....
    state:
      terminated:
        containerID: docker://b3410c57d0e7424ec0af6cac4108669222210d80184d690ebeeb9494cf25e6f2
        exitCode: 137
        finishedAt: "2019-07-30T21:08:29Z"
        reason: Error
        startedAt: "2019-07-30T21:07:04Z"

@skonto skonto changed the title [SPARK-27900][K8s] Add oom jvm option [SPARK-27900][K8s] Add jvm oom option Jul 22, 2019
@skonto skonto changed the title [SPARK-27900][K8s] Add jvm oom option [SPARK-27900][K8s] Add jvm oom flag Jul 22, 2019
@@ -65,6 +65,7 @@ case "$1" in
shift 1
CMD=(
"$SPARK_HOME/bin/spark-submit"
--driver-java-options '-XX:OnOutOfMemoryError="kill -9 %p"'
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are in client mode so best to use this arg. Also this is going to be parsed by the SparkSubmitCommandBuilder later on.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense - the parent process (think it's bin/spark-class) will eventually route this as a JVM argument to the child driver process.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we be adding this for executors also?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mccheah afaik executors have a handler for uncaught exceptions and afaik they will handle this and exit:

uncaughtExceptionHandler: UncaughtExceptionHandler = new SparkUncaughtExceptionHandler)
&
Thread.setDefaultUncaughtExceptionHandler(uncaughtExceptionHandler)

@SparkQA
Copy link

SparkQA commented Jul 22, 2019

Test build #108014 has finished for PR 25229 at commit 81ec5c1.

  • This patch passes all tests.
  • This patch merges cleanly.
  • This patch adds no public classes.

Copy link
Member

@dongjoon-hyun dongjoon-hyun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this? It seems that we can use spark.driver.extraJavaOptions already.

@skonto
Copy link
Contributor Author

skonto commented Jul 27, 2019

we need to make sure its added by default and not rely to the user to pass it. If you mean whether to pass this as a spark conf using that property then in client mode this is the way to do it as according to the docs driver has already started. I digged into the code and verified that this is being properly picked up not sure about the extraJavaOptions, docs say not to use it. So this is a working option.

Copy link
Member

@dongjoon-hyun dongjoon-hyun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@skonto . I really appreciate your active contributions in this area. If this is really needed, yes, we should have this.

However, we have an integration test for Run extraJVMOptions check on driver and this PR already breaks it. That's the reason why I think this PR is not safe enough.

So this is a working option.

As we see in the integration test failure, sometimes, by default isn't the best option. I worried about breaking the existing user workloads accidentally. We should not surprise the users.

Could you improve this PR in three directions?

  1. Pass the existing integration test by respecting spark.driver.extraJavaOptions. Please don't modify the existing test case.
  2. Provide a way to override this PR's suggestion. (In other words, users should be able to override OnOutOfMemoryError option. Removing it or adding another behavior)
  3. Add a new test coverage for (2). This will make your commit safe.

cc @mccheah and @vanzin .

@skonto
Copy link
Contributor Author

skonto commented Jul 29, 2019

@dongjoon-hyun Ok I will improve the PR, I had in mind to test spark.driver.extraJavaOptions with this anyway,but missed it. I misinterpreted the configuration docs btw for some reason. I will not modify any tests, tests should pass as is, and will add a new one.

@dongjoon-hyun
Copy link
Member

Thank you so much, @skonto !

@skonto
Copy link
Contributor Author

skonto commented Jul 30, 2019

@dongjoon-hyun I updated the PR.
Using the following submission cmd:

 ./bin/spark-submit --master k8s://https://192.168.2.5:8443\
 --deploy-mode cluster \
 --name spark-pi \
 --class org.apache.spark.examples.SparkPi \
 --conf spark.executor.memory=1G \
 --conf spark.kubernetes.namespace=spark \
 --conf spark.kubernetes.driverEnv.DRIVER_VERBOSE=true \
 --conf spark.kubernetes.authenticate.driver.serviceAccountName=spark-sa \
 --conf spark.driver.memory=500m \
 --conf spark.executor.instances=2  \
 --conf spark.kubernetes.container.image.pullPolicy=Always \
 --conf spark.kubernetes.container.image=skonto/spark:oom \
 local:///opt/spark/examples/jars/spark-examples_2.12-3.0.0-SNAPSHOT.jar 1000000000```

I get the flag added by default (tested with jsp -lvm within the container):

19 org.apache.spark.deploy.SparkSubmit --deploy-mode client --conf spark.driver.bindAddress=172.17.0.4 --properties-file /tmp/spark.properties --class org.apache.spark.examples.SparkPi --verbose local:///opt/spark/examples/jars/spark-examples_2.12-3.0.0-SNAPSHOT.jar 1000000000 -Xmx500m -XX:OnOutOfMemoryError=kill -9 %p

If I add --conf "spark.driver.extraJavaOptions=-Ds=3" \ I get as expected:

19 org.apache.spark.deploy.SparkSubmit --deploy-mode client --conf spark.driver.bindAddress=172.17.0.4 --properties-file /tmp/spark.properties --class org.apache.spark.examples.SparkPi --verbose local:///opt/spark/examples/jars/spark-examples_2.12-3.0.0-SNAPSHOT.jar 1000000000 -Xmx500m -XX:OnOutOfMemoryError=kill -9 %p -Ds=3

Right now this is a bit verbose solution but complete. The other alternatives I see are:
a) dont address the issue in code but just add to the docs how the user could simply pass the OOM flag at this command.
b) add the flag as previously using spark.driver.extraJavaOptions in the entrypoint script but inform the user that this will be overriden if he also passes spark.driver.extraJavaOptions in his command.

@dongjoon-hyun
Copy link
Member

Thank you for updating, @skonto . I've been waiting for that. Could you update the PR description, too?

echo "$@"
else
if grep -q "spark.driver.extraJavaOptions" "/opt/spark/conf/spark.properties"; then
sed 's/spark.driver.extraJavaOptions=/&-XX:OnOutOfMemoryError="kill -9 %p" /g' /opt/spark/conf/spark.properties > /tmp/spark.properties
Copy link
Contributor Author

@skonto skonto Jul 30, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/opt/spark/conf/spark.properties cannot be written in place as its a mounted config map.

case "$1" in
driver)
shift 1
DRIVER_ARGS=$(get_args_with_defaults "$@")
VERBOSE_FLAG=$(get_verbose_flag)
Copy link
Contributor Author

@skonto skonto Jul 30, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use this flag so in the tests we can detect what properties we get. Also it it useful in general users want to debug Spark.

@@ -60,14 +60,44 @@ if ! [ -z ${HADOOP_CONF_DIR+x} ]; then
SPARK_CLASSPATH="$HADOOP_CONF_DIR:$SPARK_CLASSPATH";
fi

IGNORE_DEFAULT_DRIVER_JVM_OPTIONS=${IGNORE_DEFAULT_DRIVER_JVM_OPTIONS:-false}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we have DEFAULT_DRIVER_JVM_OPTIONS instead of this flag?

  1. By default, DEFAULT_DRIVER_JVM_OPTIONS=-XX:OnOutOfMemoryError="kill -9 %p" and it will be appended before spark.driver.extraJavaOptions.
  2. If users unset DEFAULT_DRIVER_JVM_OPTIONS, then only spark.driver.extraJavaOptions works.

At (1), if spark.driver.extraJavaOptions has -XX:OnOutOfMemoryError=, it will supersede DEFAULT_DRIVER_JVM_OPTIONS because the last one is used.

Copy link
Contributor Author

@skonto skonto Jul 30, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dongjoon-hyun How can they unset DEFAULT_DRIVER_JVM_OPTIONS at runtime? Probably missing something here. Do you mean by setting the container env var eg. spark.kubernetes.driverEnv.DEFAULT_DRIVER_JVM_OPTIONS=""?

Copy link
Contributor Author

@skonto skonto Jul 31, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dongjoon-hyun the only option I see is setting DEFAULT_DRIVER_JVM_OPTIONS to empty string to signal the script to use the default, which is similar to setting that flag to true. I will the rename the flag but not sure if we are aligned.

If users unset DEFAULT_DRIVER_JVM_OPTIONS

How should the user do that? Could you elaborate a bit more?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dongjoon-hyun gentle ping :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops. Sorry for being late, @skonto . I'll take a look this PR tomorrow again~

Copy link
Member

@dongjoon-hyun dongjoon-hyun Aug 6, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initially, what I thought was something like the following

DEFAULT_DRIVER_JVM_OPTIONS=${DEFAULT_DRIVER_JVM_OPTIONS:--XX:OnOutOfMemoryError="kill -9 %p"}

And users do export DEFAULT_DRIVER_JVM_OPTIONS=' ' for unset. Ya. My comment was unclear at that time.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@skonto . What do you think about the above? For me, the above looks more direct.

Copy link
Contributor Author

@skonto skonto Aug 6, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dongjoon-hyun Ok so that is what I also concluded :) I can do that, it looks fine to me, the only issue is that this way we open the door to adding arbitrary options there. Initially that was what I didnt want to support, thus I used that the flag to make defaults unchangeable (defaults usually have fixed values).
But we could be flexible here for simplicity reasons, so I will change to what you suggest.

@SparkQA
Copy link

SparkQA commented Jul 30, 2019

@SparkQA
Copy link

SparkQA commented Jul 30, 2019

Test build #108420 has finished for PR 25229 at commit 7cfcefc.

  • This patch passes all tests.
  • This patch merges cleanly.
  • This patch adds no public classes.

cp /opt/spark/conf/spark.properties /tmp/spark.properties
echo 'spark.driver.extraJavaOptions=-XX:OnOutOfMemoryError="kill -9 %p"' >> /tmp/spark.properties
fi
echo "$@" | sed 's|/opt/spark/conf/spark.properties |/tmp/spark.properties |g'
Copy link
Contributor Author

@skonto skonto Jul 30, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We cannot avoid sed if we want to make sure the default is always added. Otherwise if we just add spark.driver.extraJavaOptions to CMD, user provided spark.driver.extraJavaOptions will override it, as Spark conf has precedence rules in case the same property is used more than once (the last value is the one chosen).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you follow https://github.com/apache/spark/pull/25229/files#r310866032, the suggested behavior is the following. So, it seems that we don't need this complexity.

At (1), if spark.driver.extraJavaOptions has -XX:OnOutOfMemoryError=, it will supersede DEFAULT_DRIVER_JVM_OPTIONS because the last one is used.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok so we say that spark.driver.extraJavaOptions overrides whatever the default is.

@SparkQA
Copy link

SparkQA commented Jul 30, 2019

@dongjoon-hyun
Copy link
Member

Retest this please.

@SparkQA
Copy link

SparkQA commented Aug 6, 2019

Test build #108689 has finished for PR 25229 at commit 7cfcefc.

  • This patch passes all tests.
  • This patch merges cleanly.
  • This patch adds no public classes.

@SparkQA
Copy link

SparkQA commented Aug 6, 2019

"(spark.driver.extraJavaOptions,-XX:OnOutOfMemoryError=\"kill -9 %p\" " +
"-Dspark.test.foo=spark.test.bar)")
runSparkPiAndVerifyCompletion(expectedLogOnCompletion = output)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for adding these test cases.

@SparkQA
Copy link

SparkQA commented Aug 6, 2019

@skonto
Copy link
Contributor Author

skonto commented Aug 6, 2019

@dongjoon-hyun a clarification here pls:

By default, DEFAULT_DRIVER_JVM_OPTIONS=-XX:OnOutOfMemoryError="kill -9 %p" and it will be appended before spark.driver.extraJavaOptions.

Should user supplied spark.driver.extraJavaOptions (part of their spark submit) have any effect if DEFAULT_DRIVER_JVM_OPTIONS is not empty? Right now user supplied spark.driver.extraJavaOptions will be ignored because they are part of a properties files and when I pass --conf spark.driver.extraJavaOptions=$DEFAULT_DRIVER_JVM_OPTIONS to CMD=( "$SPARK_HOME/bin/spark-submit" ...), this takes priority due to SparkConf rules. What do you mean by "appended before spark.driver.extraJavaOptions"? Do you refer to the CMD array or the final value of spark.driver.extraJavaOptions, so far I used sed to merge the options but probably you mean to keep the default ones only right?

I have updated the PR locally but waiting for this clarification.

@dongjoon-hyun
Copy link
Member

dongjoon-hyun commented Aug 6, 2019

Hmm. I got it.

That rang me my original question again. If the users supply spark.driver.extraJavaOptions, why do we try to enforce this by Spark-side?

Right now user supplied spark.driver.extraJavaOptions will be ignored because they are part of a properties files

For me, the documentation update seems to be enough. How do you think about that? Did I miss something?

@skonto
Copy link
Contributor Author

skonto commented Aug 6, 2019

@dongjoon-hyun that was one of my options. So this PR was intended to protect the user from the spark issues when things dont go well and the driver does not exit on K8s. Anyway some people asked this as well on the related discussion: #24796. I dont mind adding a note to the docs, what is the best UX?

@dongjoon-hyun
Copy link
Member

dongjoon-hyun commented Aug 7, 2019

@skonto . My name is Dongjoon Hyun with GitHub id @dongjoon-hyun. :)

I knew the history of #24796 and Spark uses YarnSparkHadoopUtil.addOutOfMemoryErrorArgument. I agree with the necessity of this since that PR, and tried to review/merge this PR. However, the current implementation seems a little too complicated and not robust, especially the part of copying /opt/spark/conf/spark.properties to /tmp/spark.properties and replacing it.

In general, I believe the best UX is to keep it simple by reusing the existing general one. OnOutOfMemoryError is a well-known option for JVM users and spark.driver.extraJavaOptions is for that kind of option. As of now, I prefer a new documentation, but other committers may have different opinions. Let me ping them to get their advice.

Hi, @srowen , @squito , @mccheah , @zsxwing , @tgravescs . This is the continuation of #24796 . Could you review this PR's implementation in order to make a progress and finalize the issue?

@squito
Copy link
Contributor

squito commented Aug 7, 2019

Sorry I am not sure I entirely follow the full discussion here. I think that I tend to agree with dongjoon, though -- can't users just set this themselves?

I feel a bit torn here, as I do think we should try to have sensible defaults, and this does seem better. The driver jvm is always tricky, because spark is not entirely responsible for it -- eg. the user might be running a server which is doing all sorts of things, and it happens to use a spark context for servicing some requests. Not an architecture I'd recommend, and I dunno what else it could with an OOM anyway -- but still I hesitate to have spark be changing that. Then again, maybe spark 3.0 is a good chance for us to change this to a more sane default?

anyway, I guess I'm not saying anything definitive yet, just thinking "out loud" ...

another thing which may be relevant here is #24804 / SPARK-23472 -- it at least makes it easy for an admin to set this option across the board.

@squito
Copy link
Contributor

squito commented Aug 7, 2019

ok sorry I refreshed my memory a bit more -- I think I lean towards including this by default. Yes, the user could do it themselves, but they are likely to forget, and the cases where they don't want it are very few.

I agree with dongjoon, though, that this seems uglier than necessary -- probably easier if we add an option to change it for all cluster managers, and then handle it in SparkSubmitCommandBuilder around here:

// We don't want the client to specify Xmx. These have to be set by their corresponding
// memory flag --driver-memory or configuration entry spark.driver.memory
String driverDefaultJavaOptions = config.get(SparkLauncher.DRIVER_DEFAULT_JAVA_OPTIONS);
checkJavaOptions(driverDefaultJavaOptions);
String driverExtraJavaOptions = config.get(SparkLauncher.DRIVER_EXTRA_JAVA_OPTIONS);
checkJavaOptions(driverExtraJavaOptions);

@dongjoon-hyun
Copy link
Member

Thank you for your advice, @squito . +1 for adding this for all cluster managers.

@skonto
Copy link
Contributor Author

skonto commented Aug 11, 2019

@dongjoon-hyun sorry for the name spelling thing :) I am on vacations but will slowly have a look to what @squito suggests ;)

@dongjoon-hyun
Copy link
Member

Thank you, @skonto !

@dongjoon-hyun
Copy link
Member

Although there was a request, but this is a kind of nice-to-have. I mean this is not a blocker because this is not a regression from 2.4.3. We don't need to hurry. Since Apache Spark 2.4.4 RC1 is scheduled on Monday, we can revisit this later.

@holdenk
Copy link
Contributor

holdenk commented Sep 9, 2019

I'm +1 on turning this on for default for Spark 3, I think killing the JVM on OOM is the most reasonable option. I think having this be a global config between cluster managers not just for k8s as suggested by @SQUiTTO is a good idea.

If there was a way to also write to the default termination log I think that would be a useful follow on PR possible.

@SparkQA
Copy link

SparkQA commented Sep 16, 2019

Kubernetes integration test starting
URL: https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder-K8s/15662/

@SparkQA
Copy link

SparkQA commented Sep 16, 2019

Kubernetes integration test status failure
URL: https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder-K8s/15662/

@skonto
Copy link
Contributor Author

skonto commented Sep 19, 2019

thanks @holdenk @dongjoon-hyun I will target Spark 3 and try to implement this for all managers.

@holdenk
Copy link
Contributor

holdenk commented Sep 20, 2019

Sounds good :) Please ping me when you open that PR (or if you update this PR to support all of the cluster managers).

@skonto
Copy link
Contributor Author

skonto commented Oct 16, 2019

back to this.

@holdenk
Copy link
Contributor

holdenk commented Oct 17, 2019

Cool, excited to see the progress.

@skonto
Copy link
Contributor Author

skonto commented Oct 18, 2019

closing this in favor of #26161

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants