Kubernetes as Database: 使用kubesql查詢kubernetes資源

xinkun發表於2020-05-24

寫在前面

kubectl雖然查詢單個的kubernetes資源或者列表都已經比較方便,但是進行更為多個資源的聯合查詢(比如pod和node),以及查詢結果的二次處理方面卻是kubectl無法勝任的。所以一直以來,我都有想法將kubernetes作為資料庫進行查詢。在去年,我開發了第二個版本的kubesql。相關資訊在https://xuxinkun.github.io/2019/03/11/kubesql/,程式碼留存在https://github.com/xuxinkun/kubesql/tree/python。這個版本較之我最早的spark離線方式已經有所改觀,但是無法應對中型、甚至較小規模的叢集,效能上存在較大問題。部署上也較為繁雜,且不夠穩定,有一些bug(會異常退出)。而且對於label等欄位都無法處理,可用性較差。我總起來不滿意,但是一直沒時間去重構。直到最近,聽了關於presto的一個分享,我感覺重構的機會來了。

這一次kubesql完全拋棄了原有的架構,基於presto進行開發。這裡摘抄一段presto的簡介:presto是一個開源的分散式SQL查詢引擎,適用於互動式分析查詢,資料量支援GB到PB位元組。Presto的設計和編寫完全是為了解決像Facebook這樣規模的商業資料倉儲的互動式分析和處理速度的問題。presto具有豐富的外掛介面,可以極為便捷的對接外部儲存系統。

考慮使用presto的主要原因是避免了SQL查詢引擎的邏輯耦合到kubesql中,同時其穩定和高效能保證了查詢的效率。這樣kubesql的主要邏輯專注於獲取k8s的resource變化,以及將resource轉化為關係型資料的邏輯上。

kubesql使用

重構後的kubesql開源專案地址在https://github.com/xuxinkun/kubesql。

先介紹下如何部署和使用。部署方式目前主要使用docker部署,很快會支援k8s的部署方式。

部署前需要獲取kubeconfig。假設kubeconfig位於/root/.kube/config路徑下,則只要一條命令即可執行。

docker run -it -d --name kubesql -v /root/.kube/config:/home/presto/config xuxinkun/kubesql:latest

如果橋接網路不能通k8s api,則可以使用物理機網路,加入--net=host引數即可。注意presto埠使用8080,可能會有埠衝突。

而後就可以進行使用了。使用命令為

docker exec -it kubesql presto --server localhost:8080 --catalog kubesql --schema kubesql

這時自動進入互動式查詢模式,即可進行使用了。目前已經支援了pods和nodes兩種資源的查詢,對應為三張表,nodes,pods和containers(container是從pod中拆出來的,具體原因見下文原理一節)。

三張表支援的列參見https://github.com/xuxinkun/kubesql/blob/master/docs/table.md

presto支援一些內建的函式,可以用這些函式來豐富查詢。https://prestodb.io/docs/current/functions.html

這裡我舉一些使用kubesql查詢的例子。

比如想要查詢每個pod的cpu資源情況(requests和limits)。

presto:kubesql> select pods.namespace,pods.name,sum("requests.cpu") as "requests.cpu" ,sum("limits.cpu") as "limits.cpu" from pods,containers where pods.uid = containers.uid group by pods.namespace,pods.name
     namespace     |                 name                 | requests.cpu | limits.cpu 
-------------------+--------------------------------------+--------------+------------
 rongqi-test-01    | rongqi-test-01-202005151652391759    |          0.8 |        8.0 
 ljq-nopassword-18 | ljq-nopassword-18-202005211645264618 |          0.1 |        1.0 

又比如我想要查詢每個node上剩餘可以分配的cpu情況(用node上allocatable.cpu減去node上所有pod的requests.cpu的總和)

presto:kubesql> select nodes.name, nodes."allocatable.cpu" - podnodecpu."requests.cpu" from nodes, (select pods.nodename,sum("requests.cpu") as "requests.cpu" from pods,containers where pods.uid = containers.uid group by pods.nodename) as podnodecpu where nodes.name = podnodecpu.nodename;
    name     |       _col1        
-------------+--------------------
 10.11.12.29 | 50.918000000000006 
 10.11.12.30 |             58.788 
 10.11.12.32 | 57.303000000000004 
 10.11.12.34 |  33.33799999999999 
 10.11.12.33 | 43.022999999999996 

再比如需要查詢所有所有2020-05-12後建立的pod。

presto:kube> select name, namespace,creationTimestamp from pods where creationTimestamp > date('2020-05-12') order by creationTimestamp desc;
                         name                         |        namespace        |    creationTimestamp    
------------------------------------------------------+-------------------------+-------------------------
 kube-api-webhook-controller-manager-7fd78ddd75-sf5j6 | kube-api-webhook-system | 2020-05-13 07:56:27.000 

還可以根據標籤來查詢,查詢所有標籤的appid是springboot,且尚未排程成功的pod。以及計數。

標籤appid在pods表裡則會有一列,列名為"labels.appid",使用該列作為條件來刪選pod。

presto:kubesql> select namespace,name,phase from pods where phase = 'Pending' and "labels.appid" = 'springboot';
     namespace      |     name     |  phase  
--------------------+--------------+---------
 springboot-test-rd | v6ynsy3f73jn | Pending 
 springboot-test-rd | mu4zktenmttp | Pending 
 springboot-test-rd | n0yvpxxyvk4u | Pending 
 springboot-test-rd | dd2mh6ovkjll | Pending 
 springboot-test-rd | hd7b0ffuqrjo | Pending
 
 presto:kubesql> select count(*) from pods where phase = 'Pending' and "labels.appid" = 'springboot';
  _col0 
 -------
      5 

kubesql原理

kubesql的架構如圖所示:

kubesql-arc

kubesql裡主要有三個模組部分:

  • kubesql-watcher: 監聽k8s api pod和node的變化。並將pod和node的結構化資料轉化為關係型資料(以Map的方式進行儲存)。
  • kubecache: 用於快取pod和node的資料。
  • kubesql-connector: 作為presto的connector,接受來自presto的呼叫,通過kubecache查詢列資訊和對應資料,並返回給presto關於列和資料的資訊。

其中最主要的部分是kubesql-connector。presto外掛開發指南可以參考https://prestodb.io/docs/current/develop.html。我沒有選擇從零開始,而是基於已有的localfile外掛https://github.com/prestodb/presto/tree/0.234.2/presto-local-file進行的開發。如何進行presto的外掛開發,後面我再寫文章來解讀。

由於所有資料都快取在記憶體中,因此幾乎無磁碟需求。但是也需要根據叢集的規模來提供較大的記憶體。

以pod資料為例,pod中主要資料分成三部分,metadata,spec和status。

metadata中比較難以處理的部分是label和annotation。我將label這個map進行展平,每個key都作為一列。比如

labels:
    app: mysql
    owner: xxx

我使用labels作為字首,拼合labels裡面的key作為列名。從而得到兩條資料為:

labels.app: mysql
labels.owner: xxx

對於pod A存在app的label但是pod B並沒有該標籤,則對於pod B來說,該列labels.app的值則為null。

類似的annotations也是類似的處理方式。從而讓annotations也就可以成為可以用於篩選pod的條件了。

對於spec來說,最大的困難在於containers的處理。因為一個pod裡面可能有若干個containers,因此我直接將containers作為一張新的表。同時在containers表裡增加一個uid的列,用來表明該行資料來自於哪個pod。containers裡面的欄位也對應都加入到containers表中。containers中比較重要的關於資源的如request和limit,我直接使用requests.作為字首,拼合resource作為列名。比如requests.cpurequests.memory等。這裡cpu的單獨處理為double型別,單位為核,比如100m這裡會轉化為0.1。記憶體等則為bigint,單位為B。

對於status中,比較難於處理的是conditions和containerStatus。conditions是一個列表,但是每個condition的type不相同。因此我將type作為字首,用來生成conditon的列名。比如:

  conditions:
  - lastProbeTime: null
    lastTransitionTime: 2020-04-22T09:03:10Z
    status: "True"
    type: Ready
  - lastProbeTime: null
    lastTransitionTime: 2020-04-22T09:03:10Z
    status: "True"
    type: ContainersReady

那麼在pod表中,我對應可以得到這些列:

Column Type Extra Comment
containersready.lastprobetime timestamp
containersready.lasttransitiontime timestamp
containersready.message varchar
containersready.reason varchar
containersready.status varchar
ready.lastprobetime timestamp
ready.lasttransitiontime timestamp
ready.message varchar
ready.reason varchar
ready.status varchar

這樣我就可以通過"ready.status" = "True" 來篩選condition裡type為ready且status為True的pod了。

containerStatus因為與containers一一對應,因此我將containerStatus合併到containers表裡,並且根據container name一一對應起來。

後記

本次重構後kubesql我直接釋出為1.0.0版本,並且已經在日常使用了。且藉助於記憶體和presto的高效能,我測試過5萬pod的叢集,查詢時間為毫秒級。目前暫未發現明顯的bug。大家有發現bug或者新的feature也可以提issue給我。我後期也會再維護該專案。

因為目前只有pods和nodes資源,相對於k8s龐大的資源來說,還只是冰山一角。但是增加每個資源要加入相當數量的程式碼。我也在考慮如何使用openapi的swagger描述來自動生成程式碼。

部署上現在是用docker來部署,馬上也會增加kubernetes的部署方式,這樣會更加便捷。

同時我在考慮,在未來,讓presto的每個worker負責一個叢集的cache。這樣一個presto叢集可以查詢所有的k8s叢集的資訊。該功能還需要再做設計和考慮。

相關文章