问题描述
我正在为应用程序编写端到端测试。我启动了一个应用程序实例,一个Kafka实例和一个Zookeeper(均已Docker化),然后与应用程序API交互以测试其功能。我需要在此应用程序中测试事件使用者的功能。我发布了测试中的事件,应用程序有望处理这些事件。
问题::如果我在本地(不在Docker中)运行应用程序并运行会产生事件的测试,则应用程序代码中的使用者将正确处理事件。在这种情况下,使用者和测试将bootstrapServers
设置为localhost:9092
。但是,如果应用程序作为Dockerized实例运行,则不会看到事件。在这种情况下,bootstrapServers
在应用程序中设置为kafka:9092
,在测试中设置为localhost:9092
,其中kafka
是Docker容器名称。 kafka
容器将其9092
端口暴露给主机,以便可以从Docker容器内部和主机(运行我的测试)访问Kafka的同一实例。
代码中唯一的区别是将localhost
与kafka
设置为引导服务器。在这两种情况下,消费者和生产者都可以成功启动;事件发布没有错误。只是在一种情况下,消费者没有收到事件。
问题::如何使Docker化的使用者看到从主机发布的事件?
注意:我有一个配置正确的Docker网络,其中包括应用程序实例,Zookeeper和Kafka。他们都互相“见”。 kafka
和zookeeper
的相应端口暴露给主机。
Kafka端口:0.0.0.0:9092->9092/tcp
。 Zookeeper端口:22/tcp,2888/tcp,3888/tcp,0.0.0.0:2181->2181/tcp
。
我正在使用wurstmeister/kafka和wurstmeister/zookeeper Docker映像(我无法替换它们)。
任何想法都值得赞赏。您将如何调试它?
更新:问题在于KAFKA_ADVERTISED_LISTENERS
和KAFKA_LISTENERS
env变量被设置为用于INSIDE和OUTSIDE通信的不同端口。解决方案是在Docker容器中运行应用程序代码时使用正确的端口。
解决方法
这类问题通常与卡夫卡处理经纪人地址的方式有关。
启动Kafka经纪人时,它会在0.0.0.0:9092
上进行绑定,并使用地址<hostname>:9092
在Zookeeper中进行注册。与客户建立联系后,将与Zookeeper联系以获取特定经纪人的地址。
这意味着,当您启动Kafka容器时,会遇到以下情况:
- 容器名称:kafka
- 网络名称:kafkanet
- 主机名:kafka
- 动物园管理员注册:kafka:9092
现在,如果您从kafkanet网络内部的容器中将客户端连接到Kafka,则从Zookeeper返回的地址为kafka:9092
,可以通过kafkanet
网络进行解析。
但是,如果您从外部docker连接到Kafka(即使用docker映射的localhost:9092
端点),您仍然会获得无法解析的kafka:9092
地址。
为了解决此问题,您可以在代理配置中指定advertised.host.name
和advertised.port
,以便所有客户端(see documentation)都可以解析该地址。 / p>
通常要做的是将advertised.host.name
设置为<container-name>.<network>
(在您的情况下,例如kafka.kafkanet),以便连接到网络的任何容器都能够正确解析Kafka代理的IP。
但是,在您的情况下,您具有混合的网络配置,因为某些组件位于docker内部(因此能够解析kafkanet网络),而其他组件位于docker内部。如果是生产系统,我的建议是将advertised.host.name
设置为主机的DNS / IP,并始终依靠docker端口映射来到达Kafka代理。
但是,根据我的理解,您仅需要此设置即可进行测试,因此最简单的方法是“欺骗”生活在Docker外部的系统。使用上面指定的命名,这意味着仅将/etc/hosts
行添加到您的127.0.0.1 kafka.kafkanet
(或等效于Windows的行)中。
这样,当您住在docker外部的客户端连接到Kafka时,就会发生以下情况:
- 客户端->通过localhost:9092的Kafka
- kafka查询Zookeeper并返回主机
kafka.kafkanet
- 客户端将
kafka.kafkanet
解析为127.0.0.1 - 客户端->通过127.0.0.1:9092的Kafka
编辑
正如评论中指出的那样,较新的Kafka版本现在使用listeners
和advertised.listeners
的概念,它们取代了host.name
和advertised.host.name
(已弃用)并且仅在未指定上述内容的情况下使用)。但总体思路是相同的:
-
host.name
:指定Kafka代理应将其自身绑定到的主机(与port
结合使用
-
listeners
:指定Kafka代理应绑定的所有端点(例如PLAINTEXT://0.0.0.0:9092,SSL://0.0.0.0:9091
) -
advertised.host.name
:指定将代理发布给客户端的方式(即客户端应使用哪个地址连接到客户端) -
avertised.listeners
:指定所有发布的端点(例如PLAINTEXT://kafka.example.com:9092,SSL://kafka.example.com:9091
)
在两种情况下,客户端都必须能够与Kafka成功通信,他们需要能够解析并连接到advertised
主机名和端口。
在两种情况下(如果未指定),它们都是由代理使用代理运行所在的计算机的主机名自动派生的。
,您一直引用8092
。那是故意的吗? Kafka在9092
上运行。最简单的测试是下载相同版本的Kafka,并手动运行其kafka-console-consumer
和kafka-console-producer
脚本,以查看是否可以从主机上发布订阅。
您是否在dockerized应用程序中尝试过“ host.docker.internal”?
,您可以为容器创建一个docker network,然后容器将能够相互解析主机名并进行通信。
注意:这可用于docker-compose以及独立容器