NiFi on K8s 節點之間連不上了怎麼辦
問題
公司政策變更禁止 wildcard 以及 cluster.local 申請憑證,而且規定 cluster domain 必須是 sitdownplz.com (化名),這影響了現有的 NiFi 叢集。
NiFi 叢集在同步 flow / configuration change 時,node 之間需要透過 cluster communication 彼此溝通;在 secured cluster 中,這條通訊會使用雙向 TLS,因此每個 NiFi node 都會作為 client 和其他 node 溝通,這時候就會需要用到 keystore/truststore 憑證了,原本是透過 wildcard 搭配 cluster.local 申請一張憑證給所有 NiFi node 使用,但現在這條路不能走了。
如果對 NiFi 裡 keystore / truststore 或 mTLS 不熟,可以先參考另一篇筆記:從一個 NiFi mTLS 問題搞懂 Keystore 與 Truststore。
最終解法
將 NiFi 部署在 K8s 中,會用 StatefulSet 來部署,每個 NiFi node 在 K8s 中的實作就是一個 Pod,下面會用 NiFi node 或簡單用 pod 來稱呼。
方案由三部分組成:
1. 憑證(mTLS 身分驗證)
因為不想每新增一個 NiFi node (pod) 就要重新申請憑證,所以申請一張憑證用 SAN 涵蓋所有 node FQDN。
2. hostAlias + per-pod Service
為每個 pod 建一個 ClusterIP Service(selector 用 statefulset.kubernetes.io/pod-name 鎖定單一 pod),透過 hostAlias 把每個 FQDN 對應到 per-pod Service 的 ClusterIP。
使用 per-pod Service 是為了讓公司規定的 FQDN 可以找到對應的 NiFi node。hostAlias 不能直接寫 pod 的 Pod IP 是因為它是動態的,這時候 per-pod Service 就派上用場。
那為什麼 StatefulSet 搭配的 headless Service 提供的 FQDN 不能用?
因為憑證不能用這個 FQDN 申請,所以我們才選擇這個方法。
3. IaC(Terraform)
Service 與 hostAlias 都用 Terraform 管理,隨 replicas 展開。這裡有個先後順序問題:hostAlias 要填的 ClusterIP,是前面的 per-pod Service 建好後 K8s 才分配的,那在建立 NiFi node (pod) 時該怎麼填 hostAlias?
解法是讓 hostAlias 直接引用 Service 的 cluster_ip 屬性,而非寫死:
1 | |
Terraform 因此自動排出順序:先建 Service → 取得 ClusterIP → 再建帶 hostAlias 的 pod,單次 apply 完成,不需 depends_on。Service 與 hostAlias 都隨 replicas 展開,數量可以保持一致。
用 hostAlias 把每個 node FQDN 直接對到內部 IP,寫進 pod 的 /etc/hosts,就能繞過 DNS 解決。 /etc/hosts 的查找優先於 DNS,名字走不到 CoreDNS 就先被解析掉。
pod 內 /etc/hosts 長這樣(格式是 IP 在前、名字在後):
1 | |
Deep Dive (TL;DR)
理解這個問題前,要先分清兩條完全不同的流量路徑:
| Vertical(對外) | Horizontal(node-to-node) | |
|---|---|---|
| 參與者 | 使用者瀏覽器 | NiFi node (pod) 之間 |
| Path | Gateway → HTTPRoute → Service → pod | pod 直接連對方 FQDN(mTLS) |
| URL | nifi.apps...(主入口) |
nifi-<N>.apps...(每個 node) |
| Impact | 正常 | 壞掉 |
Vertical(打開 NiFi UI) 的完整路徑:
1 | |
這裡對外使用普通 ClusterIP Service,不是 headless。另外,NiFi 的 nifi.web.proxy.host 必須跟 HTTPRoute 註冊的 hostname 一致:不然 NiFi proxy host 就會把這個流量擋下來。
改成把所有 FQDN 包在同一張憑證這個作法不難,但這會遇到一個問題就是上面提到的 horizontal 路徑:NiFi node 在做 node-to-node 連線,也就是 Pod 要連到 Pod 時,nifi-2.apps.sitdownplz.com 的 IP 在 CoreDNS 中查不到,往 K8s 外的公司 DNS Server 找也找不到。
雖然可以在 CoreDNS 中把 sitdownplz.com 註冊進去,但 K8s cluster 不是我們團隊管理的,在這個被採納前總是要先有一個 workaround 作法。
要解決 DNS 找不到的這個問題,可以透過把所有指向 NiFi node 的 FQDN 透過 hostAlias 寫進每個 NiFi node 的 /etc/hosts 中,這樣就能在不查詢 DNS 的情況下找到其他 NiFi node。
但這也衍生了另一個問題,FQDN 如果對到的是 NiFi node (pod) 的 IP,而這是動態的,為了解決這個問題使用了上面提到的 per-pod Service 解法。
搞定 DNS 查詢後,新的問題來了,那我們的 IaC (Terraform) 怎麼在建立 Pod YAML 時知道 hostAlias 怎麼填呢?
作法是先建 per-pod Service,然後利用 kubernetes_service_v1.cluster_pod_service[tostring(ord)].spec[0].cluster_ip 取得 ClusterIP。
小結
很高興可以和同事一起集思廣益解決問題,在現在都用 AI 開發的環境下,唯有遇到這種問題才能激起思考的慾望,雖然可能不是最佳解,但看到東西串起來能 work 還是小有成就感。
這個作法的缺點有幾個:
- 憑證會有字數上限,SAN 不能無限填入所有 NiFi node 的 FQDN
IT 直接改 CoreDNS 設定就不用後面這麼多麻煩事了