Service Spark - thekaveh/atlas GitHub Wiki
5.2.47. Apache Spark (standalone cluster)
Spark runs as a 5-container family in the stack's data band:
spark-master;spark-worker(replicas viaSPARK_WORKER_COUNT);spark-history;spark-connect, the dedicated Spark Connect gRPC sidecar;spark-init, an idempotent MinIO-client init on the maintainedpgsty/mcimage that creates the spark-history bucket.
1. Overview
Image: locally built ${PROJECT_NAME}-spark:local, FROM apache/spark:4.1.2. services/spark/build/Dockerfile adds jars the upstream image lacks:
- hadoop-aws and AWS SDK v2 (S3A);
- Iceberg (
iceberg-spark-runtime-4.1_2.13:1.11.0,iceberg-aws-bundle:1.11.0); - the Spark 4.1.2 Kafka connector (
spark-sql-kafka-0-10_2.13,spark-token-provider-kafka-0-10_2.13,kafka-clients3.9.2,commons-pool2).
Standalone mode: no YARN, no Kubernetes. apache/spark has no env-driven SPARK_MODE entrypoint, so services/spark/compose.yml starts each role (master, worker, history, connect) with an explicit /opt/spark/bin/spark-class or start-connect-server.sh command. Spark Connect (gRPC) runs on the dedicated spark-connect sidecar at sc://spark-connect:15002, started with start-connect-server.sh --master spark://spark-master:7077.
2. Access
| Surface | URL | Auth |
|---|---|---|
| Master UI (direct) | http://localhost:${SPARK_MASTER_UI_PORT} |
None; the UI's kill buttons are off (spark.ui.killEnabled=false), so a cross-site form cannot stop applications or drivers |
| Master UI (Kong) | http://spark.localhost:${KONG_HTTP_PORT} |
None |
| History UI (direct) | http://localhost:${SPARK_HISTORY_PORT} |
None |
| History UI (Kong) | http://spark-history.localhost:${KONG_HTTP_PORT} |
None |
| Spark Connect | sc://spark-connect:15002 |
None โ backend-network only |
| Master RPC | spark://spark-master:7077 |
None โ backend-network only |
| Master REST status API | http://spark-master:6066 |
None โ backend-network-only; used by spark-submit --status |
3. Configuration
SPARK_SOURCE=disabled # container | disabled
SPARK_IMAGE=apache/spark:4.1.2
SPARK_MASTER_UI_PORT= # auto-assigned by topology (data band)
SPARK_HISTORY_PORT= # auto-assigned
SPARK_WORKER_COUNT=2 # 1-8; also --spark-workers
SPARK_CONNECT_CORES_MAX=1 # max standalone cores held by Spark Connect
SPARK_MASTER_MEMORY_LIMIT=2g # container limits (deploy.resources.limits)
SPARK_MASTER_CPU_LIMIT=1.0
SPARK_WORKER_MEMORY_LIMIT=4g # per worker replica
SPARK_WORKER_CPU_LIMIT=2.0
SPARK_SOURCE=container requires MINIO_SOURCE other than disabled: spark-init waits on minio-init, so the bootstrapper refuses that combination.
4. Integration with the stack
-
MinIO โ
spark-historyreadss3a://spark-history/for event logs. Thespark-initcontainer creates the bucket on first start (idempotent). -
Iceberg REST โ Spark Connect ships a default
lakehousecatalog athttp://iceberg-rest:8181. It uses warehouses3a://lakehouse/, MinIO path-style S3 settings, the scoped Iceberg MinIO service account andclient.region=us-east-1. The config is present even whenICEBERG_REST_SOURCE=disabled; Spark still starts for ML-only users, and only lakehouse SQL fails until the catalog is enabled. -
MinIO credentials โ the worker, Spark Connect and history server authenticate with the scoped
MINIO_SPARK_ACCESS_KEY/MINIO_SPARK_SECRET_KEYservice account, not MinIO root. Its policy covers the Spark history, lakehouse, jars, checkpoints and landing buckets. Any other bucket returns Access Denied unless the job supplies its own credentials, as the Airflow lakehouse DAG does. -
Supabase Postgres โ Spark JDBC connector available; users add
--jars postgresql.jarand point atjdbc:postgresql://supabase-db:5432/${SUPABASE_DB_NAME}. No pre-wired connection. -
Zeppelin โ Zeppelin's Spark interpreter points at
spark://spark-master:7077(standalone Spark RPC). Spark Connect remains the JupyterHub/client path. Seeservices/zeppelin/README.md. -
Airflow โ
airflow-initseedsspark_defaultwhenSPARK_SOURCE=container. The sampleexample_etl_with_llm.pyDAG usesPythonOperatorthrough Spark Connect (sc://spark-connect:15002).The bundled Apache Spark provider supplies
SparkSubmitOperatorfor user DAGs. The manuallakehouse_spark_submit_smokeDAG usesAtlasSparkSubmitOperatorto disable the provider's incorrect :7077 polling and confirm driver status through REST.Atlas enables that backend-only endpoint at
spark-master:6066, with no host port or Kong route. See the Airflow guide. -
Redpanda โ with Redpanda enabled, every Spark role receives
SPARK_KAFKA_BOOTSTRAP_SERVERS. Use it askafka.bootstrap.serversforformat("kafka"). -
Prometheus + Grafana โ not wired. Spark exports no JMX metrics to Prometheus, and no Spark dashboard ships. Use the cAdvisor container metrics in Grafana.
4.1. Spark Connect sessions
The spark-connect sidecar publishes no host port, so sc://spark-connect:15002 resolves only inside the Docker backend-network. JupyterHub is the in-stack notebook client; host IDEs cannot reach it unless you publish the port yourself. For a managed remote endpoint, see ยง4.3.
Spark Connect is a long-lived application. SPARK_CONNECT_CORES_MAX=1 (spark.cores.max) leaves worker cores for standalone workloads such as Airflow cluster-mode drivers. Zeppelin is capped the same way by ZEPPELIN_SPARK_CORES_MAX; each start re-seeds it, so change it in .env. Raise either value (for example, for more Spark Connect parallelism) only when SPARK_WORKER_COUNT and the worker CPU limits leave free cores. Otherwise other applications stay PENDING.
Spark Connect reports Docker health once its backend-only listener accepts TCP connections on 15002. Wait-for-healthy tooling can check it with:
docker inspect --format '{{.State.Health.Status}}' ${PROJECT_NAME}-spark-connect
Expected result: starting during JVM startup, then healthy after
sc://spark-connect:15002 is accepting sessions. The probe runs inside the
container and does not publish 15002 to the host.
Minimal Spark Connect lakehouse smoke from an in-stack client:
from pyspark.sql import SparkSession
spark = SparkSession.builder.remote("sc://spark-connect:15002").getOrCreate()
spark.sql("CREATE NAMESPACE IF NOT EXISTS lakehouse.bronze")
spark.sql("CREATE TABLE IF NOT EXISTS lakehouse.bronze.t (id BIGINT, note STRING) USING iceberg")
spark.sql("SHOW NAMESPACES IN lakehouse").show()
4.2. Advanced Iceberg smoke
scripts/smoke-iceberg-advanced-sql.sh spark-connect
scripts/smoke-iceberg-advanced-sql.sh zeppelin
The advanced smoke is an opt-in check for the data-eng and all tracks. It adds no new service, no new SOURCE, and no new port; it uses the existing Spark, Iceberg REST, MinIO, JupyterHub, and Zeppelin topology. It covers:
MERGE INTO,VERSION AS OF,rollback_to_snapshot, andCREATE BRANCHwithspark.wap.branch;- schema evolution and nested JSON;
- Structured Streaming from
s3a://landing/into Iceberg, with checkpoints unders3a://checkpoints/; - maintenance calls such as
rewrite_data_files,expire_snapshots, andremove_orphan_files.
See
docs/operations/iceberg-advanced-smoke.md.
4.3. Cloud burst: Amazon EMR Serverless (optional)
A notebook or tool can use a managed Spark Connect endpoint instead of the in-stack sidecar, for example EMR Serverless interactive sessions.
A reference helper ships at services/spark/examples/emr_serverless_connect.py.
It runs the boto3 session lifecycle (start_session โ get_session_endpoint โ
SparkSession.builder.remote(...) with the session token โ terminate_session).
EMR Serverless support is this helper, not a SPARK_SOURCE value. Its sessions are ephemeral, billed and IAM-gated. The client needs a separate Python environment that matches EMR's Spark version. Terminate each session to stop billing. The helper script lists the exact version pins, IAM actions and session rules.
5. Capabilities & limitations
Support tier: experimental โ Capability contract declared; no cited cold-start, workflow, or upgrade qualification run yet (evidence at v0.1.0).
| Capability | Status | Verification | Notes |
|---|---|---|---|
| Standalone batch compute cluster | supported | tested | Atlas configures one Spark master, an operator-selected worker count, and backend-network submission surfaces for standalone jobs. |
| Spark Connect and history services | supported | tested | A health-checked backend-only Spark Connect sidecar serves remote sessions while the history server reads event logs from a provisioned MinIO bucket. |
| Iceberg and Kafka data paths | partial | tested | Atlas bakes and configures the Iceberg, S3A and Kafka connectors. Lakehouse and streaming operations require the optional Iceberg REST and Redpanda services. Live smoke remains opt-in. |
| Highly available Spark control plane | not-supported | documented | The stock standalone topology has one master, no recovery directory, and no YARN or Kubernetes scheduler integration. |
| Authenticated Spark web access | not-supported | tested | The direct SPARK_MASTER_UI_PORT and SPARK_HISTORY_PORT publishes are unauthenticated. The CORS-only Kong Spark routes are unauthenticated. The default HOST_BIND_IP=127.0.0.1: keeps direct ports loopback-bound. Set another value only for deliberate remote access. Before exposure beyond a trusted host, firewall or remove the direct ports, and use an authentication proxy or remove both Spark Kong routes. |
6. Troubleshooting
- History UI shows no jobs โ first check producer config: a driver must set
spark.eventLog.enabled=true+spark.eventLog.dir=s3a://spark-history/. Thespark-connectsidecar and Zeppelin'sSPARK_SUBMIT_OPTIONSalready set these globally, so any sc://spark-connect:15002 client + Zeppelin%sparkcell emits events automatically. User-drivenspark-submitjobs need to pass the same--confpair. Then confirm thatspark-initcreated the bucket:docker logs ${PROJECT_NAME}-spark-initends withspark-init: spark-history bucket ready. - Airflow cluster-mode task fails after submission โ use the Atlas operator from Airflow ยง5.1. The stock provider polls RPC
:7077; Atlas confirms driver status through REST:6066. Check master readiness withdocker exec ${PROJECT_NAME}-airflow-scheduler curl -fsS http://spark-master:6066/v1/submissions/readyz. For a task's driver status, queryhttp://spark-master:6066/v1/submissions/status/DRIVER_ID, replacingDRIVER_IDwith the ID from its task log. Keep port6066on the backend network; do not publish it. - Standalone jobs stay
PENDINGwhile Spark Connect is running. Check the master JSON (docker exec ${PROJECT_NAME}-spark-master curl -fsS http://localhost:8080/json/) and comparecoresusedwith the active app list. IfSpark Connect serveris consuming too much of the cluster, lowerSPARK_CONNECT_CORES_MAXor increaseSPARK_WORKER_COUNT/ worker CPU capacity. Do this before you run Airflow or Zeppelin standalone jobs. - Workers don't appear in the master UI โ Compose's
depends_on: spark-master: condition: service_healthyshould serialize this. If a worker stays "lost", checkdocker logs ${PROJECT_NAME}-spark-worker-1. - OOM in a worker โ Compose caps the worker container at
SPARK_WORKER_MEMORY_LIMIT(default4g).SPARK_WORKER_MEMORYis unset, so Spark sizes worker memory without regard to that cap, and the container can be OOM-killed. SettingSPARK_WORKER_MEMORYorSPARK_WORKER_CORESin.envhas no effect, because compose does not pass them. Add them to thespark-workerenvironment:block (or a compose override), below the cap, or keep executor memory requests under the cap. - Spark Connect refused โ the gRPC server runs on the
spark-connectsidecar, not onspark-master. Clients must usesc://spark-connect:15002from the backend network; the port is not published to the host. Check readiness with thedocker inspectcommand in ยง4.1: the status must behealthy.
7. Dependencies & Integrations
7.1. Current โ Upstream (this service calls)
| Service | Category |
|---|---|
| iceberg-rest | data |
| minio | data |
| redpanda | data |
7.2. Current โ Downstream (services that call this)
| Service | Category |
|---|---|
| kong | infra |
| airflow | agents |
| jupyterhub | apps |
| zeppelin | apps |
7.3. Architecture diagram

Open the full-size diagram for a full-screen view.
7.4. Future โ Missing pair integrations
No high-confidence opportunities identified.
7.5. Future โ Candidate new services
No high-confidence opportunities identified.
7.6. Future โ Unused features in this service
No high-confidence opportunities identified.