Acelerando los desarrollos con contenedores : Infraestructura de Apache Kafka

Publicado por Víctor Madrid el

Arquitectura de SolucionesAceleradores de DesarrolloKafka

En este artículo se va a mostrar cómo poder disponer de una infraestructura para Apache Kafka de forma rápida para utilizarla en distintos entornos: locales, desarrollo, integración y QA.

Contexto

En mi caso particular cambio de contexto muy rápido y muchas veces... y la mayoría de las veces cosas que suelen ser totalmente diferentes entre ellas. Con este planteamiento si te gusta guardar TODO (instalaciones, configuraciones, variables de entorno, diferentes versiones, librerías, etc.) lo que utilizas por si lo volverás a utilizar en un futuro y encima lo haces de forma poco organizada lo que sueles tener es ... un super-problema. En muchos casos puede que seas organizado y el problema lo tengas por la forma de instalación de cada producto y de cada versión (Por ejemplo: uso de variables de entorno diferentes)

Este super-problema es lo que yo denomino en broma el "Síndrome de Diógenes del Desarrollo". Es decir, que acabas acumulando en muchos casos ciertos "productos" instalados que muchas veces son exactamente los de fábrica, otras veces no lo son porque usan ficheros de configuración específicos tuyos, disponemos de mucha configuración instalada en la máquina, en algunos casos decidimos incorporación plugin/integraciones extras (oficiales y no oficiales), etc. Al final lo que suele pasar es que esos productos en muchos casos NO funcionan o no como deberían porque tu "manipulación" se lo ha cargado.

Curiosidad sobre el origen de este artículo
En algún momento de mi trabajo dejé mi infraestructura de Kafka local inoperativa, no la corregí en el momento (mal hecho por mi parte o quizás no me di cuenta), cambie de contexto y cuando tiempo después volví ya NO me acordaba que había tocado.
Seguro que much@s os sentís identificad@s con esto...jejeje.

Ante la situación de tener un producto instalado inoperativo siempre surgen 2 opciones:

Opción "Empezar desde 0"

Borrar la instalación y todos sus elementos asociados (temporales, registros, configuración, etc.) NO dejándote nada, instalar la misma versión de la misma manera o la nueva según digan en el manual, configurarlo todo otra vez a como tu quieres, etc.

Todo esto supone volver a dedicar un tiempo muy valioso que ya invertiste una vez y si encima se elige instalar otra versión entonces NO se puede asegurar la total compatibilidad del resto de las cosas que se necesitaban (así que cruzamos los dedos durante su uso...)

Opción "Descubrir el Expediente X"

Investigar los ficheros de configuración, variables de entorno, comparar los ficheros actuales con posibles backups de los originales (si los tienes), revisar los logs para ver posibles errores, etc.

En definitiva, es ver que ha cambiado desde la última vez que lo viste funcionar.

La solución en mi caso fue decidir plantear una solución para que NO volviera a pasarme con la infraestructura de Kafka y para ello decidí plantear mi solución mediante contenedores y así aprovecharme de todas sus ventajas.

Por cierto, además de resolver el caso anterior también me ha ayudado para solucionar otros "problemas" como:

  • Cambiar de portátil (mismo entorno o diferente entorno) y por lo tanto era necesario volver a montar la infraestructura Kafka
  • Probar otra nueva versión de Kafka y sus componentes para ver nuevas funcionalidades o resolución de bugs
  • Probar algo rápido con alguna configuración específica
  • Ayudarme a preparar PoCs rápidas
  • Etc.

Este artículo está dividido en 4 partes:

  • 1. Introducción
  • 2. Stack Tecnológico
  • 3. Ejemplos de Uso
  • 4. Mejoras
  • 5. Conclusiones

1. Introducción

En este apartado se tratarán los siguiente puntos :

  • 1.1. Introducción al uso de contenedores como aceleradores del desarrollo
  • 1.2. ¿Qué necesidades tenemos para tener una "Infraestructura de Apache Kafka" con contenedores?

1.1. Introducción al uso de contenedores como aceleradores del desarrollo

Para centralizar esta información en un único punto y así poder facilitar su consulta se ha diseñado un artículo específico

1.2. ¿Qué necesidades tenemos para requerir una "Infraestructura de Apache Kafka" con contenedores?

Pueden ser muchos los motivos para querer disponer de una infraestructura de Apache Kafka en local basada en contenedores pero yo voy a describir para lo que yo la he utilizado :

  • Disponer de un entorno inmutable
  • Entender y diseñar diferentes escenarios tipo
  • Aprender a utilizar cada uno de los componentes
  • Probar compatibilidad entre componentes
  • Probar con diferentes versiones de cada uno de los componentes utilizados y asi verificar nueva funcionalidad o posibles arreglos de bugs
  • Probar cambios de configuración sin miedo a romper nada -> "Si se rompe pues lo volvemos a reconstruir"
  • Disponer de un entorno de testing con un comportamiento predecible al ser determinista -> "Si te funciona una vez y no cambias nada, te funcionará el resto de las veces"
  • Acelerar mis desarrollos de productores y consumidores asegurando su correcta integración con la plataforma
  • Etc.

Este es el dagrama de componentes básicos implicados en la infraestructura de Kafka:

2. Stack Tecnológico

Este es stack tecnológico elegido para implementar la funcionalidad "Infraestructura de Apache Kafka":

  • Java 8
  • Docker - Tecnología de Contenedores/Containers
  • Docker Hub - Repositorio de Docker Público donde se ubican las imágenes oficiales
  • Zookeeper - Gestor centralizado para componentes distribuidos
  • Kafka - Plataforma de Streaming distribuida

3. Ejemplos de Uso

Para enseñar a utilizarlo y así practicar se ha habilitado un repositorio, este repositorio se reutilizará para otros artículos con Kafka.

La parte de que tiene que ver con este artículo se encuentra en el apartado de infrastructure/environment/basic

Se han habilitado diferentes instalaciones de Zookeeper:

Cada implementación se configurado con diferentes escenarios:

  • zk-single-kafka-single : 1 Zookeeper y 1 Broker Kafka
  • zk-single-kafka-multiple : 1 Zookeeper y 3 Brokers Kafka
  • zk-multiple-kafka-single : 3 Zookeepers y 1 Broker Kafka
  • zk-multiple-kafka-multiple : 3 Zookeepers y 3 Brokers Kafka

3.1. Scripts de Soporte

Se han habilitado una primera versión de dos scripts para ayudar a entender y probar los entornos Kafka:

  • info-kafka-infrastructure-v1
  • test-kafka-infrastructure-v1

Los scripts se ubican en el repositorio

info-kafka-infrastructure-v1

Script informativo utilizado para :

  • Mostrar los procesos involucrados en un fichero "docker-compose"
  • Validar el nº de procesos ejecutados respecto a un parámetro esperado
  • Mostrar los brokers disponibles

Ejemplo de ejecución para el caso zk-single-kafka-single de zookeeper-3.4.9

sh info-kafka-infrastructure-v1.sh ../zookeeper-3.4.9/zk-single-kafka-single.yml 2  

Nota: Requiere que la infraestructura elegida se encuentre arrancada

El primer parámetro se corresponde con el fichero docker-compose con el que se quiere trabajar.

El segundo parámetro establece el nº de procesos esperados durante su ejecución.

test-kafka-infrastructure-v1

Script de testing de la infraestructura utilizado para:

  • Probar la creación y eliminación de topics
  • Probar el envío de mensajes
  • Probar la recepción de mensajes
  • Validar el número de mensajes recibidos para validar la prueba

Ejemplo de ejecución para el caso zk-single-kafka-single de zookeeper-3.4.9

sh test-kafka-infrastructure-v1.sh ../zookeeper-3.4.9/zk-single-kafka-single.yml false  

Nota : Requiere que la infraestructura elegida se encuentre arrancada

El primer parámetro se corresponde con el fichero docker-compose con el que se quiere trabajar

El segundo parámetro establece si requiere probar a eliminas los topics utilizados durante la prueba de inicio

3.2. Investigar lo que se usa

Cuando uno crea un contenedor basado en una imagen ya se ha visto que consideraciones se debe tener para llegar a entender que es lo que hace.

Si tiene alguna duda vuelve a revisar el apartado "Soporte de Análisis de Contenedores" del artículo de introducción : Acelerando los desarrollos con contenedores: Introducción

Ejemplo de ejecución para mostrar la información de los procesos para el caso zk-single-kafka-single

Ejemplo de inspección de un Broker Kafka (para la anterior ejecución de entorno)

Ejemplo de investigación general dentro del contenedor : Broker Kafka (anterior ejecución de entorno)

Ejemplo de investigación del comando de ejecución : Broker Kafka (anterior ejecución de entorno)

Ejemplo de investigación del fichero "configure"

En mi caso esta investigación me ha ayudado a conocer mucho más como están configuradas cada una de las imágenes, ver que valores por defecto utilizan, ver las condiciones que los establecen, ver que cosas no tienen habilitadas, encontrar errores en mi configuración, verificar si la documentación estaba bien o no (un clásico), etc.

Por ejemplo, un punto que me costo mucho sacar es el de la configuración de múltiples Zookeepers que difería de una implementación a otra

  • Para la imagen de Zookeeper original la configuración mediante variables de entorno era :
ZOO_SERVERS: server.1=zookeeper-1:2888:3888 server.2=zookeeper-2:2888:3888 server.3=zookeeper-3:2888:3888  
  • Para la imagen de Zookeeper proporcionada por Confluent la configuración mediante variables de entorno era :
ZOOKEEPER_SERVERS: zookeeper-1:2888:3888;zookeeper-2:2888:3888;zookeeper-3:2888:3888  

Cambiaba el nombre de la variable utiliza, la forma de estructurar múltiples valores y la forma de representar cada propio valor.

3.3. Escenarios

Estos son los escenarios con los que se trabaja :

  • zk-single-kafka-single : 1 Zookeeper y 1 Broker Kafka
  • zk-single-kafka-multiple : 1 Zookeeper y 3 Brokers Kafka
  • zk-multiple-kafka-single : 3 Zookeepers y 1 Broker Kafka
  • zk-multiple-kafka-multiple : 3 Zookeepers y 3 Brokers Kafka

Cada uno de los escenarios se define mediante un fichero docker.compose.yml donde se define:

  • La implementación de Zookeeper y su versión
  • La implementación de un Broker Kafka y su versión
  • La configuración de ambos componentes vía variables de entorno
  • El mapeo de puertos de ambos hacia el exterior
  • El mapeo de los volumenes para el acceso y persistencia sobre la configuración, logs etc
Nota :
Se han realizado las pruebas de escenario para la implementación zookeeper-3.4.9 pero tiene un comportamiento similar para la implementacion cp-zookeeper-5.0.0

zk-single-kafka-single

Se compone de un 1 Zookeeper y 1 Broker Kafka

  • Objetivo: Escenario básico y más utilizado para pruebas rápidas y desarrollos de poca complejidad
drawing

Fichero docker-compose.yml utilizado

version: '3'

services:

  zookeeper-1:
    image: zookeeper:3.4.9
    hostname: zookeeper-1
    environment:
      ZOO_MY_ID: 1
      ZOO_PORT: 2181
      ZOO_SERVERS: server.1=zookeeper-1:2888:3888
      # ZOO_LOG4J_PROP: "INFO,CONSOLE,ROLLINGFILE" -- No Include
    ports:
      - "2181:2181"
    volumes:
      - ./zk-single-kafka-single/zookeeper-1/conf:/conf
      - ./zk-single-kafka-single/zookeeper-1/logs:/logs
      - ./zk-single-kafka-single/zookeeper-1/data:/data
      - ./zk-single-kafka-single/zookeeper-1/datalog:/datalog

  kafka-1:
    image: confluentinc/cp-kafka:5.4.0
    hostname: kafka-1
    environment:
      KAFKA_BROKER_ID: 1
      KAFKA_ZOOKEEPER_CONNECT: "zookeeper-1:2181"
      KAFKA_ADVERTISED_LISTENERS: LISTENER_DOCKER_INTERNAL://kafka-1:29092,LISTENER_DOCKER_EXTERNAL://${DOCKER_HOST_IP:-127.0.0.1}:9092
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: LISTENER_DOCKER_INTERNAL:PLAINTEXT,LISTENER_DOCKER_EXTERNAL:PLAINTEXT
      KAFKA_INTER_BROKER_LISTENER_NAME: LISTENER_DOCKER_INTERNAL
      KAFKA_LOG4J_ROOT_LOGLEVEL: INFO
      # KAFKA_LOG4J_LOGGERS: "kafka.controller=INFO,kafka.producer.async.DefaultEventHandler=INFO,state.change.logger=INFO"
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
    ports:
      - "9092:9092"
      - "29092:29092"
    volumes:
      - ./zk-single-kafka-single/kafka-1/logs:/var/log/kafka
      - ./zk-single-kafka-single/kafka-1/data:/var/lib/kafka/data
    depends_on:
      - zookeeper-1

Comando para arrancar el escenario:

docker-compose -f zk-single-kafka-single.yml up -d  
  • -f : Indica el fichero que ejecutará
  • -d : Ejecuta los contenedores en modo background

Comando para parar el escenario:

docker-compose -f zk-single-kafka-single.yml down  

Comando de ejecución para info-kafka-infrastructure-v1

sh info-kafka-infrastructure-v1.sh ../zookeeper-3.4.9/zk-single-kafka-single.yml 2  
  • ../zookeeper-3.4.9/zk-single-kafka-single.yml : Fichero de configuración utilizado
  • false : Parámetro que indica si tiene que eliminar de inicio los topics utilizados durante la prueba (En este caso al ser false NO lo debería hacer)

Comando de ejecución para test-kafka-infrastructure-v1:

sh test-kafka-infrastructure-v1.sh ../zookeeper-3.4.9/zk-single-kafka-single.yml false  
  • ../zookeeper-3.4.9/zk-single-kafka-single.yml: Fichero de configuración utilizado
  • false: Parámetro que indica si tiene que eliminar de inicio los topics utilizados durante la prueba (En este caso al ser false NO lo debería hacer)

De esta forma queda comprobado el correcto funcionamiento del escenario.

zk-single-kafka-multiple

Se compone de un 1 Zookeeper y 3 Brokers Kafka:

  • Objetivo: Escenario que al disponer de varios Brokers facilita desarrollar/probar aspectos relacionados con la replicación y la tolerancia a fallos
drawing

Fichero docker-compose.yml utilizado

version: '3'

services:

  zookeeper-1:
    image: zookeeper:3.4.9
    hostname: zookeeper-1
    environment:
      ZOO_MY_ID: 1
      ZOO_PORT: 2181
      ZOO_SERVERS: server.1=zookeeper-1:2888:3888
      # ZOO_LOG4J_PROP: "INFO,CONSOLE,ROLLINGFILE" -- No Include
    ports:
      - "2181:2181"
    volumes:
      - ./zk-single-kafka-multiple/zookeeper-1/conf:/conf
      - ./zk-single-kafka-multiple/zookeeper-1/logs:/logs
      - ./zk-single-kafka-multiple/zookeeper-1/data:/data
      - ./zk-single-kafka-multiple/zookeeper-1/datalog:/datalog

  kafka-1:
    image: confluentinc/cp-kafka:5.4.0
    hostname: kafka-1
    environment:
      KAFKA_BROKER_ID: 1
      KAFKA_ZOOKEEPER_CONNECT: "zookeeper-1:2181"
      KAFKA_ADVERTISED_LISTENERS: LISTENER_DOCKER_INTERNAL://kafka-1:29092,LISTENER_DOCKER_EXTERNAL://${DOCKER_HOST_IP:-127.0.0.1}:9092
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: LISTENER_DOCKER_INTERNAL:PLAINTEXT,LISTENER_DOCKER_EXTERNAL:PLAINTEXT
      KAFKA_INTER_BROKER_LISTENER_NAME: LISTENER_DOCKER_INTERNAL
      # KAFKA_LOG4J_LOGGERS: "kafka.controller=INFO,kafka.producer.async.DefaultEventHandler=INFO,state.change.logger=INFO"
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
    ports:
      - "9092:9092"
      - "29092:29092"
    volumes:
      - ./zk-single-kafka-multiple/kafka-1/logs:/var/log/kafka
      - ./zk-single-kafka-multiple/kafka-1/data:/var/lib/kafka/data
    depends_on:
      - zookeeper-1

  kafka-2:
    image: confluentinc/cp-kafka:5.4.0
    hostname: kafka-2
    environment:
      KAFKA_BROKER_ID: 2
      KAFKA_ZOOKEEPER_CONNECT: "zookeeper-1:2181"
      KAFKA_ADVERTISED_LISTENERS: LISTENER_DOCKER_INTERNAL://kafka-2:29093,LISTENER_DOCKER_EXTERNAL://${DOCKER_HOST_IP:-127.0.0.1}:9093
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: LISTENER_DOCKER_INTERNAL:PLAINTEXT,LISTENER_DOCKER_EXTERNAL:PLAINTEXT
      KAFKA_INTER_BROKER_LISTENER_NAME: LISTENER_DOCKER_INTERNAL
      # KAFKA_LOG4J_LOGGERS: "kafka.controller=INFO,kafka.producer.async.DefaultEventHandler=INFO,state.change.logger=INFO"
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
    ports:
      - "9093:9093"
      - "29093:29093"
    volumes:
      - ./zk-single-kafka-multiple/kafka-2/logs:/var/log/kafka
      - ./zk-single-kafka-multiple/kafka-2/data:/var/lib/kafka/data
    depends_on:
      - zookeeper-1

  kafka-3:
    image: confluentinc/cp-kafka:5.4.0
    hostname: kafka-3
    environment:
      KAFKA_BROKER_ID: 3
      KAFKA_ZOOKEEPER_CONNECT: "zookeeper-1:2181"
      KAFKA_ADVERTISED_LISTENERS: LISTENER_DOCKER_INTERNAL://kafka-3:29094,LISTENER_DOCKER_EXTERNAL://${DOCKER_HOST_IP:-127.0.0.1}:9094
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: LISTENER_DOCKER_INTERNAL:PLAINTEXT,LISTENER_DOCKER_EXTERNAL:PLAINTEXT
      KAFKA_INTER_BROKER_LISTENER_NAME: LISTENER_DOCKER_INTERNAL
      # KAFKA_LOG4J_LOGGERS: "kafka.controller=INFO,kafka.producer.async.DefaultEventHandler=INFO,state.change.logger=INFO"
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
    ports:
      - "9094:9094"
      - "29094:29094"
    volumes:
      - ./zk-single-kafka-multiple/kafka-3/logs:/var/log/kafka
      - ./zk-single-kafka-multiple/kafka-3/data:/var/lib/kafka/data
    depends_on:
      - zookeeper-1

Comando para arrancar el escenario

docker-compose -f zk-single-kafka-multiple.yml up -d  
  • -f : Indica el fichero que ejecutará
  • -d : Ejecuta los contenedores en modo background

Comando para parar el escenario

docker-compose -f zk-single-kafka-multiple.yml down  

Comando de ejecución para info-kafka-infrastructure-v1

sh info-kafka-infrastructure-v1.sh ../zookeeper-3.4.9/zk-single-kafka-multiple.yml 4  
  • ../zookeeper-3.4.9/zk-single-kafka-multiple.yml : Fichero de configuración utilizado
  • 4 : Parámetro que indica el número de procesos arrancados (uno por componente)

Comando de ejecución para test-kafka-infrastructure-v1

sh test-kafka-infrastructure-v1.sh ../zookeeper-3.4.9/zk-single-kafka-multiple.yml false  
  • ../zookeeper-3.4.9/zk-single-kafka-multiple.yml : Fichero de configuración utilizado
  • false : Parámetro que indica el número de procesos arrancados (uno por componente)

De esta forma queda comprobado el correcto funcionamiento del escenario

zk-multiple-kafka-single

Se compone de 3 Zookeepers y 1 Broker Kafka:

  • Objetivo : Escenario que al disponer de varios Zookeepers facilita desarrollar/probar aspectos relacionados su tolerancia a fallos
drawing

Fichero docker-compose.yml utilizado

version: '3'

services:

  zookeeper-1:
    image: zookeeper:3.4.9
    hostname: zookeeper-1
    environment:
      ZOO_MY_ID: 1
      ZOO_PORT: 2181
      ZOO_SERVERS: server.1=zookeeper-1:2888:3888 server.2=zookeeper-2:2888:3888 server.3=zookeeper-3:2888:3888
      # ZOO_LOG4J_PROP: "INFO,CONSOLE,ROLLINGFILE" -- No Include
    ports:
      - "2181:2181"
    volumes:
      - ./zk-multiple-kafka-single/zookeeper-1/conf:/conf
      - ./zk-multiple-kafka-single/zookeeper-1/logs:/logs
      - ./zk-multiple-kafka-single/zookeeper-1/data:/data
      - ./zk-multiple-kafka-single/zookeeper-1/datalog:/datalog

  zookeeper-2:
    image: zookeeper:3.4.9
    hostname: zookeeper-2
    environment:
      ZOO_MY_ID: 2
      ZOO_PORT: 2182
      ZOO_SERVERS: server.1=zookeeper-1:2888:3888 server.2=zookeeper-2:2888:3888 server.3=zookeeper-3:2888:3888
      # ZOO_LOG4J_PROP: "INFO,CONSOLE,ROLLINGFILE" -- No Include
    ports:
      - "2182:2182"
    volumes:
      - ./zk-multiple-kafka-single/zookeeper-2/conf:/conf
      - ./zk-multiple-kafka-single/zookeeper-2/logs:/logs
      - ./zk-multiple-kafka-single/zookeeper-2/data:/data
      - ./zk-multiple-kafka-single/zookeeper-2/datalog:/datalog

  zookeeper-3:
    image: zookeeper:3.4.9
    hostname: zookeeper-3
    ports:
      - "2183:2183"
    environment:
        ZOO_MY_ID: 3
        ZOO_PORT: 2183
        ZOO_SERVERS: server.1=zookeeper-1:2888:3888 server.2=zookeeper-2:2888:3888 server.3=zookeeper-3:2888:3888
    # ZOO_LOG4J_PROP: "INFO,CONSOLE,ROLLINGFILE" -- No Include
    volumes:
      - ./zk-multiple-kafka-single/zookeeper-3/conf:/conf
      - ./zk-multiple-kafka-single/zookeeper-3/logs:/logs
      - ./zk-multiple-kafka-single/zookeeper-3/data:/data
      - ./zk-multiple-kafka-single/zookeeper-3/datalog:/datalog

  kafka-1:
    image: confluentinc/cp-kafka:5.4.0
    hostname: kafka-1
    environment:
      KAFKA_BROKER_ID: 1
      KAFKA_ZOOKEEPER_CONNECT: "zookeeper-1:2181,zookeeper-2:2182,zookeeper-3:2183"
      KAFKA_ADVERTISED_LISTENERS: LISTENER_DOCKER_INTERNAL://kafka-1:29092,LISTENER_DOCKER_EXTERNAL://${DOCKER_HOST_IP:-127.0.0.1}:9092
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: LISTENER_DOCKER_INTERNAL:PLAINTEXT,LISTENER_DOCKER_EXTERNAL:PLAINTEXT
      KAFKA_INTER_BROKER_LISTENER_NAME: LISTENER_DOCKER_INTERNAL
      # KAFKA_LOG4J_LOGGERS: "kafka.controller=INFO,kafka.producer.async.DefaultEventHandler=INFO,state.change.logger=INFO"
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
    ports:
      - "9092:9092"
      - "29092:29092"
    volumes:
      - ./zk-multiple-kafka-single/kafka-1/logs:/var/log/kafka
      - ./zk-multiple-kafka-single/kafka-1/data:/var/lib/kafka/data
    depends_on:
      - zookeeper-1
      - zookeeper-2
      - zookeeper-3

Comando para arrancar el escenario:

docker-compose -f zk-multiple-kafka-single.yml up -d  
  • -f : Indica el fichero que ejecutara
  • -d : Ejecuta los contenedores en modo background

Comando para parar el escenario:

docker-compose -f zk-multiple-kafka-single.yml down  

Comando de ejecución para info-kafka-infrastructure-v1:

sh info-kafka-infrastructure-v1.sh ../zookeeper-3.4.9/zk-multiple-kafka-single.yml 4  
  • ../zookeeper-3.4.9/zk-multiple-kafka-single.yml : Fichero de configuración utilizado
  • 4 : Parámetro que indica el número de procesos arrancados (uno por componente)

Comando de ejecución para test-kafka-infrastructure-v1

sh test-kafka-infrastructure-v1.sh ../zookeeper-3.4.9/zk-multiple-kafka-single.yml false  

De esta forma queda comprobado el correcto funcionamiento del escenario.

zk-multiple-kafka-multiple

Se compone de 3 Zookeepers y 3 Brokers Kafka:

  • Objetivo: Escenario que al disponer de varios de todo puede parecerse más a lo utilizado en un entorno de producción
drawing

Fichero docker-compose.yml utilizado

version: '3'

services:

  zookeeper-1:
    image: zookeeper:3.4.9
    hostname: zookeeper-1
    environment:
      ZOO_MY_ID: 1
      ZOO_PORT: 2181
      ZOO_SERVERS: server.1=zookeeper-1:2888:3888 server.2=zookeeper-2:2888:3888 server.3=zookeeper-3:2888:3888
      # ZOO_LOG4J_PROP: "INFO,CONSOLE,ROLLINGFILE" -- No Include
    ports:
      - "2181:2181"
    volumes:
      - ./zk-multiple-kafka-multiple/zookeeper-1/conf:/conf
      - ./zk-multiple-kafka-multiple/zookeeper-1/logs:/logs
      - ./zk-multiple-kafka-multiple/zookeeper-1/data:/data
      - ./zk-multiple-kafka-multiple/zookeeper-1/datalog:/datalog

  zookeeper-2:
    image: zookeeper:3.4.9
    hostname: zookeeper-2
    environment:
      ZOO_MY_ID: 2
      ZOO_PORT: 2182
      ZOO_SERVERS: server.1=zookeeper-1:2888:3888 server.2=zookeeper-2:2888:3888 server.3=zookeeper-3:2888:3888
      # ZOO_LOG4J_PROP: "INFO,CONSOLE,ROLLINGFILE" -- No Include
    ports:
      - "2182:2182"
    volumes:
      - ./zk-multiple-kafka-multiple/zookeeper-2/conf:/conf
      - ./zk-multiple-kafka-multiple/zookeeper-2/logs:/logs
      - ./zk-multiple-kafka-multiple/zookeeper-2/data:/data
      - ./zk-multiple-kafka-multiple/zookeeper-2/datalog:/datalog

  zookeeper-3:
    image: zookeeper:3.4.9
    hostname: zookeeper-3
    ports:
      - "2183:2183"
    environment:
        ZOO_MY_ID: 3
        ZOO_PORT: 2183
        ZOO_SERVERS: server.1=zookeeper-1:2888:3888 server.2=zookeeper-2:2888:3888 server.3=zookeeper-3:2888:3888
    # ZOO_LOG4J_PROP: "INFO,CONSOLE,ROLLINGFILE" -- No Include
    volumes:
      - ./zk-multiple-kafka-multiple/zookeeper-3/conf:/conf
      - ./zk-multiple-kafka-multiple/zookeeper-3/logs:/logs
      - ./zk-multiple-kafka-multiple/zookeeper-3/data:/data
      - ./zk-multiple-kafka-multiple/zookeeper-3/datalog:/datalog

  kafka-1:
    image: confluentinc/cp-kafka:5.4.0
    hostname: kafka-1
    environment:
      KAFKA_BROKER_ID: 1
      KAFKA_ZOOKEEPER_CONNECT: "zookeeper-1:2181,zookeeper-2:2182,zookeeper-3:2183"
      KAFKA_ADVERTISED_LISTENERS: LISTENER_DOCKER_INTERNAL://kafka-1:29092,LISTENER_DOCKER_EXTERNAL://${DOCKER_HOST_IP:-127.0.0.1}:9092
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: LISTENER_DOCKER_INTERNAL:PLAINTEXT,LISTENER_DOCKER_EXTERNAL:PLAINTEXT
      KAFKA_INTER_BROKER_LISTENER_NAME: LISTENER_DOCKER_INTERNAL
      # KAFKA_LOG4J_LOGGERS: "kafka.controller=INFO,kafka.producer.async.DefaultEventHandler=INFO,state.change.logger=INFO"
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
    ports:
      - "9092:9092"
      - "29092:29092"
    volumes:
      - ./zk-multiple-kafka-multiple/kafka-1/logs:/var/log/kafka
      - ./zk-multiple-kafka-multiple/kafka-1/data:/var/lib/kafka/data
    depends_on:
      - zookeeper-1
      - zookeeper-2
      - zookeeper-3

  kafka-2:
    image: confluentinc/cp-kafka:5.4.0
    hostname: kafka-2
    environment:
      KAFKA_BROKER_ID: 2
      KAFKA_ZOOKEEPER_CONNECT: "zookeeper-1:2181,zookeeper-2:2182,zookeeper-3:2183"
      KAFKA_ADVERTISED_LISTENERS: LISTENER_DOCKER_INTERNAL://kafka-2:29093,LISTENER_DOCKER_EXTERNAL://${DOCKER_HOST_IP:-127.0.0.1}:9093
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: LISTENER_DOCKER_INTERNAL:PLAINTEXT,LISTENER_DOCKER_EXTERNAL:PLAINTEXT
      KAFKA_INTER_BROKER_LISTENER_NAME: LISTENER_DOCKER_INTERNAL
      # KAFKA_LOG4J_LOGGERS: "kafka.controller=INFO,kafka.producer.async.DefaultEventHandler=INFO,state.change.logger=INFO"
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
    ports:
      - "9093:9093"
      - "29093:29093"
    volumes:
      - ./zk-multiple-kafka-multiple/kafka-2/logs:/var/log/kafka
      - ./zk-multiple-kafka-multiple/kafka-2/data:/var/lib/kafka/data
    depends_on:
      - zookeeper-1
      - zookeeper-2
      - zookeeper-3

  kafka-3:
    image: confluentinc/cp-kafka:5.4.0
    hostname: kafka-3
    environment:
      KAFKA_BROKER_ID: 3
      KAFKA_ZOOKEEPER_CONNECT: "zookeeper-1:2181,zookeeper-2:2182,zookeeper-3:2183"
      KAFKA_ADVERTISED_LISTENERS: LISTENER_DOCKER_INTERNAL://kafka-3:29094,LISTENER_DOCKER_EXTERNAL://${DOCKER_HOST_IP:-127.0.0.1}:9094
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: LISTENER_DOCKER_INTERNAL:PLAINTEXT,LISTENER_DOCKER_EXTERNAL:PLAINTEXT
      KAFKA_INTER_BROKER_LISTENER_NAME: LISTENER_DOCKER_INTERNAL
      # KAFKA_LOG4J_LOGGERS: "kafka.controller=INFO,kafka.producer.async.DefaultEventHandler=INFO,state.change.logger=INFO"
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
    ports:
      - "9094:9094"
      - "29094:29094"
    volumes:
      - ./zk-multiple-kafka-multiple/kafka-3/logs:/var/log/kafka
      - ./zk-multiple-kafka-multiple/kafka-3/data:/var/lib/kafka/data
    depends_on:
      - zookeeper-1
      - zookeeper-2
      - zookeeper-3

Comando para arrancar el escenario:

docker-compose -f zk-multiple-kafka-multiple.yml up -d  
  • -f : Indica el fichero que ejecutara
  • -d : Ejecuta los contenedores en modo background

Comando para parar el escenario:

docker-compose -f zk-multiple-kafka-multiple.yml down  

Comando de ejecución para info-kafka-infrastructure-v1:

sh info-kafka-infrastructure-v1.sh ../zookeeper-3.4.9/zk-multiple-kafka-multiple.yml 6  
  • ../zookeeper-3.4.9/zk-multiple-kafka-multiple.yml : Fichero de configuración utilizado
  • 6 : Parámetro que indica el número de procesos arrancados (uno por componente)

Comando de ejecución para test-kafka-infrastructure-v1

sh test-kafka-infrastructure-v1.sh ../zookeeper-3.4.9/zk-multiple-kafka-multiple.yml false  

De esta forma queda comprobado el correcto funcionamiento del escenario.

3.4. El problema de los volúmenes

Todas las configuraciones de los escenarios realizadas aquí hacen uso de volúmenes de persistencia, de esta forma se permite que la infraestructura mantenga todo aquello que tenía en uso tras caídas inesperadas o provocadas.

Es importante tener en cuanta que cada contenedor utilizado puede hacer uso de algunos volúmenes particulares según lo que se necesite.

¿Qué es volumen en Docker ?
Un volumen en Docker es un objeto específico que define un directorio o un fichero en el Docker Engine y que se monta directamente en el contenedor
En este caso se utiliza para mapear un directorio del contenedor con un directorio de la máquina local.

Prácticas utilizadas en los escenarios definidos

En cada escenario se ha establecido que los contenedores registren sus volúmenes mapeados a un mismo directorio local principal.

Este directorio tendrá el nombre del fichero de docker-compose con lo lanza. Por ejemplo : "./zk-single-kafka-multiple/".

Esta decisión proporciona las siguientes características :

  • Facilita la centralización -> Todo lo que se genere estará en el mismo sitio
  • Facilita la identificación -> Puedo diferenciar este volumen de otros volúmenes de contenedores u otros directorios
  • Facilita la ubicación -> Se encuentra próximo al fichero docker-compose gracias a la característica de "./"

Cada uno de los contenedores utilizados y que lo requieran, tendrán sus volúmenes particulares mapeados al anterior directorio principal y a un subdirectorio con el nombre del contenedor. Por ejemplo el broker "kafka-3" del escenario "zk-single-kafka-multiple" registrará sus cosas en "./zk-single-kafka-multiple/kafka-3".

Puede ser que cada contenedor tenga subvolúmenes asociados según aquello que se quiera tener controlado (datos, logs, configuración, etc.). Por ejemplo "./zk-multiple-kafka-multiple/kafka-3/logs"

Se pueden utilizar otros criterios de definición de volúmenes, recordar que los datos aquí contenidos afectan al comportamiento de la infraestructura por lo que es importante tener cuidado.

IMPORTANTE
Recordar eliminar los volúmenes si se quiere resetear toda la instalación y/o configuración.

Ejemplo de mapeo de volúmenes

...
volumes:  
      - ./zk-multiple-kafka-multiple/kafka-3/logs:/var/log/kafka
      - ./zk-multiple-kafka-multiple/kafka-3/data:/var/lib/kafka/data
...

Se mapean unos directorios concretos de dentro del contenedor con unos directorios ubicados en la máquina local que siguen lo que se ha explicado antes.

4. Mejoras

Apartado donde se establecerán posibles mejoras sobre el artículo :

  • 4.1. Mejora 1 : Utilizar el productor / consumidor proporcionado por la plataforma
  • 4.2. Mejora 2 : Plataforma Web para la gestión de la infraestructura

4.1. Mejora 1 : Utilizar el productor / consumidor proporcionado por la plataforma

Cuando se instala Kafka normalmente se proporciona un directorio con scripts que facilitan la gestión de la infraestructura, de los topics, etc.

Normalmente se ubica en : KAFKA_HOME/bin

  • Suelen depender del entorno sobre el que se vaya a utilizar

De entre todos los scripts destacan 2 que pueden ayudar a realizar pruebas rápidas sobre la infraestructura :

  • kafka-console-consumer : Consumidor por consola de Kafka
  • kafka-console-producer : Productor por consola de Kafka

4.1.1. Arrancar el consumidor por consola (kafka-console-consumer)

//Desde comando si se tiene kafka accesible desde consola (cualquier sistema)
kafka-console-consumer --bootstrap-server localhost:9092 --topic topic-1 --property print.key=true --from-beginning

//Desde script Linux / Mac
sh kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic topic-1 --property print.key=true --from-beginning

./kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic topic-1 --property print.key=true --from-beginning

Los parámetros utilizados son :

  • --bootstrap-server : Establece la dirección de los Brokers con los que trabajara
  • Se le ha indicado que trabaje con el broker con puertos 9092
  • --topic : Establece el nombre del topic con el que trabajará
  • --property print.key=true : Mostrará el valor de la key
  • --from-beginning : Mostrará el contenido del topic desde el inicio

La consola se quedará "parada" esperando a que se empiece a recibir mensajes.

4.1.2. Arrancar el productor por consola (kafka-console-producer)

//Desde comando si se tiene kafka accesible desde consola (cualquier sistema)
kafka-console-producer --broker-list localhost:9092 --topic topic-1

//Desde script Linux / Mac
sh kafka-console-producer.sh --broker-list localhost:9092 --topic topic-1

./kafka-console-producer.sh --broker-list localhost:9092 --topic topic-1

Los parámetros utilizados son :

  • --bootstrap-server : Establece la dirección de los Brokers con los que trabajará
  • Se le ha indicado que trabaje con el broker con puertos 9092
  • --topic : Establece el nombre del topic con el que trabajará

La consola se quedará "parada" esperando a que se empiece a escribir mensajes.

4.2. Mejora 2 : Plataforma Web para la gestión de la infraestructura

Existen muchas plataformas para la gestión visual de la infraestructura Kafka y aprovechando que tenemos toda la infraestructura montada con contenedores, nos puede resultar relativamente sencillo añadir este componente.

NO es obligatorio su uso pero puede facilitar mucho la vida.

4.2.1. Kafka-Manager

Referencia Docker Hub sheepkiller/kafka-manager

manager:  
    image: sheepkiller/kafka-manager
    ports:
      - "9000:9000"
    environment:
      ZK_HOSTS: zookeeper-1:2181
    depends_on:
      - zookeeper-1

En nuestro caso utilizaremos otro contenedor con la configuración establecida para apuntar a nuestro Zookeeeper: zookeeper-1:2181

Se habilitará una URL : http://localhost:9000/

Pulsar sobre Cluster -> Add Cluster

  • Establecer un nombre
  • Establecer la dirección del zookeeper : zookeeper-1:2181
  • Utilizar el resto de configuraciones por defecto

Acceder al listado de clústeres

Seleccionar el clúster que hemos creado

A partir de este punto ya podemos acceder a la funcionalidad proporcionada por la plataforma : gestión de nodos, topics, etc.

5. Conclusiones

Con lo que se ha enseñado en este artículo podremos además de solventar algunos de los problemas que se detallaban al inicio, realizar pruebas rápidas con diferentes escenarios y con diferentes configuraciones internas para que el desarrollo sea más efectivo y por lo tanto la infraestructura utilizada NO sea un problema. Mejoramos al no tener que dedicar a instalarlo una y otra vez, asegurando que funciona de inicio al pasar esos scripts de soporte y con la seguridad de que si rompemos algo, lo podremos volver a recrear hasta el punto antes en el que nos lo cargamos.

Seguro que te han entrado ganas de usar Apache Kafka así... :-)

Si te ha gustado, ¡síguenos en Twitter para estar al día de próximos posts!

Autor

Víctor Madrid

Líder Técnico de la Comunidad de Arquitectura de Soluciones en atSistemas. Aprendiz de mucho y maestro de nada. Técnico, artista y polifacético a partes iguales ;-)