【原創】為什麼我的 Kafka 總是連線失敗呢?
阿新 • • 發佈:2020-03-09
## 提出問題
近日助友 Docker 部署 Kafka 服務,服務日誌啟動正常,但客戶端卻無法連線
往日曾踩過此坑,然方法均源於部落格,其語焉不詳,不知為何不行,亦不知為何行,印象不甚深刻,耗費大量時間
為避此坑,特地學習官方文件相關章節,讓我尋到珠絲馬跡,請聽我娓娓道來~
如嫌篇幅較長,可跳過驗證,直奔結論
本文主要記錄為何會出現無法連線到 Broker 的原因,想必看完本文你會知道該怎麼做的 :)
謹以此文獻給那些因為無知而浪費的時光!
## 大膽猜測
Kafka的服務端稱為 `Broker`,每個 Broker 啟動時會將自己的 Broker 配置資訊上報給 `Zookeeper` ,如,監聽地址與埠號等,Kafka的客戶端(生產者與消費者統稱)要連線 Broker 需要經過一層認證,不通過認證就無法連線!
## 小心求證
### 測試環境:
- GNU/Linux Debian 10 4.19.0核心
- Docker:`19.03.6`
- Docker-compose:`1.17.1`
- 映象:`zookeeper:3.5.5`
- 映象:`wurstmeister/kafka:2.12-2.2.1`
### 設計實驗:
使用 docker 映象部署一套單節點的 `Zookeeper` + `Kafka`
要求Broker監聽自定義域名(主機名)與埠,服務啟動後展示 `Zookeeper` 中 `Broker` 的註冊資訊
使用客戶端連線 `Broker` ,使用不同的 `--bootstrap-server` 組合方式,進行驗證
### 開始實驗
為了提高部署效率,這裡提供一個簡單可啟動的 `docker-compose-test.yml`
```yaml
version: "3.3"
services:
zookeeper:
image: zookeeper:3.5.5
restart: always
container_name: zookeeper
ports:
- "2181:2181"
expose:
- "2181"
environment:
- ZOO_MY_ID=1
kafka:
image: wurstmeister/kafka:2.12-2.2.1
restart: always
container_name: kafka
environment:
- KAFKA_BROKER_ID=1
- KAFKA_LISTENERS=PLAINTEXT://kafka:9090
- KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181
- KAFKA_MESSAGE_MAX_BYTES=2000000
ports:
- "9090:9090"
depends_on:
- zookeeper
```
將 `docker-compose-test.yml` 放入你的目錄,執行部署指令碼
![](https://img2020.cnblogs.com/blog/1149398/202003/1149398-20200309012513269-1982982116.png)
檢視兩者的日誌,檢查是否正常啟動
```bash
docker logs -f zookeeper
```
![](https://img2020.cnblogs.com/blog/1149398/202003/1149398-20200309012925576-846797736.png)
```bash
docker logs -f kafka
```
![](https://img2020.cnblogs.com/blog/1149398/202003/1149398-20200309013408560-662367560.png)
可以看到 Kafka 已經註冊成功
讓我們檢視一下,Zookeeper 中註冊的 Broker 資訊
```bash
docker exec -it zookeeper bash bin/zkCli.sh
```
![](https://img2020.cnblogs.com/blog/1149398/202003/1149398-20200309014107520-1584005744.png)
輸入 `quit` 退出
想到我們已經將Kafka監聽的埠號已經對映到宿主機了,使用宿主機 IP 訪問不就得了?
先建立個Topic
```bash
docker run -it --rm --network host wurstmeister/kafka:2.12-2.2.1 \
bash /opt/kafka/bin/kafka-topics.sh \
--bootstrap-server 192.168.1.19:9090 \
--create --topic logsTopic --partitions 1 --replication-factor 1
```
![](https://img2020.cnblogs.com/blog/1149398/202003/1149398-20200309015353135-668033151.png)
噢!又是這該死的錯誤!
想到剛才在 `zookeeper` 中看到的資訊,要不我把 IP 換成 `kafka` 不就結了?
```bash
docker run -it --rm --network host wurstmeister/kafka:2.12-2.2.1 \
bash /opt/kafka/bin/kafka-topics.sh \
--bootstrap-server kafka:9090 \
--create --topic logsTopic --partitions 1 --replication-factor 1
```
![](https://img2020.cnblogs.com/blog/1149398/202003/1149398-20200309015042025-1764613927.png)
不對,宿主機本身沒有對 `kafka` 作對映,問題一定出在這裡!
![](https://img2020.cnblogs.com/blog/1149398/202003/1149398-20200309015854775-1057766554.png)
再次執行剛才的命令,沒有輸出,說明建立 Topic 成功!
![](https://img2020.cnblogs.com/blog/1149398/202003/1149398-20200309020104350-1349176194.png)
生產一條訊息測試下
```bash
docker run -it --rm --network host wurstmeister/kafka:2.12-2.2.1 \
bash /opt/kafka/bin/kafka-console-producer.sh \
--broker-list kafka:9090 --topic logsTopic
```
![](https://img2020.cnblogs.com/blog/1149398/202003/1149398-20200309020612665-412307096.png)
輸入回車,傳送訊息; ctrl + c 斷開連線
建立消費者,消費訊息測試
```bash
docker run -it --rm --network host wurstmeister/kafka:2.12-2.2.1 \
bash /opt/kafka/bin/kafka-console-consumer.sh \
--bootstrap-server kafka:9090 --topic logsTopic --from-beginning
```
![](https://img2020.cnblogs.com/blog/1149398/202003/1149398-20200309021018678-1101588121.png)
嗯,消費也成功了
## 歸納總結
這個實驗說明了什麼呢?
說明 Kafka 客戶端在連線 Broker 的時候,Broker 將客戶端發來的請求帶的資訊與 Broker 啟動時上報給 Zookeeper 的資訊 進行了比對,比對相同則認證通過,反之建立連線失敗!
## 官方文件
通過耐心地檢視 `Kafka` 的官方文件,終於在角落裡看到她的影子!(好久不見妹子,看文件都眉清目秀的~)
比如下面這段:
> From Kafka version 2.0.0 onwards, host name verification of servers is enabled by default for client connections as well as inter-broker connections to prevent man-in-the-middle attacks.
大意是講通過這種`認證hostname`的機制,來避免中間人攻擊
還有這段:
> **advertised.host.name**: DEPRECATED: only used when `advertised.listeners` or `listeners` are not set. Use `advertised.listeners` instead. Hostname to publish to ZooKeeper for clients to use. In IaaS environments, this may need to be different from the interface to which the broker binds. If this is not set, it will use the value for `host.name` if configured. Otherwise it will use the value returned from java.net.InetAddress.getCanonicalHostName().
雖然這個引數已經標記為廢棄了,但是她提供了個資訊:如果設定主機名可能會被上報
最後看看這段
> **advertised.listeners**: Listeners to publish to ZooKeeper for clients to use, if different than the `listeners` config property. In IaaS environments, this may need to be different from the interface to which the broker binds. If this is not set, the value for `listeners` will be used. Unlike `listeners` it is not valid to advertise the 0.0.0.0 meta-address.
也就是說,無論設定 `listeners` 還是 `advertised.listeners` 它們其一的資訊會被上報,供客戶端使用;只有在需要繫結不同介面時,才需要設定 `advertised.listeners`
## 結論
Kafka 客戶端在連線 Broker 的時候,Broker 將客戶端發來的請求附加資訊與 Broker 啟動時上報給 Zookeeper 的 listeners引數資訊、host(來自listeners的中間域名或主機名部分)、port (來自listeners的埠部分) 進行了驗證,認證通過建立連線執行請求,反之建立連線失敗
> 寫這篇文章費了好大的功夫,猜是一回事,弄明白又是一回事,如果本文對你有所幫助,歡迎點推薦與關注
引用:
https://kafka.apache.org/documentation.html
本文采用 CC BY 4.0 協議進行授權,轉載請標註作者署名及來源。
https://www.cnblogs.com/hellxz/p/why_cnnect_to_kafka_always_fail