Configuration
Three dataclasses describe a deployment. All fields are keyword-only.
SparkBaseConfholds cluster-level settings.SparkDriverConfdescribes the Airflow worker pod that hosts the driver.SparkExecutorConfdescribes the executor pods.
pyspark_on_k8s(...), @task.pyspark_on_k8s(...) and PySparkOnK8sOperator take them as base_conf, driver_conf and executor_conf. base_conf is optional and defaults to SparkBaseConf(). They also take extra, a mapping of Spark conf keys applied last.
CPU and memory values use CPU.cores(2), CPU.milli_cores(250), Memory.gibibytes(4), Memory.mebibytes(512), Memory.gigabytes(...) and Memory.megabytes(...). The raw constructors take keyword arguments only: CPU(milli=250) and Memory(bytes=1048576).
SparkBaseConf
| Field | Default | Spark key(s) | Pod field |
|---|---|---|---|
master |
k8s://https://kubernetes.default.svc:443 |
spark.master (and spark.submit.deployMode=client) |
|
namespace |
None |
spark.kubernetes.namespace; when None it is ${env:SPARK_DRIVER_NAMESPACE} |
|
image_pull_policy |
IfNotPresent |
spark.kubernetes.container.image.pullPolicy |
|
image_pull_secrets |
None |
spark.kubernetes.container.image.pullSecrets (only when set; executor pods only) |
|
driver_port |
7078 |
spark.driver.port |
|
block_manager_port |
7079 |
spark.blockManager.port |
|
delete_executors_on_termination |
True |
spark.kubernetes.executor.deleteOnTermination |
|
executor_pod_template_path |
/tmp/pysparkonk8s/executor-pod-template.yaml |
spark.kubernetes.executor.podTemplateFile |
|
ca_cert_file |
None |
spark.kubernetes.authenticate.caCertFile (only when set) |
|
oauth_token_file |
None |
spark.kubernetes.authenticate.oauthTokenFile (only when set) |
When namespace is set, it must be the namespace of the worker pod: the driver runs in that pod and the executors are created next to it.
ca_cert_file and oauth_token_file are left out by default. Spark's Kubernetes client then uses the service account mounted in the driver pod and refreshes its token, which is what you want inside a cluster. Set them only to point the client at other files.
The base conf also always renders spark.kubernetes.driver.pod.name=${env:SPARK_DRIVER_POD_NAME} and spark.driver.host=${env:SPARK_LOCAL_IP}. Spark resolves the ${env:...} references from the driver pod's environment, which the driver pod_override fills from the Downward API (see How it works).
SparkDriverConf and SparkExecutorConf
Both classes share the fields below. image is required. The Spark keys use spark.driver. for the driver and spark.executor. for executors.
| Field | Default | Spark key(s) | Pod field |
|---|---|---|---|
image |
required | spark.kubernetes.executor.container.image (executor) |
container image |
cores |
CPU.cores(1) |
spark.{driver,executor}.cores |
cpu request, unless request_cpu is set; a whole number of cores |
memory |
Memory.gibibytes(1) |
spark.{driver,executor}.memory |
memory request (heap + overhead) |
memory_overhead |
None |
spark.{driver,executor}.memoryOverhead |
part of the memory request |
memory_overhead_factor |
None (0.4 driver, 0.1 executor) |
used to compute memoryOverhead |
part of the memory request |
request_cpu |
None (uses cores) |
spark.kubernetes.executor.request.cores (executor) |
cpu request |
limit_cpu |
None |
spark.kubernetes.executor.limit.cores (executor, raised to the cpu request when lower) |
cpu limit |
request_memory |
None |
memory request, replaces the computed one (driver only) | |
limit_memory |
None |
memory limit (driver only) | |
request_ephemeral_storage |
None |
ephemeral-storage request | |
limit_ephemeral_storage |
None |
ephemeral-storage limit | |
extra_java_options |
-XX:+ExitOnOutOfMemoryError |
spark.{driver,executor}.extraJavaOptions |
|
extra_class_path |
None |
spark.{driver,executor}.extraClassPath |
|
extra_library_path |
None |
spark.{driver,executor}.extraLibraryPath |
|
service_account_name |
None |
spark.kubernetes.authenticate.executor.serviceAccountName (executor) |
serviceAccountName |
tolerations |
None |
tolerations |
|
node_selector |
None |
nodeSelector |
|
affinity |
None for the driver, FOLLOW_DRIVER for executors (the driver rejects FOLLOW_DRIVER) |
affinity |
|
volumes |
None |
volumes |
|
volume_mounts |
None |
container volumeMounts |
|
env |
None |
container env |
|
pod_labels |
{} |
metadata.labels; the pysparkonk8s/component label is always set by the provider |
|
pod_annotations |
{} |
metadata.annotations |
cores must be a whole number of cores, because Spark takes spark.{driver,executor}.cores as an integer. CPU.milli_cores(250) as cores raises a ValueError. For a fractional pod CPU, keep cores=CPU.cores(1) and set request_cpu=CPU.milli_cores(250).
Setting cores, memory, extra_java_options, extra_class_path or extra_library_path to None leaves the matching Spark key out. Setting memory to None also drops memoryOverhead and the computed memory request.
The driver conf has no further fields. The driver container is named base, as the KubernetesExecutor expects, and its environment always gets the three Downward API variables after the entries of env.
Additional executor fields
| Field | Default | Spark key(s) |
|---|---|---|
dynamic_allocation |
True |
spark.dynamicAllocation.enabled |
min_executors |
0 |
spark.dynamicAllocation.minExecutors (dynamic allocation only) |
max_executors |
10 |
spark.dynamicAllocation.maxExecutors (dynamic allocation only) |
shuffle_tracking |
True |
spark.dynamicAllocation.shuffleTracking.enabled (dynamic allocation only) |
instances |
None |
spark.executor.instances (only when dynamic allocation is off) |
allocation_batch_size |
None |
spark.kubernetes.allocation.batch.size |
pyspark_memory |
None |
spark.executor.pyspark.memory |
The executor container is named spark-kubernetes-executor, and Spark is told this with spark.kubernetes.executor.podTemplateContainerName.
Memory sizing
The memory request of a pod is the JVM heap plus the overhead.
- The heap is
memory. - The overhead is
memory_overheadwhen set, otherwisemax(memory * factor, 384Mi). The factor ismemory_overhead_factorwhen set, otherwise 0.4 for the driver and 0.1 for executors. The driver default is higher because the driver pod also hosts the Airflow task runner and the Python process. - For executors,
pyspark_memoryis added to the request. Spark gives the Python workers that amount throughspark.executor.pyspark.memory. request_memoryreplaces the computed request entirely, for the driver pod only.
The memory limit defaults to the request. CPU and ephemeral-storage limits default to none. Every limit that is set is raised to its request if it is lower, so a pod never has a limit below its request. The executor core limit in the Spark conf (spark.kubernetes.executor.limit.cores) carries the raised value, so with cores=CPU.cores(2) and limit_cpu=CPU.cores(1) it is 2. For executor pods, request_memory and limit_memory have no effect: Spark overwrites both the memory request and the memory limit of the executor container with heap + overhead + PySpark memory, whatever the template says. Control executor memory with memory, memory_overhead or memory_overhead_factor, and pyspark_memory.
For example, an executor with memory=Memory.gibibytes(4) and pyspark_memory=Memory.gibibytes(1) gets an overhead of about 410Mi (0.1 of 4Gi, above the 384Mi floor) and requests about 5.4Gi.
extra and unsetting keys
extra is a mapping from Spark conf keys to values. It is applied after the base, driver and executor conf are merged, so it wins on any key clash. A value of None removes the key, which is how you drop a default:
extra = {
"spark.sql.shuffle.partitions": "64",
"spark.kubernetes.executor.deleteOnTermination": None,
}
Jinja templating
The Spark conf is passed to the task as config_kwargs, and Jinja works in any value that ends up in the Spark conf, including values in extra, for all three entry points. The operator and the decorator list config_kwargs as a template field. The stock @task.pyspark does not, so with the helper the pre_execute hook renders it with the task context before the session starts. Pod fields (images, resources, volumes, labels and so on) are rendered once from the conf objects and are static: they are not templated.