我的 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®发送或接收一个消息; 有两种类型的连接, 必须被建立:

  1. init 连接到bootstrap。 它将元数据返回给客户端,包括所有集群中的代理(broker)的列表及其连接的端点。
  2. 然后客户端连接到 1.init中返回的一个(或更多)的代理(broker)的元数据(metadata)。 如果代理(broker)没有被正确配置,连接将失败。

往往人们只关注上面的第1步, 结果被步骤2的网络配置卡住。 步骤1 中返回的代理(broker)详细信息被定义为 advertised.listeners --->必须是可以从客户端机器解析的。阅读更多关于协议,看到 kafka documentation

下面, 我使用一个客户端连接到kafka在各种排列的部署拓扑。 他们使用Python编写librdkafka ( confluent_kafka ), 原则适用于所有客户的语言。 你可以找到的 代码在GitHub上

kafka的插图示例客户机连接到代理(Broker)

假设我们有两个服务器。 一个是我们的客户,另一方面是我们的kafka集群的单一代理(暂时不考虑, kafka集群通常有至少三个代理(broker))。

  1. 客户端发起一bootstrap server(s),这是部署在集群上一个(或更多)的代理(broker)。
    Client ➝ Kafka Broker: Hi! I'm going to read some messages!
  2. 代理(broker)返回元数据,包括集群中所有可以达到的代理(broker)的主机(host)和端口(port)。Kafka Broker ➝ Client: Sounds good. There's just one broker here. You can reach me at
  3. 这个列表是客户机随后用于建立连接来, 生成或使用数据的列表。 初始连接中使用的地址只是为在集群 n 代理(broker)中找到一个引导服务器(bootstrap), 然后这个bootstrap给client返回所有代理(broker)的列表。 这样, 客户端不需要在配置文件中维护所有的代理(broker)的信息列表。 客户端之所以需要知道每一个broker的信息, 是因为客户端之后需要直接连接到一个或多个主题(topic)的代理(broker)上, 并基于分区(partition)来操作消息。
    Client ➝ Kafka Broker: Gimme messages for topic

经常出现的错误是: 代理(broker)配置(server.properties)的返回地址( advertised.listener )是客户端不能解析的。 在这种情况下,时序图大概是这样的:

  1. 客户端发起一bootstrap server(s),它是部署在集群中的一个(或更多)的代理(broker).
    Client ➝ Kafka Broker: Hi! I'm going to read some messages!
  2. 代理(broker)返回一个不正确的主机名(hostname)到客户端(client)
    Kafka Broker ➝ Client: Sounds good. There's just one broker here. You can reach me at
  3. 客户机试图连接到这个错误的地址,然后失败(因为kafka代理(broker)不是在客户端机器localhost上, 而在broker-0上)
    Client: Gimme messages for topic  | Kafka Broker: ???

只是一个代理(broker)?

所有这些例子使用的是一个代理(broker), 这对任何实际环境完全无用。 在实践中,你会有一个最少的三个broker cluster。 你的客户会对一个(或更多)这样的引导,代理(broker)将返回的元数据 集群中的每个代理(broker) 给客户端。

场景0:客户端和kafka同样运行在本地机器上

对于这个示例,我在 [Confluent Platform], 但是你也可以在本地复现这个kafka运行情况。

$ confluent local status kafka
…
kafka is [UP]
zookeeper is [UP]

我的Python客户机连接与引导服务器设置的 localhost: 9092

Single machine

这工作得很好:

注意:代理(broker)返回元数据 192.168.10.83 ,但由于这是我本地机器的IP,它顺利运行。

场景1:客户端和kafka运行在不同的机器上

现在让我们检查连接到kafka代理(broker)运行在另一台机器。 这可能是一个机器在你的本地网络,或者运行在云基础设施如亚马逊网络服务(AWS),微软Azure,或谷歌云平台(GCP)。

Client machine | Broker machine (asgard03)

在这个例子中,我的client在我的笔记本上运行,连接到在局域网上运行的另一台机器 asgard03 上的kafka:

初始连接成功。 但请注意 BrokerMetadata ; 我们得到的返回值显示有一个代理(broker)和一个主机名 localhost 这意味着我们的客户使用 localhost 试图连接到一个代理,来生产和消费信息。 这是坏消息,因为在客户的机器上,没有kafka代理(broker) localhost . 所以这时候无论是生产还是消费都是没办法通过这个``asgard'03`机器上的kafka服务来传递的.

Client machine | Broker machine (asgard03)

因此会发生上面视频里的事情:

那么我们如何解决呢? 我们去找可爱的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

Client machine | Broker machine (asgard03)

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 host (e.g., your laptop) – Container: Client | Container: Kafka broker

我们将从最简单的排列方式, 在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)主机。

Docker host (e.g., your laptop) – Container: Client | Container: Broker

试着修复它? 由于 Kafka 代理(broker)在网络上的名称是 broker(继承自其容器名称),我们需要将advertised_listener设置为broker

    -e KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://localhost:9092 \

修改后

    -e KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://broker:9092 \

Docker host (e.g., your laptop) – Container: Client | Container: Kafka broker
现在我们的代理(broker)是这样的:

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驻留的地方

Docker host (e.g., your laptop) – Local process: Client | Container: Kafka broker

如果我们尝试在电脑中连接场景中的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

Docker host (e.g., your laptop – Local process: Client – Can't resolve host  | Docker port forward – Container: Kafka broker

这时候我们需要下面的操作:

添加一个新的侦听器到代理(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 。 这个单一的侦听器之前使用一个默认值,但现在我们已经添加了另一个,我们需要显式配置它。

Docker host (e.g., your laptop) – Local process: Client –  | Docker port forward – Container: Kafka broker

代理(broker)拿起新的配置,我们的本地客户端以便只要我们记得它 侦听器端口(19092):

在docker组成,我们可以看到,Docker-based客户仍然有效:

Docker host (e.g., your laptop) – Container: Client | Local process: Client | Container: Kafka broker

场景5:kafka在本地运行客户机在docker容器

如果我们的情况变为kafka在本地运行在我们的笔记本就像我们做的最初,docker运行客户端? 这不是一个常见的使用方式,但是 ¯\ _(ツ)_ /¯不重要, 我们能够解决它.

Docker host (e.g., your laptop) – Container: Client | Local broker: Kafka broker

首先,我从上面关闭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 host (e.g., your laptop) – Container: Client – No broker is running on the client container | Local broker: Kafka broker

如果你不太相信我,尝试运行这个,从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 在容器内 ,这是行不通的。

Docker host (e.g., your laptop) – Container: Client – localhost:9092` – Local broker: Kafka broker

攻击的时间吗? 好的。 让我们看我们可怜的当地代理(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

Docker host (e.g., your laptop) – Container: Client – host.docker.internal:19092produce/consume` – Local broker: Kafka broker让我们试一试(确保你重新启动代理(broker)先捡起这些变化):

它的工作原理! 这是一个 很挫的方法 ,但它😅工作。 同样重要的是,我们还没有破碎的kafka对当地(non-Docker)客户原9092侦听器仍然有效:

Docker host (e.g., your laptop) – Local process: Client | Container: Client | Local broker: Kafka broker

常见问题

我不能直接设置我的 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):

  1. initial 连接到bootstrap。这将向客户机返回元数据,包括集群中所有代理(broker)及其连接端点的列表。
  2. 然后,客户端根据需要连接到第一步返回的一个(或多个)代理(broker)。如果代理(broker)没有正确配置,连接将失败。
posted @ 2022-06-09 21:45  Aibot  阅读(69)  评论(0编辑  收藏  举报