暢談雲原生(下):雲原生的飛輪理論

接上半場的內容,繼續和大家一起聊一聊雲原生這個話題,內容來自螞蟻金服中間件服務與容器團隊。

前言和上半場回顧

image

特別指出:這次分享主要是希望起到拋磚引玉的作用,讓大家更多的參與到雲原生這個話題的討論,希望後面有更多更好的分享。我們笨鳥先飛,先來開個頭。

image

在開始下半場的內容前,快速回顧一下上半場的內容。第一個話題是如何理解雲原生,然後給出了一個我們團隊的理解:應用原生被設計爲在雲上以最佳方式運行,充分發揮雲的優勢。

image

第二個話題是:雲原生應用應該是什麼樣子?在這裏給出了我們團隊的一個初步想法:雲原生應用應該往輕量化方向努力。

image

第三個話題:雲原生的中間件該如何發展?同樣嘗試給出了一個我們團隊目前的想法:中間件應該下沉到基礎設施,然後中間價對應用的賦能方式要雲原生化。

image

這是全部內容的目錄,前面三個在上半場中,今天我們將繼續下半場的三個話題。

雲和應用該如何銜接?

image

下半場的第一個話題:雲和應用該如何銜接?

image

應用和雲之間是需要密切配合的。

  • 對於非雲原生場景,由於雲正提供非常有限的能力,因此應用需要自行實現各種能力,包括通過類庫/框架的形式。
  • 務實版本的雲原生方案下,雲可以提供大部分的能力,因此應用可以大幅減負,和非雲原生相比在輕量化方面可以有明顯的改觀。但是依然存在部分能力雲無法提供,因此應用還是需要自行實現部分能力。
  • 比較理想的雲原生,是希望雲能夠提供絕大部分的能力,只是在某些特定環節無法完全剝離和解耦,但是此時應用的輕量化已經達到了非常高的水準。
  • 當然,理論上的最終目標,夢想中的雲原生,應該是雲提供所有能力,而且所有的環節都可以完全解耦,此時應用的輕量化可以做到極致,完全依賴雲提供的能力。
    整個演進過程中的哲學就是:將複雜留給雲,將簡單留給應用。

image

但是,這裏需要先強調一下,雲原生的演進過程,不僅僅需要雲的努力,也需要應用作配合。否則,即便雲已經可以提供各種能力,但是如果應用本身沒有進行輕量化改造,未能使用雲提供的能力,而是繼續保持原有的非雲原生的形態,那麼效果就會大打折扣。

image

回顧上半場的第一個話題“如何理解雲原生”:應用原生被設計爲在雲上以最佳方式運行,充分發揮雲的優勢。這裏強調的是,應用需要以原生形態來設計,以充分發揮雲的優勢。

然後回到我們的話題,假設現在雲已經準備妥當,各種能力都可以提供,而應用也按照雲原生的理念設計,那當雲原生應用部署在雲上時,這些應用和雲之間應該如何銜接?既可以讓應用使用雲提供的能力,又不至於侵入應用,破壞應用的雲原生特性?簡單說,就是要實現應用無感知。

image

首先涉及到的就是賦能方式:即當雲(包括下沉到雲中的中間件)可以爲應用提供各種能力時,這些能力又如何賦予應用呢?

爲了滿足應用輕量化的需求,不應在編譯打包等階段引入這些能力,以保持應用的雲原生特性。因此,我們的目標是:應該在運行時爲應用 動態賦能。這樣可以讓應用在開發設計階段保持簡單和專注業務,在部署運行於雲上之時再通過賦予的這些能力來對外提供服務。

這個想法自然是非常理想化的,所以問題就來了:如何實現 動態賦能?

image

流量透明劫持 是我們目前比較認可的動態賦能方式之一,在上半場談到 Service Mesh 模式的工作原理時,講到當我們講應用部署在 Service Mesh 中時,我們會在部署時動態的在應用所在的 pod 中插入 Sidecar 容器,然後在運行時,會以對用戶透明的方式來改變應用的行爲。典型如將應用發出的服務間遠程調用的請求,改爲轉向本地部署的 Sidecar,從而引入 Service Mesh 能提供的各種能力。

這個在運行時透明的改變遠程調用請求的行爲,我們稱之爲 “流量透明劫持”。

image

透明劫持的部署模式如上圖所示,右邊圖片是應用容器與 Sidecar 容器在k8s/Sigma中的部署模式,左邊圖片是單個 pod 的放大。其中綠色方塊爲應用容器,藍色方塊爲Sidecar容器,藍色線條表示服務間通訊。

image

透明劫持的具體工作流程是這樣,我們以 iptables 流量劫持方案爲例:

  • Init 流程(編號爲0的兩條紫色虛線):在Pod啓動時,通過Init Container特權容器,開啓流量劫持並設置流量劫持規則(分爲 Inbound 規則和 Outbound 規則)。注意這個流程時部署時進行,因爲不是真實請求流量所以用的虛線表示。
  • Inbound流程(左邊編號爲1,2,3的黃色實現):Inbound請求,被 traffic interception 劫持,根據 Inbound規則請求被轉發到Sidecar,然後Sidecar再轉發給應用。這是用於劫持 Inbound流量,也就是外部訪問當前應用的流量,使之在通過Sidecar再由Sidecar轉發給應用。
  • Outbound流程(右邊編號爲4,5,6的黑色實現):應用發出的 Outbound 請求會被 traffic interception 劫持,根據 Outbound 規則請求被轉發給 Sidecar,然後 Sidecar 處理之後將請求發送給目的地。這是用於劫持 Outbound 流量,也就是當前應用訪問外部服務的流量,使之先通過Sidecar,然後由sidecar進行轉發。

通過上述三個流程,我們就實現了讓應用的 Inbound 請求和 Outbound 請求在運行時改變行爲方式,在應用無感知的情況下實現了將流量劫持到 Sidecar 中。然後我們在 Sidecar 中就有機會爲當前請求賦予各種能力,典型如服務發現/負載均衡/實施各種路由策略/認證/加密等一系列能力,從而實現了對應用的動態賦能。

image

當前流量透明劫持的技術實現方案有多種,其優缺點如圖所示。

透明劫持的最大優點是對代碼無侵入:

  • 業務應用在開始時無需關注各種功能的實現細節和調用方式,也不需要依賴SDK,這些能力會在運行時動態賦予
  • 對於已有的應用,舊有代碼可以無需改動就直接運行在service mesh上
  • 從而避免修改代碼,和相關的重新打包發佈上線等複雜流程
  • 另外透明劫持支持直連(不經過sidecar)/單跳(只經過一個Sidecar)/雙跳(經過兩個Sidecar),方便開發調試,容易實現和現有系統的兼容

透明劫持近乎完美的實現了我們要求的目標:在運行時爲應用 動態賦能,應用無感知。

另外透明劫持還有一個比較隱蔽但是又非常關鍵的優點:不丟失重要信息。這指的是在透明劫持模式下,請求的原始目標地址和端口信息(original_dest)得以保留,讓應用可以工作在特定協議應該綁定的端口上,從而更符合12 factor中”Port Binding”的要求,關於這一點的細節,可以見最後的花絮部分。

由於原始目標地址和端口信息(original_dest)得以保留,因此透明劫持容許服務在多個端口上綁定多個不同協議而Sidecar只需要一個端口就可以實現流量轉發。

image

流量透明劫持的重要使用場景之一就是實現平滑遷移,即從現有的體系向 service mesh 體系逐漸遷移。

圖中時典型的服務註冊/服務發現機制:應用向註冊中心註冊,然後客戶端在發起請求時通過服務發現獲得目標服務的地址列表,再選擇其中的某個地址發出請求。

image

當我們將其中的一個應用 (如圖中的 Servcie B) 遷移到 Service Mesh 中,此時會和 Service B 一起部署 Sidecar 並設置流量劫持的規則。當 Service B 作爲服務器端接收客戶端請求時的,原有請求 Service B 的流量就會被劫持到 Sidecar。此時 Service A 和 Service B 都對此無感知。

image

當 Service B 作爲客戶端對外發起請求時,請求會被流量劫持到 Sidecar,然後 Sidecar 再轉發請求。同樣,圖中的 Service B 和 Service C 對此也是沒有感知。

image

當客戶端和服務器端的應用都遷移到 service mesh 之後,此時兩端都部署有 Sidecar,請求會按照 service mesh 的標準方式在客戶端和服務器端都做兩次透明劫持進入Sidecar。依然,兩端的服務對此無感知。

image

透明劫持對於現有系統的升級非常有幫助,主要體現在爲升級帶來的彈性。如圖所示:

  • 當客戶端和服務器端都沒有進行升級時,應用是直接連接的
  • 當客戶端接入 service mesh 時,客戶端會有改造,有Sidecar和透明劫持,因此客戶端會有一次流量劫持,再往服務器端發送請求時,由於服務器端沒有改造,因此服務器端是繼續沿用原有方式直接連接。此時流量只經過一次 sidecar,我們稱爲“單跳”,客戶端單跳。
  • 類似的,如果客戶端沒有改造,而服務器端有改造,則客戶端工作方式不變而服務器端會有一次流量劫持,這也是單跳,服務器端單跳。
  • 當客戶端和服務器端都改造完成時,在客戶端和服務器端會有兩次流量劫持,稱爲雙跳。

由於遷移的全過程都做到了對應用透明,因此在遷移過程中可以非常有彈性的安排應用的升級工作,包括個別應用升級失敗時的回退。

image

前面我們詳細介紹流量透明劫持這種動態賦能的方式,下面我們繼續介紹另外一種常見的動態賦能方式,DNS。

在上半場我們介紹 Service Mesh 的思路時,我們提到在讓應用輕量化的過程中,最終在應用裏面還是會有一個輕量級的客戶端,裏面保留有少數功能和信息。這其中就有“目標服務標識”這一項,用於標識當前請求要發送的目標服務。

而在此時,有一個很常見的方式就是用域名來做標識符,從而讓客戶端可以發起一次 DNS 解析請求到 DNS 服務器。而我們可以通過各種方式在 DNS 服務器中預設 DNS 信息,比如最中間的方式就是和服務註冊中心拉通,通過某種方式將服務註冊信息中的服務域名和IP地址信息(典型如k8s中使用ClusterIP)導入到 DNS 服務器中。

通過這樣一個方式,就可以實現通過操作 DNS 記錄來控制 DNS 解析的結果,從而實現特定目的。而將數據拉到到 DNS 服務器的方式可以有多種,域名和DNS記錄信息的使用方式也可以有很多,配合流量透明劫持,Sidecar 也是可以獲知這個請求的 DNS 解析結果…… 這裏就有很多的想象空間了。

image

具體 DNS 賦能的典型例子,在 SOFAMesh 項目中,爲了兼容現有的單進程多接口的應用,而且容許客戶端代碼繼續維持原有的用接口名而不是應用名來進行訪問,我們就是利用了 DNS。

如圖,我們通過打通服務註冊環節,在服務註冊時獲取到當前應用所提供的接口,然後將這些接口和應用的 ClusterIP 添加爲 DNS 記錄,使得這些接口名稱對應的域名都指向 cluster ip。然後在請求過程中,客戶端會通過接口名進行 DNS 解析,獲取 cluster ip,接着以 cluster ip爲目標地址發出請求,然後被透明流量劫持進Sidecar,sidecar 從請求的原始目標地址中獲取到 cluster ip,就可以得到請求的目標服務,從而可以開始後面的各種流程。

在這一過程中,我們組合使用了服務註冊 + DNS信息同步 + 流量透明劫持 + Sidecar邏輯處理,比較好的實現了對舊有應用的兼容。

image

在介紹動態賦能方式之後,我們再來繼續看應用和雲銜接的另外一個話題:在應用被賦予能力之後,應用該如何控制這些能力?

控制的方式通常有兩種:命令式和聲明式。

對於對於我們雲原生的場景而言,有些微妙:這些能力時動態賦予應用的,應用根本無法直接控制這些能力的具體實現,而且從雲原生的理念上可說,應用也不應該知道這些來自雲的能力的具體實現方式,因此,在動態賦能的場景下,命令式是不合適。

應用能爲此做什麼?應用肯定知道自己要達到的控制目標,即應用期待的目標狀態。比如,應用可以要求說,當我訪問某個服務時:

  • 要用輪詢的負載均衡算法
  • 要10%的流量去v2版本,其他的去v1版本
  • 要開啓鏈路加密
  • 要……

雖然這些能力會如何實現應用不清楚也無法直接控制,但是給出這些要求應用還是可以做到的。因此,聲明式非常符合動態賦能場景下的控制需求。

使用 聲明式API 的好處在於:

  • 簡單:應用不需要關心實現細節,這些能力的具體的實現方式/流程/細節都是能力提供方內部完成。而且這些能力隱藏在雲下,對應用是透明的,在運行時才動態賦予,應用完全可以簡單實用這些能力而無需關注其他。
  • 自描述:聲明描述的就是應用期望的目標狀態

image

聲明式API的哲學:把方便留給別人,把麻煩留給自己。

image

關於雲和應用如何銜接這個話題,目前我們能給出的方案還不多,遠談不上理想,期望能夠在未來會找到有更多更好的做法。對此有興趣的同學,非常歡迎一起探討這個話題,如果有好的想法和方式,歡迎隨時指教。

如何讓產品更符合雲原生?

image

下半場的第二個話題:如何讓產品更符合雲原生?。

注意這裏要說的是產品,而不是應用。如何讓應用更符合雲原生有足夠多的文章和理論了,但是如何提供一個產品,讓這個產品爲雲原生應用提供服務和支持,然後要讓這個產品本身更符合雲原生,能找到的資料就不多了。

image

我們先總結一下從前面內容中得到的一些規律:其核心在於,不僅提供功能,更強調體驗:

  • 在討論雲原生應用應該是一個什麼樣子時提出,雲原生應用就應該是原生形態,輕量化:雲應該讓應用更舒服
  • 在回顧雲計算曆史,探討雲的形態變化時,我們給出了中間這個圖表:雲應該讓應用少做事
  • 剛剛探討的聲明式API的哲學,把方便留給別人,把麻煩留給自己:雲應該讓應用更方便。

那麼,當我們在雲上開發產品,試圖將產品的能力融合進雲,讓雲上應用可以自如的使用這些能力時,應該遵循什麼樣的方式?

image

在這裏,我們介紹一個雲原生的飛輪理論,其創意由我們團隊的 典韋 同學,參考了亞馬遜的飛輪理論。

亞馬遜的飛輪理論相信大家都很熟悉,在這裏亞馬遜努力打造了兩個飛輪,即閉環循環,通過“選品與便利”和“更低價格”實現了更好的“客戶體驗”。

image

詳細介紹一下雲原生的飛輪理論。首先,在雲計算和雲原生出現之前,下面的這個大循環其實就已經存在了。這個循環主要關注的是功能性方面的需求,提供商的產品主要通過提供功能來滿足客戶需求,當然不是說沒有功能性之外的其他需求,只是在早期對非功能性的需求遠沒有今天這麼多。

image

進入互聯網時代,尤其是移動互聯網時代之後,這個大循環面臨新的挑戰,一方面在功能性方面要求越來越高:除了簡單功能實現之外,還有對性能/安全/穩定性/高可用/可擴展性的諸多要求,而且越來越苛刻。

image

另一個方面,在功能性之外,更多的需求來自對效率的追求:包括開發、測試、部署、維護、變更的效率,以及對成本的要求。

image

對效率的追求,推動了雲計算的產生和發展,以及雲原生理念和架構的產生,我們熟知的容器技術,微服務架構,以及新生的 Service Mesh 架構都由此誕生,不可變基礎設施和聲明式API 的理念也在實踐中被總結出來,併爲後續的雲原生架構提供理論指導。

image

雲計算的發展,雲原生的推出,爲雲和雲上產品帶來了功能性之外的一個重要特徵:易用性。體現在有了雲的支撐之後,雲上應用可以簡單開發,開發人員容易上手,由於大部分維護工作由雲承擔,因此降低了客戶的維護工作量(甚至 serverless 提出了無維護的理念)。這些產品使用簡單,對客戶心智要求低,無需客戶具體相關的專業知識。

其核心在於分離關注點:客戶和客戶應用應該關注與業務實現,而非業務實現的內容應該由雲和雲上產品提供。

image

而易用性的飛躍,在滿足各種功能性的前提下,不僅僅滿足了客戶需求,也極大的改善了開發體驗。在開發、測試、部署、維護、變更等環節的效率提升,也幫助用戶控制了成本。

image

這樣,圍繞易用性,新的閉環循環產生:對效率的追求,催生了雲和雲原生架構,帶來了易用性的提升,改善了開發體驗,從而進一步提供了效率。這個環形的過程會持續發生,雲原生架構就會沿着這個飛輪循環不斷的發展演進。在過去幾年間,這個飛輪循環已經在運轉,雲原生架構中的容器/微服務等架構都是在這個循環中不斷完善和普及。

image

這是完整的雲原生架構飛輪理論,兩個飛輪分別關注功能性和易用性,兩者結合來滿足客戶需求,改善開發體驗,最終實現雲原生架構的良性循環。

image

爲了更好的理解雲原生的飛輪理論,我們以雲計算中至關重要的虛擬化技術爲例,看看這二十年間以虛擬化技術爲基礎,雲和雲原生架構是如何一步一步演進和發展的。

首先,在物理機時代,在虛擬化技術出來之前,提供商只能提供服務器託管/服務器租用以及基於web服務器的虛擬主機服務,此時雲還不存在。

image

在2000年前後,出於對資源利用率的追求,在虛擬機技術成熟之後,基於虛擬機技術首先誕生了VPS,然後陸續出現了大家熟悉的VMWare/Xen/KVM/VirtalBox等技術和產品,雲計算開始出現。

之後圍繞易用性,先是amazon推出s3和EC2,IaaS出現;後面HeroKu推出了 PaaS。此時雲已經走向成熟,而云原生架構也出現了早期形態,比如HeroKu提出的12 factor 應用,DevOps的流行。後面陸續出現了其他Xaas形態:SaaS/FaaS等。

此時的開發體驗和物理機時代相比已經有質的飛躍。

image

2013年前後,以docker爲標誌的容器技術開始成熟,催生了容器編排、不可變基礎設施等技術和理念。而容器這種輕量化虛擬計劃的出現也極大的促進了微服務架構等的演進,2015年前後,雲原生架構被正式提出。而之前基於虛擬機技術的XaaS也開始向容器化方向轉變。

image

隨着 kubernetes 完成了對容器編排市場的統一,雲和雲原生架構進入kubernetes時代,雖然底層虛擬化技術依然是虛擬機和容器,但是上層的XaaS形態已經開始陸陸續續開始向k8s轉型。此時k8s奉行的聲明式API等理念也成爲雲原生架構的指導思想之一。

image

最近安全容器技術發展迅速,預期未來一旦技術成熟,很可能會帶來新一輪的變革,未來的雲和XaaS會可能會轉爲基於安全容器,也許還會有新的未知的形態出現,值得期待。

從上述演進過程可以看到,隨着虛擬化技術的一步一步演進,飛輪的一次一次循環,雲開始誕生,XaaS形態開始出現,各種技術和理念相繼誕生並日益完善,雲原生架構出現並開始成熟,新的理念和架構出現/實踐/改進,整個雲原生架構就這樣在一次一次的飛輪循環中走向成熟。

image

以雲原生飛輪理論爲基礎和指導,分享一些我們團隊在設計雲原生產品的一點點心得:

  • 首先,在關注功能/性能之外,應該更多的關注易用性,關注開發者的體驗,要將應用和應用開發者當成bady來呵護,努力讓產品的使用者可以更舒適更簡單的使用產品
  • 產品應該依託雲原生架構,具體說,就是應該基於雲,基於容器,基於kubernetes。其核心觀點在於,要讓產品表現的更符合雲原生,產品本身就應該是雲原生的。
  • 順勢而爲,要順着飛輪的方向,迎合雲原生的理念,迎合社區的發展方向。不要逆勢而爲,不要試圖挑戰整個社區。

image

Kubernetes 是雲原生的關鍵所在,怎麼強調都不爲過。這裏有一個被越來越多人認可的說法:

Kubernetes是雲原生時代的Linux

對這句話,我們有兩個認識,在這裏分享一下:

  • 應該以 kubernetes 爲底座進行能力建設:簡單說就是如果是 kubernetes 已有的能力則直接使用,如果 k8s 的能力不足,則在 kubernetes 上做改進和爭強,充分利用k8s的能力,而不是選擇無視。
  • 把kubernetes當kubernetes用:即要符合 kubernetes 的理念和設計,遵循kubernetes的遊戲規則

談到遵循kubernetes的遊戲規則,我們深入一下,核心在於遵循kubernetes的 CRD + Controller 模型:

  • 如果k8s底座的能力不夠:則應該去補充和加強k8s的能力,體現爲實現新的Controller
  • 如果k8s的抽象不夠:比如說對於某些複雜場景,現有CRD不適用或不夠用,則應該定義新的抽象,體現爲添加新的CRD

兩者結合起來,加固k8s底座(Controller)+ 擴展k8s抽象(CRD),就可以得到新的雲原生基礎設施。

另外,重要的事情說三遍:聲明式API,聲明式API,聲明式API!

image

舉幾個用 CRD 做擴展的例子,典型如 Istio。

image

還有 Google 新推出的 serverless 項目 knative。

image

在通過以 加固k8s底座(Controller)+ 擴展k8s抽象(CRD)的方式打造新的雲原生基礎設施後,再在這些雲原生基礎設施的基礎上,生長新的雲原生產品。

image

這裏給出一個利用k8s能力的例子:Knative的 Autoscaler 的實現。

image

儘量遵循標準,儘量和社區一起玩:一方面可以從社區借力,跟隨社區一起成長;另一方面,在產品對外輸出時也容易被社區接受。

image

最後再次強調一點:儘量不要逆勢而爲,儘量順着雲原生飛輪轉動的方向。

小結:

關於如何讓產品更符合雲原生這個話題,我們這次只帶來了一些比較基礎的想法,由於在雲原生這個領域我們也還處於摸索階段,所以目前的看法和想法可能都還不夠成熟。而且,更具體更深入的建議應該結合實際產品講,但是本次分享中不太適合再進一步深入展開了,希望後面會有機會。

此外,受工作範圍限制,我們專注的領域偏中間件和服務間通訊,對於其他產品領域,期望後續有其他同學帶來更深入分享。這也是我們本次分享的重要目的:拋磚引玉。

花絮:有哪些有趣的角色轉變?

image

在本次分享的最後是花絮內容,我們輕鬆一下,來看看在雲原生的演進過程中,有哪些有趣的角色轉變?

image

這裏所說的角色,指的是一個雲計算中的口頭禪或者說典故,通常在英文資料中經常看到:Pets VS. Cattle。

寵物或者奶牛,爲了表述的更形象一些,我喜歡翻譯爲寵物或者牲口。

image

這裏有個問題:在雲原生時代,有哪些概念發生了角色轉變?大家有興趣可以試試回答一下。

我這裏給出兩個回答,一個是IP 地址,從寵物到牲口;另一個是 端口/port,從牲口到寵物。

image

IP 地址在雲原生時代,從寵物到牲口,基本大家都比較認可了。

image

而端口/port,從雲原生之前的牲口,轉變爲雲原生之後的寵物,則還存在比較大的爭議。這裏列出當牲口和當寵物的兩個不同的端口使用方式。

image

視端口爲寵物的一個重要理論依據,來自 12 Factor 中的 Port Binding原則。實踐中很多產品,包括 Envoy / Istio 等都遵循這一原則。在我們的SOFAMesh產品中,我們也同樣遵循這一原則。

當然這個花絮的主要目的,還是希望可以借這個話題,讓大家有個心理準備:在雲原生之後,可能會有些之前理所當然的理念會發生變化。因此,請保持良好的心態 :)

總結

在最後,再一次強調,這次雲原生分享是希望起到一個拋磚引玉的作用,期待後面會有更多同學出來就雲原生這個話題進行更多的分享和討論。我們團隊目前在雲原生這條全新的道路上努力的探索,但是雲原生應該如何進行,這是一個非常大的話題,希望有更多的人一起來參與,一起來討論,一起來交流。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章