0x01 背景
Pod需要使用遠端儲存的PV,由同k8s叢集內的服務提供的儲存服務。一開始的做法是:
- CSI中解析Service的clusterIP。
- 然後使用clusterIP掛載PV卷。
但因為走clusterIP時,經過多次轉換:
- clusterIP到Pod IP 經過了1次NAT
- Pod IP到最終服務。經過1次轉發,具體效能損耗跟 CNI 實現相關。
導致了最終client寫PV的效能損失嚴重。
0x02 解決方法
既然走容器網路導致效能差,修改服務端的部署形式為 hostNetwork,繞過容器網路。但帶來一個問題,儲存服務可能切換節點,導致 client 端無法正常重連(切換節點帶來的資料不一致的問題能處理),這一點不能接受。
新的方案:
為服務端建立一個 Headless Service,針對 Deployment 型別的負載Headless Service會解析提到所有Pod的 IP 地址列表具體見官方文件,那唯一的問題還剩下 client 重連時,這個域名怎麼解析?因為使用的驅動是核心提供的,核心中無法直接使用glibc的域名解析功能,即無法使用外部的DNS Server,即使是/etc/resolv.conf中指定的。
0x03 request-key 機制
透過調查瞭解到核心提供了request-key機制,可以從核心呼叫到使用者態的應用。request-key本來是用於核心和使用者態之間的安全token管理的,後也擴充套件用於其他用途。以核心解析域名來說,大概流程如下:
- 核心發起域名解析轉到dns_resolver模組【核心態】。
- 發起request-key請求轉到key管理模組【核心態】。
- key管理模組呼叫/sbin/request-key,呼叫到使用者態【核心態】。
- /sbin/request-key根據/etc/request-key.conf中的配置,分發到對應的命令呼叫,示例為/sbin/key.dns_resolver【使用者態】。
- /sbin/key.dns_resolver呼叫glibc域名解析,完成解析,並呼叫request-key相關係統呼叫,設定好payload,即域名對應的IP地址【使用者態】。
但還有新的問題:key.dns_resolver只能使用/etc/hosts和/etc/resolv.conf解析域名,不支援從額外的dns server解析域名。
0x04 具體的方案
所有的方法都要透過修改 /etc/request-key.conf配置檔案指定自己的程式進行解析。
後面的流程有以下方案:
自己寫指令碼,透過/etc/request-key.conf配置檔案指定自己的指令碼,透過kubectl去查詢 Pod IP地址,呼叫/sbin/request-key將結果寫回。
問題:C語言中對字串的處理在dns_resolver和request-key兩個模組之間發生了衝突,使用/sbin/request-key寫入的IP地址被dns_resolver核心模組認為非法,這個方案行不通,詳見QA部分解釋。
透過C呼叫key-utils的SDK,可以實現同樣的功能,但基本上照抄key.dns_resolver的實現。突然想到可以用Python呼叫so庫的方法,驗證了下基本可行。但又有一個新的問題:
Python標準庫中的域名解析同樣不支援指定域名,想要支援就要引入第3方的dns模組。
最終方案比較:
方案 | 優點 | 缺點 |
---|---|---|
寫Python呼叫key-utils的SDK so完成IP寫回核心 | 靈活控制對coreDNS的訪問。 | 需要呼叫第3方的dns解析服務、或者直接訪問 kube-apiserver獲取IP,加重kube-apiserver的負擔。 |
shell指令碼透過unshare mount namespace 隔離,生成臨時的/etc/resolv.conf,呼叫/sbin/key.dns_resolver實現 | 不用訪問 kube-apsierver,根據kubelet的配置可獲取coreDNS的地址,不用感知具體的DNS解析細節。更通用,其他的 headless也可以用 | 無法控制呼叫頻率 |
考慮這種異常切換解析並不會太頻繁,最終選擇了第2種方案。mount namespace 可以方便地透過 unshare -m 來實現。
0x05 補充QA
Q:/sbin/key.dns_resolver支援從/etc/hosts解析域名,為什麼不修改 /etc/hosts?
A:/etc/hosts是全域性配置,修改衝突不容易控制,出現衝突時影響不可控。
Q:為什麼不能修改/etc/resolv.conf配置,指向coreDNS?
A:雖然coreDNS也支援將非k8s域名轉向宿主中/etc/resolv.conf中的指定的DNS,但這種機制依賴 coreDNS,對整個系統的影響過大。
Q: 為什麼不用/sbin/request-key回寫解析到的IP地址?
A:這種實現了驗證了,發現request-key和dns_resolver的實現關於C中字串的處理有不一致的地方,前者payload長度未包含\0,後者要求包含。這一點是透過bpf鉤子確認的。
0x06 總結
問題的解決過程中嘗試了多種方案,最終最適合的方案巧妙運用了名稱空間隔離機制,這也是瞭解容器底層原理的好處。
同時帶來一點關於名稱空間的用途回顧:
- 容器內不希望被宿主機影響。
- 容器內不期望影響宿主機(本文中的場景),可隨意設定/etc/resolv.conf。