Conéctese a Kafka ejecutándose en Docker
Configuré un contenedor Kafka Docker de un solo nodo en mi máquina local como se describe en la documentación de Confluent (pasos 2 y 3).
Además, también expuse el puerto 2181 de Zookeeper y el puerto 9092 de Kafka para poder conectarme a ellos desde un cliente que se ejecuta en la máquina local:
$ docker run -d \
-p 2181:2181 \
--net=confluent \
--name=zookeeper \
-e ZOOKEEPER_CLIENT_PORT=2181 \
confluentinc/cp-zookeeper:4.1.0
$ docker run -d \
--net=confluent \
--name=kafka \
-p 9092:9092 \
-e KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181 \
-e KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://kafka:9092 \
-e KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=1 \
confluentinc/cp-kafka:4.1.0
Problema: cuando intento conectarme a Kafka desde la máquina host, la conexión falla porque can't resolve address: kafka:9092
.
Aquí está mi código Java:
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("client.id", "KafkaExampleProducer");
props.put("key.serializer", LongSerializer.class.getName());
props.put("value.serializer", StringSerializer.class.getName());
KafkaProducer<Long, String> producer = new KafkaProducer<>(props);
ProducerRecord<Long, String> record = new ProducerRecord<>("foo", 1L, "Test 1");
producer.send(record).get();
producer.flush();
La excepción:
java.io.IOException: Can't resolve address: kafka:9092
at org.apache.kafka.common.network.Selector.doConnect(Selector.java:235) ~[kafka-clients-2.0.0.jar:na]
at org.apache.kafka.common.network.Selector.connect(Selector.java:214) ~[kafka-clients-2.0.0.jar:na]
at org.apache.kafka.clients.NetworkClient.initiateConnect(NetworkClient.java:864) [kafka-clients-2.0.0.jar:na]
at org.apache.kafka.clients.NetworkClient.ready(NetworkClient.java:265) [kafka-clients-2.0.0.jar:na]
at org.apache.kafka.clients.producer.internals.Sender.sendProducerData(Sender.java:266) [kafka-clients-2.0.0.jar:na]
at org.apache.kafka.clients.producer.internals.Sender.run(Sender.java:238) [kafka-clients-2.0.0.jar:na]
at org.apache.kafka.clients.producer.internals.Sender.run(Sender.java:176) [kafka-clients-2.0.0.jar:na]
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_144]
Caused by: java.nio.channels.UnresolvedAddressException: null
at sun.nio.ch.Net.checkAddress(Net.java:101) ~[na:1.8.0_144]
at sun.nio.ch.SocketChannelImpl.connect(SocketChannelImpl.java:622) ~[na:1.8.0_144]
at org.apache.kafka.common.network.Selector.doConnect(Selector.java:233) ~[kafka-clients-2.0.0.jar:na]
... 7 common frames omitted
Pregunta: ¿Cómo conectarse a Kafka ejecutándose en Docker? Mi código se ejecuta desde la máquina host, no desde Docker.
Nota: Sé que, en teoría, podría jugar con la configuración de DNS, /etc/hosts
pero es una solución alternativa; no debería ser así.
También hay una pregunta similar aquí , sin embargo, se basa en ches/kafka
la imagen. Utilizo confluentinc
una imagen basada que no es la misma.
tl;dr : un simple reenvío de puerto desde el contenedor al host no funcionará ... Los archivos de hosts (por ejemplo, /etc/hosts
en sistemas *NIX) no deben modificarse para funcionar con la red Kafka, ya que esta solución no es portátil.
1) ¿A qué IP/nombre de host y puerto exacto desea conectarse? Asegúrese de que el valor esté establecido como advertised.listeners
(no advertised.host.name
y advertised.port
, ya que están en desuso) en el intermediario. Si ve un error como Connection to node -1 (localhost/127.0.0.1:9092)
, significa que el contenedor de su aplicación intenta conectarse consigo mismo. ¿El contenedor de su aplicación también ejecuta un proceso de corredor de Kafka? Probablemente no.
2) Asegúrese de que los servidores enumerados como parte de bootstrap.servers
realmente se puedan resolver. Por ejemplo, ping
una IP/nombre de host, úselo netcat
para verificar los puertos... Si sus clientes están en un contenedor, debe hacerlo desde el contenedor , no (solo) desde su host. Úselo docker exec
si el contenedor no se bloquea inmediatamente para llegar a su caparazón.
3) Si ejecuta un proceso desde el host, en lugar de otro contenedor, para verificar que los puertos estén asignados correctamente en el host, asegúrese de que docker ps
muestre que el contenedor Kafka está asignado desde 0.0.0.0:<host_port> -> <advertised_listener_port>/tcp
. Los puertos deben coincidir si se intenta ejecutar un cliente desde fuera de la red Docker. No necesita reenvío de puertos entre dos contenedores; utilizar enlaces/redes acoplables
La siguiente respuesta utiliza
confluentinc
imágenes de la ventana acoplable para abordar la pregunta que se hizo, nowurstmeister/kafka
. Si tieneKAFKA_ADVERTISED_HOST_NAME
una variable configurada, elimínela (es una propiedad obsoleta)
Las siguientes secciones intentan agregar todos los detalles necesarios para utilizar otra imagen. Para otras imágenes de Kafka de uso común, es el mismo Apache Kafka ejecutándose en un contenedor.
Solo dependes de cómo esté configurado . Y qué variables lo hacen así.
wurstmeister/kafka
A partir de octubre de 2023, esto ya no existe en DockerHub. De todos modos, no se mantuvo más allá de 2022.
Consulte su sección README sobre la configuración del oyente . Lea también su wiki de conectividad .
bitnami/kafka
Si quieres un recipiente pequeño, prueba estos. Las imágenes son mucho más pequeñas que las de Confluent y están mucho mejor mantenidas que
wurstmeister
. Consulte su archivo README para conocer la configuración del oyente.
debezium/kafka
Los documentos al respecto se mencionan aquí .
Nota : las configuraciones de puerto y host anunciadas están en desuso. Los oyentes anunciados cubren ambos. Al igual que los contenedores de Confluent, Debezium puede utilizar
KAFKA_
configuraciones de intermediario con prefijos para actualizar sus propiedades.
Otros
ubuntu/kafka
requiere que agregue--override advertised.listeners=kafka:9092
a través de argumentos de imagen de Docker... Creo que es menos portátil que las variables de entorno, por lo que no se recomiendaspotify/kafka
está en desuso y desactualizado.fast-data-dev
olensesio/box
son excelentes para una solución todo en uno, con Schema Registry, Kafka Connect, etc., pero están inflados si solo quieres Kafka. Además, es un antipatrón de Docker para ejecutar muchos servicios en un contenedor.- El tuyo
Dockerfile
- ¿Por qué? ¿Hay algo incompleto con estos otros? Comience con una solicitud de extracción, no desde cero.
Para lecturas complementarias, diagramas de red y completamente funcionales , consulte este blog de @rmoffdocker-compose
Respuesta
El documento de inicio rápido de Confluent (Docker) supone que todas las solicitudes de producción y consumo estarán dentro de la red Docker.
Puede solucionar el problema de conectarse kafka:9092
ejecutando el código de su cliente Kafka dentro de su propio contenedor, ya que utiliza el puente de red Docker, pero de lo contrario necesitará agregar algunas variables de entorno más para exponer el contenedor externamente, sin dejar de funcionar dentro. la red Docker.
Primero agregue un mapeo de protocolo PLAINTEXT_HOST:PLAINTEXT
que mapeará el protocolo de escucha a un protocolo Kafka.
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP
Valor clave :PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
Luego configure dos oyentes anunciados en diferentes puertos. ( kafka
aquí se refiere al nombre del contenedor de la ventana acoplable; también podría tener un nombre broker
, así que verifique su servicio + nombres de host).
KAFKA_ADVERTISED_LISTENERS
Valor clave :PLAINTEXT://kafka:9092,PLAINTEXT_HOST://localhost:29092
Observe que los protocolos aquí coinciden con los valores del lado izquierdo de la configuración de mapeo de protocolos anterior
Al ejecutar el contenedor, agregue -p 29092:29092
para la asignación del puerto del host y PLAINTEXT_HOST
el oyente anunciado.
Entonces... ( con la configuración anterior )
Si algo aún no funciona,
KAFKA_LISTENERS
se puede configurar para incluir<PROTOCOL>://0.0.0.0:<PORT>
donde ambas opciones coincidan con la configuración anunciada y el puerto reenviado por Docker.
Cliente en la misma máquina, no en un contenedor
La publicidad de localhost y el puerto asociado le permitirán conectarse fuera del contenedor, como era de esperar.
En otras palabras, cuando ejecute cualquier Cliente Kafka fuera de la red Docker (incluidas las herramientas CLI que haya instalado localmente), utilícelo localhost:29092
para servidores de arranque y localhost:2181
para Zookeeper (requiere reenvío de puertos Docker).
Cliente en otra máquina
Si intenta conectarse desde un servidor externo, deberá anunciar el nombre/ip del host externo (p. ej. 192.168.x.y
) del host , así como/en lugar de localhost .
Simplemente anunciar localhost con un reenvío de puerto no funcionará porque el protocolo Kafka seguirá anunciando los oyentes que haya configurado.
Esta configuración requiere el reenvío de puertos Docker y el reenvío de puertos del enrutador (y cambios en el firewall/grupo de seguridad) si no están en la misma red local; por ejemplo, su contenedor se ejecuta en la nube y desea interactuar con él desde su máquina local.
Cliente (u otro corredor) en un contenedor, en el mismo host
Esta es la configuración menos propensa a errores; puede utilizar los nombres de los servicios DNS directamente.
Cuando ejecute una aplicación en la red Docker , utilice kafka:9092
(consulte PLAINTEXT
la configuración de escucha anunciada arriba) para servidores de arranque y zookeeper:2181
para Zookeeper, como cualquier otra comunicación de servicio Docker (no requiere ningún reenvío de puerto).
Si usa docker run
comandos separados o compone archivos, debe definir un archivo compartido network
manualmente usando networks
la sección de redacción odocker network --create
Consulte el archivo de redacción de ejemplo para ver la pila completa de Confluent o uno más mínimo para un solo corredor.
Si utiliza varios corredores, entonces deben usar nombres de host únicos + oyentes anunciados. ver ejemplo
Pregunta relacionada
Conéctese a Kafka en el host desde Docker (ksqlDB)
Apéndice
Para cualquiera interesado en implementaciones de Kubernetes :
- Accediendo a Kafka
- Operadores (recomendado): https://operatorhub.io/?keyword=Kafka
- Centro de artefactos de Helm: https://artifacthub.io/packages/search?ts_query_web=kafka&sort=stars&page=1
Cuando se conecta por primera vez a un nodo Kafka, le devolverá todos los nodos Kafka y la URL donde conectarse. Luego, su aplicación intentará conectarse directamente a cada Kafka.
El problema siempre es ¿qué es lo que Kafka te dará como URL? Es por eso que existe el KAFKA_ADVERTISED_LISTENERS
cuál Kafka utilizará para decirle al mundo cómo se puede acceder a él.
Ahora, para su caso de uso, hay varias cosas pequeñas en las que pensar:
Digamos que configurasteplaintext://kafka:9092
- Esto está bien si tiene una aplicación en su ventana acoplable que usa kafka. Esta aplicación obtendrá de Kafka la URL
kafka
que se puede resolver a través de la red Docker. - Si intenta conectarse desde su sistema principal o desde otro contenedor que no esté en la misma red acoplable, esto fallará, ya que el
kafka
nombre no se puede resolver.
==> Para solucionar este problema, necesita tener un servidor DNS específico, como uno de descubrimiento de servicios, pero es un gran problema para cosas pequeñas. O configura manualmente el kafka
nombre de la IP del contenedor en cada/etc/hosts
si establecesplaintext://localhost:9092
- Esto estará bien en su sistema si tiene una asignación de puertos (-p 9092:9092 al iniciar kafka)
- Esto fallará si prueba desde una aplicación en un contenedor (la misma red acoplable o no) (localhost es el contenedor en sí, no el de Kafka)
==> Si tiene esto y desea utilizar un cliente Kafka en otro contenedor, una forma de solucionarlo es compartir la red para ambos contenedores (misma IP)
Última opción: establezca una IP en el nombre: plaintext://x.y.z.a:9092
(la URL anunciada de Kafka no puede ser 0.0.0.0 como se indica en el documento https://kafka.apache.org/documentation/#brokerconfigs_advertised.listeners )
Esto estará bien para todos... PERO ¿cómo puedes obtener el nombre xyza?
La única forma es codificar esta IP cuando inicia el contenedor: docker run .... --net confluent --ip 10.x.y.z ...
. Tenga en cuenta que debe adaptar la IP a una IP válida en la confluent
subred.
antes del cuidador del zoológico
- ejecución del contenedor acoplable --name zookeeper -p 2181:2181 zookeeper
después de kafka
- ejecución del contenedor acoplable --name kafka -p 9092:9092 -e KAFKA_ZOOKEEPER_CONNECT=192.168.8.128:2181 -e KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://ip_address_of_your_computer_but_not_localhost!!!:9092 -e KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=1 confluentin c/cp-kafka
en la configuración de consumidor y productor de Kafka
@Bean
public ProducerFactory<String, String> producerFactory() {
Map<String, Object> configProps = new HashMap<>();
configProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "192.168.8.128:9092");
configProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
configProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
return new DefaultKafkaProducerFactory<>(configProps);
}
@Bean
public ConsumerFactory<String, String> consumerFactory() {
Map<String, Object> props = new HashMap<>();
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "192.168.8.128:9092");
props.put(ConsumerConfig.GROUP_ID_CONFIG, "group_id");
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
return new DefaultKafkaConsumerFactory<>(props);
}
Dirijo mi proyecto con estas regulaciones. Buena suerte amigo.