我的 Python/Java/Spring/Go/Whatever Client Won’t Connect to My Apache Kafka Cluster in Docker/AWS/My Brother’s Laptop.
My Python/Java/Spring/Go/Whatever Client Won’t Connect to My Apache Kafka Cluster in Docker/AWS/My Brother’s Laptop. Please Help!
本文翻译自stackoverflow中kafka最佳回答, 我相信这篇文章能够解决kafka初学者关于kafka网络配置的99.999%的问题
前言
当客户机想要从Apache Kafka®发送或接收一个消息; 有两种类型的连接, 必须被建立:
- init 连接到bootstrap。 它将元数据返回给客户端,包括所有集群中的代理(broker)的列表及其连接的端点。
- 然后客户端连接到 1.init中返回的一个(或更多)的代理(broker)的元数据(metadata)。 如果代理(broker)没有被正确配置,连接将失败。
往往人们只关注上面的第1步, 结果被步骤2的网络配置卡住。 步骤1 中返回的代理(broker)详细信息被定义为 advertised.listeners
--->必须是可以从客户端机器解析的。阅读更多关于协议,看到 kafka documentation 。
下面, 我使用一个客户端连接到kafka在各种排列的部署拓扑。 他们使用Python编写librdkafka ( confluent_kafka
), 原则适用于所有客户的语言。 你可以找到的 代码在GitHub上 。
kafka的插图示例客户机连接到代理(Broker)
假设我们有两个服务器。 一个是我们的客户,另一方面是我们的kafka集群的单一代理(暂时不考虑, kafka集群通常有至少三个代理(broker))。
- 客户端发起一bootstrap server(s),这是部署在集群上一个(或更多)的代理(broker)。
- 代理(broker)返回元数据,包括集群中所有可以达到的代理(broker)的主机(host)和端口(port)。
- 这个列表是客户机随后用于建立连接来, 生成或使用数据的列表。 初始连接中使用的地址只是为在集群 n 代理(broker)中找到一个引导服务器(bootstrap), 然后这个bootstrap给client返回所有代理(broker)的列表。 这样, 客户端不需要在配置文件中维护所有的代理(broker)的信息列表。 客户端之所以需要知道每一个broker的信息, 是因为客户端之后需要直接连接到一个或多个主题(topic)的代理(broker)上, 并基于分区(partition)来操作消息。
经常出现的错误是: 代理(broker)配置(server.properties
)的返回地址( advertised.listener
)是客户端不能解析的。 在这种情况下,时序图大概是这样的:
- 客户端发起一bootstrap server(s),它是部署在集群中的一个(或更多)的代理(broker).
- 代理(broker)返回一个不正确的主机名(hostname)到客户端(client)
- 客户机试图连接到这个错误的地址,然后失败(因为kafka代理(broker)不是在客户端机器
localhost
上, 而在broker-0
上)
只是一个代理(broker)?
所有这些例子使用的是一个代理(broker), 这对任何实际环境完全无用。 在实践中,你会有一个最少的三个broker cluster。 你的客户会对一个(或更多)这样的引导,代理(broker)将返回的元数据 集群中的每个代理(broker) 给客户端。
场景0:客户端和kafka同样运行在本地机器上
对于这个示例,我在 [Confluent Platform], 但是你也可以在本地复现这个kafka运行情况。
$ confluent local status kafka
…
kafka is [UP]
zookeeper is [UP]
我的Python客户机连接与引导服务器设置的 localhost: 9092
.
这工作得很好:
注意:代理(broker)返回元数据 192.168.10.83
,但由于这是我本地机器的IP,它顺利运行。
场景1:客户端和kafka运行在不同的机器上
现在让我们检查连接到kafka代理(broker)运行在另一台机器。 这可能是一个机器在你的本地网络,或者运行在云基础设施如亚马逊网络服务(AWS),微软Azure,或谷歌云平台(GCP)。
在这个例子中,我的client在我的笔记本上运行,连接到在局域网上运行的另一台机器 asgard03
上的kafka:
初始连接成功。 但请注意 BrokerMetadata
; 我们得到的返回值显示有一个代理(broker)和一个主机名 localhost
。 这意味着我们的客户使用 localhost
试图连接到一个代理,来生产和消费信息。 这是坏消息,因为在客户的机器上,没有kafka代理(broker) localhost
. 所以这时候无论是生产还是消费都是没办法通过这个``asgard'03`机器上的kafka服务来传递的.
因此会发生上面视频里的事情:
那么我们如何解决呢? 我们去找可爱的kafka管理员(很可能是我们自己)并修复在broker(s)上错误配置的的server.properties
. 只有在配置中advertised.listeners
正确提供的主机名和端口; 代理(broker)才可以联系到客户(client)。 上面我们看到返回 localhost
, 而这显然无法在lan上建立client和broker上的联系。 让我们去解决这个问题。
在我代理(broker)的 server.properties
, 原先错误的代理(broker)长这样:
advertised.listeners=PLAINTEXT://localhost:9092
listeners=PLAINTEXT://0.0.0.0:9092
和修改之后正确的 advertised.listeners
配置:
advertised.listeners=PLAINTEXT://asgard03.moffatt.me:9092
listeners=PLAINTEXT://0.0.0.0:9092
listener
本身保持不变(它与所有可用的网卡绑定在端口9092上)。 唯一的区别在于,此listeber
会告诉客户到达 asgard03.moffatt.me
而不是 localhost
。
所以在应用这些更改 advertised.listener
在每个代理(broker)和重新启动每个其中之一,生产者和消费者正常工作:
代理(broker)现在显示正确的主机名解析元数据从客户端。
场景2:卡夫卡和客户机运行在Docker
现在我们要进入docker的精彩世界。 docker网络本身是一个野兽,我不打算在这里让读者详细了解它, 在本文中只需要搞懂kafka的listener
相关配置就可以了, 志于docker network config那是另外的话题。
如果你还记得一件事: 当您运行在docker,它执行一个容器在自己的小世界。 它似乎成为自己的主机名,自己的网络地址,它自己的文件系统。 比如,当你问代码在一个docker容器连接 localhost
,它将连接到 本身 和 不是 您正在运行的主机。 这是令人费解的,因为人们习惯在使用电脑的 localhost
时是默认连接到本地的主机的。 但是记住, 不是你的电脑在运行的代码。 而是你的电脑上运行的某个docker容器在运行这些代码, 这些容器拥有独立的网络地址空间。
我们将从最简单的排列方式, 在docker在同一个docker网络运行kafka和我们的客户。 首先,创建一个Dockerfile包括Python客户机到docker的容器:
FROM python:3
# We'll add netcat cos it's a really useful
# network troubleshooting tool
RUN apt-get update
RUN apt-get install -y netcat
# Install the Confluent Kafka python library
RUN pip install confluent_kafka
# Add our script
ADD python_kafka_test_client.py /
ENTRYPOINT [ "python", "/python_kafka_test_client.py"]
构建docker形象:
docker build -t python_kafka_test_client .
然后提供一个kafka代理(broker):
docker network create rmoff_kafka
docker run --network=rmoff_kafka --rm --detach --name zookeeper -e ZOOKEEPER_CLIENT_PORT=2181 confluentinc/cp-zookeeper:5.5.0
docker run --network=rmoff_kafka --rm --detach --name broker \
-p 9092:9092 \
-e KAFKA_BROKER_ID=1 \
-e KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181 \
-e KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://localhost:9092 \
-e KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=1 \
confluentinc/cp-kafka:5.5.0
确认你有两个容器运行:一个ApacheZooKeeper 和一个 Kafka broker:
$ docker ps
IMAGE STATUS PORTS NAMES
confluentinc/cp-kafka:5.5.0 Up 32 seconds 0.0.0.0:9092->9092/tcp broker
confluentinc/cp-zookeeper:5.5.0 Up 33 seconds 2181/tcp, 2888/tcp, 3888/tcp zookeeper
请注意,我们正在创造自己的docker的网络运行这些容器,这样zookeeper和kafka就可以交流。
让我们转到客户端,看看会发生什么:
$ docker run --network=rmoff_kafka --rm --name python_kafka_test_client \
--tty python_kafka_test_client broker:9092
在返回的元数据中可以看到,即使我们成功地连接到代理(broker)最初,它给了我们 localhost
代理(broker)主机。
试着修复它? 由于 Kafka 代理(broker)在网络上的名称是 broker(继承自其容器名称),我们需要将advertised_listener
设置为broker
-e KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://localhost:9092 \
修改后
-e KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://broker:9092 \
docker stop broker
docker run --network=rmoff_kafka --rm --detach --name broker \
-p 9092:9092 \
-e KAFKA_BROKER_ID=1 \
-e KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181 \
-e KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://broker:9092 \
-e KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=1 \
confluentinc/cp-kafka:5.5.0
和client的交互就完美:
场景3:卡夫卡在docker-compose
使用docker compose可以减少输入docker 命令行的次数特别是当需要配置的参数很多的时候。
关闭场景二中的容器docker rm -f broker; docker rm -f zookeeper
; 然后创建 docker-compose.yml
在本地使用这个 例子 。
---
version: '3.5'
networks:
rmoff_kafka:
name: rmoff_kafka
services:
zookeeper:
image: confluentinc/cp-zookeeper:5.5.0
container_name: zookeeper
networks:
- rmoff_kafka
environment:
ZOOKEEPER_CLIENT_PORT: 2181
broker:
image: confluentinc/cp-kafka:5.5.0
container_name: broker
networks:
- rmoff_kafka
depends_on:
- zookeeper
environment:
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://broker:9092
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
client:
image: python_kafka_test_client
container_name: python_kafka_test_client
depends_on:
- broker
networks:
- rmoff_kafka
entrypoint:
- bash
- -c
- |
echo 'Giving Kafka a bit of time to start up…'
sleep 30
# Run the client code
python /python_kafka_test_client.py broker:9092
确保你在与上述相同的文件夹中 docker-compose.yml
运行:
docker-compose up
你会看到zookeeper和kafka代理(broker); 然后Python测试客户端:
很好,嗯👍
场景4:卡夫卡在集装箱docker客户机在本地运行
如果你想在本地运行你的客户端, 也许这就是IDE驻留的地方
如果我们尝试在电脑中连接场景中的broker ,它失败了:
$ python python_kafka_test_client.py localhost:9092
让我们做出一些改变, 使9092主机。 我可以在场景三中的dockercompose.yml
中增加端口映射ports:"9092:9092"
.
…
broker:
image: confluentinc/cp-kafka:5.5.0
container_name: broker
networks:
- rmoff_kafka
ports:
- "9092:9092"
…
让我们试着再次本地客户端。 开始我们可以连接!
但后来事情恶化了:
同时我们可bootstrap,它的返回 broker:9092
。
这时候我们需要下面的操作:
添加一个新的侦听器到代理(broker)
那么,我们如何处理到达docker的内部和外部连接? 通过创建一个 新 侦听器。 代理(broker)可以有多个听众正是出于这一目的。 网络拓扑的,当事情变得时髦,kafka broker可以设置多个听众。
这些变化是这个样子:
…
ports:
- "19092:19092"
environment:
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://broker:9092,CONNECTIONS_FROM_HOST://localhost:19092
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,CONNECTIONS_FROM_HOST:PLAINTEXT
…
我们创建一个新的侦听器 CONNECTIONS_FROM_HOST
使用端口19092和新 advertised.listener
是在 localhost
,这是至关重要的。 因为它是在一个不同port,我们改变 port
映射(是19092年而不是9092年)。
现有的侦听器( PLAINTEXT
)保持不变。 我们也需要指定 KAFKA_LISTENER_SECURITY_PROTOCOL_MAP
。 这个单一的侦听器之前使用一个默认值,但现在我们已经添加了另一个,我们需要显式配置它。
代理(broker)拿起新的配置,我们的本地客户端以便只要我们记得它 新 侦听器端口(19092):
在docker组成,我们可以看到,Docker-based客户仍然有效:
场景5:kafka在本地运行客户机在docker容器
如果我们的情况变为kafka在本地运行在我们的笔记本就像我们做的最初,docker运行客户端? 这不是一个常见的使用方式,但是 ¯\ _(ツ)_ /¯
不重要, 我们能够解决它.
首先,我从上面关闭docker容器( docker-compose down
),然后在本地运行kafka( confluent local start kafka
)。
docker run --tty python_kafka_test_client localhost:9092
如果你还记得docker/ localhost
悖论, 您应该能想明白这是怎么回事。 在docker容器里client指定的localhost
本身就不是主机的“localhost”的. 当然,在我们client所处的docker container中压根没有kafka代理(broker)运行在9092年, 因此错误。
如果你不太相信我,尝试运行这个,从docker中检查容器如果端口9092 localhost
是开放的:
$ docker run -it --rm --entrypoint "/bin/nc" \
python_kafka_test_client -vz \
localhost 9092
localhost [127.0.0.1] 9092 (?) : Connection refused
docker的宿主机(也就是本地电脑中),kafka和端口是打开:
$ nc -vz localhost 9092
Connection to localhost port 9092 [tcp/XmlIpcRegSvc] succeeded!
那么,我们如何用docker中的client连接我们的主机? (试图从在docker运行的client 访问 在本地运行的kafka),有几个选项供您选择,其中没有一个是特别推荐的(这些做法非常别扭, 我也不希望大家这么做)。 如果你在Mac上运行docker,有一个出租汽车司机解决方案使用 host.docker.internal
作为主机的地址可以从容器内的访问:
$ docker run -it --rm --entrypoint "/bin/nc" \
python_kafka_test_client -vz \
host.docker.internal 9092
host.docker.internal [192.168.65.2] 9092 (?) open
所以容器可以看到主人的9092端口。 如果我们从实际kafka试图连接到客户端?
所以初始连接确实有效,但是看看我们回来的元数据: localhost: 9092
。 为什么? 因为 advertised.listeners
。 现在生产者和消费者不会工作,因为他们试图连接 localhost: 9092
在容器内 ,这是行不通的。
攻击的时间吗? 好的。 让我们看我们可怜的当地代理(broker)kafka和组装机揭露一个侦听器 host.docker.internal
。 因为我们不想破坏kafka代理(broker)的其他客户 实际上 想要连接上 localhost
,我们将创建一个新的听众。 改变 server.properties
:
listeners=PLAINTEXT://:9092
advertised.listeners=PLAINTEXT://localhost:9092
listener.security.protocol.map=PLAINTEXT:PLAINTEXT
…:
listeners=PLAINTEXT://:9092,RMOFF_DOCKER_HACK://:19092
advertised.listeners=PLAINTEXT://localhost:9092,RMOFF_DOCKER_HACK://host.docker.internal:19092
listener.security.protocol.map=PLAINTEXT:PLAINTEXT,RMOFF_DOCKER_HACK:PLAINTEXT
最初的听众保持不变。 神奇的事情我们在这里所做的不过是添加一个 新 侦听器( RMOFF_DOCKER_HACK
),这是一个新港口。 如果你连接到代理(broker)9092,你会得到的 advertised.listener
定义在该端口上侦听器( localhost
)。 如果你连接到代理(broker)19092,你会得到另一种主机和端口: host.docker.internal: 19092
。
让我们试一试(确保你重新启动代理(broker)先捡起这些变化):
它的工作原理! 这是一个 很挫的方法 ,但它😅工作。 同样重要的是,我们还没有破碎的kafka对当地(non-Docker)客户原9092侦听器仍然有效:
常见问题
我不能直接设置我的 host
文件吗?
- 你能吗? 是的
- 你应该吗? 不! 🙂
除非你想让你的客户随机停止工作每次部署在一台机器,你忘了攻击的主机文件。 这是主机名的全部意义和DNS resolution-they机器怎么知道如何互相交谈,而不是单独你硬编码到每台机器。
在我的 server.properties
中我没有找到 advertised.listeners
设置
默认情况下,它会将listener
的值作为advertised.listener
的值。 你可以通过检查日志文件中的代理设置:
[2020-04-27 21:21:00,939] INFO KafkaConfig values:
advertised.host.name = null
advertised.listeners = PLAINTEXT://localhost:9092,RMOFF_DOCKER_HACK://host.docker.internal:19092
advertised.port = null
…
listener.security.protocol.map = PLAINTEXT:PLAINTEXT,RMOFF_DOCKER_HACK:PLAINTEXT
listeners = PLAINTEXT://:9092,RMOFF_DOCKER_HACK://:19092
…
什么是 advertised.host.name
和 advertised.port
?
kafka官方弃用了他们。 所以不要使用它们。
我可以远程登录到代理(broker),所以kafka肯定能work吗?
是的,你需要能够达成代理(broker)在初始引导您提供的主机和端口连接。 然而,如上所述,这是一 后续 连接主机和端口也必须返回的元数据从客户机访问机器。
我必须使用Python这样做吗?
No, 任何客户端库(见 列表 和 GitHub )应该能够拿到的元数据。 这里有一个例子 kafkacat
:
$ kafkacat -b asgard05.moffatt.me:9092 -L
Metadata for all topics (from broker 1: asgard05.moffatt.me:9092/1):
3 brokers:
broker 2 at asgard05.moffatt.me:19092
broker 3 at asgard05.moffatt.me:29092
broker 1 at asgard05.moffatt.me:9092 (controller)
总之…
从你的客户有两种类型的连接必须成功的kafka代理(broker):
- initial 连接到bootstrap。这将向客户机返回元数据,包括集群中所有代理(broker)及其连接端点的列表。
- 然后,客户端根据需要连接到第一步返回的一个(或多个)代理(broker)。如果代理(broker)没有正确配置,连接将失败。