Skip to main content

PyPI - Package Version PyPI - Python Version PyPI - Python Version CI workflow OpenSSF Best Practices codecov

Spark On Kubernetes

Spark on Kubernetes is a python package that makes it easy to submit and manage spark apps on Kubernetes. It provides a Python client that can be used to submit apps in your API or scheduler of choice, and a CLI that can be used to submit apps from the command line, instead of using spark-submit.

It also provides an optional REST API with a web UI that can be used to list and manage apps, and access the spark UI through the reverse proxy. The optional Airflow 3.4+ task tab reuses the same React console components inside Airflow; see the Airflow guide.

Choose your route in the start-here guide and check the support boundaries before adopting a new integration. The release notes describe every published release and the release in preparation. Report vulnerabilities privately as described in the security policy.

Installation

Published releases

The following commands install a published package from PyPI. Choose the extra for the functionality you need, inside a virtual environment:

python -m pip install spark-on-k8s # Python client, CLI and helpers
python -m pip install "spark-on-k8s[sql]" # Also submit SQL files without Airflow
python -m pip install "spark-on-k8s[api]" # Also install the REST API and web UI
python -m pip install "spark-on-k8s[airflow]" # Also install the Airflow integration

Published releases can have different Python requirements and features from the unreleased main branch. Check the selected release's metadata on PyPI and its version of the documentation. The source requirements and examples below describe main; installing from PyPI does not install unreleased changes.

Current source

The current source requires Python 3.11 or newer. The core client and API are tested on Python 3.11–3.15. The Airflow integration is tested with Airflow 3.4 on Python 3.11–3.14 and Airflow 2.11.2 on Python 3.11–3.12. Airflow 2 is end-of-life and retains dependency versions with known security advisories; use Airflow 3 for new deployments. The optional airflow-ui extra adds a native task tab for Airflow 3.4 or newer, below Airflow 4. The airflow and airflow-ui extras are not supported on Python 3.15.

Clone the repository to use the source instructions and example files in this guide:

git clone https://github.com/hussein-awala/spark-on-k8s.git
cd spark-on-k8s

With uv, choose one environment profile. These commands use the committed lockfile and create a project-local .venv:

uv sync --locked --no-default-groups # Core package
uv sync --locked --no-default-groups --extra api # API and web UI
uv sync --python 3.12 --locked --no-default-groups --extra airflow # Airflow 3 integration
uv sync --python 3.12 --locked --no-default-groups --extra airflow-ui # Also install the native task tab

Run commands from the selected environment with uv run --no-sync spark-on-k8s, or activate it with source .venv/bin/activate before using the commands below. To install the API and Airflow integration together, select --extra api --extra airflow, or --extra api --extra airflow-ui to include the task tab. A later uv sync selects a new profile and can remove unselected extras.

Alternatively, install from the checkout with pip in an activated virtual environment:

python -m pip install . # Core package
python -m pip install ".[api]" # API and web UI
python -m pip install ".[airflow]" # Airflow integration
python -m pip install ".[airflow-ui]" # Airflow 3.4+ integration and native task tab

See CONTRIBUTING.md for development dependencies and the separate Airflow 2.11 compatibility environment. See the Airflow guide for provider installation, connections and DAG examples.

Usage

Set up the Kubernetes namespace

The client creates the Spark driver pod. The driver uses its Kubernetes service account to create and manage executor pods. The following examples use namespace spark and service account spark; the submitting client also needs permission to create the application resources in that namespace.

The setup helper creates the namespace if needed, a service account named spark, and a ClusterRoleBinding to the built-in edit ClusterRole. This grants that service account edit access across all namespaces. It is broader than a namespace-scoped Spark driver role. On a shared cluster, ask the cluster administrator to provision a suitably scoped service account instead.

Running the helper requires credentials that can list namespaces, service accounts and ClusterRoleBindings, create missing resources, and bind the edit role. Use it only when that cluster-wide grant is intended. See the Kubernetes RBAC documentation for the difference between RoleBindings and ClusterRoleBindings.

With Python:

from spark_on_k8s.utils.setup_namespace import SparkOnK8SNamespaceSetup

namespace_setup = SparkOnK8SNamespaceSetup()
namespace_setup.setup_namespace(namespace="spark")

With the CLI:

spark-on-k8s namespace setup -n spark

Python client

Replace the image and application path with an image accessible to your cluster and an application file included in that image:

from spark_on_k8s.client import SparkOnK8S

client = SparkOnK8S()
driver_pod_name = client.submit_app(
    image="my-registry/my-image:latest",
    app_path="local:///opt/spark/work-dir/my-app.py",
    app_arguments=["arg1", "arg2"],
    app_name="my-app",
    namespace="spark",
    service_account="spark",
    app_waiter="log",
    image_pull_policy="IfNotPresent",
    ui_reverse_proxy=True,
)

submit_app returns the driver pod name. The application ID is generated from the application name with a timestamp suffix and is available in the application list. For a private registry, configure image-pull credentials for the pods in your cluster.

CLI

The CLI can submit applications and manage applications created by either the CLI or the Python client. Submit an application and stream its logs:

spark-on-k8s app submit \
  --image my-registry/my-image:latest \
  --path local:///opt/spark/work-dir/my-app.py \
  --namespace spark \
  --service-account spark \
  --name my-app \
  --image-pull-policy IfNotPresent \
  --ui-reverse-proxy \
  --logs \
  param1 param2

List applications to find their generated IDs:

spark-on-k8s apps list -n spark

To kill a running application, copy its actual ID from that output. The following ID is illustrative; my-app alone is the application name, not the generated ID:

APP_ID="my-app-20261107120000" # Replace with the ID from the application list
spark-on-k8s app kill -n spark --app-id "$APP_ID"

Check the help for further commands and options:

spark-on-k8s --help
spark-on-k8s app --help
spark-on-k8s apps --help

Use --output json for versioned submission receipts and machine-readable list/status results. Waiting commands report failed workloads with nonzero exit codes; --timeout bounds observation. --max-runtime SECONDS instead makes Kubernetes stop the driver, and apps prune reports (or, with --delete, deletes) finished drivers left in a namespace. See the automation guide for exit codes and Python run handles.

REST API

Install the api extra before starting the REST API and web UI. The API serves only its current namespace unless SPARK_ON_K8S_API_NAMESPACES names others; outside a cluster, that is the namespace of the kubeconfig context. For local access to the spark namespace:

export SPARK_ON_K8S_API_NAMESPACES=spark
spark-on-k8s api start \
    --host "127.0.0.1" \
    --port 8080 \
    --workers 4 \
    --log-level error \
    --limit-concurrency 100

List applications in the spark namespace:

curl 'http://127.0.0.1:8080/apps/list_apps/spark' \
  -H 'accept: application/json'

The console provides a searchable application list, status filters, namespace selection, application details, and confirmed stop/delete actions. It refreshes in the background without resetting your filters. Counts and filters describe the applications loaded so far; use Load more when additional pages are available. Completed applications from the configured history server appear as archive entries without live-driver actions.

The driver-log view supports search, copying, download, a configurable tail, and pausing auto-scroll. It keeps the most recent 10,000 lines in the browser. Connection failures are shown separately from log text, and a history-server outage leaves live applications available. JavaScript and CSS are packaged with the Python distribution; no CDN is needed.

Open http://127.0.0.1:8080/webserver/apps?namespace=spark and use Open Spark UI for an application submitted with UI reverse proxy enabled. Its direct URL is http://127.0.0.1:8080/webserver/ui/spark/APP_ID; replace APP_ID with the actual application ID. The API process must be able to reach the driver's service.

Use --host 0.0.0.0 to listen on all network interfaces when deploying behind an appropriate service or proxy. Client URLs should use the server's reachable host or address, not the wildcard bind address 0.0.0.0.

When a gateway serves the API below a path such as /platform/spark and removes that prefix before forwarding, pass --root-path /platform/spark or set SPARK_ON_K8S_API_ROOT_PATH=/platform/spark. See the mounted path guidance.

Deploy the API with Helm

The chart deploys the API, its Service and RBAC resources. Ingress and the optional Spark History Server are controlled by chart values. The chart's env list sets environment variables for the API and its Kubernetes client.

The published chart is released independently from this source checkout. Inspect the selected published chart's values before deploying it; unreleased chart fixes and defaults described below may not be present:

helm repo add spark-on-k8s https://hussein.awala.fr/spark-on-k8s-chart
helm repo update
helm show values spark-on-k8s/spark-on-k8s
helm install spark-on-k8s-release spark-on-k8s/spark-on-k8s \
  --values examples/helm/values.yaml

To use the chart from this checkout, render it for review first, then install that local chart. Set API_IMAGE_TAG to a compatible, tested image from the current source; the old 0.12.1 API does not implement the readiness endpoint:

helm template spark-on-k8s-release ./chart --values examples/helm/values.yaml \
  --set-string image.tag="${API_IMAGE_TAG:?Set a qualified API image tag}"
helm install spark-on-k8s-release ./chart --values examples/helm/values.yaml \
  --set-string image.tag="$API_IMAGE_TAG"

Choose one chart source for a release. The commands above use example values from this checkout; adapt them to your cluster and selected chart version. The source chart defaults to readOnly: true, namespaces: "" (the release namespace), a ClusterIP Service on port 8000, and disabled ingress. Set namespaces to a comma-separated string such as "spark" or "spark,analytics" to serve those namespaces, or to "*" to serve every namespace through a chart-owned cluster role with the same permissions. The chart-created Kubernetes permissions follow the same value, and the API refuses requests, Spark UI proxy connections and log streams for other namespaces. Enable readOnly: false only if the API should be able to kill or delete applications; with "*", that also requires rbac.allowClusterWideWrite: true. The separate web UI button settings control visibility, not Kubernetes authorization. The API has no built-in user authentication; its namespace scope limits what it can reach, not who can use it. Expose the API through access-controlled networking or an authenticating proxy.

The source chart supplies in-cluster Kubernetes configuration and the API bind address and port. It requires an explicit API image tag or digest; a published chart defaults to its appVersion tag. Using a local chart does not build or install the Python source into an image. Set image.repository and image.digest to a verified production image digest (image.tag remains available for named releases), independently of Spark application images passed to submit_app. Digest selection takes precedence over the tag. See chart deployment guidance for scoped permissions, externally owned identities and release qualification.

Configuration

Set environment variables before starting the CLI/API process or importing the Python client: configuration values are read at module import time. The table below lists defaults in the current source, before chart or caller overrides. unset means no environment default (None); {}, [] and an empty string are distinct empty defaults. Use lowercase true or false for boolean settings.

Most client defaults also apply to the CLI. The six node-selector, label and annotation variables marked CLI are CLI defaults; Python callers should pass the corresponding driver_* or executor_* dictionaries to submit_app. If all three executor-count variables are unset, applications use two executors with dynamic allocation disabled. The two executor-template settings are mutually exclusive, including when supplied through environment variables.

Environment variable Description Default
SPARK_ON_K8S_DOCKER_IMAGE Spark driver and executor image; required unless passed explicitly unset
SPARK_ON_K8S_APP_PATH Application file URI; required unless passed explicitly unset
SPARK_ON_K8S_NAMESPACE Application namespace default
SPARK_ON_K8S_SERVICE_ACCOUNT Driver service account spark
SPARK_ON_K8S_APP_NAME Application name; generated when unset unset
SPARK_ON_K8S_SPARK_CONF Spark properties as a JSON object {}
SPARK_ON_K8S_CLASS_NAME Main class for a JVM application unset
SPARK_ON_K8S_PACKAGES Maven coordinates separated by commas empty string
SPARK_ON_K8S_APP_ARGUMENTS Application arguments as a JSON array of strings []
SPARK_ON_K8S_APP_WAITER Wait mode: no_wait, wait or log no_wait
SPARK_ON_K8S_IMAGE_PULL_POLICY Pod image pull policy: Always, IfNotPresent or Never IfNotPresent
SPARK_ON_K8S_UI_REVERSE_PROXY Configure the application for Spark UI reverse proxy access false
SPARK_ON_K8S_DRIVER_CPU Driver CPU cores (integer) 1
SPARK_ON_K8S_DRIVER_MEMORY Driver memory in MiB (integer) 1024
SPARK_ON_K8S_DRIVER_MEMORY_OVERHEAD Driver memory overhead in MiB (integer) 512
SPARK_ON_K8S_EXECUTOR_CPU Executor CPU cores (integer) 1
SPARK_ON_K8S_EXECUTOR_MEMORY Executor memory in MiB (integer) 1024
SPARK_ON_K8S_EXECUTOR_MEMORY_OVERHEAD Executor memory overhead in MiB (integer) 512
SPARK_ON_K8S_EXECUTOR_MIN_INSTANCES Minimum executor count; enables dynamic allocation when set unset
SPARK_ON_K8S_EXECUTOR_MAX_INSTANCES Maximum executor count; enables dynamic allocation when set unset
SPARK_ON_K8S_EXECUTOR_INITIAL_INSTANCES Initial executor count; fixed count when minimum and maximum are unset unset
SPARK_ON_K8S_EXECUTOR_ALLOCATION_RATIO Dynamic allocation ratio (number) 1.0
SPARK_ON_K8S_SCHEDULER_BACKLOG_TIMEOUT Backlog duration before requesting executors 1s
SPARK_ON_K8S_SUSTAINED_SCHEDULER_BACKLOG_TIMEOUT Backlog duration for subsequent executor requests value of SPARK_ON_K8S_SCHEDULER_BACKLOG_TIMEOUT
SPARK_ON_K8S_SECRET_ENV_VAR Driver and executor environment values as a JSON object; stored in an application-owned Secret {}
SPARK_ON_K8S_DRIVER_ENV_VARS_FROM_SECRET Existing Secret names separated by commas; import their keys as driver environment variables empty list
SPARK_ON_K8S_STARTUP_TIMEOUT Driver startup timeout in seconds when waiting or streaming logs (integer); 0 disables it 0
SPARK_ON_K8S_CONFIG_FILE Kubeconfig file path unset
SPARK_ON_K8S_CONTEXT Kubeconfig context unset
SPARK_ON_K8S_CLIENT_CONFIG JSON object of synchronous kubernetes.client.Configuration constructor arguments unset
SPARK_ON_K8S_ASYNC_CLIENT_CONFIG JSON object of kubernetes_asyncio.client.Configuration constructor arguments; requires the api or airflow extra unset
SPARK_ON_K8S_IN_CLUSTER Load Kubernetes credentials from the running pod false
SPARK_ON_K8S_SPARK_DRIVER_NODE_SELECTOR CLI driver node selector as a JSON object {}
SPARK_ON_K8S_SPARK_EXECUTOR_NODE_SELECTOR CLI executor node selector as a JSON object {}
SPARK_ON_K8S_SPARK_DRIVER_LABELS CLI driver labels as a JSON object {}
SPARK_ON_K8S_SPARK_EXECUTOR_LABELS CLI executor labels as a JSON object {}
SPARK_ON_K8S_SPARK_DRIVER_ANNOTATIONS CLI driver annotations as a JSON object {}
SPARK_ON_K8S_SPARK_EXECUTOR_ANNOTATIONS CLI executor annotations as a JSON object {}
SPARK_ON_K8S_EXECUTOR_POD_TEMPLATE_PATH Executor template path already available to the driver unset
SPARK_ON_K8S_EXECUTOR_TEMPLATE Executor template YAML or local file path; creates and mounts a ConfigMap unset
SPARK_ON_K8S_API_NAMESPACES Namespaces the API serves: empty for its current namespace (the pod's, or the kubeconfig context's), * for every namespace, or a comma-separated list empty
SPARK_ON_K8S_API_DEFAULT_NAMESPACE Namespace the API/web UI uses when a request names none; must be in the scope the first namespace in scope; with *, the current namespace
SPARK_ON_K8S_API_HOST API bind address 127.0.0.1
SPARK_ON_K8S_API_PORT API listen port (integer) 8000
SPARK_ON_K8S_API_WORKERS API worker count (integer) 4
SPARK_ON_K8S_API_LOG_LEVEL API server log level info
SPARK_ON_K8S_API_LIMIT_CONCURRENCY Maximum concurrent API connections/tasks (integer) 1000
SPARK_ON_K8S_API_ROOT_PATH Public path prefix, such as /platform/spark, that a gateway removes before forwarding to the API; --root-path default empty string
SPARK_ON_K8S_API_SPARK_HISTORY_HOST Spark History Server host and port, optionally with an HTTP(S) scheme unset
SPARK_ON_K8S_API_WEBSERVER_APPS_KILL_BUTTON Show the kill button in the applications page true
SPARK_ON_K8S_API_WEBSERVER_APPS_DELETE_BUTTON Show the delete button in the applications page true
SPARK_ON_K8S_API_WEBSERVER_APPS_REFRESH_INTERVAL Applications-page refresh interval in whole seconds; 0 or a negative value disables it 10

JSON settings use JSON syntax, including double-quoted keys and strings. Packages and existing Secret names use comma-separated strings instead of JSON arrays:

export SPARK_ON_K8S_SPARK_CONF='{"spark.sql.shuffle.partitions": "8"}'
export SPARK_ON_K8S_APP_ARGUMENTS='["--input", "s3a://example/input"]'
export SPARK_ON_K8S_DRIVER_ENV_VARS_FROM_SECRET='spark-storage,spark-catalog'
export SPARK_ON_K8S_API_WEBSERVER_APPS_REFRESH_INTERVAL='10'

The named Secrets must exist in the application's namespace. Refresh values such as 10s are invalid because the setting is parsed as an integer.

Examples

Run the following commands from a source checkout. The examples use namespace spark and service account spark, which must already be configured as described above. See the examples guide for the application code, driver configuration and executor templates, and the Airflow guide for DAG examples. The ETL and Iceberg starters include real data checks and portable job definitions for shared-storage deployments. SQL jobs can be submitted from the CLI or Python without Airflow. Spark compatibility describes the Spark 3.5 and Spark 4 runtime matrix, image build arguments, and execution tests.

Python

Build the image from the repository root:

docker build -t pyspark-job examples/python

For a local cluster, load the image into one of the following cluster runtimes:

# Minikube
minikube image load pyspark-job
# Or kind
kind load docker-image pyspark-job

The supplied submission scripts use image_pull_policy="Never" (CLI: --image-pull-policy Never) for these preloaded images. For a remote cluster, replace the example registry below, push the tagged image, and update the script's image to that same name and its pull policy to IfNotPresent:

docker tag pyspark-job registry.example.com/team/pyspark-job:example
docker push registry.example.com/team/pyspark-job:example

Submit with the Python client or the CLI script:

python examples/python/submit.py
# Or
bash examples/python/submit.sh

Java

Build the image from the repository root:

docker build -t java-spark-job examples/java

For a local cluster, load the image into one of the following cluster runtimes:

# Minikube
minikube image load java-spark-job
# Or kind
kind load docker-image java-spark-job

The supplied submission scripts use image_pull_policy="Never" (CLI: --image-pull-policy Never) for these preloaded images. For a remote cluster, replace the example registry below, push the tagged image, and update the script's image to that same name and its pull policy to IfNotPresent:

docker tag java-spark-job registry.example.com/team/java-spark-job:example
docker push registry.example.com/team/java-spark-job:example

Submit with the Python client or the CLI script:

python examples/java/submit.py
# Or
bash examples/java/submit.sh

What's next

See the TODO list for future work. Contributions are welcome!

The governance policy describes maintainer roles and how contributors grow into them. If you use spark-on-k8s, add your organization to ADOPTERS.md.

Metadata

Release files for spark-on-k8s 0.13.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for spark-on-k8s 0.13.0
File Size Uploaded Provenance Actions
spark_on_k8s-0.13.0.tar.gz 364.4 kB With provenance Details for spark_on_k8s-0.13.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for spark-on-k8s 0.13.0
File Interpreter ABI Platform Actions
spark_on_k8s-0.13.0-py3-none-any.whl Python 3 none any Details for spark_on_k8s-0.13.0-py3-none-any.whl

Total release size: 692.9 kB

Release files / spark_on_k8s-0.13.0.tar.gz

Download URL spark_on_k8s-0.13.0.tar.gz
Size 364.4 kB
Tags Source
SHA-256 checksum
How to use checksums
3dcaf02abb7a737f52ceba3eed589cdc401a1cd14baccc4665813f948f492217
BLAKE2b-256 checksum
How to use checksums
25d4ea95f9c770b2060c3573cd15843f88b4f12ba81a49b4472457d65afc1c5c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 11, 2026.

Transparency log

Release files / spark_on_k8s-0.13.0-py3-none-any.whl

Download URL spark_on_k8s-0.13.0-py3-none-any.whl
Size 328.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
25fcbc002a3045ad50740084cd66ee45f3b66541b1dd73bb75444ba4c1c4638f
BLAKE2b-256 checksum
How to use checksums
edc85598a11a6255087d97a0a8e1bfeb6eef1d3257903b5f9abed698cf719856
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 11, 2026.

Transparency log

Security for spark-on-k8s 0.13.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for spark_on_k8s-0.13.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 11, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.13.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page