1. 程式人生 > >小紅書在Kubernetes容器環境的CD實踐_Kubernetes中文社群

小紅書在Kubernetes容器環境的CD實踐_Kubernetes中文社群

前言

容器推出以來,給軟體開發帶來了極具傳染性的振奮和創新,並獲得了來自各個行業、各個領域的巨大的支援——從大企業到初創公司,從研發到各類IT人員等等。跨境知名電商小紅書隨著業務的鋪開,線上部署單元的數量急劇增加,以 Jenkins 呼叫指令碼進行檔案推送的部署模式已經不能適應需求。這一期實踐日,小紅書運維團隊負責人孫國清將為大家帶來小紅書如何以最小的投入,最低的開發量快速的實現容器化映象部署,以及由此帶來的收益。以下是此次演講的整理。

作者介紹

孫國清

小紅書運維團隊負責人

浙大計算機系畢業,曾在傳統企業 IT 部門工作多年, 最近幾年開始在網際網路行業從事技術及技術管理工作,曾就職於攜程基礎架構,負責 Linux 系統標準化及分散式儲存的研究和落地,目前在小紅書帶領運維團隊,負責業務應用,基礎架構以及IT支援。個人接觸的技術比較雜,從開發到運維的一些領域都有興趣,是 Scala 語言的愛好者,曾翻譯了”The Neophyte’s Guide to Scala”,有上千 Scala 開發者從中受益,到小紅書後開始負責系統化落地 DevOps 和提高運維效率。

小紅書

圖 1

小紅書本身是一個社群做起來的。一開始是大家在平臺上發帖子,分享一些生活中的好東西,健身什麼的。目前我們已經有有 5 千萬的使用者,1 千萬的圖文,每日 有1 億次筆記曝光,涉及彩妝、護膚,健身,旅遊等等各種領域。

現在小紅書是最國內早踐行社群電商這個商業模式並獲得市場認可的一家電商,我們從社群把流量引入電商,現在在電商平臺的 SKU 已經上到了十萬級。我們從社群裡的使用者建立的筆記生成相關的標籤,關聯相關商品, 同時在商品頁面也展示社群內的使用者相關筆記。

小紅目前還處在創業階段,我們的技術團隊規模還不大, 當然運維本身也是一個小團隊,現在整個運維是八個同學。小公司資源有限,一個是人力資源有限,二是我們很多業務往前趕,在如何做好 CI/CD,怎麼務實的落地方面, 我們的策略就是開源優先,優先選擇開源的產品,在開源的基礎上,發現不足的地方做補缺。

小紅書應用上線流程

圖 2

如圖 2 是現在應用上線的過程,開發向運維提需求,需要多少臺伺服器, 運維依據需求去做初始化並交付給開發。小紅書現在有一個運維平臺,所有伺服器的部署都是有這個平臺來完成,平臺呼叫騰訊雲API生成伺服器,做環境初始化,配置監控和報警,交付給開發的是一個伺服器。

圖 3

線上釋出是用 Jenkins 指令碼的方式:用 Jenkins 的指令碼做測試,執行程式碼推送. 當有新加一臺伺服器或者下線一臺伺服器,要去修改這個釋出指令碼。 釋出流程大概是這樣的: jenkins指令碼先往beta環境發,開發者在 beta 環境裡做自測,自測環境沒有問題就全量發。

我們遇到不少的情況都是在開發者自測的時候沒有問題,然後在線上發,線上都是全量發,結果就掛了。然後回退的時候,怎麼做呢?我們只能整個流程跑一遍,開發者回退老程式碼,再跑一次 Jenkins 指令碼,整個過程最長需要10來分鐘, 這段過程線上故障一直存在,所以這個效率挺低, 再加上現在小紅書的整個技術在做一些更迭,環境的複雜度越來越高, 如果還是維持現有的程式碼上線模式, 顯然會有失控的風險.

問題&需求

首先,我們整個技術的團隊,團隊人數在增加,再加上技術棧在變。以前都是純 Python 的技術環境,現在不同的團隊在嘗試 JAVA、Go、Node。還有就是我們在做微服務的改造,以前的單體應用正在加速拆分成各個微服務,所以應用的數量也增加很多。拆分微服務後, 團隊也變得更細分了; 同時我們還在做前後端的拆分,原來很多的 API 就是在前端展現,現在在做前後端的拆分,後端程式是 API,前端是展示頁面,各種應用的依賴關係也變得越來越多。以現在的模式基本上已經不太可行了,很難持續下去。

所以團隊就在兩三個月以前就思考怎麼解決這些問題,怎麼把線上環境和程式碼釋出做得更加好一點。基本上我們需要它做到這幾點:

  • 重構“從程式碼到上線”的流程;
  • 要支援Canary釋出的策略,就是所謂的灰度策略;
  • 要能快速回退;
  • 實踐自動化測試,要有一個環境讓自動化測試可以跑;
  • 要求伺服器等資源管理透明化,不要讓開發者關心應用跑在哪個伺服器上,這對開發者沒有意義,他只要關心開發就可以了。

方法

圖 4

兩個月以前我們思考怎麼實現這樣的東西。其實一開始就考慮到容器化,一開始就是用Kubernetes 的框架做容器化的管理。為什麼是用 Kubernetes,這和執行環境和部署環境有關係。我們是騰訊雲的重度使用者, 騰訊雲原生支援 Kubernetes。所謂原生支援就是說它有幾個方面的實現: 第一個是網路層面,我們知道 Kubernetes 在裸金屬的環境下,要實現Overlay網路 ,或者有SDN網路的環境,而在騰訊雲的環境裡,它本身就是軟體定義網路,所以它在網路上的實現可以做到在容器環境裡和原生的網路一樣的快,沒有任何的效能犧牲。第二在騰訊雲的環境裡,負載均衡器和 Kubernetes 裡的 service 可以捆綁,可以通過 Kubernetes 的介面,建立 Kubernetes 的 service 去生成雲服務的負載均衡器。第三就是騰訊雲的網盤可以被 Kubernetes 管理。這些都是我們為什麼選擇 Kubernetes 的原因。

剛剛說了我們作為創業公司都是是以開源為主,在新的環境裡應用了這樣的技術(圖 4),Jenkins、Gitlab 和 Spinnaker。Jenkins 和 Gitlab 應該都聽說,用得很多了,普羅米修斯、Docker 也都是很主流的。

第一個 Traefik,在我們的環境裡是用來取代Nginx反向代理. Traefik 是用 Go 寫的一個反向代理服務軟體。第二是 Spinnaker,這是一個我個人認為非常優秀的開源的釋出系統,它是由 Netflix在去年開源的,整個社群非常活躍,它對 Kubernetes 的環境支援非常好。接下來我會重點介紹這兩塊東西。

Spinnaker

  • Netflix 開源專案
  • 開放性和整合能力
  • 較強的 pipeline 表達能力
  • 強大的表示式
  • 介面友好
  • 支援多種雲平臺

剛才介紹了 Spinnaker,它本身是一個開源專案,是 Netflix 的開源專案。Netflix 的開源專案在社群一直有著不錯的口碑。它有開放式的整合能力,它的整個設計裡對於整合能力有非常好的實現。它原生就可以支援 Jenkins、Gitlab 所有東西的整合,本身它還支援 webhook ,就是說在某一個環境裡,如果後面的某個資源的控制組件,本身是個 API,很容易就可以整合到 Spinnaker 裡。

再者它有比較強的 Pipeline 的能力,它的 Pipeline 可以複雜到無以復加,它還有很強的表示式功能,可以在任何的環節裡用表示式來做替代靜態引數和值。在整個Pipeline開始的時候,Pipeline 生成的過程變數都可以被 Pipeline 每個 stage 呼叫。比如說這個 Pipeline 是 cash 的時候,整個過程是怎麼樣,都可以訪問這個引數。它有很友好的介面,重點的是支援多種雲平臺。目前支援 Kubernetes,支援 open stack,支援亞馬遜的雲平臺。

圖 5

圖 5 是釋出系統的架構,是一個微服務的架構。裡面有很多元件,有面向用戶介面的 Deck,然後有面向呼叫,可以完全不用它的介面開發一個封裝,由後臺它幫我們執行釋出等等任務。Gate 是它的一個 API 的閘道器,Rosco 是它做 beta 映象構建的元件,Orca 是它的核心,所謂的流程引擎。Echo 是通知系統, igor是用來整合Jenkins等CI系統的一個元件。Front52 是儲存管理,Cloud driver 是它用來適配不同的雲平臺的,比如Kubernetes 就有專門的Cloud driver,也有亞馬遜的 Cloud driver。Fiat 是它一個鑑權的元件。

圖 6

圖 6 是它的介面。介面一眼看上去很亂,實際上它還是有很好的邏輯性。這裡每一個塊,每一個難點,或者是紅點或者是灰色的點,代表的是在 Kubernetes 的環境裡的某個例項。藍色是代表是活著的,右邊是例項的資訊。例項怎麼起來的,在哪個環節裡,是在哪個 group,右中是狀態,是活著還是死了等等介面介紹。

圖 7

圖 7是 Pipeline 的介面。首先,我覺得這個介面很好看。二是 Pipeline 可以做得非常靈活,可以說執行了前幾個步驟之後,等所有的步驟執行完了再執行某個步驟。這個步驟是某個使用者做某個審批,再分別執行三個步驟其中的一個步驟,然後再執行某個環節。也可以說要釋出還是回退,釋出是走釋出的流程,回退就是回退的流程。總之在這個 Pipeline 裡,你所期待的 Pipeline 的功能都可以提供。

圖 8

圖 8 是 Pipeline Stages 的型別。左上 Check Precondltions 前置條件滿足的時候才執行某個步驟。例如當前面的第一次釋出裡所有的例項都存活的時候,才執行某個步驟。或者當前面的步驟達到了某個狀態,再執行下一個步驟。deploy是在kubernetes環境裡生成replicationSet, 可以在deploy裡消滅一個伺服器組、禁用一個叢集、把叢集的容量往下降、往上升等等。也可以跑某一個指令碼,這個指令碼是在某一個容器裡,有時候可能有這樣的需求,比如說 JAVA 來說,, 這個 JAVA 跑起來之後並不是馬上能夠接入流量,可能要到 JAVA 裡跑一個 job,載入初始資料並做些初始化工作後,才可以開始承接流量。

圖 9

Pipeline 表示式很厲害,它的表示式是用 Grovvy 來做,大家知道 Grovvy 是一個動態語言。凡是 Grovvy 能用的語法,在字串的地方都可以用。所以,這些步驟中,可以說這個步驟引數是來自表示式。也可以說有條件的執行,生成環境的時候才做這樣的東西。也可以有前置條件,當滿足這個條件的時候,這個流程和 stage 可以繼續走下去。

圖 10

圖 11

如圖 10 是各種型別的表示式,從現在看起來,基本上各種要求都能滿足了。Pipeline 可以自動觸發(圖 11),可以說每天、每週、每年、每月,某一天的時候要執行 Pipeline,做一個自動釋出等等,也可以用今天新生成一個映象的時候,Pipeline 去做釋出。

Spinnaker和 Kubernetes 的關係

圖 12

Spinnaker 和 Kubernetes 有什麼關係?它有很多概念是一對一的,Spinnaker 有一個叫Account,Account 對應到 Kubernetes 是 Kubernetes Cluster,現在在我們的生產環境,我們的環境裡有三組 Kubernetes 的 Cluster,分別對應到開發、測試和生產,它也是對應到Spinnaker 的 Account、Instance,在 Spinnaker 裡 Instance 對應到 Kubernetes 裡 Pod,一個 Pod 就是一個執行的單元,它有 Server Group,這個 Server Group 對應的是 Replica Set 或者是 Deepionment。然後 Load Balance,在 Spinnaker 裡稱之為 Load Balance 的東西在 Kubernetes 裡就是 Service。

Traefik

圖 13

Traefik亮點:

  • 配置熱載入,無需重啟
  • 自帶熔斷功能

-traefik.backend.circuitbreaker:NetworkErrorRatio() > 0.5

  • 動態權重的輪詢策略

-traefik.backend.loadbalancer.method:drr

為什麼我們用 Traefik 不用 Nginx 做反向代理呢?首先 Traefik 是一個配置熱載入,用Nginx時更新路由規則者是做後端伺服器的上線、下線都需要過載,但 Traefik 不需要。還有它自帶熔斷功能,可以定義後端服務錯誤率超過比如 50% 的時候,主動熔斷它,請求再也不發給它了。還有動態的權重允許策略。一般的輪詢策略是均攤,第一個請求發給 A,第二個請求發給 B,第三個請求發給 C,第四個請求發給 D,但在 Traefik 裡的動態權重策略,它會記錄 5 秒鐘之內發給 A 的請求,是不是比發給 B 的請求更快,如果 A 處理請求的速度快過B,那接下來的 5 秒鐘有更多的請求發給 A ,有更少數的請求發給B。這個過程在不斷的調整,這是我們需要的功能。因為上了容器之後,整個基礎的硬體環境,很難去保證所有的節點效能都是一致的。

圖 14

圖 14 是它介面,它本身帶介面。這個我們定義的規則,是pass。這個規則是本身後端的應用就是 Kubernetes 的 pod,直接打到後端應用裡。

為什麼在 Kubernetes 環境裡選擇了 Traefik?

  • K8s 叢集中的 Ingress Controller
  • 動態載入 ingress 更新路由規則
  • 根據 service 的定義動態更新後端 pod
  • 根據 pod 的 liveness 檢查結果動態調整可用 pod
  • 請求直接傳送到 pod

Traefik 和 Kubernetes 有什麼關係呢?為什麼在 Kubernetes 環境裡選擇了 Traefik?因為在 Kubernetes 是以 Ingress Controller 存在,大家知道 Kubernetes 到 1.4 之後就引進了 Ingress 的概念。Kubernetes 原來只有一個叫 service,service 是四層的負載均衡,Ingress是在 Kubernetes 裡七層的實現,Kubernetes 本身不去做七層的負載均衡,它是通過 Ingress Controller 實現的,就是在Kubernetes 裡就是 Ingress Controller。它可以動態載入一個 Ingress 的路由規則。剛剛說的,它根據 service 的定義,在 Kubernetes 裡定義了很多 service 定義動態更顯後端的 Pod,這個 service 比如說關聯了這個 Pod,在 Traefik 就會把要訪問的這個 service 的流量直接請求到後端的十個 Pod。

根據 Pod 的 Liveness,Kubernetes 有一個 Liveness 的概念,去檢查結果,檢查這個 Pod 是不是活著,是不是已經準備好能夠接受請求了,然後做動態的調整。最後今天如果嚴肅的考慮 Kubernetes 的生產環境的使用,一定要考慮請求直接傳送到 pod 這個問題,因為Kubernetes,比如說剛才說的七層請求,是 http 的請求打過來,有很多的例項跑在後面。正常一般來說,Kubernetes 原生的實現是根據 service,定義一個 service。這個叫 service 叫serviceA,service 後端的 Pod 有這麼十個,在最外層使用者請求進來的時候,是進入任何容器的節點都可以,這個節點訪問的 service IP 和埠轉發到 Pod,多一個轉發的過程。Traefik 就不是這樣,Traefik 可以直接把打到 Traefik,它馬上就發到後端的 Pod,因為它和 Pod 直接關聯起來的。

圖 15

圖 15 是新發布的一個流程或者是開發的流程。我們有三個環節:一個是開發階段,一個是整合測試,一個是上線。

開發階段,開發者在 UI 裡 push,把東西推到 feature分支,這次開發迭代的時候領到一個任務,這個任務可以推到 feature 分支 。推到 feature 分支 之後,配置了一個 web hook,觸發一個Jenkins job,這個 job 做單元測試和映象構建,構建成一個 feature 分支 的映象。生成之後這個新的映象之後,觸發 Spinnaker 的部署,這個部署只在開發環境裡。

開發者怎麼訪問這個東西呢?比說 web 應用,如果這個程式叫做 APP1,就通過 APP1-A.dev.xiaohongshu.com 就可以訪問到 Feature A 的程式碼。整個過程在整個週期裡可以不斷的迭代,最後覺得可以了,就進行推送到 release。一旦把程式碼推往 release 就觸發構建,基本上差不多。最後會有一個自動化的測試,基本上是由測試團隊提供的自動化測試的工具,用 Spinnaker 呼叫它,看結果是什麼樣。

如果今天很有信心了,決定往生產發了,可以生成一個 tag,比如這個 tag 是 0.1.1,今天要發0.1.1版了,同樣可以觸發一個映象的構建。這三個不同的階段構建的映象 tag 不一樣,每生成一個 新tag, Spinnaker 會根據tag的命名規則觸發不同的 pipeline, 做不同環境的部署。

Canary

最重要的是我們有一個叫 Canary 的釋出過程,我們在 Spinnaker 的基礎上,開發了一套 Canary 的機制。Canary 和 Beta 差不多,但 Canary 是真實引入流量,它把線上使用者分為幾類:一是比較穩定的流量使用者;二是這些使用者稍微可以犧牲一點,幫我測試某一個新版本,我們的實現就是先給公司、先給辦公室的人來用,等辦公室的人用得大家的反饋都 OK,沒有什麼問題,看看監控資料也覺得沒有問題,才開始在線上做釋出了。

圖 16

這個是 Canary 的意思,線上使用者還是線上特定使用者分成兩組,線上使用者訪問老版本,特定使用者通過負載均衡轉發到特定的版本里,在後面有監控及和比較兩個版本之間的差異。

圖 17

這是在容器環境裡實現的 Canary 的機制(圖 17),使用者請求從前面進來,首先打到 Traefik,如果沒有做 Canary 的過程,Traefik 是直接把請求打到組例項。如果要釋出一個新的版本,有一個 http 的 API 控制 project service,決定把什麼樣的流量可以打到這個裡面版本。我們的策略可能是把辦公室使用者,可以通過 IP 看到 IP,或者把線上的安卓使用者,或者線上 1% 的安卓使用者打給它,這些都是可以定義的。

圖 18

如圖 18 所示是線上真實的部署流程。首先是要設定一個 Canary 策略,這個策略是完全隨機還是根據使用者的特定來源。比如說是一個辦公室使用者,還是上海的使用者,還是北京的使用者等等,然後去調整引數,是 1% 的北京使用者,還是所有的北京使用者。然後開始,我們是部署在伺服器。然後把這個 Canary 例項做擴充套件,在流量進來之前,例項的容量一定要跟著上線。進來之後把流量做重新定向,把流量原來直接打給後端的 Pod,打到代理伺服器。整個過程不斷的迭代,有 1% 的線上使用者,最後 2%,10%、50%、100%,最後就是全量使用者。在全量過程中是採用紅黑版本,先把所有的新版本老版本的例項數生成出來,等所有的新版本通過健康檢測,都線上了,舊的版本再下線,這樣完成一個灰度。如果不行的話,馬上就可以回退,所謂的回退就是把 Canary 下線,把流量打到線上版本去。

圖 19

圖上(圖 19)是我們的 Canary 策略。這是我們自己實現的一套東西。就是說我們把這個網段一半的 IPhone 使用者,首先它是 IPhone 使用者,它的在這個網段,這個 1000 和這個 1000 有各自的權重,最終實現的結果在這個網段裡一半的 IPhone 使用者進行灰度,整個灰度的維度可以有很多。現在我們支援的是完全隨機,要線上的 1%、2%,不管是誰,反正是隨機的,可以支援從 IP 來的,可以支援是什麼裝置,是 IPhone 還是安卓,維度可以組合起來。

我們實現的灰度是業務要求,如果是灰度,這個使用者必須從頭到尾,即使是隨機,比如說 1%線上的隨機。1% 的使用者灰度的話,永遠需要灰度,不會是說 1%的全域性請求會去到灰度,而是 1%的使用者,這個使用者是固定的。

下一步打算

  • ACA – 自動灰度分析
  • 自動容量管理

下一步我們打算做幾件事情:第一,我們想做自動灰度分析,叫 ACA, 現在很流行所謂的 AIOps,自動灰度分析可以說是一個最具體的 AIOps 落地了。在灰度的過程中,現在是人肉判斷新版本是否正常,其實如果日誌採集夠完整的話,這個判斷可以由機器來做。比如說今天釋出新版本,響應時間比舊版本增加了 100%,顯然這個灰度會失敗,就可以終止這個灰度,不用人去判斷。第二,再往下可以做自動的容量管理,當然是基於 Kubernetes 的基礎上,做自動容量管理。

最後總結一下: 我們傾向於採用開源的方法解決問題,如果開源不夠的話,我們再開發一些適配的功能。謝謝大家!以上就是我的講解。

來源:Go中國