diff --git a/catch-all/05_infra_test/04_elastic_stack/README.md b/catch-all/05_infra_test/04_elastic_stack/README.md new file mode 100644 index 0000000..2a45954 --- /dev/null +++ b/catch-all/05_infra_test/04_elastic_stack/README.md @@ -0,0 +1,191 @@ + +# Cómo Usar Elasticsearch en Python con Docker + + + +**Índice** +- [Cómo Usar Elasticsearch en Python con Docker](#cómo-usar-elasticsearch-en-python-con-docker) + - [¿Qué es Elasticsearch?](#qué-es-elasticsearch) + - [Requisitos Previos](#requisitos-previos) + - [Crear un Clúster Local del Elastic Stack con Docker Compose](#crear-un-clúster-local-del-elastic-stack-con-docker-compose) + - [Desplegar el Elastic Stack](#desplegar-el-elastic-stack) + - [Dockerizar el Programa Python](#dockerizar-el-programa-python) + - [Programa Python](#programa-python) + - [Conectar a Tu Clúster desde el Contenedor Python](#conectar-a-tu-clúster-desde-el-contenedor-python) + - [Ejecutar los Scripts Python en Docker](#ejecutar-los-scripts-python-en-docker) + - [Construir y Ejecutar el Contenedor Python](#construir-y-ejecutar-el-contenedor-python) + - [Visualización de Datos con Kibana](#visualización-de-datos-con-kibana) + - [Usar Logstash para Ingestión de Datos](#usar-logstash-para-ingestión-de-datos) + - [Recopilar Métricas con Metricbeat](#recopilar-métricas-con-metricbeat) + - [Eliminar Documentos del Índice](#eliminar-documentos-del-índice) + - [Eliminar un Índice](#eliminar-un-índice) + - [Conclusión](#conclusión) + +[Elasticsearch](https://www.elastic.co/elasticsearch/) (ES) es una tecnología utilizada por muchas empresas, incluyendo GitHub, Uber y Facebook. Aunque no se enseña con frecuencia en cursos de Ciencia de Datos, es probable que te encuentres con ella en tu carrera profesional. + +Muchos científicos de datos enfrentan dificultades al configurar un entorno local o al intentar interactuar con Elasticsearch en Python. Además, no hay muchos recursos actualizados disponibles. + +Por eso, decidí crear este tutorial. Te enseñará lo básico y podrás configurar un clúster de Elasticsearch en tu máquina para el desarrollo local rápidamente. También aprenderás a crear un índice, almacenar datos en él y usarlo para buscar información. + +¡Vamos a empezar! + + +## ¿Qué es Elasticsearch? + +Elasticsearch es un motor de búsqueda distribuido, rápido y fácil de escalar, capaz de manejar datos textuales, numéricos, geoespaciales, estructurados y no estructurados. Es un motor de búsqueda popular para aplicaciones, sitios web y análisis de registros. También es un componente clave del Elastic Stack (también conocido como ELK Stack), que incluye Logstash y Kibana, junto con Beats para la recolección de datos. + +Para entender el funcionamiento interno de Elasticsearch, piensa en él como dos procesos distintos. Uno es la ingestión, que normaliza y enriquece los datos brutos antes de indexarlos usando un [índice invertido](https://www.elastic.co/guide/en/elasticsearch/reference/current/documents-indices.html). El segundo es la recuperación, que permite a los usuarios recuperar datos escribiendo consultas que se ejecutan contra el índice. + +Eso es todo lo que necesitas saber por ahora. A continuación, prepararás tu entorno local para ejecutar un clúster del Elastic Stack. + +## Requisitos Previos + +Debes configurar algunas cosas antes de comenzar. Asegúrate de tener lo siguiente listo: + +1. Instala Docker y Docker Compose. +2. Descarga los datos necesarios. + +## Crear un Clúster Local del Elastic Stack con Docker Compose + +Para desplegar el Elastic Stack (Elasticsearch, Kibana, Logstash y Beats) en tu máquina local, utilizaremos Docker Compose. Este enfoque simplifica el despliegue y la administración de múltiples servicios. + +Mediante el fichero [docker-compose.yaml](docker-compose.yaml) vamos a configurar los servicios para Elasticsearch, Kibana, Logstash, Metricbeat, y la aplicación Python, todos conectados a una red llamada `elastic`. + + +### Desplegar el Elastic Stack + +Para iniciar los servicios, abre una terminal en el directorio donde se encuentra `docker-compose.yml` y ejecuta el siguiente comando: + +```bash +docker compose up -d +``` + +Este comando iniciará Elasticsearch, Kibana, Logstash, Metricbeat y la aplicación Python en contenedores separados. Aquí tienes un desglose de los servicios: + +- **Elasticsearch**: El motor de búsqueda que almacena y analiza los datos. +- **Kibana**: Una interfaz de usuario para visualizar y explorar datos en Elasticsearch. +- **Logstash**: Una herramienta de procesamiento de datos que ingiere datos desde múltiples fuentes, los transforma y los envía a un destino como Elasticsearch. +- **Metricbeat**: Un agente de monitoreo que recopila métricas del sistema y las envía a Elasticsearch. +- **Python App**: Un contenedor que ejecutará tus scripts de Python para interactuar con Elasticsearch. (Hasta que no construyamos el contenedor, este servicio fallará). + + +## Dockerizar el Programa Python + +Para dockerizar la aplicación Python, utilizaremos el archivo [Dockerfile](./app/Dockerfile) del directorio `app`. El directorio `app` también debe contener el programa Python y un archivo [requirements.txt](./app/requirements.txt) para manejar las dependencias. + +### Programa Python + +El archivo [main.py](./app/main.py) en el directorio `app` manejará la conexión a Elasticsearch, la creación de índices y la carga de datos. + +Este programa realiza las siguientes acciones: + +1. **Crea un índice** en Elasticsearch con las configuraciones de mapeo necesarias. +2. **Carga datos** desde un archivo CSV (`wiki_movie_plots_deduped.csv`) al índice creado. + + +## Conectar a Tu Clúster desde el Contenedor Python + +Tu aplicación Python se conectará al clúster de Elasticsearch usando el hostname `elasticsearch`, que es el nombre del servicio definido en `docker-compose.yml`. + + +## Ejecutar los Scripts Python en Docker + +Una vez que hayas creado los archivos `Dockerfile`, `requirements.txt` y `main.py`, puedes construir la imagen de Docker para tu aplicación Python y ejecutarla usando Docker Compose. + + +### Construir y Ejecutar el Contenedor Python + +1. Construye la imagen de Docker para tu aplicación Python: + + ```bash + docker compose build python-app + ``` + +2. Ejecuta el contenedor: + + ```bash + docker compose up python-app + ``` + +La aplicación Python se ejecutará y cargará los datos en Elasticsearch. Puedes verificar que los datos se hayan indexado correctamente ejecutando una consulta en Elasticsearch o usando Kibana para explorar los datos: + +```python +es.search(index="movies", body={"query": {"match_all": {}}}) +``` + + +## Visualización de Datos con Kibana + +Kibana es una herramienta de visualización que se conecta a Elasticsearch y te permite explorar y visualizar tus datos. + +Para acceder a Kibana, abre un navegador web y navega a `http://localhost:5601`. Deberías ver la interfaz de Kibana, donde puedes crear visualizaciones y dashboards. + +1. **Crea un índice en Kibana**: Ve a *Management > Index Patterns* y crea un nuevo patrón de índice para el índice `movies`. +2. **Explora tus datos**: Usa la herramienta *Discover* para buscar y explorar los datos que has indexado. +3. **Crea visualizaciones**: En la sección *Visualize*, crea gráficos y tablas que te permitan entender mejor tus datos. + + +## Usar Logstash para Ingestión de Datos + +Logstash es una herramienta para procesar y transformar datos antes de enviarlos a Elasticsearch. Aquí tienes un ejemplo básico de cómo configurar Logstash para que procese y envíe datos a Elasticsearch. + +Mediante el archivo de configuración en la carpeta `logstash-config/` llamado [pipeline.conf](./logstash-config/pipeline.conf) realizaremos los siguientes pasos: + +- Lee un archivo CSV de entrada. +- Usa el filtro `csv` para descomponer cada línea en campos separados. +- Usa `mutate` para convertir el año de lanzamiento en un número entero. +- Envía los datos procesados a Elasticsearch. + +Para usar Logstash con esta configuración, asegúrate de que el archivo `wiki_movie_plots_deduped.csv` esté accesible en tu sistema y modifica la ruta en el archivo de configuración según sea necesario. Luego, reinicia el contenedor de Logstash para aplicar los cambios. + +```bash +docker compose restart logstash +``` + + +## Recopilar Métricas con Metricbeat + +Metricbeat es un agente ligero que recopila métricas del sistema y las envía a Elasticsearch. Está configurado en el archivo `docker-compose.yml` que has creado anteriormente. + +Para ver las métricas en Kibana: + +1. **Configura Metricbeat**: Edita el archivo de configuración de Metricbeat si necesitas recopilar métricas específicas. +2. **Importa dashboards preconfigurados**: En Kibana, navega a *Add Data > Metricbeat* y sigue las instrucciones para importar los dashboards preconfigurados. +3. **Explora las métricas**: Usa los dashboards importados para explorar las métricas de tu sistema. + + +## Eliminar Documentos del Índice + +Puedes usar el siguiente código para eliminar documentos del índice: + +```python +es.delete(index="movies", id="2500") +``` + +El código anterior eliminará el documento con ID 2500 del índice `movies`. + + +## Eliminar un Índice + +Finalmente, si por alguna razón deseas eliminar un índice (y todos sus documentos), aquí te mostramos cómo hacerlo: + +```python +es.indices.delete(index='movies') +``` + + +## Conclusión + +Este tutorial te enseñó los conceptos básicos de Elasticsearch y cómo usarlo junto con el Elastic Stack y Docker. Esto será útil en tu carrera, ya que seguramente te encontrarás con Elasticsearch en algún momento. + +En este tutorial, has aprendido: + +- Cómo configurar un clúster del Elastic Stack en tu máquina usando Docker Compose. +- Cómo dockerizar una aplicación Python para interactuar con Elasticsearch. +- Cómo crear un índice y almacenar datos en él. +- Cómo buscar tus datos usando Elasticsearch. +- Cómo visualizar datos con Kibana. +- Cómo procesar datos con Logstash. +- Cómo recopilar métricas con Metricbeat. + +Explora más funcionalidades de Elasticsearch y el Elastic Stack para sacar el máximo provecho de tus datos. diff --git a/catch-all/05_infra_test/04_elastic_stack/app/Dockerfile b/catch-all/05_infra_test/04_elastic_stack/app/Dockerfile new file mode 100644 index 0000000..4c6a0ad --- /dev/null +++ b/catch-all/05_infra_test/04_elastic_stack/app/Dockerfile @@ -0,0 +1,15 @@ +# Usa la imagen base de Python +FROM python:3.9-slim + +# Establece el directorio de trabajo +WORKDIR /app + +# Copia el archivo requirements.txt e instala las dependencias +COPY requirements.txt /app/ +RUN pip install --no-cache-dir -r requirements.txt + +# Copia el código fuente a /app +COPY . /app/ + +# Comando para ejecutar la aplicación +CMD ["python", "main.py"] diff --git a/catch-all/05_infra_test/04_elastic_stack/app/main.py b/catch-all/05_infra_test/04_elastic_stack/app/main.py new file mode 100644 index 0000000..c36206a --- /dev/null +++ b/catch-all/05_infra_test/04_elastic_stack/app/main.py @@ -0,0 +1,114 @@ +import pandas as pd +from elasticsearch import Elasticsearch +from elasticsearch.helpers import bulk + +# Configura la conexión a Elasticsearch +es = Elasticsearch("http://elasticsearch:9200") + + +def create_index(): + """ + Crea un índice en Elasticsearch con el nombre 'movies' si no existe. + Define el mapeo del índice para los campos de los documentos. + """ + + # Define el mapeo del índice 'movies' + mappings = { + "properties": { + # Campo para el título de la película + "title": {"type": "text", "analyzer": "english"}, + # Campo para la etnicidad + "ethnicity": {"type": "text", "analyzer": "standard"}, + # Campo para el director + "director": {"type": "text", "analyzer": "standard"}, + # Campo para el elenco + "cast": {"type": "text", "analyzer": "standard"}, + # Campo para el género + "genre": {"type": "text", "analyzer": "standard"}, + # Campo para el argumento de la película + "plot": {"type": "text", "analyzer": "english"}, + # Campo para el año de lanzamiento + "year": {"type": "integer"}, + # Campo para la página de Wikipedia + "wiki_page": {"type": "keyword"} + } + } + + # Verifica si el índice 'movies' ya existe + if not es.indices.exists(index="movies"): + + # Crea el índice 'movies' si no existe + es.indices.create(index="movies", mappings=mappings) + print("\n[+] Índice 'movies' creado.") + + else: + + print("\n[!] El índice 'movies' ya existe.") + + +def load_data(): + """ + Carga datos desde un archivo CSV a Elasticsearch. + """ + + try: + + # Lee el archivo CSV + df = pd.read_csv("/app/wiki_movie_plots_deduped.csv", quoting=1) + + # Verifica el número de filas en el DataFrame + num_rows = len(df) + sample_size = min(5000, num_rows) + + # Elimina filas con valores nulos y toma una muestra + df = df.dropna().sample(sample_size, random_state=42).reset_index(drop=True) + + except Exception as e: + + print(f"\n[!] Error al leer el archivo CSV: {e}") + + return + + # Prepara los datos para la carga en Elasticsearch + bulk_data = [ + { + "_index": "movies", # Nombre del índice en Elasticsearch + "_id": i, # ID del documento en Elasticsearch + "_source": { + "title": row["Title"], # Título de la película + "ethnicity": row["Origin/Ethnicity"], # Etnicidad + "director": row["Director"], # Director + "cast": row["Cast"], # Elenco + "genre": row["Genre"], # Género + "plot": row["Plot"], # Argumento + "year": row["Release Year"], # Año de lanzamiento + "wiki_page": row["Wiki Page"], # Página de Wikipedia + } + } + for i, row in df.iterrows() # Itera sobre cada fila del DataFrame + ] + + try: + + # Carga los datos en Elasticsearch en bloques + bulk(es, bulk_data) + print("\n[+] Datos cargados en Elasticsearch.") + + except Exception as e: + + print(f"\n[!] Error al cargar datos en Elasticsearch: {e}") + + +def main(): + """ + Función principal que crea el índice y carga los datos. + """ + + create_index() # Crea el índice en Elasticsearch + load_data() # Carga los datos en Elasticsearch + + +if __name__ == "__main__": + # Ejecuta la función principal si el script se ejecuta directamente + + main() diff --git a/catch-all/05_infra_test/04_elastic_stack/app/requirements.txt b/catch-all/05_infra_test/04_elastic_stack/app/requirements.txt new file mode 100644 index 0000000..2f7cf7b --- /dev/null +++ b/catch-all/05_infra_test/04_elastic_stack/app/requirements.txt @@ -0,0 +1,3 @@ +pandas==2.0.1 +numpy==1.24.2 +elasticsearch==8.8.0 diff --git a/catch-all/05_infra_test/04_elastic_stack/app/wiki_movie_plots_deduped.csv b/catch-all/05_infra_test/04_elastic_stack/app/wiki_movie_plots_deduped.csv new file mode 100644 index 0000000..d4b82bd --- /dev/null +++ b/catch-all/05_infra_test/04_elastic_stack/app/wiki_movie_plots_deduped.csv @@ -0,0 +1,7 @@ +Title,Origin/Ethnicity,Director,Cast,Genre,Plot,Release Year,Wiki Page +The Shawshank Redemption,American,Frank Darabont,"Tim Robbins, Morgan Freeman",Drama,"Two imprisoned men bond over a number of years, finding solace and eventual redemption through acts of common decency.",1994,https://en.wikipedia.org/wiki/The_Shawshank_Redemption +The Godfather,American,Francis Ford Coppola,"Marlon Brando, Al Pacino",Crime,"The aging patriarch of an organized crime dynasty transfers control of his clandestine empire to his reluctant son.",1972,https://en.wikipedia.org/wiki/The_Godfather +The Dark Knight,American,Christopher Nolan,"Christian Bale, Heath Ledger",Action,"When the menace known as the Joker emerges from his mysterious past, he wreaks havoc and chaos on the people of Gotham.",2008,https://en.wikipedia.org/wiki/The_Dark_Knight +Pulp Fiction,American,Quentin Tarantino,"John Travolta, Uma Thurman",Crime,"The lives of two mob hitmen, a boxer, a gangster's wife, and a pair of diner bandits intertwine in four tales of violence and redemption.",1994,https://en.wikipedia.org/wiki/Pulp_Fiction +The Lord of the Rings: The Return of the King,American,Peter Jackson,"Elijah Wood, Viggo Mortensen",Fantasy,"The final battle for Middle-earth begins. The forces of good and evil are drawn into a confrontation and the outcome will determine the fate of the world.",2003,https://en.wikipedia.org/wiki/The_Lord_of_the_Rings:_The_Return_of_the_King +Inception,American,Christopher Nolan,"Leonardo DiCaprio, Joseph Gordon-Levitt",Sci-Fi,"A thief who enters the dreams of others to steal secrets from their subconscious is given the inverse task of planting an idea into the mind of a CEO.",2010,https://en.wikipedia.org/wiki/Inception diff --git a/catch-all/05_infra_test/04_elastic_stack/docker-compose.yaml b/catch-all/05_infra_test/04_elastic_stack/docker-compose.yaml new file mode 100644 index 0000000..18aabad --- /dev/null +++ b/catch-all/05_infra_test/04_elastic_stack/docker-compose.yaml @@ -0,0 +1,59 @@ +version: '3.8' + +services: + elasticsearch: + image: docker.elastic.co/elasticsearch/elasticsearch:7.15.2 + container_name: elasticsearch + environment: + - discovery.type=single-node + - xpack.security.enabled=false + ports: + - "9200:9200" + networks: + - elastic + + kibana: + image: docker.elastic.co/kibana/kibana:7.15.2 + container_name: kibana + environment: + ELASTICSEARCH_HOSTS: http://elasticsearch:9200 + ports: + - "5601:5601" + networks: + - elastic + + logstash: + image: docker.elastic.co/logstash/logstash:7.15.2 + container_name: logstash + volumes: + - ./logstash-config/:/usr/share/logstash/pipeline/ + environment: + LS_JAVA_OPTS: "-Xmx256m -Xms256m" + ports: + - "5000:5000" + - "9600:9600" + networks: + - elastic + + metricbeat: + image: docker.elastic.co/beats/metricbeat:7.15.2 + container_name: metricbeat + command: metricbeat -e -E output.elasticsearch.hosts=["http://elasticsearch:9200"] + depends_on: + - elasticsearch + networks: + - elastic + + python-app: + build: + context: ./app + dockerfile: Dockerfile + container_name: python-app + volumes: + - ./app:/app + networks: + - elastic + +networks: + elastic: + driver: bridge diff --git a/catch-all/05_infra_test/04_elastic_stack/logstash-config/pipeline.conf b/catch-all/05_infra_test/04_elastic_stack/logstash-config/pipeline.conf new file mode 100644 index 0000000..5279b38 --- /dev/null +++ b/catch-all/05_infra_test/04_elastic_stack/logstash-config/pipeline.conf @@ -0,0 +1,26 @@ +input { + file { + path => "/path/to/your/data/wiki_movie_plots_deduped.csv" + start_position => "beginning" + sincedb_path => "/dev/null" + } +} + +filter { + csv { + separator => "," + columns => ["Release Year", "Title", "Origin/Ethnicity", "Director", "Cast", "Genre", "Wiki Page", "Plot"] + } + + mutate { + convert => { "Release Year" => "integer" } + } +} + +output { + elasticsearch { + hosts => ["http://elasticsearch:9200"] + index => "movies" + } + stdout { codec => rubydebug } +} \ No newline at end of file diff --git a/catch-all/05_infra_test/README.md b/catch-all/05_infra_test/README.md index d8b56b7..c6ea77b 100644 --- a/catch-all/05_infra_test/README.md +++ b/catch-all/05_infra_test/README.md @@ -6,10 +6,11 @@ -| Nombre | Descripción | Nivel | -| --------------------------------- | --------------------------------------------------- | ---------- | -| [redis](./01_redis_flask_docker/) | Despliegue de un contenedor de redis y flask | Básico | -| [rabbit](./02_rabbitmq/README.md) | Despliegue de distintas arquitecturas para rabbitmq | Intermedio | -| Apache Kafka (proximamente) | Despliegue de un contenedor de Apache Kafka | Básico | -| Prometheus Grafana (proximamente) | Despliegue de un contenedor de Prometheus Grafana | Básico | -| SonarQube (proximamente) | Despliegue de un contenedor de SonarQube | Básico | +| Nombre | Descripción | Nivel | +| -------------------------------------------------------------------------------------------------- | ----------------------------------------------------- | ---------- | +| [redis](./01_redis_flask_docker/) | Despliegue de un contenedor de redis y flask | Básico | +| [rabbit](./02_rabbitmq/README.md) | Despliegue de distintas arquitecturas para rabbitmq | Intermedio | +| Apache Kafka (próximamente) | Despliegue de Apache Kafka con productor y consumidor | Básico | +| [Elastic stack](./04_elastic_stack/README.md) | Despliegue de Elastic Stack | Básico | +| Prometheus Grafana (proximamente) | Despliegue de un contenedor de Prometheus Grafana | Básico | +| SonarQube (proximamente) | Despliegue de un contenedor de SonarQube | Básico |