作者| Sylvain Kalache
譯者| 張業(yè)貴
Kubernetes(簡稱K8s)上數(shù)據(jù)服務的自動化越來越受歡迎。在K8s上運行有狀態(tài)的工作負載意味著使用Operator。然而,它發(fā)展演化到今天已經(jīng)變得非常復雜,像Operator這樣的應用模式和擴展方式對于開發(fā)者與運維者而言愈發(fā)受到歡迎。
但工程師們經(jīng)常對編寫K8s Operator的復雜性感到吃力,這會影響到最終用戶。據(jù)《2021年K8s數(shù)據(jù)報告》指出,K8s Operator的質(zhì)量阻礙了公司進一步擴大K8s占有率。
Anynines首席執(zhí)行官Julian Fischer已經(jīng)構(gòu)建自動化工具近十年了,他非常了解在處理在云原生平臺和K8s等分布式基礎設施上做狀態(tài)管理的復雜性。
Julian首先分享了在構(gòu)建 Operator時應該遵循的方法,他稱之為運營模式,分為四個部分:
- 第1級:SysOp或DB要做什么
- 第2級:容器化,YAML + kubectl
- 第3級:編寫 Operator
- 第4級: Operator 生命周期管理
通過他的分享,可以了解數(shù)據(jù)服務自動化中的常見陷阱以及如何避免,進而從技術和方法學角度編寫更好的K8s Operator。
數(shù)據(jù)服務自動化
它在數(shù)據(jù)服務自動化的一般主題和K8s之間有點跳躍。一般來說,如果你談論數(shù)據(jù)服務自動化,你必須做的第一件事就是明確范圍,即你所說的數(shù)據(jù)服務自動化真正指什么。對于我們來說,任何時候都有一個使命,那就是將各種數(shù)據(jù)服務的整個生命周期完全自動化,以便在跨基礎設施的云原生平臺上大規(guī)模運行。
這里不是一些營銷噱頭,它是如何對數(shù)據(jù)服務自動化進行范圍分析的一個例子。例如,為了使多個數(shù)據(jù)服務自動化,你希望看到某些共享效果,比如你可以將Operatar SDK以外的數(shù)據(jù)服務納入自動化框架。因此,任務的背景會產(chǎn)生很大影響。例如,一個簡單的K8s集群,有一個小單位用來運行他們的應用程序。假設使用Postgres數(shù)據(jù)庫,Postgres一直是我最喜歡的例子。大家都知道,一個K8s集群對應一個Operator一個服務實例,應用程序?qū)⑦B接到那個數(shù)據(jù)庫。這與我們今天想在這里談論的故事不同。假設他們按需配置服務實例,Postgres數(shù)據(jù)庫被表示為有狀態(tài)集。Operator允許你創(chuàng)建多個實例。事情就會變得復雜,因為你有更多的數(shù)據(jù)服務實例,你必須處理好它。如果你隨后引入更多數(shù)據(jù)服務,例如,將RabbitMQ、MongoDB或任何其他數(shù)據(jù)庫添加到Operator的集合中,挑戰(zhàn)將變得更加大。
現(xiàn)在我們協(xié)作的組織中,有時有數(shù)百或數(shù)千名員工,有數(shù)千名甚至一萬名開發(fā)人員,令人難以置信的是,他們擁有數(shù)量龐大的工程師,也同時擁有許多K8s集群。我們認為,數(shù)以十計、數(shù)以百計的K8s集群對我們的經(jīng)驗是個考驗。例如,在基于虛擬機的數(shù)據(jù)服務自動化中,它們通常有上千臺虛擬機運行上千個服務實例,這取決于它們?nèi)绾谓M建集群。你可以假設有一個服務實例對應三個pod,恰好正在運行這樣一個小集群實例。在現(xiàn)有這個規(guī)模上,自動化的需求經(jīng)常變更,對規(guī)模的影響很大。
如果你解決了制作“香腸”和分發(fā)“香腸”等簡單任務,你可以想象,如果你要服務更大規(guī)模的用戶,堆棧技術解決方案要進行調(diào)整。數(shù)據(jù)服務自動化也幾乎一樣。因此,如果我們考慮那些大型應用場景,擁有很多這樣的服務實例,每個數(shù)據(jù)服務實例對某些用戶都很重要。因此,自動化需要符合一定的標準。如果該標準沒有達到自動化水平,那么自動化將用不起來,組織和技術的采用就不會發(fā)生。
使用K8s的數(shù)據(jù)服務
那么如何使用K8s的數(shù)據(jù)服務?首先,你如何實現(xiàn)一個Operator,如我認為社區(qū)應該知道的那樣?最簡單的方法是使用K8s、CRD和自定義資源定義,傳輸給K8s新的數(shù)據(jù)結(jié)構(gòu)。例如,在描述你的Postgres實例時,要創(chuàng)建一個復數(shù)的Postgres實例,因為我們是按需配置,有一個控制器負責管理實例。該控制器將按你指定的對象的規(guī)范,將其轉(zhuǎn)換為可運行的程序。因此,基本上,Operator所做的是將具有Postgres 12.2版本的主對象(如Postgres實例)的規(guī)范轉(zhuǎn)換為輔助資源。據(jù)我所知,Operator SDK是構(gòu)建CRD、生成CRD并為控制器提供模板代碼的主流工具。這是討論K8s相關的數(shù)據(jù)服務自動化時,我們想到了這兩件事。同時,還有KUDO。
如果你對此感興趣,我?guī)字芮白隽艘淮窝葜v,DoK社區(qū)會議關于數(shù)據(jù)服務自動化的話題非常有趣,今天這里將無法更深入地討論原型。
在開發(fā)階段,如果你開發(fā)了一個Operator,挑戰(zhàn)之一是你想如何系統(tǒng)地處理這項工作。有一個簡單的模型,我們稱之為操作模型,分為四個級別,這有助于你處理數(shù)據(jù)服務自動化,這是第一次提出。
給一點建設性的意見,要把你的注意力放在任務上。例如,我們建議在第一級實現(xiàn)Postgres的自動化。你需要掌握的第一件事是助理或者DBA要做什么。特別是,這對應用程序開發(fā)人員有什么影響?他們到底想要什么?
例如,應用程序開發(fā)人員對Postgres的平均期望是什么?他們需要自動故障轉(zhuǎn)移的集群實例嗎?在這種情況下,他們更喜歡同步復制還是異步復制?你想使用哪種故障轉(zhuǎn)移和集群管理器,還是使用首選倉庫管理器,或者更確切地說,使用Prometheus(普羅米修斯)?
而且,基本上你要搞清楚如何配置文件,對Postgres完成基本設置,這是操作模型一級。只需假設你有一臺虛擬機,你可以做任何你想做的事情,安裝軟件包,配置數(shù)據(jù)庫等等。因此,一旦你這樣做了,你知道配置文件應該是什么樣子,所有的這些都是Operator可以做到的。你可以考慮容器化,它可以選擇現(xiàn)有的容器映像,并將其組裝到有狀態(tài)集服務的K8s規(guī)范中,并創(chuàng)建自有的模板,這是操作模型二級中的YAML部分。因此,在操作模型二級的最終操作中,無論你是選擇了現(xiàn)有的容器映像還是自己創(chuàng)建了它們,你都有K8s規(guī)范,可以與kubectl一起使用,以手動創(chuàng)建自己的服務實例。一旦你這樣做了,你基本上可以創(chuàng)建你的Postgres實例,比如說,用三個副本和同步流復制,假定你已經(jīng)知道如何手動做到這一點,然后你可以通過思考問題,如何編寫gde,創(chuàng)建特定的有狀態(tài)設置的無頭服務來處理特定的保密數(shù)據(jù),你可以更容易地實現(xiàn)這樣的一個Operator。
現(xiàn)在,假設我們提醒自己,我們正在談論的環(huán)境可能包含1000多個數(shù)據(jù)服務實例,跨越許多K8s集群的多個數(shù)據(jù)服務。在這種情況下,我們還需要接受Operator生命周期管理本身是我們工具鏈的重要組成部分。因此,我們還需要自動化來管理Operator本身的生命周期。
無論是Operator生命周期管理器,還是其他技術在這一點上都無關緊要,最重要的是你需要知道,這是你整體數(shù)據(jù)服務自動化挑戰(zhàn)的一部分?,F(xiàn)在,如果你想到K8s Operator,并且提到自定義資源定義,像這樣的YAML結(jié)構(gòu)描述了一種可以傳遞給K8s API的新數(shù)據(jù)類型,然后K8s API將向你提供節(jié)點,并持久地將規(guī)范存儲在etcd中。這里格式不是很好。但你可以在這里看到特定資源定義的自定義資源會是什么樣子,我們教K8s如何創(chuàng)建這樣的對象。
然而,僅憑你的CRD不會有任何效果,因為你需要控制器,控制器通過代碼實現(xiàn)事件觀測,例如創(chuàng)建了一個對象。然后,控制器可以確認這個特定的服務實例是否已經(jīng)存在,確認輔助資源,需要一個服務密鑰訪問,以及需要創(chuàng)建的有狀態(tài)集。因此,正如我之前所說,K8s控制器基本上將主要資源轉(zhuǎn)換為次要資源的組合。在我們的示例中,到目前為止,這些資源一直是K8s的內(nèi)部資源,但實際情況不一定如此。我們稍后再討論。
如果你還想在那里開始編寫Operator,Operator SDK會就Operator的成熟度級別提出建議,Operator分為五個不同的等級。我真的不確定你們是否都了解這些等級的區(qū)別。但如果從現(xiàn)在開始,這絕對是一個好的開端。學會正確提問,這些問題也在文檔中。如果你真的構(gòu)建Operator,你需要用到一些核心功能,例如在沒有備份的情況下更新補丁,以及備份和恢復功能。通常這些是必備的,但用戶可能會拒絕解決方案,或者他們沒有解決方案。但你知道,你早晚要這么做,因此這會對你有所幫助。所以請記住,常見的陷阱,由分布式系統(tǒng)的編程問題引起的Bug,會有很多個,就看我們排除多少。
例如企業(yè)使用Git引發(fā)的問題。根據(jù)我的經(jīng)驗,總的來說,數(shù)據(jù)服務自動化最有可能的最大問題是,人們低估了實現(xiàn)數(shù)據(jù)服務自動化的復雜性和所需努力,表現(xiàn)形式包括基本生命周期操作的覆蓋范圍不足,以及魯棒性和可觀察性等質(zhì)量特性較低?;谶@點來說,了解使用的門檻是有必要的,你需要知道,自動化要做什么才能被目標受眾接受。雖然這在很大程度上取決于目標受眾本身,但現(xiàn)在我可以分享一些我學到的對我們大客戶很重要的事情,但說不完,因為現(xiàn)在時間不夠多了,這有點耗時。
接受配置更新很重要,因為應用程序開發(fā)人員能夠通過自動化配置來使用數(shù)據(jù)庫和應用程序。如果應用程序有特殊要求,你需要稍微調(diào)整一下數(shù)據(jù)庫配置。這是真實的需求,要盡可能地利用資源。因此,你需要采訪目標受眾,并了解這些配置選項是否已經(jīng)在自動化文檔中。你需要善于根據(jù)特定需求調(diào)整自動化。如果你知道組織內(nèi)有更多的開發(fā)人員,所有的云原生需求都在那里,比如,友好的可觀測性,透明使用基礎設施。有了K8s,在某種程度上你已經(jīng)獲得了。但在備份的上下文中,當你需要將備份存儲在某個地方時,你通常必須將備份寫入對象存儲。這就是人們對S3 API的存在做出假設的地方,例如,你應該選擇一些隱藏底層對象存儲的抽象庫。
服務實例的水平可擴展性
例如,你需要一個服務實例,你可以考慮單個Postgres用一個pod,也可以考慮集群Postgres使用異步流復制。一旦你想進行水平擴展,將副本從一個擴展到三個,就會在自動化中引入許多復雜性。因為Postgres不是那么簡單做自動化服務,這讓人喜歡用它舉例。因此,你需要添加一個集群管理器來進行故障檢測,你需要有一個主節(jié)點選舉和主節(jié)點晉升邏輯來幫助你實現(xiàn)。
此外,如果你恰好有多個可用區(qū)域的數(shù)據(jù)中心,可以分發(fā)你的pod來使用它們,這樣不會出現(xiàn)單個K8s節(jié)點。只要是可用區(qū)域,并建立了K8s集群,那么幾乎會100%這樣使用。一般來說,在整個生命周期中會重建狀態(tài)集很多次,比如計劃、切換、升級,或者垂直擴展使pod變大,數(shù)據(jù)被合并。我們將再次討論備份和恢復的問題,這顯然非常重要,因為應用程序開發(fā)人員無需等待平臺運營商的手動干預即可恢復應用程序,這通常是最后的措施。
因此,這一切都與按需自助服務有關,到目前為止,應用程序開發(fā)人員可以自助服務,創(chuàng)建服務實例,然后修改它們,重新配置它們,如果服務實例碰巧出現(xiàn)異常,或者數(shù)據(jù)被意外刪除,他們需要按應用程序的要求恢復數(shù)據(jù),防止?jié)撛跀?shù)據(jù)丟失。
有一個需求不太明顯,有時要提供最新的數(shù)據(jù)服務版本。假設Postgres的最新版本是不錯的,活躍用戶自然會喜歡。但對于某個組織來說,有些應用程序可能處于長期維護狀態(tài),它們不會立即使用新版本,因此,應用程序開發(fā)人員需要能夠選擇數(shù)據(jù)服務版本,可以使用版本號管理Operator,以支持所有自動化版本的啟用和退出。這是你必須為自動化制定的政策。如果你提供太多的版本,這也會給團隊提供很多的支持。但是,文檔也可能會減少對你的支持。
安全性也很重要,通常要求具備加密存儲、傳輸加密。例如,你希望被加密的磁盤上的數(shù)據(jù)沒有被讀取使用,從客戶端發(fā)送到數(shù)據(jù)服務實例的數(shù)據(jù)也是如此,有狀態(tài)集中的端口都應該加密。
服務綁定
請注意,這些服務實例不會很快消失。情況可能如此,但對于一些使用周期長的應用程序,服務實例可能會存活數(shù)年。如果你考慮生命周期用例和服務實例發(fā)生的事情,你將獲得一個很長的列表,比這個列表長得多。但這給了你對可能發(fā)生的事情的第一印象,比如縮小規(guī)模和擴大規(guī)模。經(jīng)歷各種版本升級,將應用程序綁定到服務實例,我在這里稱之為服務綁定。但也處理網(wǎng)絡分區(qū)以及網(wǎng)絡帶寬和延遲的波動。
你必須考慮的所有這些服務綁定,它們表示應用程序與數(shù)據(jù)服務實例的連接。例如,對不同應用程序的微服務,連接到同一個服務實例,最好每個應用程序都可以訪問數(shù)據(jù)服務實例,比如Postgres有一個專用的Postgres用戶,這樣密鑰是唯一的。
為了這樣的服務綁定,你必須做兩件事,首先在存儲憑據(jù)區(qū)創(chuàng)建一個密鑰,然后創(chuàng)建實際的數(shù)據(jù)服務用戶。
這有一些復雜性,我們稍后再討論。在我看來,數(shù)據(jù)服務自動化的一個類似方面應該表示為CRD, K8s是備份和備份計劃。因此,如果你想為特定服務實例創(chuàng)建備份,請將其描述為CRD。與作業(yè)和cron作業(yè)類似,備份計劃描述了如何定期創(chuàng)建這些備份。
現(xiàn)在,從方法論的角度來看,數(shù)據(jù)服務自動化有一些原則,如果你堅持下去,你可能會從中受益。因此,在我們回到技術要點之前,你可能想考慮的原則,正如我們之前所看到的,了解你的受眾,要求和期望的品質(zhì)至關重要,你需要了解你是否做了一些事情,比如團隊是否高度依賴特定數(shù)據(jù)源。
我見過整個公司圍繞單個MongoDB實例或其中幾個實例發(fā)展的公司。因此,為該案例構(gòu)建Operator將與我之前介紹的上下文不同。因此,請注意,上下文是良好的數(shù)據(jù)服務自動化的核心要素之一。明智地選擇數(shù)據(jù)服務也是一件好事。正如我之前提到的,比如自動化Postgres,你需要做許多工作。對于其他數(shù)據(jù)服務,你可能會遇到許可證問題,因為它們有時會更改許可證,有些甚至會遠離開源,你也必須處理好這一點。因此,通常情況下,一個具有開源許可證的單一供應商支持的數(shù)據(jù)服務,例如,你按需配置,每當你更改有工具集時,都很可能會重新創(chuàng)建。因此,這就是通過重建失敗的實例而不是修復它們來解決的問題。
你可以將已知狀態(tài)的重建用作幫助你解決問題的工具,在某些情況下,首先使用操作模型是我之前解釋的,在開始自動化工作前先了解數(shù)據(jù)服務,成為備份和恢復的救火員是必要和重要的,因為這是運維電話、平臺運營商電話鈴響起之前的最后手段,這可以避免。
因此,如果你碰巧自動化了多個數(shù)據(jù)服務,它們之間有很多協(xié)同關聯(lián)。這應該納入同一個框架。這個框架可能有代碼庫可以在控制器之間共享,但也可能在容器映像擁有的腳本,或者類似測試之類的配置都很重要,因為有了自動化,你基本上會寫一些代碼,代碼將分發(fā)到許多環(huán)境,從這些環(huán)境中,你將創(chuàng)建許多服務實例。
對代碼以及生成的服務實例都有良好的測試覆蓋率,并通過集成測試指導他們完成用例,無論你怎么稱呼它,都非常有趣。我的建議也有測試用例,對于那些場景,如果你知道你的自動化仍然存在弱點,請與你的客戶分享測試案例的測試庫,并告訴他們并允許他們在本地環(huán)境中運行這些測試。因為這會產(chǎn)生信任。而且,這也讓他們有機會更好地了解他們可以監(jiān)控哪些情況,以避免遇到問題。
不要修補上游源代碼是我們使用的原則,因為我們有這么多不同的數(shù)據(jù)服務,我們做分叉,并允許拉取請求、熱修復和臨時熱修復。主發(fā)布管理意味著從Postgres發(fā)布之日起到你的自動化發(fā)布當天,延遲應該很短。一旦你啟用了自動化,你希望將該版本快速交付到目標環(huán)境中,因為只有這樣,應用程序開發(fā)人員才能升級他們的實例。
示例
關于技術的話不多說了,因為時間不多了。在編寫控制器時,如果你是第一次調(diào)用外部資源,這會有點棘手。在服務綁定示例中,你需要創(chuàng)建一個密鑰,同時也需要創(chuàng)建一個Postgres數(shù)據(jù)庫用戶。
現(xiàn)在,有幾種方法可以做到這一點。但總的來說,挑戰(zhàn)在于,你如何確保這兩種資源的一致性?答案是采用兩級方法,例如,你也可以將Postgres用戶表示為自定義資源,并為此有一個控制器,設置控制器只有一個目的,即協(xié)調(diào)這些Postgres用戶,這使他們在你創(chuàng)建密鑰時降低了服務綁定控制器的復雜性,該密鑰是K8s和Postgres用戶已經(jīng)知道的次要資源。如果你已經(jīng)擁有它,作為CRD,也是你的K8s集群已知的資源。作為要點之一,沒有原子性保證,這里沒有事務。因此,使用聲明性方法更貼近習慣,在K8s中,允許數(shù)據(jù)狀態(tài)不一致,因為你最終與K8s保持一致。它應該能夠被周知,如果Postgres用戶無法創(chuàng)建,我們將嘗試進行協(xié)調(diào)。
保持操作的冪等性,這樣如果循環(huán)再次調(diào)用相同規(guī)格的服務,應該是可能的,這樣你就不會卡住,也不會在這里進入錯誤狀態(tài)。因此,與其創(chuàng)建用戶,創(chuàng)建不存在的用戶,我們可以做得更多。上例只是一個值得思考的簡單例子。
最后要分享一件事,緩存。如果你修改控制器中的資源,基本上是在使用K8s API對其進行修改,你的本地緩存可能不會直接和立即反映更改,這有時可能導致奇怪的行為。因此,請注意,本地緩存的更新與K8s API中對象的更新之間可能存在滯后,特別是與具有層次結(jié)構(gòu)的控制器打交道。
總結(jié)一下,這次演講的技術部分有點簡短,我們圍繞數(shù)據(jù)服務自動化的一般挑戰(zhàn)以及了解目標受眾的重要性進行了大量討論。以及上下文如何影響你在編寫特定Operator時的任務。我提出了一些通常要求的想法。也許這是一個好的開始,如果你寫自己的Operator,問問自己,這是不是我們的應用程序開發(fā)人員所需要的?如果不是,就和他們談談,試著了解他們的想法。然后,試著邁出第一步。
譯者介紹
張業(yè)貴,51CTO社區(qū)編輯,從事企業(yè)信息化建設多年,致力于信息集成、數(shù)據(jù)治理和人工智能應用等。
原文標題:Principles for Building K8s Operators,作者:Sylvain Kalache