-
Notifications
You must be signed in to change notification settings - Fork 29k
[SPARK-38582][K8S] Add KubernetesUtils.buildEnvVars(WithFieldRef)? utility functions
#35886
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
Conversation
…ieldRef for KubernetesUtils to eliminate duplicate code pattern
|
@dongjoon-hyun |
Yikun
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general, I think refactoring helps to simplify the code. +1 on the idea. But for core featurestep changes we need to make sure test coverage all cases.
...ce-managers/kubernetes/core/src/main/scala/org/apache/spark/deploy/k8s/KubernetesUtils.scala
Outdated
Show resolved
Hide resolved
...ce-managers/kubernetes/core/src/main/scala/org/apache/spark/deploy/k8s/KubernetesUtils.scala
Outdated
Show resolved
Hide resolved
...ce-managers/kubernetes/core/src/main/scala/org/apache/spark/deploy/k8s/KubernetesUtils.scala
Outdated
Show resolved
Hide resolved
...ce-managers/kubernetes/core/src/main/scala/org/apache/spark/deploy/k8s/KubernetesUtils.scala
Outdated
Show resolved
Hide resolved
...nagers/kubernetes/core/src/test/scala/org/apache/spark/deploy/k8s/KubernetesUtilsSuite.scala
Show resolved
Hide resolved
|
Can one of the admins verify this patch? |
19dad4b to
b4f4e32
Compare
martin-g
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
The suggestions below are just nits which you could ignore!
...ce-managers/kubernetes/core/src/main/scala/org/apache/spark/deploy/k8s/KubernetesUtils.scala
Outdated
Show resolved
Hide resolved
...nagers/kubernetes/core/src/test/scala/org/apache/spark/deploy/k8s/KubernetesUtilsSuite.scala
Outdated
Show resolved
Hide resolved
...nagers/kubernetes/core/src/test/scala/org/apache/spark/deploy/k8s/KubernetesUtilsSuite.scala
Outdated
Show resolved
Hide resolved
|
Gentle ping @dongjoon-hyun @martin-g |
|
I am just a contributor to the project. A project member could merge PRs. |
|
Sorry for being late. To prepare Apache Spark 3.3.0, I was too busy in these days. |
...ce-managers/kubernetes/core/src/main/scala/org/apache/spark/deploy/k8s/KubernetesUtils.scala
Outdated
Show resolved
Hide resolved
...ce-managers/kubernetes/core/src/main/scala/org/apache/spark/deploy/k8s/KubernetesUtils.scala
Outdated
Show resolved
Hide resolved
| } | ||
|
|
||
| val driverCustomEnvs = KubernetesUtils.buildEnvVars( | ||
| conf.environment + (ENV_APPLICATION_ID -> conf.appId)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ur, are you sure about this change? This looks inconsistent to me because this breaks the existing behavior. Previously, conf.environment's ENV_APPLICATION_ID supersedes if there is a duplication.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Your worries are right. After the latest commit, I don't think this refactoring will introduce lots of regressions.
- When there are two environment variables with the same key in the pod, the last value shall work.
I apply a pod with yaml
apiVersion: v1
kind: Pod
metadata:
name: static-web
labels:
role: myrole
spec:
containers:
- name: web
image: nginx
env:
- name: DEMO_GREETING
value: "Hello from the environment"
- name: DEMO_GREETING
value: "Such a sweet sorrow"
ports:
- name: web
containerPort: 80
protocol: TCPAnd execute the echo command, get the value of DEMO_GREETING is Such a sweet sorrow.
$ kubectl exec -ti static-web -n default -- /bin/bash
$ echo $DEMO_GREETING
Such a sweet sorrow++operation in Scala Map is overwrite append operation. As long as the order of our++remains the same as the previousenvorder, the value of the environment variable in the pod will not change. For example, user specifiedENV_APPLICATION_IDastemp_app_idin sparkConf
spark.kubernetes.driverEnv.ENV_APPLICATION_ID=user_specified_app_id
According to the previous logic, two envs with the same key will be generated
env:
- name: ENV_APPLICATION_ID
value: spark-XXXXXXX # https://github.com/apache/spark/blob/b852645f69b3b7a0a2140a732c4c03b302f8795a/resource-managers/kubernetes/core/src/main/scala/org/apache/spark/deploy/k8s/submit/KubernetesClientApplication.scala#L222
- name: ENV_APPLICATION_ID
value: user_specified_app_idAccording to the first point, the value of ENV_APPLICATION_ID is user_specified_app_id.
We replace seq with map. It causes the env as follow
env:
- name: ENV_APPLICATION_ID
value: user_specified_app_idBecause ++ in map is overwrite operation, so the value of ENV_APPLICATION_ID is overwritten. Finally, the value of ENV_APPLICATION_ID in pod is user_specified_app_id.
I agree that this refactoring does not bring env changes and can reduce unnecessary env.
dongjoon-hyun
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 for the intention, but the proposed PR looks a little intrusive and inconsistent to me.
This PR may introduce lots of regression if we are not careful enough to avoid any regression like the following comment.
|
FYI, we had better hold on these kind of PRs during the planned release process. It's the same for the other refactoring PRs. |
|
@dongjoon-hyun Hi, I'm ok to hold on this PR during the planned release process. Thanks for your work on the release plan. And I'm addressing your comment. |
|
Thank you, @dcoliversun ! |
@dongjoon-hyun Hi, reply about this worry is here |
|
Thank you for your reply, @dcoliversun . |
| .orElse(environmentVariables.get(ENV_PYSPARK_DRIVER_PYTHON)) | ||
| .orElse(environmentVariables.get(ENV_PYSPARK_PYTHON)) | ||
| .orNull)) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you check what happens when both PYSPARK_PYTHON and ENV_PYSPARK_PYTHON is not defined? New logic looks not identical with the previous one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let me check it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe new logic is identical with previous one.
Old logic
Lines 87 to 105 in 36dd531
| val pythonEnvs = | |
| Seq( | |
| conf.get(PYSPARK_PYTHON) | |
| .orElse(environmentVariables.get(ENV_PYSPARK_PYTHON)).map { value => | |
| new EnvVarBuilder() | |
| .withName(ENV_PYSPARK_PYTHON) | |
| .withValue(value) | |
| .build() | |
| }, | |
| conf.get(PYSPARK_DRIVER_PYTHON) | |
| .orElse(conf.get(PYSPARK_PYTHON)) | |
| .orElse(environmentVariables.get(ENV_PYSPARK_DRIVER_PYTHON)) | |
| .orElse(environmentVariables.get(ENV_PYSPARK_PYTHON)).map { value => | |
| new EnvVarBuilder() | |
| .withName(ENV_PYSPARK_DRIVER_PYTHON) | |
| .withValue(value) | |
| .build() | |
| } | |
| ).flatten |
In old logic, when both
PYSPARK_PYTHON and ENV_PYSPARK_PYTHON is not defined, spark pod has empty environment variables, which of keys are ENV_PYSPARK_PYTHON and ENV_PYSPARK_DRIVER_PYTHON, and values are NULL. The corresponding Pod yaml is as follows
...
spec:
...
env:
- name: PYSPARK_PYTHON
- name: PYSPARK_DRIVER_PYTHONExec echo command, the result is as follows.
$ echo $PYSPARK_PYTHON
<BLANK LINE>
$ echo $PYSPARK_DRIVER_PYTHON
<BLANK LINE>New logic
When both PYSPARK_PYTHON and ENV_PYSPARK_PYTHON is not defined, no envVars are set in Pod. It behaves the same as environment variable with NULL value.
Existing code can also illustrate these, unit test about auth secret propagation expects that SPARK_CLASSPATH doesn't exists in executor environment variables.
Lines 266 to 279 in 36dd531
| test("auth secret propagation") { | |
| val conf = baseConf.clone() | |
| .set(config.NETWORK_AUTH_ENABLED, true) | |
| .set("spark.master", "k8s://127.0.0.1") | |
| val secMgr = new SecurityManager(conf) | |
| secMgr.initializeAuth() | |
| val step = new BasicExecutorFeatureStep(KubernetesTestConf.createExecutorConf(sparkConf = conf), | |
| secMgr, defaultProfile) | |
| val executor = step.configurePod(SparkPod.initialPod()) | |
| checkEnv(executor, conf, Map(SecurityManager.ENV_AUTH_SECRET -> secMgr.getSecretKey())) | |
| } |
Lines 500 to 530 in 36dd531
| // Check that the expected environment variables are present. | |
| private def checkEnv( | |
| executorPod: SparkPod, | |
| conf: SparkConf, | |
| additionalEnvVars: Map[String, String]): Unit = { | |
| val defaultEnvs = Map( | |
| ENV_EXECUTOR_ID -> "1", | |
| ENV_DRIVER_URL -> DRIVER_ADDRESS.toString, | |
| ENV_EXECUTOR_CORES -> "1", | |
| ENV_EXECUTOR_MEMORY -> "1024m", | |
| ENV_APPLICATION_ID -> KubernetesTestConf.APP_ID, | |
| ENV_SPARK_CONF_DIR -> SPARK_CONF_DIR_INTERNAL, | |
| ENV_SPARK_USER -> Utils.getCurrentUserName(), | |
| ENV_RESOURCE_PROFILE_ID -> "0", | |
| // These are populated by K8s on scheduling | |
| ENV_EXECUTOR_POD_IP -> null, | |
| ENV_EXECUTOR_POD_NAME -> null) | |
| val extraJavaOptsStart = additionalEnvVars.keys.count(_.startsWith(ENV_JAVA_OPT_PREFIX)) | |
| val extraJavaOpts = Utils.sparkJavaOpts(conf, SparkConf.isExecutorStartupConf) | |
| val extraJavaOptsEnvs = extraJavaOpts.zipWithIndex.map { case (opt, ind) => | |
| s"$ENV_JAVA_OPT_PREFIX${ind + extraJavaOptsStart}" -> opt | |
| }.toMap | |
| val containerEnvs = executorPod.container.getEnv.asScala.map { | |
| x => (x.getName, x.getValue) | |
| }.toMap | |
| val expectedEnvs = defaultEnvs ++ additionalEnvVars ++ extraJavaOptsEnvs | |
| assert(containerEnvs === expectedEnvs) | |
| } |
Lines 163 to 170 in 36dd531
| } ++ { | |
| kubernetesConf.get(EXECUTOR_CLASS_PATH).map { cp => | |
| new EnvVarBuilder() | |
| .withName(ENV_CLASSPATH) | |
| .withValue(cp) | |
| .build() | |
| } | |
| } ++ { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for in-depth analysis.
| Seq( | ||
| (ENV_EXECUTOR_POD_IP, "v1", "status.podIP"), | ||
| (ENV_EXECUTOR_POD_NAME, "v1", "metadata.name") | ||
| )) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically, new logic looks inconsistent to me in terms of the order of variable definition.
New logic seems to have a limitation where buildEnvVars-defined ones are overwritten by buildEnvVarsWithFieldRef-defined ones always. So, do we assume variable uniqueness here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we need variable uniqueness. As you said, envVar is overwrite in pod.spec.container.envVar.
buildEnvVarsWithFieldRefmethod can project Pod-level fields into the running container as environment variables, which value is defined until pod scheduled or later.buildEnvVarsmethod only put prepared key-value into pre-submission container as environment variables.
From normal usage, I believebuildEnvVarsWithFieldRefhas higher priority thanbuildEnvVars.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ack, @dcoliversun .
d2395f0 to
2144b18
Compare
...etes/core/src/main/scala/org/apache/spark/deploy/k8s/features/BasicExecutorFeatureStep.scala
Outdated
Show resolved
Hide resolved
...nagers/kubernetes/core/src/test/scala/org/apache/spark/deploy/k8s/KubernetesUtilsSuite.scala
Outdated
Show resolved
Hide resolved
...nagers/kubernetes/core/src/test/scala/org/apache/spark/deploy/k8s/KubernetesUtilsSuite.scala
Outdated
Show resolved
Hide resolved
...etes/core/src/main/scala/org/apache/spark/deploy/k8s/features/BasicExecutorFeatureStep.scala
Outdated
Show resolved
Hide resolved
| } | ||
|
|
||
| /** | ||
| * This function builds the EnvVar objects for each key-value env. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function description is inconsistent from the code body because we don't build EnvVar when value is null.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, in the function description, can we explicitly mention that we can use an empty string value to define a key-only EnvVar?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should mention it, because it's supported to set on yaml and works normally in container.
...ce-managers/kubernetes/core/src/main/scala/org/apache/spark/deploy/k8s/KubernetesUtils.scala
Outdated
Show resolved
Hide resolved
...nagers/kubernetes/core/src/test/scala/org/apache/spark/deploy/k8s/KubernetesUtilsSuite.scala
Show resolved
Hide resolved
...ce-managers/kubernetes/core/src/main/scala/org/apache/spark/deploy/k8s/KubernetesUtils.scala
Outdated
Show resolved
Hide resolved
...ce-managers/kubernetes/core/src/main/scala/org/apache/spark/deploy/k8s/KubernetesUtils.scala
Outdated
Show resolved
Hide resolved
...ce-managers/kubernetes/core/src/main/scala/org/apache/spark/deploy/k8s/KubernetesUtils.scala
Outdated
Show resolved
Hide resolved
...ce-managers/kubernetes/core/src/main/scala/org/apache/spark/deploy/k8s/KubernetesUtils.scala
Show resolved
Hide resolved
...ce-managers/kubernetes/core/src/main/scala/org/apache/spark/deploy/k8s/KubernetesUtils.scala
Outdated
Show resolved
Hide resolved
...ce-managers/kubernetes/core/src/main/scala/org/apache/spark/deploy/k8s/KubernetesUtils.scala
Outdated
Show resolved
Hide resolved
dongjoon-hyun
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made a PR to you, @dcoliversun .
Use Seq instead of Map
|
@dongjoon-hyun Thanks for you PR and advice :) I'm working on it. Update later |
address comments address comments address comments
dongjoon-hyun
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1, LGTM. Thank you, @dcoliversun .
I fixed a few typos at the last commit.
Merged to master for Apache Spark 3.4.0.
|
Thanks for your help @dongjoon-hyun |
What changes were proposed in this pull request?
This PR introduces
buildEnvVarsandbuildEnvVarsWithFieldReffunctions forkubernetesUtilsto eliminate duplicate code pattern.Why are the changes needed?
Code simplification. Remove redundant EnvVar Build code for driver and executor pod.
Does this PR introduce any user-facing change?
No.
How was this patch tested?