Desde finales de 2018 estoy cursando un Bootcamp en Keepcoding de Big Data y machine learning. El temario promete y por ahora estoy contento con lo aprendido. Es un poco duro volver ponerse a estudiar de esta manera después de muchos años, pero lo voy llevando bien. Por suerte en este oficio siempre tenemos que estar aprendiendo, por lo que no me pilla oxidado del todo.

Así que he decido aprovechar este blog que nunca termino de arrancar para ir plasmando algunos de los conocimientos que estoy adquiriendo. Puede que a alguien le pueda servir de ayuda en algún momento, a mi me sirve para repasar conocimientos, para tenerlo documentado para mi mismo y para ampliar de alguna forma lo que me han enseñado.


Lo primero que voy a plasmar es como crear y configurar un cluster de Hadoop con docker. Voy a intentar hacer todo desde 0 y en futuras entradas de este blog iré ampliando el cluster con Hive, Spark, Kafka, etc y también incluyendo ejemplos de procesamiento de información con toda esta tecnología.

En un principio había pensado hacer todo esto con Vagrant, por diferir un poco como lo he hecho en clase, pero al final va a ser lo mismo y me resulta más práctico hacerlo con Docker. Ademán en un futuro puedo ampliar la información integrándolo con Kubernetes o Swarm. Posteriormente también quiero hacerlo todo con Google Cloud y Amazon AWS. Son muchas cosas las que quiero hacer, espero llegar a todo.


Creando nuestra imagen base

De todos los ficheros que iré generando los voy a ir generando en un repositorio de GitHub. Como el mismo repositorio me irá sirviendo para muchos artículos, iré haciendo ramas por cada artículo para que se pueda obtener los ficheros de cada artículo.

Primero voy a hacer un Dockerfile con una imagen base que deje Hadoop instalado y configurado para usar en cualquier maquina en modo Stand Alone (todo en un solo servidor) y a partir de ahí generaré otros Dockerfiles para los nodos master, slave, etc.

Explico paso a paso el contenido del Dockerfile:

Primero indicamos que nos basamos en la imagen de Ubuntu 16.04 como imagen base.

FROM ubuntu:16.04
MAINTAINER irm

Instalamos un servidor SSH y la JDK8

RUN apt-get update && apt-get install -y openssh-server openjdk-8-jdk wget

Ahora nos bajamos el software de Hadoop y lo dejamos en /usr/local/hadoop

# install hadoop 2.7.2
RUN wget http://apache.rediris.es/hadoop/common/hadoop-2.7.7/hadoop-2.7.7.tar.gz && \
    tar -xzvf hadoop-2.7.7.tar.gz && \
    mv hadoop-2.7.7 /usr/local/hadoop && \
    rm hadoop-2.7.7.tar.gz

Establecemos las variables de entorno JAVA_HOME apuntando a la ruta del JDK, HADOOP_HOME apuntando a la ruta en la que hemos copiado Hadoop e incluimos en la variable PATH las rutas con los binarios y scripts para gestionar Hadoop.

# set environment variable
ENV JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64 
ENV HADOOP_HOME=/usr/local/hadoop 
ENV PATH=$PATH:/usr/local/hadoop/bin:/usr/local/hadoop/sbin 

Creamos las claves ssh necesarias para que se comunique el nodo master con los nodos slaves. Al tener todos las mismas claves e incluirlas como claves autorizadas, el acceso ssh entre los distintos nodos está garantizado.

# ssh without key
RUN ssh-keygen -t rsa -f ~/.ssh/id_rsa -P '' && \
    cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys

Creamos los directorios para los namenodes, los datanodes y los logs de Hadoop

RUN mkdir -p ~/hdfs/namenode && \ 
    mkdir -p ~/hdfs/datanode && \
    mkdir $HADOOP_HOME/logs

Copiamos los ficheros de configuración y scripts que tenemos en el directorio config al directorio /tmp. De ahí los vamos moviendo cada uno a su ubicación final. Más adelante explico cada fichero y su contenido.

COPY config/* /tmp/

RUN mv /tmp/ssh_config ~/.ssh/config && \
    mv /tmp/hadoop-env.sh /usr/local/hadoop/etc/hadoop/hadoop-env.sh && \
    mv /tmp/hdfs-site.xml $HADOOP_HOME/etc/hadoop/hdfs-site.xml && \ 
    mv /tmp/core-site.xml $HADOOP_HOME/etc/hadoop/core-site.xml && \
    mv /tmp/mapred-site.xml $HADOOP_HOME/etc/hadoop/mapred-site.xml && \
    mv /tmp/yarn-site.xml $HADOOP_HOME/etc/hadoop/yarn-site.xml && \
    mv /tmp/slaves $HADOOP_HOME/etc/hadoop/slaves && \
    mv /tmp/start-hadoop.sh ~/start-hadoop.sh

RUN chmod +x ~/start-hadoop.sh && \
    chmod +x $HADOOP_HOME/sbin/start-dfs.sh && \
    chmod +x $HADOOP_HOME/sbin/start-yarn.sh 

Formateamos el sistema de ficheros de hadoop.

RUN /usr/local/hadoop/bin/hdfs namenode -format

Como proceso de los contenedores configuramos el demonio de ssh. As’el nodo master podrá acceder a los nodos slave para arrancar los procesos de Hadoop necesarios para que formen parte del cluster.

CMD [ "sh", "-c", "service ssh start; bash"]

Por último dejamos abiertos unos cuantos puertos. En principio no son necesarios todos, pero los dejo así por si en un futuro los necesito.

# Hdfs ports
EXPOSE 9000 50010 50020 50070 50075 50090
EXPOSE 9871 9870 9820 9869 9868 9867 9866 9865 9864
# Mapred ports
EXPOSE 19888
#Yarn ports
EXPOSE 8030 8031 8032 8033 8040 8042 8088 8188
#Other ports
EXPOSE 49707 2122

Este sería el fichero Dockerfile completo. Así es más fácil copiarlo y pegarlo si quieres probarlo.

FROM ubuntu:16.04
MAINTAINER irm

WORKDIR /root

# install openssh-server, openjdk and wget
RUN apt-get update && apt-get install -y openssh-server openjdk-8-jdk wget

# install hadoop 2.7.2
RUN wget http://apache.rediris.es/hadoop/common/hadoop-2.7.7/hadoop-2.7.7.tar.gz && \
    tar -xzvf hadoop-2.7.7.tar.gz && \
    mv hadoop-2.7.7 /usr/local/hadoop && \
    rm hadoop-2.7.7.tar.gz

# set environment variable
ENV JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64 
ENV HADOOP_HOME=/usr/local/hadoop 
ENV PATH=$PATH:/usr/local/hadoop/bin:/usr/local/hadoop/sbin 

# ssh without key
RUN ssh-keygen -t rsa -f ~/.ssh/id_rsa -P '' && \
    cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys

RUN mkdir -p ~/hdfs/namenode && \ 
    mkdir -p ~/hdfs/datanode && \
    mkdir $HADOOP_HOME/logs

COPY config/* /tmp/

RUN mv /tmp/ssh_config ~/.ssh/config && \
    mv /tmp/hadoop-env.sh /usr/local/hadoop/etc/hadoop/hadoop-env.sh && \
    mv /tmp/hdfs-site.xml $HADOOP_HOME/etc/hadoop/hdfs-site.xml && \ 
    mv /tmp/core-site.xml $HADOOP_HOME/etc/hadoop/core-site.xml && \
    mv /tmp/mapred-site.xml $HADOOP_HOME/etc/hadoop/mapred-site.xml && \
    mv /tmp/yarn-site.xml $HADOOP_HOME/etc/hadoop/yarn-site.xml && \
    mv /tmp/slaves $HADOOP_HOME/etc/hadoop/slaves && \
    mv /tmp/start-hadoop.sh ~/start-hadoop.sh

RUN chmod +x ~/start-hadoop.sh && \
    chmod +x $HADOOP_HOME/sbin/start-dfs.sh && \
    chmod +x $HADOOP_HOME/sbin/start-yarn.sh 

# format namenode
RUN /usr/local/hadoop/bin/hdfs namenode -format

CMD [ "sh", "-c", "service ssh start; bash"]

# Hdfs ports
EXPOSE 9000 50010 50020 50070 50075 50090
EXPOSE 9871 9870 9820 9869 9868 9867 9866 9865 9864
# Mapred ports
EXPOSE 19888
#Yarn ports
EXPOSE 8030 8031 8032 8033 8040 8042 8088 8188
#Other ports
EXPOSE 49707 2122

La configuración del cluster

Bueno, vamos a ver los ficheros de configuración que se copian dentro de la imagen que acabamos de configurar. La mayoría de ficheros de configuración de Hadoop son ficheros XML y tienen una estructura de este tipo:

<?xml version="1.0"?>
<configuration>
    <property>
        <name>param1</name>
        <value>value1</value>
    </property>
    <property>
        <name>param2</name>
        <value>value2</value>
        <description>description 2</description>
    </property>
</configuration>

core-site.xml En este fichero vamos a configurar la localización del NameNode. En nuestro caso será una máquina que denominaremos hadoop-master.

<?xml version="1.0"?>
<configuration>
    <property>
        <name>fs.defaultFS</name>
        <value>hdfs://hadoop-master:9000/</value>
    </property>
</configuration>

hdfs-site.xml En este fichero configuramos algunas propiedades del sistema de ficheros.

<?xml version="1.0"?>
<configuration>
    <property>
        <name>dfs.namenode.name.dir</name>
        <value>file:///root/hdfs/namenode</value>
        <description>NameNode directory for namespace and transaction logs storage.</description>
    </property>
    <property>
        <name>dfs.datanode.data.dir</name>
        <value>file:///root/hdfs/datanode</value>
        <description>DataNode directory</description>
    </property>
    <property>
        <name>dfs.replication</name>
        <value>2</value>
    </property>
</configuration>

mapred-site.xml En este fichero se el MapReduce. En nuestro caso configuraremos el MapReduce de nuestro Hadoop al nuevo al framework MapReduce Yarn.

<?xml version="1.0"?>
<configuration>
    <property>
        <name>mapreduce.framework.name</name>
        <value>yarn</value>
    </property>
</configuration>

yarn-site.xml En este fichero configuramos el framework de MarReduce Yarn. Con estos parámetros indicamos que se habilite la fase de Suffle para que se pueda hacer entre las fases de Map y Reduce ya que YARN por defecto no lo incluye. También indicamos que el ResourceManager estará en hadoop-master

<?xml version="1.0"?>
<configuration>
    <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>
    <property>
        <name>yarn.resourcemanager.hostname</name>
        <value>hadoop-master</value>
    </property>
</configuration>

slaves Este fichero es el único que tenemos que no es XML. En este fichero se indican los nodos que tendrá el cluster, indicando cada uno en una línea. En nuestro caso indicamos el master y dos slaves.

hadoop-master
hadoop-slave1
hadoop-slave2

Creando nuestra imagen del nodo master

Vamos a crear otro fichero Dockerfile para la imagen del nodo master del cluster. En este caso es más sencillo que el anterior. De hecho esta imagen se basa en la imagen base que hemos creado antes.

FROM irm/hadoop-cluster-base
MAINTAINER irm

WORKDIR /root

ADD config/bootstrap.sh /etc/bootstrap.sh
RUN chown root:root /etc/bootstrap.sh
RUN chmod 700 /etc/bootstrap.sh

ENV BOOTSTRAP /etc/bootstrap.sh

CMD ["/etc/bootstrap.sh", "-d"]

Como se puede ver lo que hace es copiar el script bootstrap que se encargará de arrancar el servicio de ssh y nuestro cluster. Al final lanza un proceso de bash para que el contenedor se mantenga en ejecución y podamos ver después que se ha lanzado todo correctamente. Este es el contenido del script:

#!/bin/bash
rm /tmp/*.pid


service ssh start
$HADOOP_HOME/sbin/start-dfs.sh
$HADOOP_HOME/sbin/start-yarn.sh

bash

Probando nuestro cluster

Ya tenemos casi todo para tener nuestro cluster funcionando. Para generar las imágenes y lanzar el cluster tenemos el siguiente script de bash:

#!/bin/bash

# create base hadoop cluster docker image
docker build -f docker/base/Dockerfile -t irm/hadoop-cluster-base:latest docker/base

# create master node hadoop cluster docker image
docker build -f docker/master/Dockerfile -t irm/hadoop-cluster-master:latest docker/master


# the default node number is 3
N=${1:-3}

docker network create --driver=bridge hadoop &> /dev/null

# start hadoop slave container
i=1
while [ $i -lt $N ]
do
	docker rm -f hadoop-slave$i &> /dev/null
	echo "start hadoop-slave$i container..."
	docker run -itd \
	                --net=hadoop \
	                --name hadoop-slave$i \
	                --hostname hadoop-slave$i \
	                irm/hadoop-cluster-base
	i=$(( $i + 1 ))
done 



# start hadoop master container
docker rm -f hadoop-master &> /dev/null
echo "start hadoop-master container..."
docker run -itd \
                --net=hadoop \
                -p 50070:50070 \
                -p 8088:8088 \
                --name hadoop-master \
                --hostname hadoop-master \
				-v $PWD/data:/data \
                irm/hadoop-cluster-master



# get into hadoop master container
docker exec -it hadoop-master bash

¿Que hace este script?

  1. En primer lugar genera la imágenes de docker a partir de los ficheros Dockerfile.
  2. Después crea una red docker para uso del los nodos del cluster.
  3. Crea dos contenedores a partir de la imagen base que acabamos de crear.
  4. Crea un contenedor para el nodo master.
  5. Abre una sesión de bash en el nodo master.

Tardará unos segundos, pero cuando termine de arrancar el cluster podemos entrar a la web que provee Hadoop para ver el estado del cluster.

Hadoop cluster

Si pulsamos en la sección de Datanodes podremos ver los tres nodos de nuestro cluster. El master y los dos slaves

Datanodes

También podemos ver la el cluster Yarn y la información que ofrece de los trabajos

Yarn

Próximos pasos

En futuras entradas ampliaremos nuestro cluster integrando Hive. Veremos como lanzar procesos de MapReduce y de Hive. También quiero integrar Spark y lanzar trabajos realizado en Scala, pero para eso queda un poco todavía.

Mi idea es ir usando el mismo repositorio de Github para la serie de artículos y que cada artículo tenga su propia rama. Para este artículo la rama es create_hadoop_cluster, así que si clonas el repositorio recuerda cambiar a la rama create_hadoop_cluster para tener los ficheros correspondientes a este artículo.

git clone https://github.com/ivanrumo/KC_Practica_Big-Data-Architecture_docker.git

cd KC_Practica_Big-Data-Architecture_docker

git checkout create_hadoop_cluster

Así tendrás todo el contenido disponible y funcional. Solo hay que ejecutar el script start-cluster.sh y hará toda la magia.