Apache Spark es un motor de procesamiento distribuido que ejecuta trabajos de datos en memoria y ofrece APIs para Python (PySpark), SQL, Scala y Java. En este tutorial instalarás Spark 4.2 en Ubuntu 24.04, lo configurarás como clúster standalone de un nodo (un master y un worker gestionados por systemd) y enviarás trabajos con spark-submit y PySpark. El mismo esquema sirve después para añadir más workers.

Requisitos previos

Para seguir esta guía necesitas:

  • Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath, con al menos 2 vCPU y 4 GB de RAM.
  • Un usuario no root con privilegios sudo.
  • Acceso SSH desde tu equipo, que usarás también para abrir un túnel hacia las interfaces web de Spark.

Spark 4.2 funciona con Java 17, 21 o 25 y con Python 3.10 o superior. Ubuntu 24.04 trae OpenJDK 21 y Python 3.12, así que ambos requisitos se cubren con paquetes del sistema.

Paso 1: Instalar Java

Spark se ejecuta sobre la JVM, por lo que necesitas un runtime de Java. Instala OpenJDK 21 sin componentes gráficos:

sudo apt update
sudo apt install -y openjdk-21-jre-headless

Comprueba la versión instalada:

java -version
openjdk version "21.0.8" 2025-07-15
OpenJDK Runtime Environment (build 21.0.8+9-Ubuntu-0ubuntu124.04.1)
OpenJDK 64-Bit Server VM (build 21.0.8+9-Ubuntu-0ubuntu124.04.1, mixed mode, sharing)

Python 3 ya viene instalado en Ubuntu 24.04 y es el intérprete que usará PySpark. Puedes confirmarlo con python3 --version.

Paso 2: Descargar y verificar Spark

Spark se distribuye como un archivo precompilado. Descarga la versión 4.2.0 empaquetada para Hadoop 3 desde el CDN de Apache:

cd /tmp
wget https://dlcdn.apache.org/spark/spark-4.2.0/spark-4.2.0-bin-hadoop3.tgz
wget https://downloads.apache.org/spark/spark-4.2.0/spark-4.2.0-bin-hadoop3.tgz.sha512

Comprueba que el archivo no está corrupto comparándolo con la suma SHA-512 publicada:

sha512sum -c spark-4.2.0-bin-hadoop3.tgz.sha512
spark-4.2.0-bin-hadoop3.tgz: OK

Paso 3: Instalar Spark en /opt/spark

Crea un usuario de sistema sin privilegios que ejecutará los procesos del clúster:

sudo useradd --system --home-dir /opt/spark --shell /usr/sbin/nologin spark

Extrae el archivo en /opt y crea un enlace simbólico /opt/spark. Así, al actualizar a otra versión solo tendrás que cambiar el enlace:

sudo tar -xzf spark-4.2.0-bin-hadoop3.tgz -C /opt
sudo ln -s /opt/spark-4.2.0-bin-hadoop3 /opt/spark
sudo mkdir -p /opt/spark/logs /opt/spark/work
sudo chown -R spark:spark /opt/spark-4.2.0-bin-hadoop3

Añade los binarios de Spark al PATH de todos los usuarios con un archivo en /etc/profile.d:

sudo nano /etc/profile.d/spark.sh
export SPARK_HOME=/opt/spark
export PATH="$PATH:$SPARK_HOME/bin"

Carga el archivo en la sesión actual y comprueba que spark-submit responde:

source /etc/profile.d/spark.sh
spark-submit --version
Welcome to
      ____              __
     / __/__  ___ _____/ /__
    _\ \/ _ \/ _ `/ __/  '_/
   /___/ .__/\_,_/_/ /_/\_\   version 4.2.0
      /_/

Using Scala version 2.13.16, OpenJDK 64-Bit Server VM, 21.0.8

Paso 4: Configurar el clúster standalone

El modo standalone es el gestor de clúster que incluye Spark: un proceso master reparte los recursos y uno o varios workers ejecutan los executors. No necesita Hadoop ni YARN.

Spark lee sus variables de entorno de conf/spark-env.sh. Crea el archivo:

sudo nano /opt/spark/conf/spark-env.sh
# Direccion en la que escuchan el master, el worker y sus interfaces web
SPARK_LOCAL_IP=127.0.0.1
SPARK_MASTER_HOST=127.0.0.1

# Recursos que este worker ofrece a los executors
SPARK_WORKER_CORES=2
SPARK_WORKER_MEMORY=2g

SPARK_LOG_DIR=/opt/spark/logs
SPARK_WORKER_DIR=/opt/spark/work

Con SPARK_LOCAL_IP=127.0.0.1 todos los puertos (7077 del master, 8080 y 8081 de las interfaces web, 4040 de cada aplicación) quedan ligados a localhost. Las interfaces web de Spark no tienen autenticación por defecto, así que no deben quedar expuestas a Internet. Ajusta SPARK_WORKER_CORES y SPARK_WORKER_MEMORY a tu servidor, dejando al menos 1 GB para el sistema operativo.

Define ahora los valores por defecto de los trabajos en spark-defaults.conf, para no tener que indicar el master en cada spark-submit:

sudo nano /opt/spark/conf/spark-defaults.conf
spark.master             spark://127.0.0.1:7077
spark.driver.memory      1g
spark.executor.memory    1g

Ajusta el propietario de los archivos nuevos:

sudo chown spark:spark /opt/spark/conf/spark-env.sh /opt/spark/conf/spark-defaults.conf

Paso 5: Crear servicios systemd para el master y el worker

Los scripts start-master.sh y start-worker.sh lanzan los procesos en segundo plano por defecto. Con la variable SPARK_NO_DAEMONIZE se ejecutan en primer plano, que es lo que systemd necesita para supervisarlos y enviar su salida al journal.

Crea la unidad del master:

sudo nano /etc/systemd/system/spark-master.service
[Unit]
Description=Apache Spark master
After=network-online.target
Wants=network-online.target

[Service]
User=spark
Group=spark
Environment=SPARK_NO_DAEMONIZE=true
ExecStart=/opt/spark/sbin/start-master.sh
Restart=on-failure

[Install]
WantedBy=multi-user.target

Crea la unidad del worker, que se registra en el master local:

sudo nano /etc/systemd/system/spark-worker.service
[Unit]
Description=Apache Spark worker
After=spark-master.service
Requires=spark-master.service

[Service]
User=spark
Group=spark
Environment=SPARK_NO_DAEMONIZE=true
ExecStart=/opt/spark/sbin/start-worker.sh spark://127.0.0.1:7077
Restart=on-failure

[Install]
WantedBy=multi-user.target

Recarga systemd y arranca los dos servicios:

sudo systemctl daemon-reload
sudo systemctl enable --now spark-master spark-worker

Comprueba que el worker se ha registrado en el master:

sudo journalctl -u spark-master --no-pager | grep -i "registering worker"
... INFO Master: Registering worker 127.0.0.1:39417 with 2 cores, 2.0 GiB RAM

También puedes ver los puertos en escucha con sudo ss -tlnp | grep java: deberían aparecer 7077, 8080 y 8081 en 127.0.0.1.

Paso 6: Acceder a la interfaz web del master

Como la interfaz solo escucha en localhost, ábrela a través de un túnel SSH. Ejecuta este comando en tu equipo local, sustituyendo your_user y your_server_ip:

ssh -L 8080:127.0.0.1:8080 your_user@your_server_ip

Con la sesión abierta, visita http://localhost:8080 en el navegador. Verás el estado del master como ALIVE, un worker activo con 2 cores y 2 GiB de memoria, y la lista de aplicaciones en ejecución y completadas.

Paso 7: Ejecutar un trabajo de ejemplo

La distribución incluye ejemplos listos para usar. Envía al clúster el cálculo de pi escrito en Python, repartido en 10 particiones:

spark-submit /opt/spark/examples/src/main/python/pi.py 10 2>/dev/null
Pi is roughly 3.141120

Se ha redirigido stderr a /dev/null solo para ocultar los logs de Spark. Si recargas la interfaz web, la aplicación PythonPi aparecerá en Completed Applications, lo que confirma que el trabajo se ejecutó en el worker y no en modo local.

Paso 8: Escribir tu propio trabajo PySpark

Crea un script que cuente las palabras más frecuentes del README.md incluido con Spark, usando la API de DataFrames:

nano ~/conteo_palabras.py
from pyspark.sql import SparkSession
from pyspark.sql.functions import col, explode, lower, split

spark = SparkSession.builder.appName("ConteoPalabras").getOrCreate()

texto = spark.read.text("/opt/spark/README.md")

palabras = (
    texto.select(explode(split(lower(col("value")), r"\W+")).alias("palabra"))
    .filter(col("palabra") != "")
)

conteo = palabras.groupBy("palabra").count().orderBy(col("count").desc())
conteo.show(5)

spark.stop()

No hace falta indicar el master en el código: spark-submit lo toma de spark-defaults.conf. Ejecuta el script:

spark-submit ~/conteo_palabras.py 2>/dev/null
+-------+-----+
|palabra|count|
+-------+-----+
|    the|   25|
|  spark|   18|
|     to|   17|
|    for|   12|
|    and|   10|
+-------+-----+
only showing top 5 rows

Las cifras exactas dependen de la versión del README. Para trabajar de forma interactiva, ejecuta pyspark: abre una consola de Python con la variable spark ya creada y conectada al clúster.

Añadir más workers

Para crecer a varios servidores, repite los pasos 1 a 3 en cada nodo nuevo y usa la red privada entre ellos:

  • En el master, cambia SPARK_LOCAL_IP y SPARK_MASTER_HOST a su IP privada y actualiza spark.master en spark-defaults.conf.
  • En cada worker, pon su propia IP privada en SPARK_LOCAL_IP y apunta el ExecStart de spark-worker.service a spark://ip_privada_master:7077.
  • Permite el tráfico entre nodos solo desde la subred privada, por ejemplo sudo ufw allow from 10.0.0.0/24. Los executors abren puertos dinámicos hacia el driver, por lo que filtrar únicamente el 7077 no es suficiente.

Solución de problemas

El worker no aparece en el master. Revisa sudo journalctl -u spark-worker -n 50. Si ves errores de conexión a 7077, comprueba que la URL del ExecStart coincide exactamente con SPARK_MASTER_HOST: el master rechaza conexiones dirigidas a un nombre distinto del que anuncia.

El trabajo se queda en espera con "Initial job has not accepted any resources". El worker no tiene memoria o cores suficientes para lo que pide la aplicación. Reduce spark.executor.memory o aumenta SPARK_WORKER_MEMORY y reinicia el worker con sudo systemctl restart spark-worker.

spark-submit: command not found. El PATH se carga al iniciar sesión. Cierra y abre la sesión SSH, o ejecuta source /etc/profile.d/spark.sh.

java.lang.OutOfMemoryError en el driver. Evita collect() sobre conjuntos grandes y sube spark.driver.memory en spark-defaults.conf.

Conclusión

Tienes Apache Spark 4.2 funcionando en Ubuntu 24.04 como clúster standalone, con el master y el worker supervisados por systemd, las interfaces web protegidas tras un túnel SSH y trabajos PySpark ejecutándose en el worker. Como siguientes pasos puedes activar el servidor de historial (spark.eventLog.enabled y start-history-server.sh) para conservar la interfaz de las aplicaciones terminadas, añadir workers en otros nodos por la red privada o leer datos en Parquet desde un almacenamiento S3 compatible.