TCDM — Tecnologías de Computación de Datos Masivos

Sesión 1 — Entorno Hadoop con contenedores¶

En esta primera sesión se pone en marcha el clúster Hadoop que se empleará durante todo el curso. El clúster ya está construido y publicado como imágenes de contenedor: aquí no se instala Hadoop, se arranca, se entiende y se comprueba un entorno distribuido pequeño con HDFS, YARN y MapReduce.

Este notebook mezcla explicación conceptual (celdas markdown) con órdenes que se ejecutan de verdad contra el clúster (celdas de código). No es necesario memorizar las órdenes; sí es importante entender en qué máquina se ejecuta cada una, con qué usuario y sobre qué sistema de ficheros actúa, porque esa distinción se repetirá en toda la asignatura.

Cómo se evalúa el trabajo de esta sesión. Cada sesión práctica termina con una sección «Evidencias para la siguiente sesión» que indica qué preparar. En una sesión posterior tendrás una reunión individual breve (unos minutos) con el profesor en la que deberás mostrar en vivo, en tu propio ordenador, las evidencias indicadas allí (el clúster en marcha, las órdenes ejecutadas, las interfaces web) y entregar una memoria breve (una o dos páginas) que explique lo realizado. Este esquema —evidencias en pantalla más memoria escrita, revisadas en una reunión breve— se repite en el resto de sesiones del curso.

Requisitos previos en Windows: WSL2 y Docker Desktop¶

Esta sesión ejecuta el kernel de Jupyter en tu propio equipo — a partir de S2 el kernel se ejecutará dentro del propio clúster, pero aquí todavía no existe clúster que lo aloje. Si usas Windows, !docker no funciona solo con WSL2 instalado: hace falta además Docker Desktop, correctamente configurado. Sigue estos pasos antes de empezar:

  1. Instala WSL2. Abre PowerShell como administrador y ejecuta:

    wsl --install
    

    Esto instala WSL2 y una distribución Ubuntu por defecto. Reinicia el equipo si te lo pide.

  2. Instala Docker Desktop para Windows desde docker.com y, durante o después de la instalación, comprueba en Settings → General que "Use the WSL 2 based engine" está activado.

  3. Activa la integración WSL para tu distribución. En Docker Desktop, ve a Settings → Resources → WSL Integration y activa el interruptor junto al nombre de tu distribución (por ejemplo, Ubuntu). Este paso es el que más se olvida: sin él, docker no se encuentra dentro de WSL2 aunque Docker Desktop esté instalado y en ejecución.

  4. Arranca Docker Desktop y espera a que quede en marcha (no basta con tenerlo instalado; el motor debe estar realmente arrancado).

  5. Instala Visual Studio Code con las extensiones WSL, Python y Jupyter. Abre el proyecto desde dentro de WSL2 —por ejemplo, ejecutando code . desde una terminal de Ubuntu, o con "Connect to WSL" desde la paleta de comandos—, no como una carpeta nativa de Windows. Es preferible clonar el repositorio dentro del sistema de ficheros de WSL2 (p. ej. ~/tcdm-public) en lugar de /mnt/c/...: además de evitar problemas de integración, el rendimiento de E/S es mucho mejor.

  6. El kernel del notebook será un Python de WSL2 (no uno de Windows) y lo prepara Visual Studio Code. Al abrir s1/s1.ipynb, pulsa Select Kernel → Python Environments → Create Python Environment → Venv: crea un entorno virtual .venv en la carpeta abierta, instala en él ipykernel (acepta si lo pide al ejecutar la primera celda) y lo deja elegido como kernel de la sesión.

  7. Instala make y el soporte de entornos virtuales de Python en Ubuntu de WSL2. make se usa a partir de S2 (make -C entorno ... desde la terminal) y, sin python3-venv, Visual Studio Code no puede crear el entorno del paso 6:

    sudo apt update && sudo apt install -y make python3-venv
    
  8. Comprueba antes de continuar, en una terminal de WSL2:

    docker --version
    docker compose version
    docker info
    make --version
    python3 --version
    

    Si algún comando falla, revisa los pasos 2–4 (o el 7, si faltan make o python3) antes de seguir: el resto del notebook asume que estas órdenes ya funcionan desde la propia terminal de WSL2. La última muestra la versión de Python con la que Visual Studio Code creará el entorno del paso 6; anótala si tienes que consultar un problema con el kernel.

En Linux y macOS no hacen falta WSL2 ni la integración anterior: basta con tener Docker (Docker Engine en Linux, o Docker Desktop en macOS) instalado y en ejecución, y make en el equipo (en Linux, paquete make; en macOS viene con las herramientas de línea de órdenes de Xcode, xcode-select --install). Sí se necesitan, igual que en Windows, Visual Studio Code con las extensiones Python y Jupyter y un Python en el equipo, con el que Visual Studio Code crea el entorno del kernel de esta sesión (en Debian y Ubuntu hace falta el paquete python3-venv). A partir de S2 el kernel y todas las bibliotecas vienen ya instalados dentro del contenedor namenode.

Obtener el material de las sesiones¶

El material se distribuye en el repositorio público dsevilla/tcdm-public, rama 26-27. En una terminal de tu equipo (en Windows, la terminal de Ubuntu de WSL2):

Animación: en una terminal de tu equipo (host) se teclean git clone, cd tcdm-public y code . &

git clone --depth 1 --branch 26-27 https://github.com/dsevilla/tcdm-public.git ~/tcdm-public
cd ~/tcdm-public

La animación abrevia la orden de clonado: la completa es la del bloque anterior, que fija la rama 26-27 y el directorio de destino. Su última orden, code . &, abre la carpeta en Visual Studio Code y devuelve el control a la terminal.

A este directorio (~/tcdm-public) lo llamaremos raíz de la distribución. Contiene un directorio por sesión (s1/, s2/, ...) y entorno/, con los ficheros Docker Compose y el Makefile del laboratorio. entorno/ crece a medida que se publican sesiones: cada una trae sólo lo que necesita. Antes de empezar una sesión nueva, actualiza la copia desde la raíz:

git pull

Diapositivas de la sesión¶

Las diapositivas se generan con el paquete jupyter-notebook-slide. El alias %%diapositiva permite mantener en español los tipos usados en la sesión y produce la misma salida HTML en Jupyter, Colab y la referencia HTML publicada.

En cada sesión encontrarás varios tipos de diapositivas que te ayudarán a seguir el hilo:

  • Sección (titulo): abre la sesión y resume qué vamos a ver y qué haremos.
  • A continuación (avance): anuncia el contenido de la parte siguiente, para saber en qué punto del programa estamos.
  • Recapitulación (resumen): condensa la parte anterior en puntos clave para recordarlos y revisarlos después.
  • Pregunta guía (pregunta): plantea cuestiones que orientan lo que viene a continuación; conviene intentar responderlas antes de seguir.
  • Evaluación de la sesión (evaluacion): recuerda cómo se evalúa el trabajo de esta sesión.

La celda siguiente instala y configura ese paquete: ejecútala una vez por sesión de kernel, antes que cualquier celda %%diapositiva. Si no, esas celdas fallan con UsageError: Cell magic %%diapositiva not found. Las diapositivas acompañan la explicación; el trabajo práctico no depende de ellas.

In [ ]:
%pip install -q "jupyter-notebook-slide @ git+https://github.com/dsevilla/jupyter-notebook-slide.git"
%load_ext notebook_slide

import notebook_slide as jnbs

# Colores para TCDM.
jnbs.configure(
    background="#eaf2f8",
    foreground="#1f2933",
    border="#c9d6e1",
    heading="#0c304d",
    subheading="#0c304d",
    link="#174f7a",
    code_background="#eaf2f8",
    code_foreground="#0c304d",
    quote_background="#ffffff",
    font_family="Atkinson Hyperlegible, Inter, Aptos, Segoe UI, Helvetica, Arial, sans-serif",
    font_url="https://fonts.googleapis.com/css2?family=Atkinson+Hyperlegible:ital,wght@0,400;0,700;1,400;1,700&display=swap",
)
jnbs.register_slide_type("avance", "A continuación", "#174f7a")
jnbs.register_slide_type("resumen", "Recapitulación", "#a54467")
jnbs.register_slide_type("pregunta", "Pregunta guía", "#0c304d")
jnbs.register_slide_type("evaluacion", "Evaluación de la sesión", "#b3701a")
jnbs.register_slide_type(
    "titulo",
    "Sección",
    "#174f7a",
    layout="title",
    background="linear-gradient(135deg, #0c304d, #174f7a 62%, #a54467)",
    foreground="#ffffff",
    border="transparent",
    heading="#ffffff",
    subheading="#dceaf4",
)
jnbs.register_alias("diapositiva")

Sesión 1: HDFS, YARN y MapReduce

  • Por qué existe Big Data y qué problema resuelve MapReduce
  • HDFS, YARN y MapReduce: qué papel tiene cada pieza
  • Arrancamos un clúster real de cuatro máquinas simuladas con contenedores
  • Comprobamos HDFS y YARN con datos y trabajos reales
  • Ejecutamos WordCount y vemos dónde se calculó cada parte
Evaluación de la sesión

Cómo se evalúa esta sesión

  • Cada sesión termina con «Evidencias para la siguiente sesión»
  • Reunión individual breve con el profesor en una sesión posterior
  • Se muestra en vivo, en tu ordenador, lo que pide esa sección
  • Se entrega también una memoria breve (una o dos páginas)
A continuación

Las tres piezas de Hadoop

  • HDFS guarda los ficheros como bloques replicados: el NameNode lleva los metadatos y los DataNodes el contenido
  • YARN reparte CPU y memoria: ResourceManager, NodeManagers y un ApplicationMaster por aplicación
  • MapReduce calcula sobre esos datos: map → shuffle → reduce
  • La idea que recorre la sesión: dónde están los datos (HDFS) no es dónde se ejecutan las tareas (YARN)

Hadoop, HDFS, YARN y MapReduce¶

Apache Hadoop es una plataforma de software libre para almacenar y procesar grandes conjuntos de datos mediante un clúster de ordenadores. Su diseño parte de una idea sencilla: cuando el volumen de información deja de caber o de procesarse cómodamente en una sola máquina, se reparten los datos y el trabajo entre varios nodos. El sistema debe coordinar esos nodos, aprovechar sus recursos y seguir funcionando cuando alguno deja de estar disponible.

En esta sesión intervienen tres piezas principales:

  • HDFS proporciona almacenamiento distribuido;
  • YARN negocia y asigna los recursos de cómputo del clúster;
  • MapReduce proporciona un modelo y un motor de procesamiento distribuido que se ejecuta utilizando los recursos concedidos por YARN.

Hadoop puede acceder también a otros sistemas de almacenamiento, como el sistema local o almacenes de objetos compatibles con S3. En esta primera parte del curso se utiliza HDFS porque permite estudiar de forma explícita la división en bloques, la replicación y la relación entre almacenamiento y procesamiento. HDFS alojará tanto un data lake de ficheros Parquet como un data warehouse gobernado por un catálogo. Más adelante se repetirá parte del diseño sobre un almacenamiento de objetos compatible con Amazon S3 (sesión 3) para comparar ambos soportes.

HDFS como sistema de ficheros distribuido¶

HDFS, Hadoop Distributed File System, está diseñado para almacenar ficheros grandes en un conjunto de máquinas relativamente convencionales. Un fichero se divide en bloques y esos bloques se distribuyen entre los DataNodes. El cliente sigue viendo un único nombre de fichero: la división y la localización física de cada bloque quedan ocultas tras la interfaz de HDFS.

El sistema favorece el acceso secuencial con un ancho de banda elevado. No intenta comportarse como un disco local de baja latencia:

  • funciona bien con ficheros grandes y lecturas completas o extensas;
  • permite que varios nodos lean bloques diferentes en paralelo;
  • obtiene tolerancia a fallos manteniendo réplicas de los bloques;
  • tiene una latencia mayor que un sistema de ficheros local;
  • es poco eficiente cuando se almacenan cantidades enormes de ficheros muy pequeños, porque cada uno requiere metadatos en el NameNode;
  • está orientado a escribir una vez y leer muchas veces;
  • no ofrece el mismo modelo de actualización aleatoria ni de múltiples escritores concurrentes que un sistema de ficheros POSIX.

Los conceptos esenciales de HDFS son:

  • NameNode: mantiene el espacio de nombres, los permisos y la relación entre ficheros, bloques y DataNodes. Coordina el acceso, pero no guarda el contenido ordinario de todos los ficheros de usuario.
  • DataNode: almacena bloques en su sistema de ficheros local y atiende las lecturas y escrituras solicitadas por los clientes.
  • Bloque HDFS: unidad lógica en la que se divide un fichero. En este laboratorio se utilizan bloques de 64 MiB para que sea más fácil observar su distribución con datos de tamaño moderado.
  • Replicación: mantenimiento de varias copias de un bloque en DataNodes diferentes. El laboratorio solicita tres réplicas porque dispone de tres DataNodes.
  • Espacio de nombres: jerarquía de directorios y ficheros que presenta HDFS. Se parece visualmente a un sistema Unix, pero no es la jerarquía local de ninguno de los contenedores.

Una instalación de producción necesita además una estrategia de alta disponibilidad para el NameNode (NameNodes activo/standby, JournalNodes, etc.). El laboratorio local utiliza un único NameNode y no simula esa parte de una instalación de producción.

YARN como gestor de recursos¶

YARN, Yet Another Resource Negotiator, separa la gestión de los recursos del clúster del motor que procesa los datos. Gracias a esa separación, distintas aplicaciones (MapReduce, y más adelante Spark) pueden solicitar CPU y memoria sin que cada una implemente su propio planificador global.

  • el ResourceManager mantiene la visión global de los recursos y decide dónde se pueden asignar contenedores de ejecución;
  • cada NodeManager anuncia la CPU y la memoria disponibles en su nodo, inicia los contenedores que le asigna YARN y supervisa su ejecución;
  • cada aplicación utiliza un ApplicationMaster, que negocia recursos para sus tareas y sigue su progreso.

Un contenedor YARN no es necesariamente un contenedor Docker: en la terminología de YARN es una asignación de recursos (memoria y vcores) en un NodeManager. En este laboratorio esos contenedores YARN se ejecutan dentro de los contenedores Docker que representan los nodos del clúster.

MapReduce como modelo de procesamiento¶

MapReduce organiza un cálculo distribuido alrededor de dos tipos de función. Las tareas map leen fragmentos de la entrada y producen pares clave-valor intermedios. Hadoop agrupa y transfiere esos pares durante la fase de shuffle. Las tareas reduce reciben los valores asociados a cada clave y generan el resultado final.

MapReduce no es el único motor que puede trabajar sobre Hadoop: Spark también lee HDFS y solicita recursos a YARN, y se usará más adelante en la asignatura (sesión 4). WordCount se ejecuta aquí con MapReduce porque permite observar con pocas órdenes todo el recorrido de una aplicación distribuida:

  1. luser copia varios ficheros de entrada a HDFS;
  2. el NameNode registra sus nombres, bloques y ubicaciones;
  3. los DataNodes almacenan las réplicas de esos bloques;
  4. el cliente envía WordCount al ResourceManager;
  5. el ApplicationMaster solicita contenedores a YARN;
  6. los NodeManagers ejecutan las tareas map y reduce;
  7. el resultado vuelve a escribirse en HDFS.

Seguir este flujo permite distinguir entre dónde están los datos (responsabilidad de HDFS) y dónde se ejecutan las tareas (decisión de YARN).

Referencias¶

Para profundizar en estos temas, se recomienda:

  • White, Tom (2015). Hadoop: The Definitive Guide (4ª edición). O'Reilly Media. — La referencia clásica y completa sobre Hadoop, HDFS, YARN y MapReduce.
  • Miner, Donald & Shook, Adam (2018). MapReduce Design Patterns (2ª edición). O'Reilly Media. — Patrones y mejores prácticas para escribir aplicaciones MapReduce eficientes.
  • Lin, Jimmy & Dyer, Chris (2010). Data-Intensive Text Processing with MapReduce. Morgan & Claypool. — Enfoque práctico en procesamiento de texto a gran escala.
A continuación

De dónde sale Hadoop

  • Big Data: volumen, velocidad y variedad; ninguna se resuelve comprando un ordenador más grande
  • Google publica GFS (2003) y MapReduce (2004); Hadoop los reimplementa como HDFS y MapReduce
  • YARN llega con Hadoop 2: separa los recursos del motor, y por eso Spark también podrá usarlos
  • Lo comprobaremos hoy: bloques grandes y replicados, y cómputo que va a donde están los datos

Por qué existe Big Data y por qué apareció MapReduce¶

Big Data (Wikipedia): se refiere al estudio y a las aplicaciones de conjuntos de datos tan grandes y complejos que las aplicaciones tradicionales de procesamiento de datos resultan inadecuadas para tratarlos.

La definición señala tres retos que siguen vigentes: el volumen de datos que hay que almacenar, la velocidad con la que se generan y se necesitan procesar, y la variedad de formatos —texto, sensores, registros de aplicación, vídeo— que conviven en un mismo sistema. Ninguno de los tres se resuelve comprando un ordenador más grande: en algún momento un único disco, una única memoria o un único procesador dejan de ser suficientes, y hay que repartir los datos y el trabajo entre varias máquinas.

Ese cambio de escala llegó con una motivación muy concreta. En 2003 y 2004, Google publicó dos artículos —The Google File System (Ghemawat, Gobioff y Leung) y MapReduce: Simplified Data Processing on Large Clusters (Dean y Ghemawat)— que describían cómo almacenar y procesar datos sobre miles de ordenadores convencionales, asumiendo que algunos de ellos fallarían mientras el trabajo estuviera en marcha. Hacia 2005, Doug Cutting y Mike Cafarella se basaron en esas ideas para crear Apache Hadoop, el proyecto de código abierto que reimplementa GFS como HDFS y ese modelo de programación como MapReduce. YARN se añadió después, en Hadoop 2, para separar la gestión de recursos del clúster del motor de procesamiento concreto que los utiliza; por eso hoy MapReduce es una de varias aplicaciones que pueden pedir recursos a YARN, y Spark —que se usará en una sesión posterior— es otra.

Esa historia explica varias decisiones de diseño que se van a comprobar en esta misma sesión: por qué HDFS divide los ficheros en bloques grandes y los replica en vez de confiar en un único disco, por qué el cómputo se envía a donde están los datos en lugar de mover los datos al cómputo, y por qué el sistema está pensado para seguir funcionando —no para bloquearse— cuando un nodo deja de responder.

A continuación

El mapa de tecnologías Big Data

  • Un catálogo para consultar, no para memorizar: diez áreas, de la búsqueda a la visualización
  • Almacenamiento y formatos que usa el curso: HDFS, S3 (RustFS), Parquet, Arrow e Iceberg
  • Procesamiento que usa el curso: Hadoop, Spark, Trino, DuckDB y Polars
  • Todo el laboratorio se ejecuta sobre Docker

Retos tecnológicos de Big Data¶

La gestión de Big Data requiere abordar múltiples desafíos tecnológicos en diferentes áreas:

Búsqueda y refinamiento¶

Para localizar y limpiar datos de múltiples fuentes dispersas y heterogéneas:

  • Lucene
  • Solr
  • Elasticsearch
  • OpenSearch — fork abierto de Elasticsearch tras el cambio de licencia
  • Meilisearch — motor de búsqueda ligero y moderno
  • OpenRefine

Serialización¶

Transformar datos entre distintos sistemas y formatos de almacenamiento:

  • JSON
  • BSON
  • Apache Avro
  • Google Protocol Buffers
  • Apache Arrow — formato columnar en memoria, base de Pandas, Polars y DuckDB
  • gRPC — RPC moderno construido sobre Protocol Buffers

Sistemas de almacenamiento¶

Distribuir el almacenamiento entre múltiples servidores para máximo rendimiento:

  • HDFS
  • Amazon S3
  • MinIO — almacenamiento de objetos compatible con S3, autoalojable
  • RustFS — almacenamiento de objetos compatible con S3 escrito en Rust; es el que usa el laboratorio en la sesión 3
  • Google Cloud Storage
  • Azure Data Lake Storage

Servidores¶

Infraestructura en la nube para escalar recursos según demanda:

  • Amazon EC2
  • Google Cloud Platform
  • Azure
  • OpenShift
  • Tanzu
  • Kubernetes — orquestación de contenedores, estándar de facto para desplegar clústeres
  • Docker — la tecnología de contenedores usada en este propio laboratorio

Procesamiento¶

Plataformas y herramientas para procesar datos distribuidos:

  • Hadoop
  • Hive
  • Spark
  • Flink
  • Kafka — la plataforma de streaming de eventos más extendida
  • Airflow
  • Dagster — orquestador de datos moderno, alternativa a Airflow
  • dbt — transformación de datos como código dentro del almacén analítico
  • Beam
  • Dask
  • Trino — motor de consultas SQL distribuido sobre múltiples fuentes
  • DuckDB — motor analítico embebido, muy popular para análisis local
  • Polars — DataFrames de alto rendimiento, alternativa moderna a pandas
  • Kinesis

Bases de datos¶

Almacenamiento de datos estructurados y semiestructurados a gran escala:

  • HBase
  • Cassandra
  • MongoDB
  • Amazon DynamoDB
  • Google BigTable
  • Parquet
  • Apache Iceberg — formato de tabla abierto sobre almacenamiento de objetos
  • Delta Lake — formato de tabla con transacciones ACID, alternativa a Iceberg
  • ClickHouse — base de datos analítica columnar de muy alto rendimiento
  • Snowflake — almacén de datos en la nube totalmente gestionado
  • BigQuery — almacén de datos serverless de Google Cloud

Análisis y BI¶

Análisis estadístico y visualización de datos:

  • R
  • Greenplum
  • Splunk
  • Tableau
  • PowerBI
  • Apache Superset — BI de código abierto
  • Metabase — BI de código abierto sencillo de desplegar

Procesamiento de lenguaje natural¶

Análisis de contenidos textuales generados por usuarios:

  • Natural Language Toolkit
  • spaCy — librería de NLP de producción, más rápida que NLTK
  • Hugging Face Transformers — modelos de lenguaje preentrenados y pipelines de NLP

Machine Learning y Deep Learning¶

Automatización de decisiones basadas en datos:

  • Weka
  • SciKit-learn
  • PyTorch
  • TensorFlow
  • Keras
  • Hugging Face — repositorio y herramientas para modelos preentrenados
  • XGBoost — gradient boosting de referencia para datos tabulares
  • LightGBM — gradient boosting rápido, alternativa a XGBoost
  • JAX — computación numérica de alto rendimiento para investigación en ML

Visualización¶

Representación gráfica de datos para facilitar su comprensión:

  • R
  • D3.js
  • Looker Studio — antes Google Data Studio
  • Observable — cuadernos interactivos para visualización con D3

Panorama de tecnologías Big Data¶

El ecosistema de Big Data es amplio y en continua evolución. La siguiente imagen muestra el panorama de 2021 con las principales categorías y soluciones disponibles:

2021 MAD Big Data Landscape

A continuación

Por qué Hadoop asume que todo falla

  • Muchas máquinas baratas cuestan menos que pocas caras, pero fallan: con 1.000 nodos, un fallo al día
  • Los nodos fallan → HDFS replica los bloques y YARN relanza las tareas
  • La red es lenta → la computación va a los datos, no los datos a la computación
  • Programar en distribuido es difícil → el usuario sólo escribe map() y reduce()

Procesamiento del Big Data: por qué Hadoop existe¶

Cuando los datos dejan de caber en una máquina, la solución obvia es usar varias máquinas. Pero hay un dilema económico:

  • Opción A: máquinas caras y confiables. Si compras servidores enterprise de alta calidad, son relativamente fiables, pero muy caros. Procesar petabytes de datos exigiría una inversión gigantesca en hardware.

  • Opción B: máquinas baratas que fallan. Comprar cientos o miles de máquinas commodity (baratas, convencionales) es mucho más económico. El problema es que fallan continuamente. Con miles de máquinas, algo está fallando casi todo el tiempo.

La respuesta de Hadoop: usar la opción B, pero asumir y gestionar automáticamente los fallos en el software. No es que Hadoop tolere los fallos a pesar de usar máquinas baratas; es que la tolerancia a fallos automática es lo que hace económicamente viable usar máquinas baratas en primer lugar.

Para que esto funcione, necesitamos:

  • Escalabilidad con cientos o miles de máquinas y decenas de miles de discos
  • Equipos y redes de bajo coste (aunque poco fiables y con elevadas latencias)
  • Tolerancia a fallos automática — no como un lujo, sino como requisito fundamental
  • Facilidad de programación — el usuario no debe pensar en fallos; el sistema los gestiona

Problemas en la ejecución y cómo los resuelve Hadoop¶

La realidad de usar máquinas baratas plantea tres desafíos que Hadoop debe resolver:

Desafío 1: Los nodos baratos fallan constantemente¶

El problema:

  • Tiempo medio entre fallos (MTBF) para 1 nodo: ~3 años
  • Tiempo medio entre fallos para 1.000 nodos: ~1 día

Con 10.000 nodos, el modelo proporcional sugiere que algo falla aproximadamente cada 2,6 horas. Sin tolerancia a fallos automática, el trabajo nunca terminaría.

Cómo lo resuelve Hadoop:

  • HDFS replica cada bloque en varios nodos (típicamente 3). Cuando un nodo falla, los datos siguen disponibles en otros.
  • MapReduce y YARN relanzan automáticamente tareas fallidas en otros nodos.
  • El usuario no necesita intervenir: el sistema detecta el fallo, reasigna el trabajo y continúa.

Desafío 2: Las redes baratas tienen elevada latencia¶

El problema:

  • Una red de centro de datos enterprise es cara y rápida.
  • Una red que conecta cientos de máquinas baratas tiene mucha latencia.
  • Mover petabytes por la red es lentísimo.

Cómo lo resuelve Hadoop:

  • Llevar la computación a los datos, no los datos a la computación.
  • MapReduce lanza tareas map en los nodos donde están los bloques de entrada.
  • El resultado local se procesa antes de transferirse por la red.
  • Resultado: minimiza el movimiento de datos entre máquinas.

Desafío 3: Programar un sistema distribuido es difícil¶

El problema:

  • Sin abstracciones, los usuarios tendrían que escribir código para:
    • Particionar datos entre nodos
    • Serializar y deserializar
    • Detectar y recuperar fallos
    • Sincronizar trabajo distribuido
  • Esto es tan complejo que la mayoría de errores sería no por lógica de negocio, sino por gestión distribuida.

Cómo lo resuelve Hadoop:

  • MapReduce oculta la complejidad distribuida tras un modelo de programación simple.
  • El usuario escribe dos funciones: map() y reduce().
    • map() procesa pares clave-valor y emite pares intermedios.
    • reduce() agrupa valores por clave.
  • El sistema Hadoop:
    • Distribuye el trabajo automáticamente
    • Gestiona serialización y transferencia de datos
    • Detecta y recupera fallos sin intervención del usuario
    • Coordina tareas map y reduce

Conclusión: La combinación de HDFS (almacenamiento tolerante a fallos), MapReduce (modelo de programación simple) y YARN (coordinación de recursos) hace posible que un usuario escriba map() y reduce() sin pensar en cientos de máquinas que fallan. Eso es lo que hace a Hadoop revolucionario.

Recapitulación

Las piezas antes de tocarlas

  • HDFS guarda bloques replicados; YARN decide dónde se ejecutan las tareas
  • MapReduce reparte un cálculo en funciones map y reduce sobre esos bloques
  • Estas piezas nacieron para repartir datos y trabajo entre máquinas, no para comprar una más grande
A partir de aquí se arranca un clúster real y se comprueba sobre él.

Objetivos de la sesión¶

Al finalizar esta sesión deberías poder:

  • describir la arquitectura de Hadoop y sus componentes principales;
  • explicar la configuración de un clúster Hadoop en contenedores Docker;
  • iniciar, detener y regenerar el clúster con Docker Compose;
  • distinguir el sistema de ficheros local de cada contenedor del espacio de nombres distribuido de HDFS;
  • relacionar NameNode y DataNode con el almacenamiento de datos;
  • relacionar ResourceManager y NodeManager con la ejecución de trabajos;
  • consultar el estado de los tres nodos de trabajo;
  • crear, copiar, mover, leer y borrar ficheros en HDFS como luser;
  • interpretar el factor de replicación y la ubicación de los bloques;
  • enviar un trabajo MapReduce a YARN y consultar su resultado;
  • recuperar un clúster limpio después de una prueba fallida.

El entorno debe quedar operativo al terminar la sesión porque se reutilizará en sesiones posteriores con Parquet/Arrow, Spark, Trino y tablas Iceberg.

A continuación

Preparamos el entorno y conocemos el clúster

  • Comprobamos que Docker y Docker Compose funcionan en el equipo
  • El laboratorio simula 4 máquinas: namenode, datanode1, datanode2, datanode3
  • Vemos qué recursos anuncia cada contenedor y qué imágenes descarga Compose

Antes de empezar¶

Se necesita Docker Engine o Docker Desktop con Docker Compose v2, iniciado y con capacidad para descargar las imágenes publicadas del curso. Para esta sesión basta con asignar al menos 8 GiB de memoria a Docker y detener otros contenedores que no sean necesarios. Los límites del Compose (4 GiB para namenode y 3 GiB por DataNode) son techos, no reservas: el clúster de S1 usa bastante menos. Las sesiones con Spark, a partir de S4, se acercan más a esos límites y conviene subir la memoria de Docker hacia 14 GiB si el equipo lo permite.

Las celdas de este notebook suponen que Jupyter usa como directorio de trabajo el propio directorio del notebook (s1/), que es hermano de entorno/ en la distribución de sesiones. Por eso las rutas del entorno se escriben como ../entorno/....

Dónde se ejecuta cada orden¶

En esta sesión el kernel de Jupyter se ejecuta en tu equipo, fuera del clúster. Casi todas las celdas tienen esta forma:

!docker exec namenode su - luser -c 'hdfs dfs -ls /'
│ │           │        │            └─ orden que se ejecuta en el contenedor
│ │           │        └─ como el usuario luser, con su entorno de login
│ │           │           (PATH con hdfs, yarn, java...)
│ │           └─ dentro del contenedor namenode
│ └─ el cliente Docker de tu equipo entra en un contenedor
└─ Jupyter pasa el resto de la línea a una shell de tu equipo

Por tanto hay tres niveles:

Nivel Ejemplo Dónde se ejecuta
Shell de tu equipo !docker compose -f ../entorno/compose-hadoop-cluster.yml ps En tu equipo; sólo necesita el cliente Docker
Contenedor !docker exec namenode ... Dentro de namenode (o del datanodeN que se indique)
Usuario del contenedor su - hdadmin -c '...' o su - luser -c '...' hdadmin administra los demonios; luser trabaja con los datos

hdfs, yarn o java no están instalados en tu equipo: sólo existen dentro de los contenedores. Por eso !hdfs dfs -ls / a secas falla en esta sesión. A partir de S2 el kernel se ejecutará dentro de namenode y la misma orden se escribirá sin docker exec.

Comprobar el cliente Docker¶

La salida debe identificar una versión de Docker. Esto sólo confirma que existe el cliente; todavía no demuestra que el motor de contenedores esté en ejecución.

In [ ]:
!docker --version

Comprobar Docker Compose¶

El clúster está descrito como un conjunto de servicios coordinados que se gestionan con el complemento Compose v2, invocado como parte de la orden docker. Se espera una salida similar a Docker Compose version v....

In [ ]:
!docker compose version

Comprobar el motor de contenedores¶

Esta orden contacta con el daemon de Docker y muestra información del equipo y de los recursos que administra. Si aparece un error como Cannot connect to the Docker daemon, hay que iniciar Docker Desktop o el servicio Docker antes de continuar: repetir las celdas de Hadoop no soluciona un daemon que no está disponible.

In [ ]:
!docker info
A continuación

El clúster que vamos a usar

  • Cuatro contenedores: namenode (NameNode, ResourceManager y Timeline Server) y tres datanodeN (DataNode y NodeManager)
  • Límite de Docker ≠ memoria anunciada a YARN: 3072 MiB por DataNode, 2560 MiB para las aplicaciones
  • Dos imágenes ya construidas, con Hadoop 3.5.0 y Java 17; demonios como hdadmin, ejercicios como luser
  • HDFS ya trae /user/luser, /datalake/raw, /datalake/silver, /datalake/gold y /warehouse

El clúster que se va a utilizar¶

El laboratorio simula un clúster de cuatro ordenadores mediante cuatro contenedores que comparten una red Docker, tienen nombres DNS estables dentro de ella y pueden comunicarse como si fueran hosts separados.

Contenedor Servicios Responsabilidad principal
namenode NameNode, ResourceManager y Timeline Server Metadatos de HDFS, coordinación de recursos de cómputo e histórico de aplicaciones YARN
datanode1 DataNode y NodeManager Almacena bloques y ejecuta tareas
datanode2 DataNode y NodeManager Almacena bloques y ejecuta tareas
datanode3 DataNode y NodeManager Almacena bloques y ejecuta tareas
Despliegue Hadoop de S1 en cuatro contenedores de la red hadoop-cluster: namenode aloja NameNode, ResourceManager y ApplicationHistoryServer; cada datanode aloja DataNode y NodeManager. Las líneas azul verdosas representan HDFS y las naranjas YARN. Cada DataNode tiene un límite Docker de 2 vCPU y 3072 MiB y anuncia 2 vcores y 2560 MiB a YARN.
Origen: S1, «El clúster que se va a utilizar». HDFS coordina los bloques; YARN asigna recursos a NodeManagers. Los marcos agrupan los procesos de cada contenedor Docker.

Abrir SVG editable · Descargar PNG

El NameNode no contiene una copia completa de los ficheros de usuario: conserva el espacio de nombres, los permisos y la relación entre ficheros, bloques y DataNodes; los DataNodes son quienes almacenan el contenido de los bloques. De forma análoga, el ResourceManager decide dónde se pueden ejecutar aplicaciones, mientras que los NodeManagers ofrecen recursos y ponen en marcha sus tareas.

El Timeline Server recibe y conserva información sobre las aplicaciones YARN para poder consultarlas después de que terminen. En este laboratorio comparte el contenedor del NameNode y del ResourceManager, aunque se publica con el nombre DNS timelineserver para que los clientes usen una dirección coherente con una instalación en la que fuera un host separado.

Recursos asignados¶

Contenedor Límite Docker Recursos anunciados a YARN
namenode 2 vCPU y 4096 MiB No ejecuta NodeManager
datanode1 2 vCPU y 3072 MiB 2 vcores y 2560 MiB
datanode2 2 vCPU y 3072 MiB 2 vcores y 2560 MiB
datanode3 2 vCPU y 3072 MiB 2 vcores y 2560 MiB

El límite de memoria de Docker y la memoria anunciada a YARN no son la misma cosa. Cada DataNode puede consumir como máximo 3072 MiB, pero el NodeManager sólo ofrece 2560 MiB a los contenedores de las aplicaciones; los 512 MiB restantes permiten ejecutar el propio DataNode, el NodeManager y otros procesos auxiliares. namenode dispone de 4096 MiB porque en sesiones posteriores alojará también Jupyter y el driver de Spark. Los dos vcores de cada DataNode permiten que YARN ubique más adelante un ApplicationMaster y un executor de Spark sin dejar el nodo sin capacidad para otras tareas.

Imágenes del curso¶

Docker Compose descarga dos imágenes ya preparadas:

Imagen Contenedores que la utilizan
dsevilla/namenode-image:26-27 namenode
dsevilla/datanode-image:26-27 Los tres datanodeN

Un mismo contenedor DataNode ejecuta también un NodeManager porque en Hadoop resulta habitual acercar el procesamiento a los datos. Ambas imágenes incorporan Hadoop 3.5.0 y Java 17; los demonios se ejecutan como hdadmin y los ejercicios como luser. Un poco más abajo se explica cómo se construyen estas imágenes, aunque no es necesario reconstruirlas para seguir la sesión.

Identidades y seguridad del laboratorio¶

HDFS utiliza autenticación simple: cada cliente se presenta con su propia identidad. Cuando se ejecuta su - luser, Hadoop realiza las operaciones como luser; con su - hdadmin, como el usuario administrativo. La autenticación simple permite centrarse en HDFS y YARN, pero no demuestra criptográficamente quién es el cliente: este entorno sólo es apropiado para docencia local, sus puertos publicados por Compose están limitados a 127.0.0.1 y el clúster no debe exponerse a una red externa o a Internet.

Directorios HDFS iniciales¶

Durante la construcción de la imagen del NameNode se formatea HDFS y se crean, entre otros, estos directorios:

/user/hdadmin
/user/luser
/tmp
/tmp/hadoop-yarn/staging
/tmp/logs
/datalake
/datalake/raw
/datalake/raw/tpcds
/datalake/silver
/datalake/gold
/warehouse

/user/luser es el directorio de trabajo del usuario de las sesiones. Los directorios temporales tienen sticky bit, de forma parecida a /tmp en un sistema Unix.

/datalake y /warehouse pertenecen a luser:hadoop con permisos 770.

A partir de la sesión 2, Jupyter se ejecutará en namenode, y desde allí se lanzarán como luser los clientes Python y los trabajos Spark. Esos clientes leerán HDFS por HTTP (WebHDFS) y recibirán redirecciones hacia datanode1, datanode2 y datanode3: esos nombres serán resolubles porque todos los contenedores están conectados a la misma red Docker hadoop-cluster.

A continuación

Cómo se construyen las imágenes (referencia)

  • No hace falta reconstruirlas: esta parte explica de dónde sale lo que observarás en la sesión
  • Una imagen base (Ubuntu, Java, Hadoop y usuarios) y dos de servicio: namenode-image y datanode-image
  • La configuración y el formateo de HDFS se hacen al construir la imagen, no en docker compose up
  • /inicio.sh arranca los demonios como hdadmin; el entorno Python /opt/tcdm/venv sólo está en namenode

Cómo se construyen las imágenes del curso¶

El alumnado utiliza imágenes ya publicadas; esta sección resume cómo se incorporan los componentes y la configuración que se observan durante la sesión — no es necesario reconstruirlas para seguir el resto del notebook.

Tres imágenes relacionadas¶

La construcción separa los elementos comunes de la configuración de cada tipo de nodo:

Imagen Contenido añadido Imagen de partida
dsevilla/hadoop-base:26-27 Ubuntu, Java, Hadoop, utilidades y usuarios ubuntu:26.04
dsevilla/namenode-image:26-27 Configuración de NameNode y ResourceManager, HDFS inicial hadoop-base:26-27
dsevilla/datanode-image:26-27 Configuración de DataNode y NodeManager hadoop-base:26-27

Esta separación evita repetir la descarga e instalación de Hadoop en las dos imágenes de servicio, y distingue qué decisiones pertenecen a todo cliente Hadoop y cuáles son propias del nodo que guarda metadatos o del nodo que almacena bloques.

Receta de la imagen base¶

FROM ubuntu:26.04

ARG HADOOP_VERSION=3.5.0
ENV HADOOP_VERSION=${HADOOP_VERSION} \
    JAVA_HOME=/usr/lib/jvm/java-17-openjdk \
    HADOOP_HOME=/opt/bd/hadoop \
    HADOOP_CONF_DIR=/opt/bd/hadoop/etc/hadoop

ARG permite elegir la versión durante la construcción; ENV deja disponibles dentro de la imagen las rutas que usarán scripts y clientes. $HADOOP_HOME no apunta directamente a un directorio con el número de versión, sino al enlace /opt/bd/hadoop, de modo que los comandos y la configuración no cambian de ruta al actualizar Hadoop.

La imagen instala Java 17 sin entorno gráfico, Python, pip, entornos virtuales, Maven y varias herramientas de terminal, además de tini (proceso inicial del contenedor, reenvía señales y recoge procesos terminados). El paquete python3 es el intérprete de Ubuntu 26.04, incluido para utilidades y sesiones posteriores; el código Python nuevo del curso se escribe para Python 3.13, y no debe deducirse la versión objetivo del lenguaje a partir de que este cliente esté instalado en la imagen Hadoop. El patrón de instalación y limpieza es:

export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install --no-install-recommends -y \
    ca-certificates curl tini openjdk-17-jdk-headless \
    python3 python3-venv python3-pip maven \
    libarchive-tools git make iputils-ping
apt-get clean
rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/* /tmp/* /var/tmp/*

El Dockerfile sigue añadiendo otros paquetes, hadoop y configurando los usuarios luser y hdadmin.

Dockerfiles de las imágenes de servicio¶

ARG BASE_IMAGE=dsevilla/hadoop-base:26-27
FROM ${BASE_IMAGE}

# Durante la construcción se escribe la configuración específica del NameNode.
COPY namenode.sh /tmp/
RUN sh /tmp/namenode.sh && \
    rm -f /tmp/namenode.sh

# Entorno Python del curso (Jupyter, PySpark y bibliotecas de S2-S8).
COPY requirements-namenode.txt install-python-environment.sh /tmp/
RUN sh /tmp/install-python-environment.sh && \
    rm -f /tmp/install-python-environment.sh /tmp/requirements-namenode.txt

EXPOSE 9870 9871 8088 8000-10000
CMD ["/inicio.sh"]

El Dockerfile del DataNode es equivalente, pero escribe la configuración de los DataNodes y del NodeManager y publica sus puertos característicos (9864 9865 9866 9867 50000-50200 8000-10000). En ambos casos la configuración se escribe durante la construcción, no cuando el alumnado ejecuta docker compose up; la diferencia entre ambas imágenes está principalmente en hdfs-site.xml, en yarn-site.xml, en el contenido de /inicio.sh y en el entorno Python del curso, que sólo lleva el NameNode.

Entorno Python del curso (sólo en el NameNode)¶

A partir de S2, Jupyter y el driver de Spark se ejecutan dentro de namenode, así que su imagen incluye el entorno Python que usarán las sesiones. Ubuntu marca el python3 del sistema como gestionado externamente (PEP 668): un pip install sobre él falla con el error externally-managed-environment. Por eso se crea un entorno virtual aparte, /opt/tcdm/venv, y se instalan en él Jupyter Lab, PySpark y las bibliotecas de S2-S8:

python3 -m venv /opt/tcdm/venv
/opt/tcdm/venv/bin/python -m pip install --no-cache-dir \
    -r /tmp/requirements-namenode.txt
chown -R luser:hadoop /opt/tcdm
cat > /etc/profile.d/tcdm-python.sh <<'EOF'
export PATH="/opt/tcdm/venv/bin:$PATH"
EOF

/etc/profile.d/tcdm-python.sh pone /opt/tcdm/venv/bin al principio del PATH de los shells de login: tras su - luser, python, python3, pip, jupyter y spark-submit son los de este entorno. Será el intérprete del kernel de Jupyter desde S2 y, por tanto, el lugar donde acaba todo %pip install de un notebook. luser es su propietario, de modo que puede instalar paquetes sin privilegios de administrador.

Los DataNodes no llevan este entorno: cuando Spark envía trabajo a YARN, spark-submit distribuye a los ejecutores pyspark.zip, py4j y los .jar de Spark, y éstos sólo necesitan el python3 de la imagen base.

Usuarios y entorno de ejecución¶

groupadd --system hadoop
useradd --system --create-home --gid hadoop \
    --home-dir /opt/bd --shell /bin/bash hdadmin
useradd --system --create-home --gid hadoop \
    --home-dir /home/luser --shell /bin/bash luser

hdadmin posee la instalación de /opt/bd y ejecuta los demonios; luser posee /home/luser, pertenece también al grupo hadoop y representa al cliente ordinario. Compartir grupo permite preparar recursos docentes comunes sin ejecutar los ejercicios como administrador. /etc/profile.d/hadoop.sh configura todos los shells de inicio (JAVA_HOME, HADOOP_HOME, HADOOP_CONF_DIR, PATH), por lo que luser puede escribir hdfs, yarn o mapred sin indicar su ruta absoluta.

Formateo e inicialización incluidos en la imagen¶

El NameNode necesita un espacio de nombres formateado antes del primer arranque. Durante la construcción de namenode-image:

mkdir -p /var/data/hdfs/namenode
chown -R hdadmin:hadoop /var/data/hdfs/namenode
hdfs namenode -format -force -nonInteractive

Este formateo se ejecuta al construir la imagen, no cada vez que arranca un contenedor: repetirlo sobre un NameNode con datos generaría un nuevo identificador de clúster y haría que los DataNodes existentes dejasen de pertenecer a él. La construcción arranca temporalmente el NameNode, espera a que salga del modo seguro y crea la estructura inicial:

hdfs dfs -mkdir -p \
    /user/hdadmin /user/luser \
    /tmp /tmp/logs /tmp/hadoop-yarn/staging \
    /datalake/raw/tpcds /datalake/silver /datalake/gold /warehouse
hdfs dfs -chmod 1777 /tmp /tmp/logs /tmp/hadoop-yarn/staging
hdfs dfs -chown luser /user/luser
hdfs dfs -chown -R luser:hadoop /datalake
hdfs dfs -chmod 770 \
    /datalake /datalake/raw /datalake/raw/tpcds \
    /datalake/silver /datalake/gold
hdfs dfs -chown luser:hadoop /warehouse
hdfs dfs -chmod 770 /warehouse

Después el NameNode se detiene de nuevo; el estado formateado y esos directorios pasan a formar parte de la imagen publicada. El metastore y Trino no se incluyen en esta imagen: se arrancarán como servicios independientes en la misma red y accederán remotamente a HDFS (a partir de la sesión 2; se estudian a fondo en las sesiones 6 y 7).

Arranque automático de los demonios¶

La imagen del NameNode termina generando /inicio.sh:

#!/usr/bin/env sh
set -eu
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk
export HADOOP_HOME=/opt/bd/hadoop
export HADOOP_CONF_DIR="$HADOOP_HOME/etc/hadoop"

su - hdadmin -c "JAVA_HOME=${JAVA_HOME} HADOOP_HOME=${HADOOP_HOME} HADOOP_CONF_DIR=${HADOOP_CONF_DIR} ${HADOOP_HOME}/bin/hdfs --daemon start namenode"
su - hdadmin -c "JAVA_HOME=${JAVA_HOME} HADOOP_HOME=${HADOOP_HOME} HADOOP_CONF_DIR=${HADOOP_CONF_DIR} ${HADOOP_HOME}/bin/yarn --daemon start resourcemanager"
su - hdadmin -c "JAVA_HOME=${JAVA_HOME} HADOOP_HOME=${HADOOP_HOME} HADOOP_CONF_DIR=${HADOOP_CONF_DIR} ${HADOOP_HOME}/bin/yarn --daemon start timelineserver"

# Bucle infinito
exec tail -f /dev/null

El Dockerfile declara este script como CMD, así que se ejecuta al crear el contenedor; los demonios se inician como hdadmin, no como root. El proceso tail mantiene activo el contenedor tras dejar los servicios en segundo plano, y tini (declarado en la imagen base) permanece como proceso inicial gestionando correctamente las señales. En la salida de jps, el Timeline Server aparece con el nombre de clase Java ApplicationHistoryServer, aunque las órdenes de Hadoop lo gestionen como servicio timelineserver. Los DataNodes generan una versión equivalente que arranca datanode y nodemanager en vez de namenode y resourcemanager/timelineserver. Esto explica por qué docker compose start vuelve a poner en marcha los servicios sin que el alumnado ejecute manualmente hdfs --daemon start en cuatro terminales.

Las imágenes publicadas incluyen variantes linux/amd64 y linux/arm64; Docker selecciona automáticamente la adecuada para el equipo al descargar la etiqueta 26-27.

Preparar la red del laboratorio¶

El fichero Compose declara hadoop-cluster como una red externa. Esto hace que su ciclo de vida sea independiente del clúster Hadoop y permitirá conectar más adelante otros contenedores —Trino, PostgreSQL, Hive Metastore— sin incluirlos en el mismo fichero Compose.

La siguiente orden primero comprueba si la red ya existe y sólo la crea si hace falta; crear la red no arranca ningún contenedor, únicamente prepara el espacio de red y el DNS interno que compartirán los servicios. Si no imprime nada es que la red ya existía.

In [ ]:
!docker network inspect hadoop-cluster >/dev/null 2>&1 || docker network create hadoop-cluster
Recapitulación

El entorno está listo para ser lanzado

  • Docker, Compose y el motor de contenedores responden correctamente
  • Conocemos los 4 contenedores, sus servicios y los recursos que anuncian a YARN
  • La red hadoop-cluster ya existe y conectará también servicios futuros (Trino, Hive Metastore)
A continuación

Arrancamos el clúster

  • Cuatro contenedores: namenode, datanode1, datanode2, datanode3
  • Cada uno representa una máquina distinta de un clúster real
  • Vamos a comprobar que Docker los levanta y que Hadoop los reconoce

Arrancar el clúster¶

La siguiente orden lee el fichero Compose, descarga las imágenes que falten en el equipo y crea los cuatro contenedores. -d (detached) los deja en segundo plano, de modo que la celda termina en cuanto están creados. La primera ejecución puede tardar varios minutos, porque tiene que descargar las dos imágenes; las siguientes suelen ser mucho más rápidas.

In [ ]:
!docker compose -f ../entorno/compose-hadoop-cluster.yml up -d

Ver el estado de los contenedores¶

Se espera que namenode y los tres datanodeN aparezcan en estado running. Que un contenedor esté en ejecución sólo significa que su proceso principal sigue vivo: los demonios Hadoop necesitan además unos segundos para arrancar y registrarse. Las comprobaciones de la siguiente sección son las que confirman que el clúster está realmente preparado.

Si algún contenedor no aparece o figura como exited, o si las comprobaciones siguientes siguen fallando después de esperar, ve a «Diagnosticar un arranque incompleto», al final de este notebook.

In [ ]:
!docker compose -f ../entorno/compose-hadoop-cluster.yml ps

Observar el clúster desde el navegador¶

Cuando el clúster esté listo, puedes abrir estas direcciones desde el navegador del equipo anfitrión (usan localhost porque Docker publica esos puertos sólo en la interfaz local del equipo; no hay que sustituir localhost por la dirección de un DataNode):

  • NameNode/HDFS: http://localhost:9870 (prueba también Utilities → Browse the file system para relacionar las órdenes de terminal con los ficheros visibles desde la interfaz)
  • ResourceManager/YARN: http://localhost:8088
  • YARN Timeline Server: http://localhost:8188/applicationhistory/ (la barra final es necesaria en esta versión de Hadoop; sin ella el servidor responde 404 Not Found)

El Timeline Server no añade un quinto contenedor: el alias Docker timelineserver apunta a namenode, donde el proceso ApplicationHistoryServer se ejecuta junto al NameNode y al ResourceManager, con su propio almacén LevelDB. Este servicio no es el JobHistoryServer de MapReduce: el Timeline Server recoge el histórico genérico de aplicaciones YARN (el que se usa en esta sesión), mientras que el JobHistoryServer es otro servicio para los eventos detallados de trabajos MapReduce.

Estas interfaces son una ayuda de observación; las comprobaciones reproducibles se hacen también desde este notebook.

Ejecutar las órdenes desde una terminal del contenedor¶

A partir de aquí, casi todas las celdas siguen el patrón !docker exec namenode su - hdadmin -c '<orden>' o !docker exec namenode su - luser -c '<orden>'. Cada celda lanza desde el equipo anfitrión una única orden dentro del contenedor namenode como el usuario indicado: hdadmin para las tareas de administración de los demonios (dfsadmin, fsck, yarn node...) y luser para el trabajo habitual con datos.

Las mismas órdenes se pueden escribir directamente en una terminal dentro de namenode. Desde una terminal del equipo (en Windows, la de WSL2):

docker exec -it namenode bash

Ya dentro del contenedor, se cambia al usuario que aparezca en la celda y se escribe la orden que va entre comillas tras -c. Para las celdas con hdadmin:

su - hdadmin
hdfs dfsadmin -report

y para las celdas con luser:

su - luser
hdfs dfs -ls /

exit vuelve al usuario anterior y, repetido, cierra la terminal del contenedor. Las celdas del notebook usan la forma con docker exec porque así cada paso queda reproducible y se puede repetir de forma aislada; la terminal es cómoda para probar variantes o encadenar varias órdenes seguidas con el mismo usuario. Lo mismo vale para los DataNodes, cambiando namenode por datanode1, datanode2 o datanode3.

Comprobar HDFS y YARN¶

hdfs dfsadmin -report pregunta al NameNode por los DataNodes registrados. Tras el resumen de capacidad debe aparecer la línea Live datanodes (3):, seguida de un bloque por cada DataNode. Si el número es menor que tres, o la orden responde con un error de conexión poco después del arranque, espera 15-30 segundos y repite únicamente esta celda: es posible que los tres nodos todavía no se hayan registrado.

In [ ]:
!docker exec namenode su - hdadmin -c 'hdfs dfsadmin -report'

YARN administra recursos de procesamiento, no bloques HDFS, y sus nodos de trabajo se consultan por separado. La salida esperada empieza por Total Nodes:3 y tiene una fila por NodeManager con cuatro columnas: Node-Id, Node-State (debe ser RUNNING), Node-Http-Address y Number-of-Running-Containers (cero mientras no haya aplicaciones en marcha). La opción -showDetails añade bajo cada nodo sus recursos: la línea Configured Resources : <memory:2560, vCores:2> es lo que ese NodeManager anuncia a YARN, los 2560 MiB y 2 vcores de la tabla de recursos de más arriba. Es posible que HDFS tenga tres DataNodes vivos y YARN no tenga tres NodeManagers, o al contrario, porque son servicios diferentes aunque compartan contenedores:

  • hdfs dfsadmin -report responde a «¿dónde se pueden guardar bloques?»;
  • yarn node -list -all responde a «¿dónde se pueden ejecutar tareas?».
In [ ]:
!docker exec namenode su - hdadmin -c 'yarn node -list -all -showDetails'

Relacionar servicios y procesos con jps¶

jps muestra los procesos Java visibles para el usuario que lo ejecuta. Los demonios pertenecen a hdadmin, por lo que hay que usar esa identidad para obtener una comprobación fiable. En el NameNode se espera encontrar NameNode, ResourceManager y ApplicationHistoryServer (además del propio proceso Jps, que sólo existe mientras se hace la consulta).

In [ ]:
!docker exec namenode su - hdadmin -c 'jps'

En cada DataNode se espera encontrar DataNode y NodeManager.

In [ ]:
!docker exec datanode1 su - hdadmin -c 'jps'
In [ ]:
!docker exec datanode2 su - hdadmin -c 'jps'
In [ ]:
!docker exec datanode3 su - hdadmin -c 'jps'
Proceso Qué mantiene o ejecuta
NameNode Espacio de nombres, permisos y localización de los bloques HDFS
DataNode Bloques de datos guardados en el almacenamiento local del nodo
ResourceManager Vista global de recursos y planificación de aplicaciones
ApplicationHistoryServer Histórico y consulta de información de aplicaciones YARN (Timeline Server)
NodeManager Contenedores YARN y recursos disponibles en un nodo

Los identificadores numéricos de proceso no tienen por qué coincidir entre contenedores ni mantenerse tras un reinicio; lo relevante son los nombres de los demonios.

Consultar el Timeline Server desde la red interna¶

El ResourceManager sabe si una aplicación está ejecutándose, ha terminado o ha fallado. El Timeline Server añade una perspectiva histórica: recibe los eventos y métricas que publican el ResourceManager y las aplicaciones, y los conserva en su almacén local. La siguiente celda comprueba que la API HTTP responde (la salida es un documento JSON) usando el alias interno, lo que también verifica que el nombre timelineserver resuelve al contenedor correcto. Más adelante, tras ejecutar WordCount, se volverá a esta interfaz para localizar la aplicación ya finalizada.

In [ ]:
!docker exec namenode curl --fail --silent http://timelineserver:8188/ws/v1/timeline/
Recapitulación

El clúster está vivo

  • 3 DataNodes replicando bloques, 3 NodeManagers ofreciendo recursos
  • El NameNode manda en el espacio de nombres; no guarda los datos
  • El ResourceManager decide dónde se ejecutan las tareas, no dónde viven los bloques
A partir de aquí se usa ese clúster: primero HDFS, después MapReduce.
A continuación

Exploramos el clúster como luser

  • Qué herramientas trae la imagen y desde dónde se resuelven
  • El espacio de nombres HDFS: /user, /datalake, /warehouse
  • Replicación, tamaño de bloque y su relación con los splits de MapReduce

Explorar las herramientas instaladas¶

Las imágenes no son cajas opacas. Aunque no haga falta construirlas, conviene comprobar qué ejecutables contienen y desde dónde se invocan. Todas las órdenes siguientes se ejecutan dentro de namenode como luser: será la identidad habitual para trabajar con datos y, más adelante, para iniciar Jupyter y Spark.

In [ ]:
!docker exec namenode su - luser -c 'id'

command -v muestra el ejecutable que resolvería el shell a través de PATH, sin llegar a ejecutar el programa. La imagen incluye Java 17, y Python/Maven para sesiones posteriores. En namenode, python3 y pip3 se resuelven dentro de /opt/tcdm/venv/bin: es el entorno Python del curso descrito en «Cómo se construyen las imágenes del curso», que ya trae Jupyter Lab y PySpark para las sesiones siguientes.

In [ ]:
!docker exec namenode su - luser -c 'command -v java && java -version'
In [ ]:
!docker exec namenode su - luser -c 'command -v python3 && python3 --version'
In [ ]:
!docker exec namenode su - luser -c 'command -v pip3'
In [ ]:
!docker exec namenode su - luser -c 'command -v jupyter spark-submit'
In [ ]:
!docker exec namenode su - luser -c 'command -v mvn'

Los clientes de Hadoop deben resolverse todos dentro de /opt/bd/hadoop/bin, sin que haga falta escribir la ruta completa: la instalación se expone mediante variables de entorno ($HADOOP_HOME, $PATH) definidas en /etc/profile.d/hadoop.sh.

In [ ]:
!docker exec namenode su - luser -c 'command -v hadoop; command -v hdfs; command -v yarn; command -v mapred'

La primera línea de hadoop version debe indicar Hadoop 3.5.0; el resto de la salida aporta datos de compilación y de la JVM, útiles cuando un mensaje de error o una documentación externa dependen de una versión concreta.

In [ ]:
!docker exec namenode su - luser -c 'hadoop version'
In [ ]:
!docker exec namenode su - luser -c 'printf "%s\n" "$HADOOP_HOME"'

Explorar HDFS como luser¶

A partir de aquí todas las operaciones ordinarias de datos se ejecutan como luser; hdadmin queda reservado para consultas administrativas (fsck, dfsadmin). Trabajar con la identidad correcta permite detectar problemas de permisos que quedarían ocultos si todo se hiciera como administrador.

La raíz de HDFS no es la raíz de Linux del contenedor: es el espacio de nombres distribuido. Deberían aparecer /user, /tmp, /datalake y /warehouse, cada uno con su propietario, grupo, permisos, tamaño y fecha.

In [ ]:
!docker exec namenode su - luser -c 'hdfs dfs -ls /'

Cada usuario puede tener su área bajo /user (una convención parecida a /home/<usuario> en Unix, aunque ambos sistemas de ficheros sean independientes). Debe aparecer al menos /user/hdadmin y /user/luser.

In [ ]:
!docker exec namenode su - luser -c 'hdfs dfs -ls /user'

/datalake/raw/tpcds es donde una sesión posterior materializará TPC-DS en Parquet. /datalake/silver y /datalake/gold reservan las siguientes capas medallion, y /warehouse queda para tablas gobernadas por catálogo. Estas rutas deben pertenecer a luser:hadoop. En esta sesión sólo se comprueba que existen y son accesibles.

In [ ]:
!docker exec namenode su - luser -c 'hdfs dfs -ls -d /datalake /datalake/raw /datalake/raw/tpcds /datalake/silver /datalake/gold'
In [ ]:
!docker exec namenode su - luser -c 'hdfs dfs -ls -d /warehouse'

Replicación y tamaño de bloque¶

El factor de replicación por defecto se consulta con getconf, sin necesidad de editar ficheros de configuración. El valor esperado es 3: HDFS intentará mantener tres copias de cada bloque, una en cada DataNode disponible. Replicar tres veces no crea tres nombres de fichero ni hace que hdfs dfs -ls muestre tres entradas — las réplicas son una decisión interna de almacenamiento.

In [ ]:
!docker exec namenode su - luser -c 'hdfs getconf -confKey dfs.replication'

El tamaño de bloque configurado corresponde a 64 MiB. Un bloque HDFS no equivale al bloque físico de un disco: es una unidad lógica grande, elegida para reducir metadatos y favorecer lecturas secuenciales de datos masivos. Los ficheros de esta sesión son mucho más pequeños y ocuparán un único bloque cada uno; en conjuntos de datos grandes, HDFS divide cada fichero en varios bloques que pueden almacenarse y procesarse en nodos diferentes.

In [ ]:
!docker exec namenode su - luser -c 'hdfs getconf -confKey dfs.blocksize'
A continuación

La configuración real de Hadoop

  • Las propiedades de cada fichero, tal y como se escribieron al construir la imagen
  • core-site.xml, hdfs-site.xml, yarn-site.xml y mapred-site.xml, fichero a fichero
  • Qué cambia entre namenode y un datanodeN en cada fichero

Comprobar la configuración efectiva de Hadoop¶

Los cuatro ficheros principales de configuración de Hadoop (core-site.xml, hdfs-site.xml, yarn-site.xml y mapred-site.xml) viven en $HADOOP_CONF_DIR, es decir /opt/bd/hadoop/etc/hadoop, y se escriben al construir la imagen. Aquí se muestran las propiedades de cada uno, tal y como quedaron escritas (el script de la imagen añade además comentarios que aquí se omiten), y se explica justo debajo el significado de las más importantes.

core-site.xml es común a NameNode y DataNodes, y fija fs.defaultFS: hace que una ruta como /user/luser se interprete como hdfs://namenode:9000/user/luser.

<?xml version="1.0"?>
<configuration>
  <property>
    <name>fs.defaultFS</name>
    <value>hdfs://namenode:9000/</value>
    <final>true</final>
  </property>
  <property>
    <name>hadoop.tmp.dir</name>
    <value>/var/tmp/hadoop-${user.name}</value>
    <final>true</final>
  </property>
</configuration>

El puerto 9000 de fs.defaultFS es el endpoint RPC que usan los clientes HDFS (hdfs dfs, fsspec, Spark); no debe confundirse con la interfaz web 9870. hadoop.tmp.dir fija dónde escribe cada identidad sus temporales locales, separados gracias a ${user.name}, que Hadoop resuelve en tiempo de ejecución. Ambas propiedades llevan <final>true</final>: ningún trabajo puede sustituirlas con su propia configuración, porque son decisiones estructurales de este laboratorio, no ajustes que deba tocar quien lo usa.

hdfs-site.xml cambia según el tipo de nodo. La diferencia principal entre el NameNode y un DataNode será dfs.namenode.name.dir frente a dfs.datanode.data.dir: uno señala dónde se guardan metadatos y el otro dónde se guardan bloques.

hdfs-site.xml en namenode:

<?xml version="1.0"?>
<configuration>
  <property>
    <name>dfs.replication</name>
    <value>3</value>
    <final>true</final>
  </property>
  <property>
    <name>dfs.blocksize</name>
    <value>64m</value>
    <final>true</final>
  </property>
  <property>
    <name>dfs.namenode.name.dir</name>
    <value>file:///var/data/hdfs/namenode</value>
    <final>true</final>
  </property>
  <property>
    <name>dfs.image.compress</name>
    <value>true</value>
  </property>
  <property>
    <name>dfs.webhdfs.enabled</name>
    <value>true</value>
  </property>
  <property>
    <name>hadoop.http.authentication.type</name>
    <value>simple</value>
  </property>
  <property>
    <name>hadoop.http.filter.initializers</name>
    <value></value>
  </property>
  <property>
    <name>dfs.namenode.http-address</name>
    <value>namenode:9870</value>
  </property>
</configuration>

hdfs-site.xml en cada datanodeN:

<?xml version="1.0"?>
<configuration>
  <property>
    <name>dfs.replication</name>
    <value>3</value>
    <final>true</final>
  </property>
  <property>
    <name>dfs.blocksize</name>
    <value>64m</value>
    <final>true</final>
  </property>
  <property>
    <name>dfs.datanode.data.dir</name>
    <value>file:///var/data/hdfs/datanode</value>
    <final>true</final>
  </property>
  <property>
    <name>dfs.webhdfs.enabled</name>
    <value>true</value>
  </property>
</configuration>

dfs.namenode.name.dir y dfs.datanode.data.dir son las dos propiedades que distinguen ambos ficheros: la primera es una ruta local del contenedor namenode donde vive la imagen del espacio de nombres y el registro de ediciones; la segunda es una ruta local de cada datanodeN donde se guardan sus bloques. Aunque el texto de la ruta coincida entre los tres DataNodes, cada uno la resuelve en su propio sistema de ficheros: no la comparten. Por eso no se usa RAID para la redundancia de los bloques de HDFS —esa responsabilidad ya la cumple la replicación entre nodos—, mientras que perder el disco del NameNode sí sería grave, porque ahí sólo hay una copia de los metadatos.

dfs.webhdfs.enabled=true activa la API HTTP de HDFS, que usarán fsspec, PyArrow y Polars en una sesión posterior para leer datos sin pasar por el cliente hdfs. dfs.namenode.http-address es la dirección que publica esa interfaz de administración, la misma que se abre en http://localhost:9870.

compose-hadoop-cluster.yml monta cada una de estas dos rutas sobre un volumen Docker con nombre (namenode-data, datanode1-data, datanode2-data, datanode3-data), no sobre la capa de escritura del contenedor. Por eso el espacio de nombres y los bloques sobreviven a docker compose down y a la recreación de los contenedores: solo desaparecen si se borra explícitamente el volumen (ver «Detener, limpiar y recuperar el entorno», más adelante en esta sesión).

A continuación

yarn-site.xml, recursos y servicios de YARN

  • ResourceManager en namenode y un NodeManager en cada datanodeN; resourcemanager y timelineserver son alias de red
  • Cada NodeManager anuncia 2 vcores y 2560 MiB; el planificador no concede contenedores mayores
  • La agregación de logs los copia a /tmp/logs de HDFS: se leen con yarn logs -applicationId
  • mapreduce_shuffle es el servicio auxiliar sin el que MapReduce no completa el shuffle

yarn-site.xml configura el ResourceManager (en namenode) y los NodeManagers (en cada datanodeN). Compara los límites de planificador del ResourceManager con los recursos que anuncia un NodeManager concreto.

Del yarn-site.xml de los DataNodes se muestra, tras el del NameNode, sólo el bloque de propiedades propias del NodeManager: las otras ocho (host del ResourceManager, Timeline Server y agregación de logs) son idénticas a las del NameNode.

yarn-site.xml en namenode:

<?xml version="1.0"?>
<configuration>
  <property>
    <name>yarn.resourcemanager.hostname</name>
    <value>resourcemanager</value>
    <final>true</final>
  </property>
  <property>
    <name>yarn.timeline-service.enabled</name>
    <value>true</value>
    <final>true</final>
  </property>
  <property>
    <name>yarn.timeline-service.hostname</name>
    <value>timelineserver</value>
    <final>true</final>
  </property>
  <property>
    <name>yarn.timeline-service.address</name>
    <value>timelineserver:10200</value>
    <final>true</final>
  </property>
  <property>
    <name>yarn.timeline-service.webapp.address</name>
    <value>timelineserver:8188</value>
    <final>true</final>
  </property>
  <property>
    <name>yarn.timeline-service.bind-host</name>
    <value>0.0.0.0</value>
    <final>true</final>
  </property>
  <property>
    <name>yarn.system-metrics-publisher.enabled</name>
    <value>true</value>
    <final>true</final>
  </property>
  <property>
    <name>yarn.timeline-service.generic-application-history.enabled</name>
    <value>true</value>
    <final>true</final>
  </property>
  <property>
    <name>yarn.timeline-service.store-class</name>
    <value>org.apache.hadoop.yarn.server.timeline.LeveldbTimelineStore</value>
    <final>true</final>
  </property>
  <property>
    <name>yarn.timeline-service.leveldb-timeline-store.path</name>
    <value>/var/data/yarn/timeline</value>
    <final>true</final>
  </property>
  <property>
    <name>yarn.timeline-service.http-authentication.type</name>
    <value>simple</value>
    <final>true</final>
  </property>
  <property>
    <name>yarn.timeline-service.http-authentication.simple.anonymous.allowed</name>
    <value>true</value>
    <final>true</final>
  </property>
  <property>
    <name>yarn.log-aggregation-enable</name>
    <value>true</value>
  </property>
  <property>
    <name>yarn.nodemanager.remote-app-log-dir</name>
    <value>/tmp/logs</value>
  </property>
  <property>
    <name>yarn.nodemanager.log-aggregation.compression-type</name>
    <value>gz</value>
  </property>
  <property>
    <name>yarn.scheduler.maximum-allocation-vcores</name>
    <value>2</value>
  </property>
  <property>
    <name>yarn.scheduler.minimum-allocation-mb</name>
    <value>256</value>
  </property>
  <property>
    <name>yarn.scheduler.maximum-allocation-mb</name>
    <value>2560</value>
  </property>
</configuration>

Propiedades propias del NodeManager, en el yarn-site.xml de cada datanodeN:

<property>
  <name>yarn.nodemanager.resource.detect-hardware-capabilities</name>
  <value>false</value>
</property>
<property>
  <name>yarn.nodemanager.resource.cpu-vcores</name>
  <value>2</value>
</property>
<property>
  <name>yarn.nodemanager.resource.memory-mb</name>
  <value>2560</value>
</property>
<property>
  <name>yarn.nodemanager.vmem-check-enabled</name>
  <value>false</value>
</property>
<property>
  <name>yarn.nodemanager.aux-services</name>
  <value>mapreduce_shuffle</value>
</property>
<property>
  <name>yarn.nodemanager.aux-services.mapreduce_shuffle.class</name>
  <value>org.apache.hadoop.mapred.ShuffleHandler</value>
</property>

resourcemanager y timelineserver son alias de red del propio contenedor namenode, para separar el nombre de cada servicio de su papel en HDFS aunque compartan contenedor; el puerto RPC 10200 del Timeline Server sólo se usa dentro de la red Docker, y su interfaz HTTP se publica en el host como http://localhost:8188. La propiedad que permite que el ResourceManager publique información de aplicaciones es yarn.system-metrics-publisher.enabled (en Hadoop 3.5.0 la variante con el prefijo resourcemanager. está deprecada). La agregación de logs (yarn.log-aggregation-enable) copia a /tmp/logs de HDFS los logs de los contenedores, comprimidos como TFile con gz: no se puede leer ese resultado con un cat directo, hace falta yarn logs -applicationId <application_id>, que conoce el formato. Los límites del planificador (yarn.scheduler.maximum-allocation-*) impiden pedir un contenedor mayor que los recursos que anuncia un DataNode.

El Timeline Server guarda su histórico con LeveldbTimelineStore, en la ruta local /var/data/yarn/timeline del contenedor namenode (yarn.timeline-service.leveldb-timeline-store.path). A diferencia de /var/data/hdfs/namenode, esa ruta no está montada en un volumen con nombre: el histórico de aplicaciones desaparece con docker compose down aunque los datos HDFS se conserven. yarn.timeline-service.bind-host=0.0.0.0 hace que el servicio escuche en todas las interfaces del contenedor —Compose publica después ese puerto sólo en 127.0.0.1—, y las dos propiedades http-authentication (tipo simple, con acceso anónimo permitido) explican que la interfaz http://localhost:8188/applicationhistory/ se abra en el navegador sin credenciales, en línea con la autenticación simple del resto del laboratorio.

En el yarn-site.xml de los NodeManagers, yarn.nodemanager.resource.detect-hardware-capabilities=false evita que YARN interprete los recursos del host en vez de los límites que se quieren enseñar: cada NodeManager anuncia explícitamente 2 vcores y 2560 MiB, dejando 512 MiB del límite Docker (3072 MiB) para el propio DataNode, el NodeManager y otros procesos. La comprobación de memoria virtual se desactiva (yarn.nodemanager.vmem-check-enabled=false) para evitar falsos positivos por cómo Linux y las JVM reservan espacio de direcciones dentro de un contenedor; esto no elimina el límite real de memoria de Docker ni el presupuesto que YARN asigna a sus contenedores. mapreduce_shuffle es el servicio auxiliar que permite a los reducers obtener de cada nodo las salidas intermedias de los mapas: sin él, YARN podría iniciar procesos, pero un trabajo MapReduce no completaría su fase de shuffle.

mapred-site.xml es común a las dos imágenes de servicio: los NodeManagers son quienes ejecutan allí el ApplicationMaster, las tareas map y las tareas reduce.

<?xml version="1.0"?>
<configuration>
  <property>
    <name>mapreduce.framework.name</name>
    <value>yarn</value>
    <final>true</final>
  </property>
  <property>
    <name>yarn.app.mapreduce.am.env</name>
    <value>HADOOP_MAPRED_HOME=${HADOOP_HOME}</value>
  </property>
  <property>
    <name>yarn.app.mapreduce.am.resource.cpu-vcores</name>
    <value>1</value>
  </property>
  <property>
    <name>yarn.app.mapreduce.am.resource.mb</name>
    <value>512</value>
  </property>
  <property>
    <name>mapreduce.job.heap.memory-mb.ratio</name>
    <value>0.8</value>
  </property>
  <property>
    <name>mapreduce.map.env</name>
    <value>HADOOP_MAPRED_HOME=${HADOOP_HOME}</value>
  </property>
  <property>
    <name>mapreduce.map.cpu.vcores</name>
    <value>1</value>
  </property>
  <property>
    <name>mapreduce.map.java.opts</name>
    <value>-Xmx512M</value>
  </property>
  <property>
    <name>mapreduce.map.memory.mb</name>
    <value>768</value>
  </property>
  <property>
    <name>mapreduce.reduce.env</name>
    <value>HADOOP_MAPRED_HOME=${HADOOP_HOME}</value>
  </property>
  <property>
    <name>mapreduce.reduce.cpu.vcores</name>
    <value>1</value>
  </property>
  <property>
    <name>mapreduce.reduce.java.opts</name>
    <value>-Xmx512M</value>
  </property>
  <property>
    <name>mapreduce.reduce.memory.mb</name>
    <value>768</value>
  </property>
</configuration>

mapreduce.framework.name=yarn evita la ejecución local del trabajo: los recursos siempre se piden al clúster. El ApplicationMaster reserva 1 vcore y 512 MiB; cada tarea map y reduce solicita 1 vcore y un contenedor de 768 MiB, pero limita el heap de su JVM a 512 MiB (-Xmx512M). El ratio mapreduce.job.heap.memory-mb.ratio=0.8 sólo se aplicaría si faltara ese -Xmx: Hadoop deduciría entonces un heap de 768 × 0,8 = 614,4 MiB. Aquí el valor explícito tiene prioridad. La diferencia entre el contenedor y el heap deja memoria para la propia JVM, bibliotecas nativas y otros costes fuera del heap. HADOOP_MAPRED_HOME se propaga a los procesos remotos (*.env) para que cualquier DataNode localice las bibliotecas de MapReduce.

También se puede pedir a Hadoop una propiedad ya interpretada —incluyendo sustituciones y valores predeterminados— en lugar de leer el XML a mano. Leer el XML explica qué se escribió al construir la imagen; consultar la propiedad mediante Hadoop confirma qué valor está utilizando el proceso. Ambas formas de inspección son complementarias.

In [ ]:
!docker exec namenode su - luser -c 'hdfs getconf -confKey fs.defaultFS'
Recapitulación

La configuración explica lo ya observado

  • fs.defaultFS=hdfs://namenode:9000/ explica por qué no hace falta indicar una URL al especificar un fichero en HDFS
  • dfs.replication=3 y dfs.blocksize=64m son los valores que ya comprobamos con getconf
  • mapreduce.framework.name=yarn obliga a MapReduce a pedir recursos al clúster, no a ejecutarse en local
A continuación

Ficheros de verdad en HDFS

  • Creamos el área de trabajo /user/luser/s1 y subimos ficheros locales
  • Copiamos, movemos, leemos y descargamos con hdfs dfs
  • Comprobamos bloques y réplicas con du y fsck

Crear el área de trabajo de la sesión¶

Los datos de esta sesión se aíslan bajo /user/luser/s1/input. -p crea también los directorios intermedios y no falla si ya existen, por lo que se puede repetir esta celda al volver a hacer la sesión.

In [ ]:
!docker exec namenode su - luser -c 'hdfs dfs -mkdir -p /user/luser/s1/input'
In [ ]:
!docker exec namenode su - luser -c 'hdfs dfs -ls -R /user/luser/s1'

Operaciones básicas con ficheros en HDFS¶

Primero se crean tres ficheros locales en /tmp del contenedor namenode: todavía no están replicados, no son visibles desde los DataNodes mediante HDFS y desaparecerán si se elimina el contenedor. Esta distinción entre ruta local y ruta HDFS será importante durante todo el curso.

Los tres ficheros comparten varias palabras a propósito, para que el resultado de WordCount permita comprobar tanto recuentos repetidos como palabras que sólo aparecen una vez.

In [ ]:
!docker exec namenode su - luser -c 'printf "hadoop hdfs mapreduce\nhadoop yarn\n" > /tmp/s1-a.txt'
In [ ]:
!docker exec namenode su - luser -c 'printf "hdfs hadoop\nyarn recursos\n" > /tmp/s1-b.txt'
In [ ]:
!docker exec namenode su - luser -c 'printf "mapreduce datos hadoop\ndatos datos\n" > /tmp/s1-c.txt'

Se puede comprobar el contenido local del primer fichero con cat, sin usar todavía el cliente HDFS:

In [ ]:
!docker exec namenode su - luser -c 'cat /tmp/s1-a.txt'

Subir los ficheros a HDFS¶

-put -f transfiere el fichero local al directorio HDFS indicado; -f permite reemplazar un fichero HDFS del mismo nombre si se repite la sesión. Tener varios ficheros de entrada permite a Hadoop crear varias tareas map: con datos tan pequeños el rendimiento no mejora, pero la estructura ayuda a observar que una entrada puede dividirse en trabajos independientes.

In [ ]:
!docker exec namenode su - luser -c 'hdfs dfs -put -f /tmp/s1-a.txt /user/luser/s1/input/'
In [ ]:
!docker exec namenode su - luser -c 'hdfs dfs -put -f /tmp/s1-b.txt /user/luser/s1/input/'
In [ ]:
!docker exec namenode su - luser -c 'hdfs dfs -put -f /tmp/s1-c.txt /user/luser/s1/input/'

Listar y leer los datos distribuidos¶

Se esperan tres entradas; -h presenta los tamaños en formato legible. El factor de replicación no multiplica el tamaño lógico mostrado en esta lista.

In [ ]:
!docker exec namenode su - luser -c 'hdfs dfs -ls -h /user/luser/s1/input'

-cat envía los bytes recuperados desde los DataNodes a la salida estándar; no crea una copia local permanente.

In [ ]:
!docker exec namenode su - luser -c 'hdfs dfs -cat /user/luser/s1/input/s1-a.txt'

Copiar, mover y descargar ficheros¶

-cp copia entre dos rutas que pertenecen a HDFS; no interviene el sistema de ficheros local del contenedor.

In [ ]:
!docker exec namenode su - luser -c 'hdfs dfs -cp /user/luser/s1/input/s1-a.txt /user/luser/s1/s1-a-copia.txt'

-mv cambia la ruta dentro de HDFS. Dentro de un mismo espacio de nombres, renombrar suele ser una operación de metadatos del NameNode: no hace falta volver a copiar los bloques.

In [ ]:
!docker exec namenode su - luser -c 'hdfs dfs -mv /user/luser/s1/s1-a-copia.txt /user/luser/s1/s1-a-renombrado.txt'

El fichero renombrado queda directamente bajo /user/luser/s1, fuera de input, así que MapReduce no lo contará más adelante.

In [ ]:
!docker exec namenode su - luser -c 'hdfs dfs -ls /user/luser/s1'

-get es la operación inversa a -put: descarga un fichero de HDFS al sistema local del contenedor. Tras esto, /tmp/s1-descargado.txt es una copia local que se puede comprobar con cat, sin usar ya el cliente HDFS.

In [ ]:
!docker exec namenode su - luser -c 'hdfs dfs -get -f /user/luser/s1/s1-a-renombrado.txt /tmp/s1-descargado.txt'
In [ ]:
!docker exec namenode su - luser -c 'cat /tmp/s1-descargado.txt'

Espacio, bloques y réplicas¶

-du -h muestra el tamaño lógico y el espacio ocupado por las réplicas. Con factor de replicación tres, el segundo valor debería ser aproximadamente el triple del primero para estos ficheros, aunque sean muy pequeños.

In [ ]:
!docker exec namenode su - luser -c 'hdfs dfs -du -h /user/luser/s1/input'

fsck consulta el estado de bajo nivel de bloques y réplicas como administrador. La última línea debe indicar Status: HEALTHY; las ubicaciones muestran en qué DataNodes se guarda cada réplica y se pueden comparar con los tres nodos obtenidos antes con dfsadmin -report.

In [ ]:
!docker exec namenode su - hdadmin -c 'hdfs fsck /user/luser/s1/input -files -blocks -locations'
Recapitulación

HDFS ya tiene datos propios

  • /user/luser/s1/input contiene tres ficheros replicados y verificados con fsck
  • -put, -cp, -mv y -get distinguen siempre entre ruta local y ruta HDFS
  • Con datos ya en HDFS, toca procesarlos: MapReduce
A continuación

El modelo MapReduce, antes de lanzarlo

  • map(K1, V1) -> list(K2, V2): convierte cada línea de entrada en pares intermedios
  • Hadoop agrupa esos pares por clave durante el shuffle
  • reduce(K2, list(V2)) -> (K3, list(V3)): combina los valores de cada clave
  • WordCount aplicará este modelo con datos reales sobre el clúster

El modelo de programación MapReduce¶

Antes de enviar un trabajo a YARN conviene fijar el modelo que MapReduce oculta detrás de esa orden. El programador sólo escribe dos funciones:

  • map: map(K1, V1) -> list(K2, V2). Recibe un par clave/valor de la entrada y produce una lista de pares intermedios. En WordCount, K1 no se usa, V1 es una línea de texto y cada palabra de esa línea produce un par intermedio (palabra, 1).
  • reduce: reduce(K2, list(V2)) -> (K3, list(V3)). Recibe todos los valores intermedios asociados a una misma clave —Hadoop los agrupa durante la fase de shuffle— y produce el resultado final. En WordCount, list(V3) contiene un único valor: la suma de los 1 recibidos para esa palabra.

Dos piezas adicionales optimizan ese esquema sin cambiar el resultado:

  • Un combinador (combiner) agrega localmente, en el mismo nodo que ejecutó el map, los valores de una misma clave antes de enviarlos por red. Reduce el tráfico del shuffle, pero sólo es correcto si la función es conmutativa y asociativa —sumar recuentos de palabras lo es—.
  • Un particionador decide a qué tarea reduce va cada clave, típicamente con hash(K) mod R, donde R es el número de reducers. Garantiza que todas las apariciones de una misma clave llegan al mismo reducer, sin lo cual no se podría sumar correctamente su recuento.

MapReduce también está diseñado para que un fallo aislado no tumbe el trabajo completo. Un proceso maestro detecta mediante heartbeats qué tareas siguen vivas; si una tarea falla, se reintenta en otro nodo, y si una tarea concreta no avanza —una tarea rezagada o straggler— puede lanzarse una segunda copia en paralelo en otro nodo y quedarse con la que termine antes (ejecución especulativa). Las tareas map no dependen unas de otras, así que pueden reejecutarse sin coordinación; las tareas reduce pueden recuperarse porque las salidas de map quedan en disco local, no sólo en memoria.

La siguiente figura resume el flujo completo, incluida la fase de shuffle que agrupa por clave entre la fase map y la fase reduce:

Flujo completo de MapReduce: las tareas map leen las entradas del sistema de ficheros distribuido, el barajado agrupa y ordena por clave y las tareas reduce producen la salida

Con este modelo en mente, el resto de la sección ejecuta WordCount tal cual lo haría cualquier trabajo MapReduce: preparar la entrada, enviar el trabajo a YARN y leer la salida.

Ejemplo: el mismo WordCount escrito con MrJob¶

El ejemplo wordcount que se ejecuta más abajo contra YARN está escrito en Java. Para que la correspondencia entre la teoría y el código se vea de un vistazo, esta es la misma cuenta de palabras escrita en Python con MrJob, una biblioteca que expone directamente las funciones mapper y reducer y oculta el resto del motor MapReduce (shuffle, combinador, reintentos...). El curso ya no usa MrJob en las prácticas —se trabaja con Spark—, pero verlo una vez ayuda a fijar el modelo. Es un ejemplo sólo para leer: no se ejecuta en esta sesión y MrJob no está instalado en el laboratorio.

from mrjob.job import MRJob


class MRWordCount(MRJob):
    def mapper(self, _, line: str):
        # map(K1, V1) -> list(K2, V2)
        # K1 no se usa; V1 es la línea de texto.
        # Cada palabra de la línea produce un par intermedio (palabra, 1).
        for word in line.split():
            yield word.lower(), 1

    def combiner(self, word: str, counts: list[int]):
        # Suma parcial en el mismo nodo que ejecutó el mapper, antes de
        # enviar nada por la red. Válido porque sumar es conmutativo y
        # asociativo (ver más arriba).
        yield word, sum(counts)

    def reducer(self, word: str, counts: list[int]):
        # reduce(K2, list(V2)) -> (K3, list(V3))
        # `counts` es list(V2): los valores que MrJob ya agrupó por clave
        # durante el shuffle. Aquí la lista de salida tiene un único valor.
        yield word, sum(counts)


if __name__ == "__main__":
    MRWordCount.run()

Guardado como wordcount_mrjob.py, se ejecutaría en local sin Hadoop ni clúster —MrJob agrupa las claves como haría el shuffle antes de llamar al reducer—. Con el mismo contenido de s1-a.txt, s1-b.txt y s1-c.txt que se subió a HDFS más arriba, la orden y su salida serían:

$ printf 'hadoop hdfs mapreduce\nhadoop yarn\nhdfs hadoop\nyarn recursos\nmapreduce datos hadoop\ndatos datos\n' \
    | python3 wordcount_mrjob.py
"yarn"	2
"recursos"	1
"mapreduce"	2
"hdfs"	2
"hadoop"	4
"datos"	3

MrJob serializa cada par intermedio como JSON —de ahí las comillas en las palabras—. El orden entre palabras distintas no está garantizado (aquí no coincide con el alfabético, a diferencia de la salida de Hadoop más abajo, que sí usa un único reducer y por eso queda ordenada); lo que importa es que el recuento de cada palabra es el mismo que va a producir el wordcount de Hadoop sobre estos mismos ficheros. Comparar ambas salidas permite comprobar que mapper/reducer en MrJob y map/reduce en el wordcount de Hadoop calculan exactamente lo mismo; lo único que cambia es qué motor reparte el trabajo entre las máquinas.

Pregunta guía
  • ¿En qué contenedor se ejecuta la orden yarn jar ...?
  • ¿Ese contenedor es el mismo que ejecuta las tareas map y reduce?
  • ¿Cómo se puede comprobar, después de lanzar el trabajo, dónde se ejecutó cada tarea?

Ejecutar un trabajo MapReduce con YARN¶

Se utiliza el ejemplo wordcount incluido en la instalación de Hadoop, que cuenta cuántas veces aparece cada palabra. Aunque el programa es pequeño, recorre el mismo camino básico que una aplicación MapReduce real: el cliente prepara y envía la aplicación a YARN, el ResourceManager asigna recursos para el ApplicationMaster, se crean tareas map que leen las entradas, los resultados intermedios se agrupan por clave durante el shuffle, y una o más tareas reduce producen los ficheros finales en HDFS. El JAR de ejemplos ya forma parte de la imagen; el patrón hadoop-mapreduce-examples-*.jar evita repetir el número de versión en la orden.

MapReduce exige que el directorio de salida no exista, para evitar sobrescribir resultados por accidente. Antes de (re)ejecutar el trabajo se borra sólo la salida anterior; los ficheros de entrada no se tocan.

In [ ]:
!docker exec namenode su - luser -c 'hdfs dfs -rm -r -f /user/luser/s1/output'

Esta orden se lanza en namenode, pero eso no significa que el NameNode ejecute las tareas: el proceso que arranca allí es el cliente. YARN asigna los contenedores de ejecución a los NodeManagers de los datanodeN. Durante la ejecución se muestran un identificador de aplicación y los porcentajes de avance de las fases map y reduce; el trabajo debe terminar con éxito. Si falla, conserva el identificador y revisa primero el mensaje de error — borrar todo el clúster debería ser el último recurso, no la primera respuesta.

In [ ]:
!docker exec namenode su - luser -c 'yarn jar "$HADOOP_HOME"/share/hadoop/mapreduce/hadoop-mapreduce-examples-*.jar wordcount /user/luser/s1/input /user/luser/s1/output'

Debe aparecer _SUCCESS (finalización correcta) y al menos un fichero part-r-00000 (el sufijo r indica que es salida de una tarea reduce).

In [ ]:
!docker exec namenode su - luser -c 'hdfs dfs -ls /user/luser/s1/output'

Se esperan estos recuentos (Hadoop separa palabra y contador con un tabulador, así que el espaciado exacto puede variar):

datos      3
hadoop     4
hdfs       2
mapreduce  2
recursos   1
yarn       2
In [ ]:
!docker exec namenode su - luser -c 'hdfs dfs -cat /user/luser/s1/output/part-r-*'

Consultar la aplicación en YARN y en el Timeline Server¶

mapred job -list all presenta con la nomenclatura de MapReduce las aplicaciones YARN de tipo MapReduce: consulta al ResourceManager, así que no necesita un JobHistoryServer (este clúster no lo arranca, como se explicó al observar el Timeline Server). Después de ejecutar wordcount debe aparecer con un identificador job_... —con los mismos números que el application_... de YARN— y estado SUCCEEDED.

In [ ]:
!docker exec namenode su - hdadmin -c 'mapred job -list all'

yarn application -list -appStates ALL muestra el mismo trabajo desde la perspectiva de YARN. Busca un estado FINISHED y un resultado final SUCCEEDED; la interfaz web de http://localhost:8088 muestra la misma información de forma gráfica.

In [ ]:
!docker exec namenode su - hdadmin -c 'yarn application -list -appStates ALL'

Ver en qué nodo se ejecutó cada tarea¶

La orden yarn jar se lanzó desde namenode, pero las tareas se ejecutaron en los DataNodes. Se puede comprobar en el navegador, sin más órdenes:

  1. Abre el histórico del Timeline Server, http://localhost:8188/applicationhistory/. Hay una fila por aplicación: localiza la de nombre word count con el identificador application_... que mostró yarn jar.
  2. Pulsa ese identificador. La página de la aplicación muestra su estado final y una tabla con sus intentos (appattempt_...); lo normal es que haya uno solo.
  3. Pulsa el identificador del intento. La tabla de esa página lista los contenedores YARN que usó la aplicación, cada uno con su columna Node: el datanodeN en el que se ejecutó.

Con tres ficheros de entrada se esperan cinco contenedores. El primero (..._000001) es el ApplicationMaster; los demás son las tres tareas map —una por fichero— y la tarea reduce. Anota en qué nodo cayó cada uno: es la respuesta a la pregunta guía de antes de lanzar el trabajo.

Los enlaces de la columna Node y los de los logs apuntan a nombres como datanode1:8042, que sólo se resuelven dentro de la red Docker: desde tu navegador no se abren, pero el nombre del nodo ya está a la vista.

Si no lo has visto a la primera, repite el trabajo —las celdas de «Ejecutar un trabajo MapReduce con YARN», empezando por la que borra la salida— con http://localhost:8088 abierto en otra pestaña. Mientras se ejecuta, la aplicación aparece allí como RUNNING y el mismo recorrido —aplicación, intento, contenedores— muestra los contenedores activos en ese momento. El trabajo dura poco: si no llegas a tiempo, el histórico del Timeline Server conserva la ejecución y ahora tendrá dos aplicaciones que comparar.

La distribución exacta de tareas entre DataNodes puede cambiar entre ejecuciones: un planificador distribuido no promete que cada trabajo pequeño utilice todos los nodos por igual. Lo que importa comprobar aquí es que el cliente entrega la aplicación a YARN, que los NodeManagers aportan contenedores y que el resultado se escribe en HDFS.

Detener, limpiar y recuperar el entorno¶

No ejecutes estas órdenes por accidente mientras trabajas: se presentan como texto para copiarlas a una terminal cuando de verdad las necesites, no como celdas de código, precisamente para que un «ejecutar todo» del notebook no destruya tu propio trabajo. Ejecútalas en una terminal de tu equipo, situada en la raíz de la distribución (el directorio que contiene s1/ y entorno/); por eso aquí las rutas empiezan por entorno/ y no por ../entorno/ como en las celdas del notebook.

Situación Orden Consecuencia
Parar temporalmente docker compose -f entorno/compose-hadoop-cluster.yml stop Conserva contenedores y datos HDFS; se reanuda con start. Tras start los demonios tardan unos segundos en registrarse: repite dfsadmin -report y yarn node -list -all antes de continuar.
Reanudar docker compose -f entorno/compose-hadoop-cluster.yml start Vuelve a poner en marcha los mismos contenedores. up -d también sirve: Compose reutiliza los contenedores existentes mientras su configuración no obligue a recrearlos.
Limpiar sólo tus datos de la sesión docker exec namenode su - luser -c 'hdfs dfs -rm -r -f /user/luser/s1' Borra el área /user/luser/s1 para repetir la sesión desde cero sin regenerar el clúster. No borra /warehouse, /tmp, /user ni los directorios de otros usuarios, ni detiene contenedores ni borra /tmp local.
Eliminar los contenedores docker compose -f entorno/compose-hadoop-cluster.yml down Elimina los contenedores, pero conserva los datos HDFS: viven en los volúmenes Docker tcdm-26-27-namenode-data y tcdm-26-27-datanodeN-data (en el Compose aparecen con los nombres cortos namenode-data/datanodeN-data), no en la capa de escritura de los contenedores. Sí borra el almacén local (no persistido) del Timeline Server. La red externa hadoop-cluster no se elimina.
Recrear los contenedores down seguido de up -d Los mismos volúmenes HDFS se vuelven a montar: /user/luser, /datalake, /warehouse, los datos de /user/luser/s1 y un TPC-DS ya materializado siguen ahí. No hace falta reconstruir las imágenes.
Borrar también el HDFS (destructivo) docker volume rm tcdm-26-27-namenode-data tcdm-26-27-datanode1-data tcdm-26-27-datanode2-data tcdm-26-27-datanode3-data (con los contenedores ya eliminados) Vacía el espacio de nombres y los bloques: el próximo up -d arranca con un HDFS recién formateado, como la primera vez. Es la única orden de esta tabla que borra /datalake/raw/tpcds y cualquier otro dato ya materializado en sesiones posteriores.

El Makefile de entorno/ ofrece estas mismas operaciones para todos los servicios del laboratorio a la vez y con los mismos nombres: make -C entorno stop para los contenedores sin eliminarlos, make -C entorno down los elimina conservando los volúmenes y make -C entorno clean-hdfs borra además los volúmenes HDFS. Tras stop o down se vuelve a arrancar con make -C entorno hadoop-up. La sección «Referencia: arrancar y parar el laboratorio», al final de este notebook, los reúne en una tabla.

Diagnosticar un arranque incompleto¶

Si algo no arranca como se espera, conviene mirar en este orden, en una terminal de tu equipo y desde la raíz de la distribución: qué contenedores siguen activos, las últimas líneas de todos los servicios y, si el problema parece localizado, los logs de un contenedor concreto:

docker compose -f entorno/compose-hadoop-cluster.yml ps -a
docker compose -f entorno/compose-hadoop-cluster.yml logs --tail=120
docker logs namenode --tail=120
docker logs datanode1 --tail=120

Los fallos más habituales en esta fase son que Docker no tenga recursos suficientes, que la red externa no exista todavía, o que los demonios sigan arrancando. Después de revisar los logs, repite las comprobaciones de HDFS y YARN de este notebook: no hace falta reconstruir las imágenes para una incidencia habitual. entorno/README.md reúne, en «Solución de problemas», los fallos más frecuentes de todas las sesiones.

Recapitulación

Sesión 1

  • HDFS separa metadatos (NameNode) de bloques (DataNodes) y los replica
  • YARN separa la gestión de recursos (ResourceManager/NodeManager) de la aplicación (ApplicationMaster)
  • MapReduce ejecuta map -> shuffle -> reduce sobre los datos, no al revés
  • stop y down conservan los datos HDFS (viven en volúmenes); sólo borrar los volúmenes devuelve el HDFS al estado de las imágenes
Siguiente sesión: un dataset de negocio (TPC-DS) como ficheros Parquet en /datalake/raw/tpcds.

Preguntas para interpretar la experiencia¶

Antes de dar por terminada la sesión, conviene poder responder con las propias palabras:

  • ¿Qué diferencia hay entre que Docker muestre un contenedor como running y que Hadoop muestre un DataNode como Live?
  • ¿Por qué ls /tmp (en el contenedor) y hdfs dfs -ls /tmp no consultan el mismo lugar?
  • ¿Qué información guarda el NameNode y qué información guardan los DataNodes?
  • ¿Por qué tres réplicas no aparecen como tres ficheros al ejecutar hdfs dfs -ls?
  • ¿Qué diferencia existe entre un DataNode y un NodeManager, aunque ambos se ejecuten dentro del mismo contenedor?
  • ¿Por qué MapReduce rechaza un directorio de salida que ya existe?
  • ¿Qué parte de la orden yarn jar actúa como cliente y dónde se ejecutan realmente las tareas?
  • ¿Qué diferencia hay entre la vista de aplicaciones activas del ResourceManager y el histórico del Timeline Server?
  • ¿Por qué timelineserver resuelve al mismo contenedor que namenode y qué ventaja didáctica tiene conservar el nombre de servicio separado?
  • ¿Por qué los datos HDFS sobreviven a stop y también a down, y qué hay que borrar para devolver el laboratorio al estado inicial de las imágenes?

Evidencias para la siguiente sesión¶

Antes de la segunda o tercera sesión tendrás una reunión individual breve (unos minutos) con el profesor para revisar el trabajo de esta sesión. Esa reunión combina dos partes distintas: una demostración en vivo sobre tu propio ordenador y una memoria escrita breve. Mantén el entorno de esta sesión operativo hasta entonces.

Qué mostrar en el ordenador durante la reunión¶

Ten preparado y a mano, funcionando en tu propio equipo:

  1. docker compose ... ps con los cuatro contenedores en ejecución.
  2. hdfs dfsadmin -report con tres DataNodes vivos.
  3. yarn node -list -all con tres NodeManagers en estado RUNNING.
  4. La interfaz de histórico del Timeline Server en http://localhost:8188/applicationhistory/, con una aplicación YARN visible.
  5. Los ficheros copiados en /user/luser/s1/input y las ubicaciones de bloques obtenidas con hdfs fsck.
  6. El resultado correcto de WordCount en /user/luser/s1/output (hadoop 4, mapreduce 2, datos 3, hdfs 2, yarn 2, recursos 1).
  7. Que sabes detener y regenerar el clúster sin reconstruir las imágenes.

Memoria escrita (una o dos páginas)¶

Trae también un documento breve —una o dos páginas, no hace falta más— que no se limite a pegar capturas de las órdenes anteriores: debe explicar con tus propias palabras la relación entre NameNode y DataNode, la relación entre ResourceManager y NodeManager, la diferencia entre una ruta local y una ruta HDFS, y qué ocurre con los datos al usar stop frente a down.

A partir de ahora conserva este entorno: las siguientes sesiones usarán el mismo HDFS para acceder a datos desde Arrow, Spark y las herramientas de catálogo.

Preparación para la sesión 2¶

En esta sesión el kernel ha sido un Python local que Visual Studio Code arranca por su cuenta: no has lanzado ningún servidor Jupyter. Desde S2 el kernel se ejecuta dentro del contenedor namenode, como luser, y tendrás que arrancar tú el servidor Jupyter allí y conectar el notebook a él. Antes de abrir s2/s2.ipynb, en una terminal de tu equipo situada en la raíz de la distribución:

  1. Actualiza el material; trae s2/ y los ficheros de entorno/ que añade esa sesión (el warehouse con Hive Metastore y Trino):

    cd ~/tcdm-public
    git pull
    
  2. Comprueba que el clúster de esta sesión está en marcha. Si lo habías parado, esta orden lo arranca de nuevo con los mismos volúmenes HDFS —no hace falta borrar nada—; si ya funciona, no hace nada:

    Animación: la orden make -C entorno hadoop-up se teclea en una terminal de tu equipo (host)

    make -C entorno hadoop-up
    

    PostgreSQL, Hive Metastore y Trino no se arrancan todavía: S2 los añade más adelante con make -C entorno warehouse-up.

  3. Arranca Jupyter dentro de namenode. En otra terminal de tu equipo, entra en el contenedor:

    Animación: docker exec -it namenode bash se teclea en tu equipo y abre una terminal dentro del contenedor namenode, donde se ejecutan su - luser y jupyter lab

    docker exec -it namenode bash
    

    Esa terminal pasa a estar dentro de namenode (el indicador muestra root@namenode). Ahí, cambia a luser y arranca Jupyter Lab:

    su - luser
    jupyter lab --ip=0.0.0.0 --port=8888 --no-browser --IdentityProvider.token=tcdm
    

    su - luser carga el entorno de login de luser, que pone primero en el PATH el Python del curso (/opt/tcdm/venv, con Jupyter Lab, PySpark y las bibliotecas de las sesiones). --IdentityProvider.token=tcdm fija el token de acceso, así que no hay que buscarlo en los mensajes de Jupyter: la dirección de conexión es siempre la misma. Deja esa terminal abierta mientras trabajes; Ctrl+C detiene Jupyter y dos exit te devuelven a tu equipo. make -C entorno jupyter hace lo mismo en una sola orden.

  4. En Visual Studio Code, abre s2/s2.ipynb y conéctalo con Select Kernel → Select Another Kernel → Existing Jupyter Server, usando la dirección http://127.0.0.1:8888/lab?token=tcdm y eligiendo el kernel Python 3 (ipykernel).

El Python local que usaste en esta sesión ya no será necesario. El propio notebook de S2 repite estos pasos con más detalle al principio, en «Antes de empezar: Jupyter en namenode», también en su versión HTML publicada.

Referencia: arrancar y parar el laboratorio¶

Desde la sesión 2 el laboratorio se maneja con el Makefile de entorno/, que agrupa las órdenes de Docker Compose de esta sesión y las aplica a todos los servicios que se vayan añadiendo. Estas dos tablas las reúnen para tenerlas a mano. Todas se teclean en una terminal de tu equipo, desde la raíz de la distribución (el directorio que contiene s1/ y entorno/); ninguna es una celda de notebook.

Arrancar¶

Orden Qué hace Cuándo se usa
make -C entorno hadoop-up Crea la red hadoop-cluster si falta y arranca namenode y los tres DataNodes. Si estaban parados, los reanuda; si ya funcionan, no hace nada Al empezar cualquier sesión. Equivale a las órdenes docker network create y docker compose ... up -d de esta sesión
make -C entorno warehouse-up Lo anterior y, además, PostgreSQL, Hive Metastore y Trino Desde la sesión 2; sus ficheros llegan con el git pull de esa sesión
make -C entorno jupyter Arranca Jupyter Lab dentro de namenode como luser; la terminal queda ocupada mientras Jupyter esté en marcha Desde la sesión 2. Es el atajo de docker exec -it namenode bash, su - luser y jupyter lab ...
make -C entorno status Lista los contenedores del laboratorio, tanto los que están en marcha como los parados En cualquier momento, para saber en qué estado está el laboratorio

Parar y borrar¶

Cuatro órdenes, de menos a más destructiva. Las dos primeras conservan todos los datos; las dos últimas borran volúmenes.

Orden Qué hace Qué se conserva Qué se pierde
make -C entorno stop Para los contenedores sin eliminarlos (docker compose stop) Todo: los datos HDFS y también el interior de cada contenedor (ficheros locales, histórico del Timeline Server) Sólo los procesos en marcha, entre ellos Jupyter
make -C entorno down Elimina los contenedores (docker compose down) Los volúmenes Docker, con todos los datos HDFS Lo que vivía dentro de los contenedores: ficheros locales e histórico del Timeline Server
make -C entorno clean down y, además, borra los volúmenes de los servicios que añaden la sesión 2 (Hive Metastore) y la sesión 3 (almacenamiento de objetos) Todo HDFS Las definiciones de tablas y los objetos de esos servicios. Con sólo el clúster de esta sesión equivale a down
make -C entorno clean-hdfs clean y, además, borra los volúmenes HDFS: es el docker volume rm de «Detener, limpiar y recuperar el entorno» Sólo las imágenes ya descargadas Todos los datos: el siguiente arranque parte de un HDFS recién formateado

Para el uso normal entre sesiones basta stop. down sirve para recrear los contenedores sin perder datos, por ejemplo si alguno ha quedado en mal estado. clean y clean-hdfs son deliberadamente destructivas y se dejan para cuando se quiera empezar de cero.

Después de cualquiera de las cuatro se vuelve a arrancar con make -C entorno hadoop-up (o warehouse-up desde la sesión 2) y, desde la sesión 2, hay que lanzar Jupyter de nuevo. Si has hecho un down, el Timeline Server habrá perdido su histórico: para volver a tener una aplicación visible en él —una de las evidencias de esta sesión— repite las celdas de WordCount de «Ejecutar un trabajo MapReduce con YARN».