Иногда появляется необходимость обращения к брокерам Kafka из другой сети через NAT. Но, даже если NAT настроен корректно, то при попытках подключения, обратно возвращается ошибка о том, что брокер недоступен. Хотя ping и telnet по нужному порту проходят. В статье мы подробно разберем, как необходимо настроить Kafka для того, чтобы к системе можно было обращаться через NAT.
Корень проблемы
Kafka имеет особенный алгоритм обработки входящих подключений. Если его очень сильно упростить, то он сводится к следующим шагам:
Получил запрос на подключение от клиента по прослушиваемому порту
Посмотрел у себя в настройках какой ip-адрес прописан для этого порта
Отдал обратно клиенту ip-адрес для подключения с целью работы с брокером
Клиент подключился по полученному ip-адресу к брокеру
Дополнительно акцентирую, что это сильно упрощенный алгоритм для понимания сути проблемы.
Но если между клиентом и Kafka находится NAT, то при подключении из интернета случится следующий сценарий.
Клиент из интернета отправил запрос на подключение через внешний ip-адрес на порт Kafka
NAT запрос прокинул и Kafka запрос получила, посмотрела, что в настройках для данного порта указан внутренний IP-адрес
Kafka отдал клиенту свой внутренний ip-адрес с директивой подключаться на него
Клиент пытается подключаться по внутреннему ip адресу и получает ошибку, так как этот адрес недоступен из интернета
Уточнение: По умолчанию мы считаем что NAT настроен:
10.22.10.50:9092 -> 172.16.1.11:9092
Зачем в kafka заложено такое поведение? В составе кластера Kafka может быть несколько брокеров и партиции топиков могут быть расположены на разных серверах. Чтобы не было узкого бутылочного горлышка, кластер Kafka сообщает ip-адреса своих брокеров и на каком ip-адресе расположена требуемая партиция, чтобы клиент самостоятельно туда обратился для отправки и получения данных.
Решение данной проблемы имеет несколько способов. И мы пойдём от простых случаев к сложным. Но перед этим необходимо разобрать, какие параметры Kafka отвечают за настройку подключения к ней.
Параметры настройки подключения
Мы рассмотрим только основные настройки, которые отвечают только за подключение внешних клиентов. Настройки связанные с авторизацией, TLS, внутренними подключениями внутри кластера и др. рассматриваться не будут.
За настройку подключения отвечают два параметра:
listeners - на каких сетевых интерфейсах Kafka будет слушать порт.
Указывается список, в котором каждый элемент состоит из имени слушателя, ip-адрес\хост порт kafka, который будет прослушиваться.advertised.listeners - как клиенты будут к Kafka подключаться.
Указывается список, в котором каждый элемент состоит из имени слушателя, ip-адрес\хост порт kafka, которые будут отдаваться клиенту.
То есть, это фактически метаданные кластера, которые будут отправляться назад клиенту для подключения.
Данный параметр не является обязательным и если он не указан, то Kafka в качестве метаданных отправляет то, что прописано в listeners. И данный параметр как раз и полезен тем, что в нём можно указать IP адрес отличный от того на котором реально kafka поднял свой порт для прослушивания.
Далее мы разберем несколько случаев решения проблемы с примерами настроек двигаясь от простого к сложному и в конце разберем наиболее интересный случай, когда у нас имеется всего один внешний адрес и несколько брокеров.
Kafka (1 брокер)
Разберем самый простой случай, когда у нас только 1 брокер в кластере
Kafka (1 брокер) + FQDN
Самое простое решение настраивать kafka не на ip-адресах, а на fqdn. Конечно, предварительно озаботившись, что имя успешно разрешается со стороны клиента.
Настройки подключения на сервере будут выглядеть следующим образом:
###kafka1.cluster.com:###
listeners=PLAINTEXT://:9092
advertised.listeners=PLAINTEXT://kafka1.cluter.com:9092
И мы получим следующее взаимодействие:

Kafka (1 брокер) + FQDN (ext, int)
Но что если, по каким-либо причинам, вы не можем наружу выставить внутренний FQDN сервера и вынуждены выставлять другой:
kafka1.cluster.com - FQDN самого сервера
kafka1-ext.cluster.com - FQDN транслируемый за NAT
Конфигурация не сильно меняется:
###kafka1.cluster.com:###
listeners=PLAINTEXT://:9092
advertised.listeners=PLAINTEXT://kafka1-ext.cluster.com:9092
Но если нам требуется так же обеспечить прием подключений и снаружи и изнутри, то необходимо поднять второй порт, определить новый слушатель и указать соответствие между слушателем и используемым протоколом. Кафка не позволяет для одного и того же слушателя указывать разные порты. Поэтому слушатели должны быть разные.
Для удобства слушателей назовём INT и EXT.
###kafka1.cluster.com:###
listeners=INT://:9092,EXT://:9093
advertised.listeners=INT://kafka1.cluster.com:9092,EXT://kafka1-ext.cluster.com:9093
listener.security.protocol.map=INT:PLAINTEXT,EXT:PLAINTEXT
В параметре listener.security.protocol.map как раз прописывается соответствие "имя слушателя : используемый протокол безопасности".
В примерах выше мы не указывали соответствие listener.security.protocol.map=PLAINTEXT:PLAINTEXT потому что kafka имеет такое соответствие по умолчанию.

Kafka (1 брокер) + EXT IP
Случай когда Kafka необходимо настроить на IP-адресах является частным от предыдущего случая. И практически идентичен ему:
###kafka1.cluster.com###
listeners=INT://:9092,EXT://:9093
advertised.listeners=INT://172.16.1.11:9092,EXT://10.22.10.50:9093
listener.security.protocol.map=INT:PLAINTEXT,EXT:PLAINTEXT

Kafka (3 брокера)
Очевидно, что в реально жизни, на практических, промышленных задачах, кластер Kafka поднимается из нескольких брокеров как для обеспечения отказоустойчивости, так и для горизонтального масштабирования нагрузки. Поэтому, далее мы рассмотрим способы настройки кластера из трех серверов.
Kafka (3 брокера) + FQDN
Начнём с простого варианта, когда и внутри, и снаружи у нас транслируются одни и те же FQDN серверов.
Теперь на каждом сервере вносим свои настройки:
###kafka1.cluster.com:###
listeners=INT_EXT://:9092
advertised.listeners=INT_EXT://kafka1.cluster.com:9092
listener.security.protocol.map=INT_EXT:PLAINTEXT
###kafka2.cluster.com:###
listeners=INT_EXT://:9092
advertised.listeners=INT_EXT://kafka2.cluster.com:9092
listener.security.protocol.map=INT_EXT:PLAINTEXT
###kafka3.cluster.com:###
listeners=INT_EXT://:9092
advertised.listeners=INT_EXT://kafka3.cluster.com:9092
listener.security.protocol.map=INT_EXT:PLAINTEXT
При этом, конфигурацию NAT необходимо донастроить:
10.22.10.50:9092 -> 172.16.1.11:9092
10.22.10.60:9092 -> 172.16.1.12:9092
10.22.10.70:9092 -> 172.16.1.13:9092
Так как снаружи каждый FQDN должен разрешаться в свой внешний адрес:
kafka1.cluster.com -> 10.22.10.50:9092
kafka2.cluster.com -> 10.22.10.60:9092
kafka3.cluster.com -> 10.22.10.70:9092
Случай, когда у нас имеется всего один внешний адрес мы рассмотрим далее.
И мы получаем следующую схему взаимодействия:

Kafka (3 брокера) + FQDN (ext, int)
Как и в примере с одним брокером мы на каждом сервере прописываем два разных слушателя:
###kafka1.cluster.com:###
listeners=INT://:9092,EXT://:9093
advertised.listeners=INT://kafka1.cluster.com:9092,EXT://kafka1-ext.cluster.com:9093
listener.security.protocol.map=INT:PLAINTEXT,EXT:PLAINTEXT
###kafka2.cluster.com:###
listeners=INT://:9092,EXT://:9093
advertised.listeners=INT://kafka1.cluster.com:9092,EXT://kafka2-ext.cluster.com:9093
listener.security.protocol.map=INT:PLAINTEXT,EXT:PLAINTEXT
###kafka3.cluster.com:###
listeners=INT://:9092,EXT://:9093
advertised.listeners=INT://kafka1.cluster.com:9092,EXT://kafka3-ext.cluster.com:9093
listener.security.protocol.map=INT:PLAINTEXT,EXT:PLAINTEXT
И получаем схему взаимодействия:

Kafka (3 брокера) + FQDN (ext, int) + single EXT IP
И вот мы добрались к наиболее интересному случаю. Если наши ресурсы ограничены и у нас имеется всего один внешний адрес на все брокеры кластера Kafka, которых может быть и десять, и двадцать, то придется делать разделение не по адресам, а по портам.
Сначала перенастроим NAT:
- 10.22.10.50:9093 ---> 172.16.1.11:9093
- 10.22.10.50:9094 ---> 172.16.1.12:9093
- 10.22.10.50:9095 ---> 172.16.1.13:9093
Так же обеспечим предварительно в интернете разрешение имени kafka-ext-common.cluster.com в во внешний адрес 10.22.10.50
Теперь подготовим конфигурацию Kafka
###kafka1.cluster.com:###
listeners=INT://:9092,EXT://:9093
advertised.listeners=INT://kafka1.cluster.com:9092,EXT://kafka-ext-common.cluster.com:9093
listener.security.protocol.map=INT:PLAINTEXT,EXT:PLAINTEXT
###kafka2.cluster.com:###
listeners=INT://:9092,EXT://:9093
advertised.listeners=INT://kafka1.cluster.com:9092,EXT://kafka-ext-common.cluster.com:9094
listener.security.protocol.map=INT:PLAINTEXT,EXT:PLAINTEXT
###kafka3.cluster.com:###
listeners=INT://:9092,EXT://:9093
advertised.listeners=INT://kafka1.cluster.com:9092,EXT://kafka-ext-common.cluster.com:9095
listener.security.protocol.map=INT:PLAINTEXT,EXT:PLAINTEXT
Обратим внимание, что у каждого сервера теперь в параметре advertised.listeners у слушателя EXT указан внешний адрес со своим отдельным портом.
И схема взаимодействия получится следующая:

Итог
Мы узнали, что не так уж и сложно настроить Kafka на работу через NAT. Особенно если NAT является одной точкой входа. Гораздо интереснее, если точек входа у нас несколько. И в следующей статье мы рассмотрим как настроить Kafka, если в роли NAT у нас выступает HAProxy. Особенно это будет интересно рассмотреть учитывая то, что HaProxy у нас будет несколько работающих в режиме Active-Active.