KAFKA跨主机部署网络不通解决思路
问题背景:
Kafka的部署不仅需要集群可用,同时需要对orderer节点可连,这就是为什么有的时候,kafka集群本身没问题,但是orderer却总是报错。为了试验kafka剥离方案的可行性,跨阿里云网络和内网进行BAAS部署。
部署环境如下:
K8s部署在阿里云环境上,
192.168.8.108可连外网,作为master;
192.168.8.107不能连外网,作为slave;
Kafka集群部署在内网,
192.168.9.21等机器上,都可以连外网。
因为orderer节点会起在slave机器上,也就是107这台机器,它无法直连外网。因此,通过nginx转发来保证orderer可以连上kafka集群,如下图所示。
那么advertised listeners的配置就尤为重要,毕竟这是kafka节点保存在zookeeper集群中的brokers元信息,orderer最终是通过这些地址去访问kafka的。
如果将kafka0的KAFKA_ADVERTISED_LISTENERS地址设为192.168.9.21:9092,虽然集群创建正常,但是orderer无法连上内网地址,也就是无法连上kafka。所以,选择将kafka0的KAFKA_ADVERTISED_LISTENERS地址设为192.168.8.108:9092,然后在108上设置nginx代理,转发到 内网代理IP:9092(反向代理,通过该ip,外网可以连接内网。),这样就可以连上kafka0节点了。
尝试Setupbaas 发现orderer仍然报错kafka集群异常,但是kafka的启动日志没有任何异常。
setup的流程很长,还要清理环境,用kafkaclient来调试会方便很多。 fabric用的go语言client是sarama,简单改一下fabric_test里面的producer就可以起一个简单的client,来测试kafka集群是否可用了。
用producer向kafka写入数据,发现报错信息如下:
报错说明,现在这个partiton没有leader,我们知道kafka每个partiton都会有一个leader,负责client的读写。
为了确认测试用的partition到底有没有leader,通过kafka内部的kafka-topic.sh来查看详细信息,结果如下图所示:
结果发现,topic首先是创建成功了,partition leader也是存在的,那么为什么client没有获取到该partition的leader信息呢?
带着疑问,查看sarama的部分源码,发现传给kafkaclient(例如orderer里面的producer)的addrlist只是作为seedbrokers,从seedbrokers里面尝试去连接kafka server来获取metadata。
这个metadata里面包括了,注册在zk里面的所有brokers的信息, kafkaclient实际上是与这些brokers进行交互的,所以即使seedbroker填的不全,有时候也不影响kafka集群的使用。
流程如下图所示:
根据报错信息,可以发现GetMetadata返回的信息里面有ErrLeaderNotAvailable报错。
由上图可知,GetMetadata向kafkabroker发送了获取metadata的请求,并且key是3。查看kafka源码,可以找到kafkaAPI如何处理key为3的请求。
跳转到 handleTopcMetadataRequest里面:
跳转到getTopicMetadata:
跳转到createTopic:
如果topic不存在,GetMetadata在zk里面注册topic,然而在kafka里面把该topic标记为无leader状态。实际上,每个新建的topic都是处于LEADER_NOT_AVAILABLE的状态的,那问题应该出现在metadata的更新上面,负责管理各个partition状态的组件是controller,是不是controller哪里出了问题了?难道kafka启动日志里有报错被忽略了吗?搜索Controller相关log,发现并没有报错。
ZookeeperLeaderElector: 主要用于KafkController Leader选举,选举出Controller是broker1,但是后续却没有给出controller报错信息。实际上,controller作为kafka的组件,日志另有输出,报错如下,确实是访问不到broker的地址。
controller是随机选择一个kafka节点上启动的,为了同步副本状态,controller需要连接上每一个kafka节点,因为advertised listener地址在容器里访问不到,所以controller与各个broker的连接出现异常。进入容器查看网络连接情况,通过netstat –ae发现其中一个kafka有不正常的连接。
通过zkCli.sh发现,这正是controller所在的kafka,可以坐实是controller的问题了。
问题的原因找到了,但是为什么用kafka自带的脚本查出来的topic状态却是正常的呢?
查看该脚本调用的函数,发现改脚本调用的函数查询的数据居然来自于zk,并不是从kafka中获得。因为所有kafka连接zk并不存在问题,所以可以得出一致的topic 描述,看来使用这个脚本去查看topic状态也得慎重。
GetMetadata有报错,kafka-topic.sh却显示正常,终于有了解释。
Client在GetMetadata的时候,第一次创建了无主topic,在retry的时候,kafkaclient获取的metadata信息是来自于kafka的MetadataCache,因为controller的原因partitionState没有更新,所以返回的topic信息仍然有LEADER_NOT_AVAILABLE报错。
但是为什么正常情况,却没有返回这个LEADER_NOT_AVIALABLE呢?继续往下看:
跳转到getPartitionMetadata:
可见查询partitionMetadata时,是通过partitionState来判断存活的brokers里面是否有leader。如果有partitionState未更新,就返回LEADER_NOT_AVIALABLE的metadata,否则就可以返回最新的metadata。
Controller是如何更新partitionState的呢?
集群所有partition状态是由PartitionStateMachine来管理的。
controller的日志中也可看到:
初始化partition的时候:
进入addLeaderAndIsrRequestForBrokers:
由以上代码可见,partitionState更新需要通过ControllerChannelManager。
ControllerChannelManager负责维护Controller Leader与集群中其他broker之间连接,是管理这个集群的基础。然而,ControllerChannelManager在启动时就出问题了,连不上其他的broker,因此所有的kafka metadata都没能更新。因此,controller必须连上advertised listeners,包括其自身所在的broker。
问题解决方案:
如果将kafka0的KAFKA_ADVERTISED_LISTENERS设为 内网服务映射到外网的IP:9092,阿里云192.168.8.107上倒是可以通过修改host文件,把 内网服务映射到外网的IP 解析成192.168.8.108。这样,107在访问内网IP时,会连到108并通过nginx转发到192.168.9.21:9092。orderer需要连kafka集群的话,需要在k8s容器里添加host才行。
问题总结:
- advertised listeners不仅需要让orderer可连接,还需要让每个可能成为controller的kafkabroker容器可连才行。
- 这种表面可以创建topic,实际集群无法使用的情况,可以考虑查看controller的日志。
- kafka自带的kafka-topic脚本,描述的是zk里面的信息,并不一定于kafka里面的数据一致,需要慎重使用。