文是我司 CTO 黃東旭同學在 DTCC2017 上的《When TiDB Meets Kubernetes》演講實錄,主要分享了關于 TiDB 與 Kubernetes 整合的一些工作。文章較長,且干貨滿滿。

以下為演講實錄:
今天給大家帶來的分享是關于 TiDB 與 Kubernetes 整合的一些工作。在講之前,想了解一下,在場的各位有聽說過 Kubernetes 的同學舉個手;聽說過 TiDB 的舉個手,非常好。因為我也是 DTCC 的常客了,前幾年來講的時候基本上沒有人聽說過 TiDB ,隨著這個項目越來越成熟,很欣慰,有一些社區的小伙伴已經用起來了。
我先簡單介紹一下我自己。我是 PingCAP 的聯合創始人兼 CTO ,也是 TiDB 項目的碼農 ,之前一直也是在做 Infrastructure 和分布式系統相關的一些工作。同時也是特別喜歡開源,基本上做的所有東西都是開源,包括像 Codis 、TiDB 、 TiKV 這些項目。比較喜歡的編程語言有 GO 、Rust 、Python。
對于今天的話題,如果說在 40 分鐘之內要去完整的介紹怎么做一個數據庫或者說怎么去做一個集成調度的系統,我覺得是不太可能的。
所以今天這個 Talk 我主要會分享 TiDB 作為一個分布式關系型數據庫與云整合的經驗,以及我們遇到的一些問題,來幫大家在遇到相似的問題時提供一個解決思路。大家其實在存儲系統上可以把 TiDB 換成任何東西,然后對于在 Kubernetes 上的融合,我覺得都會有一些啟發的意義。
首先想強調的是,Cloud 才是未來。現場做一個小調研:有在線上生產環境中,正使用 Kubernetes 或者 Mesos 這樣的容器化管理方案的同學嗎?好,一個兩個三個。
我相信,如果在三到五年以后,再去問這個問題,應該是至少一半的同學會舉手。因為其實在可見的未來,數據量是一直在膨脹,業務會越來越復雜。包括現在很多微服務的這種思想,當你的業務比較大的時候,會把整個服務拆成非常細的模塊,然后在眾多的模塊的拆分之下,怎么去高效的運維你的分布式集群,其實靠 SRE 或者運維人員手動去管理各個微服務或者說各個的系統,其實是不太現實的。
但是大家也都知道,對于這種無狀態的業務,比如像 Application 應用層,其實它并不會真正的存儲數據,狀態一般都持久化到數據庫或者緩存中,所以它基本是無狀態的。所以其實一直以來大家在使用容器化遇到的第一個問題就是,有狀態的服務,特別是數據庫或者分布式存儲系統,怎么去運維。比如說我在 Docker 里面寫的數據,這個容器銷毀它直接就掛了,它其實是很難去做這種數據層面上的東西。
然后另外一方面就是數據庫的運維,不管是放在云上做還是 DBA 自己在物理機上做,一樣的痛苦,所以怎樣去設計一個面向云的環境下、或者說在分布式系統上去做數據庫。其實這也是在做 TiDB 的過程中我一直在想的。TiDB 這個項目一開始的設計就是:它一定會放在云上去運轉。

上圖是 Amazon 在它的云上選擇創建一個數據庫實例的一個界面,大家不用看具體的字是什么,只是給大家感受下:就是說我作為一個業務的開發,我要去存儲一個數據,啟用一個 PG 或者 MySQL 的數據庫,我還要去關心這么多個選項,或者說我一定要去關心我的這個物理機到底是什么樣的情況:磁盤有多大,什么機型,各式各樣的配置等。因為畢竟不是所有人都是專業的 DBA ,也不是所有人都是操作系統的專家,當時看到這個頁面的時候,基本上業務開發可能是一臉懵逼的狀態。
現在所有人都跟你說,我的系統是一個分布式系統,在廣告里寫的非常漂亮。現在哪一個數據庫說自己不是分布式的,那基本上只能是落后于時代。但是,大家有沒有想過,現在所有的這些分布式系統、分布式數據庫,運維起來都是非常痛苦,沒有辦法去很好的把它用好。
以 Hadoop 為例,現在 setup 一個 Hadoop 的集群竟然能成為一個生意,這個生意還讓兩個創業公司都上市了,一個是 Cloudera,一個是 Hortonworks。其實嚴格來說,他們只是做 Hadoop 運維的創業公司。然后還有無數的公司在做 Spark 的維護、Spark 的管理。各種各樣的數據庫運維公司,靠這個都過的非常好。
這個其實在我看來是非常不正常的,因為大家想,如果你去運維一兩臺機器那沒有問題,我寫一個腳本,輕輕松松的就可以搞定;然后三五十臺機器,也還行,招一個 OPs 或者說招一個 DBA ,還是可以人工的管理。
但是如果是在 100 臺、1000 臺甚至 10000 臺規模之上,機器的故障會是每天每夜無時無刻都發生的,網絡的抖動,磁盤 IO 的異常,一直都在發生,靠人是沒有辦法做的。總的來說就是,當你去運維一個 single node 的系統時,基本上沒有什么難度;但是如果要去運維一個特別大的 P2P 的 distributed system,尤其是節點數特別多的時候,你的狀態和維護的成本就變得非常高。
之前我做過一個項目叫 Codis ,可能有很多同學聽說過,也可能很多同學已經用在生產環境之中。還有另外一個不是我做的項目,就是官方的 Redis Cluster。當時很多社區里面的 Redis Cluster 的粉絲一直噴我,說 Codis 的配置怎么這么復雜,一點都不好用,組件怎么這么多。現在這個事情又在重演。
很多系統做成了 P2P 的模型了以后,組件很少部署很方便,但是真正在去運維它的時候,比如說要去做一個滾動升級或者我想清楚的知道整個集群的數據分布和各個組件的狀態,又或者說是我的分布式邏輯出了個bug,但是我的存儲層沒事,需要做個熱更新這個時候,p2p 系統的運維復雜度就凸顯了。Codis 它其實有一個 Proxy,一個存儲層,這兩層在邏輯上其實是分離的;但是 Redis Cluster ,每一個節點既是它的分布式調度模塊,同時又是它的數據存儲模塊,這時候整個系統架構是混在一起的,對于運維的同學來說這就是一個惡夢。
如果我想清楚的知道我的數據到底是在哪幾臺機器上,比如說這塊數據特別熱我想把它挪走,這時候像在 Redis Cluster 這種純 P2P 的系統里面是很難得到它的當前狀態。所以這也是影響了我后來一系列的系統設計的想法,所以 operation 是一個非常困難的事情,在一個特別大的分布式系統里,是一個非常困難的事情。
因為你的服務和組件特別多,不同的組件,不同的模塊,然后再加上一個分布式系統里邊特別不穩定的網絡狀態,使得各種各樣的異常情況,人是沒有辦法去掌控的。
還好,Google 是一個非常偉大的公司,像 TiDB 整個模型大家也知道是參考了Google Spanner/F1。Kubernetes 背后的系統的前身就是 Google 的 Borg。
Borg 其實是 Google 內部一直在用著的大規模的集群調度器。Borg 這個單詞就是星際迷航里面的一個角色,相當于它作為整個集群的一個大腦來去控制集群的業務的分布跟數據的均衡,所以 Google 給我們帶來了 Kubernetes 這個項目。
Kubernetes 主要的工作就是一個面向 Container 的集群管理的服務。它同時會去做服務編排,Auto deployment、 Auto scaling、Auto healing ,你的整個集群的這些服務的生命周期的管理,然后故障的轉移、擴容...你可以認為它是一個集群的操作系統。大家可能認為操作系統就是單機上的一個概念,但是如果放到一個大規模的分布式系統里面,你有無數的 CPU 資源,無數的內存,無數的磁盤資源,怎么高效的去把你的服務在這些海量的資源上進行合理的分配,這個就是 Kubernetes 干的事情。
TiDB 大家也都非常熟悉了,我簡單介紹一下吧。
我們做 TiDB 的目標就是,希望構建一個完全彈性的,用戶不需要去知道數據的分布信息,也不需要去做手工的數據分片,可以把它當做一個單機的數據庫、 MySQL 的數據庫在用,但是它背后是一個高度彈性和智能的分布式的數據庫。對業務層你不需要再去想分庫分表,也不需要再去想熱點的 balance 這種事情。它支持百分之百的 OLTP 的功能,可以支持跨行事務,像 MySQL 一樣:我開始一個 transaction,然后寫寫寫,最后 commit,全成功或者全失敗,沒有第三種可能 。支持事務的前提之下還支持 80% 的 OLAP 。
所以 TiDB 是非常適合去做這種一邊有實時寫入,一邊有復雜 Join 和實時分析的場景,它的 SQL 優化器其實也有從 Spark SQL 里面學到了很多東西。
當然對外的接口是完整的 MySQL 的接口,你可以直接用 MySQL 的客戶端就連上了。另外,背后它支持高可用。因為底層的復制協議并不是通過像這種主從模型去做數據冗余的,而是用 Raft,Raft 跟 multi-paxos 是比較接近的,都是基于選舉的算法。
在遇到 MySQL 的擴展性問題的時候,大家過去只能一臉懵逼,然后反回來去拆庫拆表,或者去用 MyCat 或者依托 MySQL 的中間件去做 sharding 。其實這對于業務層來說,侵入性非常大,所以 TiDB 的初衷就是解決這個問題,但是它沒有用任何一行 MySQL 的代碼。
TiDB 大概是這樣一個架構:

TiDB 其實也是由很多的組件組成,它并不是一個純粹的 P2P 系統,如果看這個架構其實非常像 Codis 。
今天的主題是說我們怎么在 Kubernetes 上去做 cluster 的 setup、rolling update,怎么去解決 Kubernetes 對于本地磁盤存儲的綁定問題。
所以面臨著一個與大家之前在 Kubernetes 上去部署帶狀態的服務非常接近的問題。因為 TiDB 本身是一個帶狀態的數據庫,數據庫沒有狀態那不可能。Kubernetes 的調度其實是對這種 stateless applications 非常友好的,但是如果你是一個帶狀態的,比如像 MySQL、PG、TiDB,或者 Etcd、Zookeeper 等等,怎么去做?真正的困難并不是 Kubernetes 做不了,而是每一個不同的系統都有自己的數據分布模型,同時每一個不同的系統它的運維方式也不太一樣。所以作為 Kubernetes 的平臺來說,沒有辦法去針對每一個不同的存儲系統去設計一套自己的調度策略。這是 Kubernetes 沒有辦法去做的一個事情。
舉個例子,比如說你想去運維好一個 Redis Cluster ,那你必須得了解 Redis Cluster 一些原理,還有必須得去知道它怎么運維;如果你想要去運維 Codis ,你必須得知道 Codis 的一些原理方法才能把它運維好。但這些領域知識怎么去告訴 Kubernetes 說你幫我按照這個方法來去運維這個系統?
這時候有一個公司,叫做 CoreOS ,相信大家可能也都熟悉,就是 Etcd 背后的那個公司,也是我們 PingCAP 的好伙伴。CoreOS 也是社區里面最大的 Kubernetes 的運營的公司,他們引入了一個新的 Kubernetes 的組件,叫做 Operator 。Operator的意義在于它其實是相當于使用了 Kubernetes 的 TPR(third party resources)的 API,去把你的系統運維的一些領域知識,封裝到 Operator 里面,然后把 Operator 這個模塊注入到 Kubernetes 上面,整個這些集群是通過 Operator 這個模塊來去做調度。
CoreOS 官方還提供了一個 Etcd 的 Operator 的實現。其實這思路也很簡單,就是說把這個集群的創建滾動更新,然后各種運維的一些領域知識放到這個 Operator 里面,然后在 Operator 里面去調用 Kubernetes 原生的 API,來做集群的管理,相當于是 Kubernetes 的一個 Hook 。
一般來說一個 Operator 它其實有這樣一些對外暴露的接口或者是能力。它做的事情的就是:比如我想要讓 Kubernetes 去建立一個 TiDB 的集群,比如說 deployment;比如我加入新的物理節點以后,我要想對現有的這個集群做擴容,然后 rebalance;比如說我的集群的 TiDB 本身的這些 binary需要升級,我要去做業務透明的滾動更新,比如說我有 100 個節點,要在上面去做升級,不可能手動去做,這個其實都是封裝在我們的 Operator 里面,然后包括自動化的 backup 跟 restore 這些功能。
本質上來說你可以認為 Operator 是一個 Kubernetes 的批處理方案。我剛才也簡單提到了一下,Kubernetes 可以作為一個集群的操作系統,但是這個操作系統總應該能讓運維去寫腳本的,這個腳本就是 Operator 機制。
其實它的原理很簡單,就是它注入到 Kubernetes 里面,會實時不停的去觀察集群的狀態,去 Hook Kubernetes 的一些集群的狀態,一些 API,然后得到整個集群的狀態。把一些分析的東西放在 Operator 里面,它可能會有一些地方被觸發,比如說我該擴容了或者我該去做 Failover ,然后去反饋。
然后 TiDB 的 Operator 目前來說有這么幾個功能:創建集群、滾動更新、Scale out, Failover、Backup/Restore。
因為其實今天的這個話題是說怎么去跟云做結合。我們現在在跟一些公有云的提供方在做合作。但是不可能說每一個公有云都自己去接入它的資源管理的 API,因為每個公有云可能都用的是不一樣的 API database,所以我們相當于做的一個方案就是說,不管你是公有云也好還是私有云也好,你給我一堆物理的機器,然后在這一堆物理的機器上面去部署 Kubernetes ,在這個 Kubernetes 上面,我相當于把我的 TiDB Operator 給放進去,當某個公有云客戶要它去創建一個集群的時候,會通知 Operator 去創建,比如說劃出一些機器,去做物理隔離。
這在私有云里邊也是一個比較常見的場景了。用戶他其實想要去做這種業務之間的租戶隔離,TiDB Operator 是做一個比較簡單的物理隔離。
但是做這個 Operator 最難的一個部分其實剛才也簡單講了一下,就是存儲的問題。如果大家關注 Kubernetes 社區的話,一般都會注意到 persistend local storage ?的這個方案一直在社區里邊扯皮和吵架。現在大家認為 Kubernetes 本地的磁盤是沒法用的,或者說沒有辦法直接當做一個資源來使用的。

上圖是放在 Kubernetes ?的 issues 里面的一個問題,就是 persistent local storage ,這個看上去非常不可思議,這么簡單的功能為什么一直到現在沒有支持。
我個人感覺 Google 之所以遲遲不去做這個功能,它背后有一個原因可能是在 Google 內部,它的網絡存儲是非常強的,它有自己非常好的網絡設備。你在同一個數據中心里,去換一塊網絡盤,它的這個 latency 基本上很多業務可以接受的,所以這樣的話,持久化存儲的問題基本上是靠網絡的磁盤來解決。
想像你跑一個 MySQL 的業務,MySQL 的業務本身它寫入的磁盤并不是你的物理機的本地盤,而是一塊網絡盤,這個網絡盤我能給你保證它的 IOPS 跟 latency 都是非常好的狀態,這個時候你的業務掛掉了,我再重新啟一個容器把這個網絡盤再掛到那個 pod 的后邊,這時候你的業務是幾乎無感知的。這也是 Google 比較推崇的使用存儲的一個模式,所以這就是 Kubernetes 背后的那個 persistent volumes 的這個方案。
它現在是有這個方案的,但是對于像我們這樣的分布式數據庫或者說對這種本地磁盤有特別強要求的( TiDB 底層的存儲引擎對單機的 SSD 做了非常多的優化),并沒有辦法去容忍我底下寫入的一個磁盤是網絡盤。因為本身比如說 TiDB 這一層,已經做了三個副本,甚至五個副本的復制,但是在底下網絡盤又要去做這個復制其實是沒有太多必要的,所以 Google 一直遲遲沒有推 Local Storage Resource。如果在 Google Cloud 上它能更好的去賣它的云盤,或者說對于這些公有云廠商來說,這是更友好的。
當然它也不是沒做,它是要在 1.9 里面才會去做這個支持,但以 Kubernetes社區的迭代速度,我估計 1.9 可能還要等個兩三年,所以這是完全不能忍的一個狀態。那既然我們又需要這個本地磁盤的支持,但是官方又沒有,那該怎么辦呢?這時我們就發揮主觀能動性了。
我們給 Kubernetes ?做了一個 patch。這個 patch 也是通過 Kubernetes resource 的方案去做的一個本地磁盤資源的管理模塊,這個怎么做的呢?也比較簡單。這部分內容就比較干了,需要大家對 Kubernetes 整個架構有一點點了解。
第一步,先會去創建一個 Kubernetes 的 Configuration map,我們稱之為 TiDB 的 ?storage ,就是針對 storage 的物理資源寫在配置的文件里邊。比如說機器的 IP,它的不同盤對應的文件夾在哪兒,相當于是一個配置階段的東西。
第二步,創建一個利用 Kubernetes 的 Third Party Resources(TPR) 的 API,去創建一個叫 tidb-volume 的第三方資源,然后這個資源去剛才 Configuration map 里面去讀它去注冊的那些物理磁盤分布的狀態資源,相當于 TPR 會把那個配置里面的磁盤資源 load 出來,變成在 Kubernetes 里的一個第三方 resource,這個對象大概是這樣一個狀態。
第三步,我們在這邊會去寫一個 controller,我們寫這個 controller 是干嘛呢?叫 volume-controller 。比如說我們的一個磁盤的資源分配給了一個 pod,然后這個 pod 現在在占用著這個資源,我需要有一個 controller 的模塊來標記這個資源的使用情況,不能說我新的業務在起來的時候,我把資源分配給了兩個正在用的業務,這是不行的。這里的對應關系其實是由 volume ?controller 去維護的。然后另外一方面它還實時的監盯著剛才 Configuration map 里面的物理資源,我可以動態的添加物理的磁盤資源的一些狀態。
第四步,剛才我們說到 opreator 其實是一個運維的工具,去做創建集群還有滾動升級,相當于總的入口。在這里面在去創建集群,在啟動進程的時候,把剛才我們創建的本地磁盤資源啟動實例綁定在一起,讓它能夠成功的創建這個資源。
第五步,就是創建一個 DaemonSet。DaemonSet 在 Kubernetes 里就是在每一個物理節點上每一個長度的進程,這個進程是用來去維護磁盤上資源的使用狀況。比如說一個 TiKV 的節點下線了,這個物理的磁盤資源就要被回收。回收了以后你要去做刪除數據、清空數據的操作,或者這個物理機就宕機了,你需要有一個東西來去通知 controllor 把這個資源給下線掉,這個是 DaemonSet 在干的事情。
這一整套加起來就是相當于只通過 Third Party Resources 來嵌入 Kubernetes,整體來說在旁路卻實現了一種本地的 local 磁盤的管理方案。
因為這個代碼其實還蠻多的,現在還沒有辦法直接 push 回 Kubernetes 社區。雖然現在這還是一個 private 的項目,但是在后續把代碼整理以后我們還是會開源出來。所以這其實也是為大家以后在 Kubernetes 去調度這種單雙向服務的方案,提供了一套可行的路,至少我們用這個還挺爽的。
然后,想稍微展望一下未來的數據庫應該是什么樣的。
在我看來,未來的數據庫不會再需要 DBA ,或者說未來你在寫業務的時候,并不需要去關心底下數據該去怎么分片,怎么去 sharding,一切都應該是在后面的云服務本身或者基礎設施去提供的。所有的東西都應該是 self-driving,相當于自動駕駛。就像在未來大家覺得自動駕駛應該是一個方向,在基礎軟件里我覺得也是越來越多的自動化導致大家對運維的依賴變得越來越輕。但是在很多的極端的情況下,circuit-breaker(斷路器)還是要有的。比如說我的業務產品現在突然出現了一個特別熱的熱點,我需要業務這邊去緊急的做手動的數據切分、移動,把負載手動的均衡出來,所以手動模式還是仍然要有的。類似于你自動駕駛的汽車,突然來一個人加塞,大家還是非常希望能保證自己的安全,那就必須得有一個手動的模式。
第二點是 database as a service ,前面也說到了,serverless 可能會在 database 里面,有一種新的形態的數據的數據庫。大家如果關注數據庫領域的話,最近出了一個新的數據庫叫 FaunaDB 。FaunaDB 非常有意思,它其實是跟公有云綁定在一起對外提供服務的,你看不見它實際的進程和部署,也看不見它物理的進程在什么地方,整個數據庫對外的展現形式就是你去買我的服務,我給你多少的 QPS。比如說你買一萬個 QPS,這個服務就能保證一萬個 QPS,你買十萬就是十萬,按這個價格來去付費,至于你的容量全都是在背后隱藏著,所有業務的開發者實際上是看不見。
第三點就是 Local storage isn’t necessary。為什么 Google 它一直沒有在 Kubernetes 里面去做 Local storage,其實仔細想一下也是有道理的,就是說隨著未來硬件的發展,當網絡社會的速度和分布跟你的磁盤的存儲差不多的時候,那它到底是不是網絡的,已經對業務層沒有什么意義了,所以這可能是一個未來的趨勢。
這條是一個比較極端的路,就是把整個磁盤放到網絡上,用網絡盤。還有另外一條反向的特別極端的路,也是我現在正在嘗試的一個東西,就是更加去對硬件做定制。比如說我現在嘗試把一些 TiDB 的數據庫的一些邏輯放到 FPGA 上面,或者是放在 SSD 的控制芯片里面,這其實是更深的定制,在未來我覺得兩者可能會融合。就是說我雖然可能是掛了一個網絡盤,但是對于數據庫來說我有了這個計算邏輯可能直接的去操作硬件,而不需要去例如通過標準的 POSIX API 來轉換內核走 本地 IO 的接口。
總結一下,分布式系統的運維特別的痛苦,然后 Kubernetes 是一個未來集群調度必然的趨勢,但是在存儲層它現在還沒有太多的好辦法。目前來說我們在做的一個事情就是 Operator,把整個存儲層的運維的領域知識放在 Operator 里邊,然后讓 Kubernetes 能去調度我們的東西。這個有點像 DCOS 的 batch script。
TiDB-Operator,其實是把運維干的事情,全都通過 Operator 的形式來封裝在程序里邊,然后自動的去運維。
Local Storage 的問題我們是解決了。雖然 Kubernetes 沒有辦法去提供這個能力,但是我們暫時解決了。
您也可以關注我們的官方微信公眾號(ID:ctoutiao),給您更多好看的內容。
2023-07-18 PingCAP發布了 《時刻領先丨PingCAP 用戶峰會 2023 圓滿收官》的文章
2023-02-13 PingCAP發布了 《促進關鍵軟件高層次人才培養:平凱星辰與華東師范大學簽訂聯合博士培養合作協議》的文章
2023-01-10 PingCAP發布了 《同盾科技 x TiDB丨實時數據架構為風控智能決策保駕護航》的文章
2022-12-09 PingCAP發布了 《PingCAP 成為中國唯一入選 Forrester Wave 數據庫廠商,被評為卓越表現者》的文章
2022-12-09 PingCAP發布了 《案例故事丨老虎國際 x TiDB ,降低架構復雜性,保障全球用戶安全可靠投資》的文章