使用Docker部署Kafka时的网络应该如何配置

本文是我在使用Docker部署kafka遇到一些问题之后,在网上看到的一篇比较优秀的英文资料Link。借此翻译一下这篇文章,也帮助自己搞懂在使用Docker时遇到的一些网络问题,尤其是Host怎样配置。

作者的Kafka使用环境是Kafka Producer 和 Broker 均在 Docker 网络中, Kafka Consumer 在宿主机环境中。结构如下图这样子:

use-case.png

首先,我从Docker hub 中找到了一个Kafka Docker image。 我使用的是Wurstmeister Kafka and ZooKeeper images, 然后Docker-compose的文件是按照下面的格式定义的:

version: '2'

services:

  zookeeper:
    image: wurstmeister/zookeeper:3.4.6
    expose:
    - "2181"

  kafka:
    image: wurstmeister/kafka:2.11-2.0.0
    depends_on:
    - zookeeper
    ports:
    - "9092:9092"
    environment:
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092
      KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181

启动Docker,然后按照kafka QuickStart 的步骤来使用kafka-console-producer.shkafka-console-consumer.sh

在宿主机中运行 Producer 的结果:

andrew@host$ bin/kafka-console-producer.sh --broker-list localhost:9092 --topic test
>Hi there!
>It is a test message.

在宿主机中运行 Consumer的结果:

andrew@host$ bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic test --from-beginning
Hi there!
It's a test message.

可以看到,不管是producer还是consumer都可以在宿主机网络中正常工作。需要注意的是KAFKA_ADVERTISED_LISTENERS这个环境变量在Compose文件中的设置.Kafka会在client第一次连接的时候把这个变量值发送给client。在接收到这个变量值之后,client就可以使用它来从kafka 的 broker 中消费或者生产数据了。

由于我们定义的变量值为 PLAINTEXT://localhost:9092, producer和consumer在初始化连接时都会使用它并且之后所有的通信都会通过 9092 这个端口。

client-on-host-kafka-in-docker-wrong.png

这里的关键要点是客户端使用指定的Kafka地址(--bootstrap-server and --broker-list的值)。Kafka之后冲顶下他们的值为KAFKA_ADVERTISED_LISTENERS

下面让我们在运行Kafka容器的同一Docker网络内的任意Docker容器内运行Producer:

oot@869f83f2f265:/kafka# bin/kafka-console-producer.sh --broker-list localhost:9092 --topic test
>Hi there!
[2018-10-10 14:37:40,397] WARN [Producer clientId=console-producer] Connection to node -1 could not be established. Broker may not be available. (org.apache.kafka.clients.NetworkClient)

这时候就会报错了。这时候发生的事情是client收到了KAFKA_ADVERTISED_LISTENERS这个环境变量的值(PLAINTEXT://localhost:9092),之后尝试去连接它然后发现失败了,因为在自己docker网络中并没有这个地址。显然,从client的角度来说可以通过kafka:9092这个地址来连接kafka。因此,为了使 client 能够和 broker 通信,KAFKA_ADVERTISED_LISTENERS这个变量值就必须设置为PLAINTEXT://kafka:9092,那么,下面就来重新构建一下我们的Compose文件:

kafka:
    image: wurstmeister/kafka:2.11-2.0.0
    depends_on:
    - zookeeper
    ports:
    - "9092:9092"
    environment:
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092
      KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181

然后测试producer:

oot@7dfb9eaa81dc:/kafka# bin/kafka-console-producer.sh --broker-list kafka:9092 --topic test
>Hi there!

成功!现在我们可以在Docker容器内使用Producer发送消息了。

下面我们来尝试通过宿主机上的Consumer消费Kafka的数据:

andrew@host$ bin/kafka-console-consumer.sh --bootstrap-server kafka:9092 --topic test --from-beginning
[2018-10-10 23:57:06,827] WARN Removing server kafka:9092 from bootstrap.servers as DNS resolution failed for kafka (org.apache.kafka.clients.ClientUtils)

正如预料的,此时consumer并不能连接到broker因为宿主机并不能识别kafka:9092这个地址。我们需要再重新设置一下上面的Compose文件:

  kafka:
    image: wurstmeister/kafka:2.11-2.0.0
    depends_on:
    - zookeeper
    ports:
    - "9092:9092"
    expose:
    - "9093"
    environment:
      KAFKA_ADVERTISED_LISTENERS: INSIDE://kafka:9093,OUTSIDE://localhost:9092
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INSIDE:PLAINTEXT,OUTSIDE:PLAINTEXT
      KAFKA_LISTENERS: INSIDE://0.0.0.0:9093,OUTSIDE://0.0.0.0:9092
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
      KAFKA_INTER_BROKER_LISTENER_NAME: INSIDE

下面让我们来解释一下上面的几个环境变量:

  • KAFKA_ADVERTISED_LISTENERS

    kafka的broker将监听地址表(0.0.0.0:9093, 0.0.0.0:9092)和 listener (INSIDE, OUTSIDE

  • KAFKA_ADVERTISED_LISTENERS

    指向Broker的可用地址列表,kafka 将会在初始连接时将地址发送给client

  • KAFKA_LISTENER_SECURITY_PROTOCOL_MAP

    将上面定义的 listener 名称(INSIDE,OUTSIDE)映射到PLAINTEXT Kafka协议。

  • KAFKA_INTER_BROKER_LISTENER_NAME

    指向跨Broker间通信时的命名地址

这里我们定义了两个listeners(INSIDE://0.0.0.0:9093, OUTSIDE://0.0.0.0:9092)来分别表示Docker网络内部的流量和Docker主机外部的流量。我们为跨Broker间的通信定义了 INSIDE listener。通过KAFKA_ADVERTISED_LISTENERSKAFKA_LISTENER_SECURITY_PROTOCOL_MAP

我们将PLAINTEXT://kafka:9093发送给那些使用kafka:9093连接的客户端和

PLAINTEXT://localhost:9092发送给那些使用localhost:9092连接的客户端。

总之,我们定义了两种类型的客户端-内部和外部-并且配置kafka返回不同的地址给对应的客户端。

现在我们再来尝试下在Docker网络中使用Producer:

root@7dfb9eaa81dc:/kafka# bin/kafka-console-producer.sh --broker-list kafka:9093 --topic test
>Hi there!

在宿主机中使用Consumer:

andrew@host$ bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic test --from-beginning
Hi there!

现在全部都是成功的了!

producer和kafka broker都是在docker网络的内部。

client-and-kafka-in-docker.png

Consumer在外部, borker在Docker网络内部

client-on-host-kafka-in-docker.png

现在,当服务和kafka 部署在不同的网络环境中的时候,我们也知道该如何去配置Docker了。

推荐阅读更多精彩内容