TiDB on Kubernetes 最佳實踐

作者介紹:吳葉磊 PingCAP Cloud 工程師。

隨着 Kubernetes(K8s) 的全面成熟,越來越多的組織開始大規模地基於 K8s 構建基礎設施層。然而,考慮到數據庫在架構中的核心地位與 K8s 在有狀態應用編排上的短板,仍有不少組織認爲在 K8s 上運行核心數據庫會帶來頗高的風險。事實上,在 K8s 上運行 TiDB 不僅能實現企業技術棧的統一,降低維護成本,還能帶來更高的可用性與安全性。本次分享將介紹 TiDB 在 K8s 上的運維管理系統 TiDB Operator,再從各類故障場景入手剖析 TiDB on K8s 如何實現高效的故障自愈並保障數據安全。最後,我們會分享來自國內外一線公司的 TiDB Operator 生產環境案例,並總結出一套 TiDB on K8s 最佳實踐。

要不要在 Kubernetes 上運行 TiDB ?

這個問題其實一直以來也有很多的爭議。大家都知道,Kubernetes 的很多概念是爲無狀態應用(比如微服務)所設計的。由於雲原生技術的不斷普及,Kubernetes 目前在國內外的很多技術領域都得到大規模的落地,有一種全盤上 Kubernetes 的趨勢。但在這期間也有不少人提出了質疑,比如:是不是所有的業務場景都適合 Kubernetes ?我們的組織是不是真的需要 Kubernetes?這樣的爭議,其實這背後的各種聲音都有各自的出發點。

首先 Kubernetes 有沒有價值?肯定有價值,它的普及度就是很好的證明。那 Kubernetes 有沒有問題?當然也有問題,比如, Kubernetes 會增上技術上的複雜度,此外它還有有比較大的遷移成本。

回到 TiDB 要不要在 Kubernetes 上面運行這個問題。答案其實往往來自於你當前的技術棧和技術團隊。

舉個例子,假如你的大部分應用已經上了 Kubernetes,而且你的工程師也對 Kubernetes 很熟悉,在 Kubernetes 上去做一些業務,他感覺非常舒服,那麼 TiDB 就不該成爲一個例外。換句話說,如果當前整個組織的業務都已經上 Kubernetes 了,但還需要專門招一個運維團隊在虛擬機上運維 TiDB 的話,不僅會增加額外的維護成本,也沒辦法發揮出技術棧投資的規模優勢。

那麼,在 K8s 運行 TiDB,我們想要什麼呢?通常,我們想要的是 TiDB 可以和我們在 K8s 上運行的微服務一樣,可以做到聲明式管理,通過 K8s 實現 TiDB 的自動化運維以及彈性資源配置。我想擴容的時候,並不需要去專門開新的物理機,專門購買一批機器,而是直接從整個的 K8s 資源池中,彈性的給 TiDB 分配一些資源進行擴容,然後再縮容之後又把這些資源還回去。

可是,現在的情況是,即使很多公司已經上了 K8s,但對於把數據庫放在 K8s 上運行還是有一定的擔憂,這個擔憂主要在哪兒呢?

從我個人瞭解到的信息來說,核心是穩定性,穩定性,最後還是穩定性,因爲畢竟我們要上的是數據庫。幾年前,大家剛開始上 K8s 的時候,會覺得我的業務上在 K8s 上用的很爽,那要不要把數據庫也搞上去?那總會有一種聲音是,數據庫怎麼能上 K8s 呢?我們數據庫的穩定性要求比 K8s 還要高啊!假如 K8s 掛了,那可能只是線上的微服務無法提供服務,但是假如數據庫掛了,我們不僅無法提供服務,還有可能面臨數據丟失的風險。所以通常來說,在組織中,數據庫可靠性是處於一個核心地位的,也因此在數據庫上 K8s 時,一個核心的擔憂就是可靠性。

那麼,我們在部署傳統數據庫應用的時候,是怎麼保證穩定性的?一般來說,是開幾臺固定的物理機,然後我們會把數據庫的 Binary 傳上去運行起來,這時候要點就是,運行起來之後只要不出問題就不要再動它(If it works don’t touch it)。即使要動它,也需要經過一個非常嚴苛的審計流程和預演,保證它最佳的穩定性。聽起來,對於傳統的關係型數據庫而言,高度動態化的 K8s 環境並不是一個很好的解決方案。

那爲什麼還會推薦大家把 TiDB 放在 K8S 上運行呢?

第一點,TiDB 本身能夠適應動態化的 K8S 環境

  • TiDB 可以多副本做容錯,當掛掉一個集羣當中的小數節點並不會對整個集羣的運行產生影響;

  • TiDB 能夠很好的藉助 K8s 環境做水平伸縮;

  • TiDB 提供了開箱自用的可觀測性,大家基本上把 TiDB 部署起來,就可以看到 TiDB 的所有指標,即使是在一個動態化混部的環境裏面,也能很清楚的明白 TiDB 現在的狀態是什麼,即使有副本掛掉,也沒有問題。

第二點,反回來,K8s 也能很高效的去撬動 TiDB 的潛力

  • 聲明式 API 簡化集羣管理,作爲一個分佈式數據庫,TiDB 的管理會比傳統的單機數據庫相對複雜一點,而在 K8s 上,它的聲明是 API,就能把這一點多出來的複雜度很好的消化掉;

  • 彈性資源池簡化擴容縮與故障轉移, TiDB 的故障容忍和無限的水平伸縮都需要額外的資源,比如說,我們去橫向擴張 TiDB 集羣時,需要申請一部分新的資源進來,在故障容錯之後,要做故障轉移把故障副本數補齊。那麼 K8s 的彈性資源池就能很好的把 TiDB 在水平伸縮和故障容忍上的潛力發揮出來。

因此, TiDB 和 K8S 能夠非常好的進行一個結合,並且釋放出更大的潛力達成 1+1 大於 2 的效果的。這當然不是一個巧合,這背後的原因是,TiDB 和 K8s 都遵循同樣的雲原生最佳實踐原則。那麼假如說我們已經熟悉 TiDB 和 K8s 的知識,只要稍微花費一些工夫,用 K8s 的雲原生對象就可以部署一個 TiDB 集羣,而且能運行的不錯。

但是,TiDB 和 Kuberentes 都有大量的最佳實踐,基本上你想快速看完是有點困難的。 那麼,我們在 K8s 上部署 TiDB 的時候,應該怎樣去遵守最佳實踐呢?一個基本的辦法就是把這些坑都瞭解清楚,並且寫非常多的 run book 來告訴 SRE team,怎麼去運維好它們。 run book 需要口口相傳,需要不斷的進行知識的交互、交流。但其實大家都知道,程序員一般不喜歡做這個。

我們更喜歡把這些知識寫成代碼,做自動化。那麼,我們怎麼樣把這些東西寫成代碼自動化呢?

你的 TiDB on Kubernetes 運維專家:TiDB Operator

答案就是 TiDB Operator 。 TiDB Operator 是什麼?本質就是利用 K8s 本身的擴展機制,把領域性的運維知識編寫成代碼,大家可以把 TiDB Operator 理解爲在 K8s 上運行 TiDB 的運維專家,把一個專家型人物對 TiDB 和 K8s 的所有知識都編寫成了代碼。

TiDB Operator 首先添加了一組自定義類型,比如說 TiDB Cluster,代表一個 TiDB 集羣,可以在這些類型裏描述你想要的 TiDB 長什麼樣;第二,TiDB Operator 添加了一組自定義控制器,這組自定義控制器就是這些運維知識的集合,它會用這些代碼化的知識幫助你自動化的運維 K8s 上的 TiDB 集羣。

看一個例子:首先是自定義類型,當 TiDB Operator 把自定義類型添加到 K8s 中後,作爲用戶就可以提交資源定義到 K8s 裏面,比如我們需要一個 TiDB 集羣,這個集羣要的版本是什麼,要多少 PD,要多少 KV。以及我們可以定義一個 TiDBMonitor 對象,用來監控 TiDB,在對象定義裏則只需要定義我們要監控哪一個 TiDB 就可以了。

那麼,大家可以發現,我們在做這些資源定義時是遵循 K8s 本身的聲明式定義的。那這些的理想狀態由誰來實現呢?——由自定義控制器。自定義控制器會把所有的需求和 K8s 集羣裏的實際狀態做一個對比,對比之後就能發現兩者的不同,就需要把實際狀態向期望狀態轉移。比如說我們把剛剛的 TiDB 集羣對象和 TiDB 監控對象提交到 K8s ,那麼 TiDB Operator 的控制器就會幫助我們創建很多的容器以及監控。

當然,僅僅只有創建是不夠的,TiDB Operator 中還集成了 TiDB 運維專家的所有的領域知識,下面我分享幾個運維知識。

運維知識:部署

  1. TiDB Operator 會爲每個組件選擇最佳的 K8s 原生對象;

  2. 會自動地引導 PD 做 Peer Discovery,無需手工配置;

  3. 最重要的,還會打散 TiKV 容器並自動添加 store-label ,輔助 PD 實現 Region 高可用拓撲。

運維知識:升級

滾動升級 TiKV 的時候,給每個 TiKV 實例發個SIGTEM,這時 TiKV 其實只會做一些基本的 Graceful Shutdown 操作。那就有一個問題,TiKV 退出時並不會主動的把所有的 Raft Leader 都遷移出去。假設我們有比較大的流量,滾動升級時讓沒有受影響的 Raft Group 被動地去做一個 leader 超時重新選舉,那很可能會導致我們的數據庫延遲會有一定的抖動。

那麼 TiDB Operator 怎麼做升級呢?在每次升級一個 TiKV 的容器之前,Operator 會先調用 PD 接口,把 TiKV 上邊的 leader 全部遷移完,不接收讀寫請求後纔會去重建 TiKV 的容器。依次往復,比如把 TiKV2 遷完了,就要把 TiKV2 重建, TiKV2 重建後,又可以把 leader 遷上去,接收請求,然後下一個就是把 TiKV1 的 leader 遷完,再往下。實現一個優雅的滾動升級。

運維知識:故障轉移

比如,現在 TiKV1 運行的不正常,那麼 Operator 的控制器就可以結合 PD 裏的 TiKV1 對應的 store 狀態信息和 K8s 裏它所在容器的狀態信息,來判斷這一個 TiKV 的 store 是否異常。判斷邏輯大致是 store 處於異常狀態,並且持續超過一定時間。檢測到異常後隔多久再做故障恢復以及怎麼樣判斷是否發生異常,本身也是一種運維知識。

那 Operator 有了這些知識並且把它代碼化之後,就可以在檢測到異常後,過一段合理的時間後再補充新的 store ,把副本數補齊。這樣即使我們接下來 TiKV2 再掛,那集羣就不會受影響,這就是 Operator 幫助我們做的故障轉移。

Operator 在最新版裏還提供了 on to scaling 的功能,我們去查看集羣當中的所有的組件的監控信息,並且根據這些監控信息,做自動的擴縮容,可想而知就需要更多的支持了。

大家可能會想,儘管 Operator 帶來了這麼多好處,但我還是擔心假如用 Operator 上了 K8s 之後會有一些問題。比如,上 K8s 會帶來多少性能損耗?會影響我的穩定性嗎?假如 K8s 掛了,我的數據庫會不會受影響呢?一個 TiDB 和 K8s 的領域專家是可以解答這些問題的,因此,TiDB Operator 當然也可以。

運維知識:性能

  1. TiDB Operator 支持獨享節點與混部,可以按照優先級權衡性能與成本;

  2. 自動化運維知識考慮了本地盤,使用本地盤就可以消除遠程存儲的損耗;

  3. 支持 K8s 的 HostNetwork 部署集羣,消除二層網絡開銷,所以說在一定的配置下,用 TiDB Operator 部署 TiDB 其實是可以做到零 overhead 的。

運維知識:穩定性

  1. K8s Master 故障不會影響集羣,因爲只是控制節點故障,跑 TiDB 的節點並沒有故障;

  2. K8s Node 故障會幫我們做自動故障轉移;

  3. 假如 K8s 的整個集羣所有節點都故障了怎麼辦?Operator 本身有一個默認配置是在這種情況下會保留所有的存儲,首先先保證數據不丟。

  4. 假設真的是發生了災難性的故障,整個機房比如說被水淹了怎麼辦?Operator 本身也會幫你做週期的備份,至少可以找回最近一次的備份,把你的數據先恢復到某個近期的時間點上。

Operator 開箱即用給我們很多穩定性上的增強,也就是說在穩定性方面, Operator 給了我們一個很好的基石,我們可以繼續在基石上再做一些增強,這可以省很多的工夫,並且獲得更好的穩定性。

最佳實踐案例:PayPay&馬上消費金融

最後再來看兩個案例,第一個案例是 日本領先的在線支付公司 PayPay。PayPay 在日本就可以理解爲中國的 AliPay 加微信支付。 PayPay 現在是用 Operator 部署了 100 多個數據庫節點,生產環境有 30 多個由 Operator 管理的節點。PayPay 當時在做 PoC 時,做了相當詳盡的故障演練,包括各種進程故障、節點故障、以及 AWS 整個可用區故障和還有災難恢復。比如模擬 AWS 整個全掛了,還能不能通過週期性備份把集羣恢復出來,當時也是這些所有的故障演練都很好的通過了 PayPay 的審覈,PayPay 才得以放心把整套集羣放到到 TiDB Operator 和 K8s 上來。

第二案例是我們國內的馬上消費金融。上線的是系統歸檔和跑批業務,整個線上集羣是有 60 多個物理節點,他們最顯著的一點就是在用了 TiDB Kuberentes 之後,整個混部的硬件成本下降到原來物理機部署的 30% 左右。因此在整體的性價比上是一個巨大的提升。

最後總結一下,什麼是 TiDB 在 K8s 上的最佳實踐?其實只有一句話,Keep Calm and Use TiDB Operator。當然,用 TiDB Operator 本身還是需要一定的上手成本的,這點我們也在不斷的做改進,大家可以參考我們的官網,看一下 TiDB Operator 的 一系列文檔,讓這個運維專家來爲你的 TiDB Kuberentes 之旅保駕護航。