內容來源:2017年9月2日,小紅書運維團隊負責人孫國清在「七牛雲&美麗聯合集團架構師實踐日:CI/CD落地最佳實踐」進行《小紅書在容器環境的 CD 實踐》演講分享。本文轉載自Go中國,IT 大咖說(id:itdakashuo)做爲獨家視頻合做方,經主辦方和講者審閱受權發佈。前端
閱讀字數:2380 | 4分鐘閱讀數據庫
嘉賓演講視頻回顧及PPT:suo.im/2THF9s後端
容器推出以來,給軟件開發帶來了極具傳染性的振奮和創新,並得到了來自各個行業、各個領域的巨大的支持——從大企業到初創公司,從研發到各種 IT 人員等等。跨境知名電商小紅書隨着業務的鋪開,線上部署單元的數量急劇增長,以 Jenkins 調用腳本進行文件推送的部署模式已經不能適應需求。本文做者介紹小紅書如何以最小的投入,最低的開發量快速的實現容器化鏡像部署,以及由此帶來的收益。服務器
小紅書是一個從社區作起來的跨境電商。用戶喜歡在咱們的平臺上發關於生活、健身、購物體驗、旅遊等相關帖子。目前咱們已經有有 5 千萬的用戶,1 千萬的圖文,每日有 1 億次筆記曝光,涉及彩妝、護膚、健身、旅遊等等各類領域。網絡
小紅書是國內最先踐行社區電商這個商業模式並得到市場承認的一家電商,咱們從社區把流量引入電商,如今在電商平臺的 SKU 已經上到了十萬級。咱們從社區裏的用戶建立的筆記生成相關的標籤,關聯相關商品,同時在商品頁面也展現社區內的和這商品有關的用戶筆記。架構
如圖 2 是以前的應用上線的過程,開發向運維提需求,須要多少臺服務器,運維依據需求去作初始化並交付給開發。咱們如今有一個運維平臺,全部服務器的部署都是由這個平臺來完成的,平臺調用騰訊雲 API 生成服務器,作環境初始化,配置監控和報警,交付給開發的是一個標準化好的服務器。
負載均衡
開發者拿到服務器準備線上發佈時用 Jenkins 觸發腳本的方式:用 Jenkins 的腳本作測試,執行代碼推送。當須要新加一臺服務器或者下線一臺服務器,要去修改這個發佈腳本。 發佈流程大概是這樣的:Jenkins 腳本先往 beta 環境發,開發者在 beta 環境裏作自測,自測環境沒有問題就全量發。框架
咱們遇到很多的狀況都是在開發者自測的時候沒有問題,而後在線上發,線上都是全量發,結果就掛了。而後回退的時候,怎麼作呢?咱們只能整個流程跑一遍,開發者回退老代碼,再跑一次 Jenkins 腳本,整個過程最長鬚要10來分鐘,這段過程線上故障一直存在,因此這個效率挺低。運維
以上的作法實際上是大多數公司的現狀,可是對於咱們已經不太能適應了,目前咱們整個技術在作更迭,環境的複雜度愈來愈高,若是仍是維持現有的代碼上線模式,顯然會有失控的風險,並且基於這樣的基礎架構要作例如自動容量管理等都是很難作到的。微服務
首先,咱們整個技術團隊人數在增長,再加上技術棧在變。之前都是純 Python 的技術環境,如今不一樣的團隊在嘗試 Java、Go、Node。還有就是咱們在作微服務的改造,之前的單體應用正在加速拆分紅各個微服務,因此應用的數量也增長不少。拆分微服務後,團隊也變得更細分了;同時咱們還在作先後端的拆分,原來不少 APP 的頁面是後端渲染的,如今在作先後端的拆分,後端程序是 API,前端是展現頁面,各類應用的依賴關係也變得愈來愈多。再加上電商每一年大促銷,擴容在現有模式也很耗時耗力。因此如今的模式基本上已經不太可行了,很難持續下去。
咱們團隊在兩三個月之前就思考怎麼解決這些問題,怎麼把線上環境和代碼發佈作得更加好一點。基本上咱們須要作這幾點:
重構「從代碼到上線」的流程;
支持 Canary 發佈的策略,實現流量的細顆粒度管理;
能快速回退;
實踐自動化測試,要有一個環境讓自動化測試能夠跑;
要求服務器等資源管理透明化,不要讓開發者關心應用跑在哪一個服務器上,這對開發者沒有意義,他只要關心開發就能夠了。
要可以方便的擴容、縮容。
咱們一開始就考慮到容器化,一開始就是用 Kubernetes 的框架作容器化的管理。爲何是容器化?由於容器和微服務是一對「好朋友」,從開發環境到線上環境能夠作到基本一致;爲何用 Kubernetes?這和運行環境和部署環境有關係,咱們是騰訊雲的重度用戶,騰訊雲有對 Kubernetes提供了很是到位的原生支持,所謂原生支持是指它有幾個方面的實現:第一個是網絡層面,咱們知道 Kubernetes 在裸金屬的環境下,要實現 Overlay 網絡,或者有 SDN 網絡的環境,而在騰訊雲的環境裏,它自己就是軟件定義網絡,因此它在網絡上的實現能夠作到在容器環境裏和原生的網絡同樣的快,沒有任何的性能犧牲。第二在騰訊雲的環境裏,負載均衡器和 Kubernetes 裏的 service 能夠捆綁,能夠經過建立 Kubernetes 的 service 去維護雲服務的 L4 負載均衡器。第三就是騰訊雲的網盤能夠被 Kubernetes 管理,實現 PVC 等,固然 Kubernetes 自己提供的特性是足夠知足咱們的需求的。
剛剛說了咱們做爲創業公司都是是以開源爲主,在新的環境裏應用了這樣的一些開源技術(圖 4),Jenkins、GitLab、Prometheus 和 Spinnaker。Jenkins 和 GitLab 應該都據說,你們都在用,Prometheus、Docker 也都是很主流的。
Netflix 開源項目
開放性和集成能力
較強的 Pipeline 表達能力
強大的表達式
界面友好
支持多種雲平臺
剛纔介紹了 Spinnaker,它是一個開源項目,是 Netflix 的開源項目。Netflix 的開源項目在社區一直有着不錯的口碑。它有開放式的集成能力,它原生就能夠支持 Jenkins、GitLab 的整合,它還支持 Webhook,就是說在某一個環境裏,若是後面的某個資源的控制組件,自己是個 API,那它就很容易整合到 Spinnaker 裏。
再者它有比較強的 Pipeline 的能力,它的 Pipeline 能夠複雜很是複雜,Pipeline 之間還能夠關聯,它還有很強的表達式功能,能夠在任何的環節裏用表達式來替代靜態參數和值,在 Pipeline 開始的時候,生成的過程變量均可以被 Pipeline 每一個 stage 調用。好比說這個 Pipeline 是何時開始的,觸發時的參數是什麼,某一個步驟是成功仍是失敗了,這次要部署的鏡像是什麼,線上目前是什麼版本,這些均可以經過變量訪問到。它還有一個比較友好的操做界面,重點的是支持多種雲平臺。目前支持 Kubernetes、OpenStack、亞馬遜的容器雲平臺。
圖 5 是 Spinnaker 的架構,是一個微服務的架構。這是一個微服務架構,裏面包含用戶界面 Deck,API 網關 Gate 等,API 網關是能夠對外開放的,咱們能夠利用它和其它工具作一些深度整合,Rosco 是它作鏡像構建的組件,咱們也能夠不用 Rosco 來作鏡像構建,Orca 是它的核心,就是流程引擎。Echo 是通知系統,Igor 是用來集成 Jenkins 等 CI 系統的一個組件。Front52 是存儲管理,Cloud driver 是它用來適配不一樣的雲平臺的,好比 Kubernetes 就有專門的 Cloud driver,也有亞馬遜容器雲的 Cloud driver。Fiat 是它一個鑑權的組件。
圖 6 是它的界面。界面一眼看上去挺亂,實際上它仍是有很好的邏輯性。這裏每個塊都有三種顏色來表示 Kubernetes 的環境裏的某個實例的當前狀態。綠色是表明是活着的,右邊是實例的信息。實例的 YML 配置,實例所在的集羣,實例的狀態和相關 event。
圖 7 是 Pipeline 的界面。首先,我以爲這個界面很好看很清晰。二是 Pipeline 能夠作得很是靈活,能夠說執行了前幾個步驟以後,等全部的步驟執行完了再執行某個步驟。這個步驟是某個用戶作某個審批,再分別執行三個步驟其中的一個步驟,而後再執行某個環節。也能夠說要發佈仍是回退,發佈是走發佈的流程,回退就是回退的流程。總之在這裏,你所期待的 Pipeline 的功能均可以提供,若是實在不行,還有 Webhook 的模式讓你方便的和外部系統作整合。
圖 8 是 Pipeline 步驟的類型。左上 Check Precondltions 前置條件知足的時候才執行某個步驟。例如當前面的第一次發佈裏全部的實例都存活的時候,才執行某個步驟。或者當前面的步驟達到了某個狀態,再執行下一個步驟。deploy 是在 Kubernetes 環境裏生成 Replication Set,能夠在 deploy 裏更新一個服務器組、禁用一個集羣、把集羣的容量往降低、往上升等等。也能夠跑某一個腳本,這個腳本是在某一個容器裏,有時候可能有這樣的需求,好比說 Java 來講這個 Java 跑起來以後並非立刻可以接入流量,可能要到 Java 裏跑一個 job,從數據庫加載數據並作些初始化工做後,才能夠開始承接流量。
Pipeline 表達式很厲害,它的表達式是用 Grovvy 來作,你們知道 Grovvy 是一個動態語言。凡是 Grovvy 能用的語法,在字符串的地方均可以用。因此,這些步驟中,能夠說這個步驟參數是來自表達式。也能夠說有條件的執行,生成環境的時候才作這樣的東西。也能夠有前置條件,當知足這個條件的時候,這個流程和 stage 能夠繼續走下去。
如圖 10 是各類類型的表達式,從如今看起來,基本上咱們各類需求都能知足了。Pipeline 能夠自動觸發(圖 11),能夠天天、每週、每個月、每一年,某一天的時候被自動觸發,作一個自動發佈等等,也能夠在鏡像有新 tag 推送到鏡像倉庫時,Pipeline 去作發佈。
Spinnaker 和 Kubernetes 有什麼關係?它有不少概念是一對一的,Spinnaker 有一個叫 Account的,Account 對應到 Kubernetes 是 Kubernetes Cluster,咱們的環境裏如今有三個 Kubernetes 的 Cluster,分別對應到開發、測試和生產,它也是對應到 Spinnaker 的 三個 Account;Instance 對應到 Kubernetes 裏是 Pod,一個 Pod 就是一個運行的單元;還有有 Server Group,這個 Server Group 對應的是 Replica Set 或者是 Deepionment。而後是 Load Balance,在 Spinnaker 裏稱之爲 Load Balance 的東西在 Kubernetes 裏就是 Service。
Traefik 亮點:
配置熱加載,無需重啓
自帶熔斷功能
-traefik.backend.circuitbreaker:NetworkErrorRatio() > 0.5
動態權重的輪詢策略
-traefik.backend.loadbalancer.method:drr
爲何咱們用 Traefik 而不用 Nginx 作反向代理呢?首先 Traefik 是一個配置熱加載,用 Nginx 時更新路由規則則是作後端服務器的上線、下線都須要重載,但 Traefik 不須要。Traefik 自帶熔斷功能,能夠定義後端某個實例錯誤率超過好比 50% 的時候,主動熔斷它,請求不再發給它了。還有動態的負載均衡策略,它會記錄 5 秒鐘以內全部後端實例對請求的響應時間或鏈接數,若是某個後端實例響應特別慢,那接下來的 5 秒鐘就會將這個後端的權重下降直到它恢復到正常性能,這個過程是在不斷的調整中,這是咱們須要的功能。由於上了容器以後,咱們很難保證一個應用的全部實例都部署在相同處理能力的節點上,雲服務商採購服務器也是按批量來的,每一批不可能徹底一致,很難去保證全部的節點性能都是一致的。
圖 14 是 Traefik 自帶的界面。咱們定義的規則,後端實例的狀況均可以實時的展示。
Kubernetes 集羣中的 Ingress Controller
動態加載 Ingress 更新路由規則
根據 Service 的定義動態更新後端 Pod
根據 Pod 的 Liveness 檢查結果動態調整可用 Pod
請求直接發送到 Pod
Traefik 和 Kubernetes 有什麼關係呢?爲何在 Kubernetes 環境裏選擇了 Traefik?Traefik 在 Kubernetes 是以 Ingress Controller 存在,你們知道 Kubernetes 到 1.4 以後就引進了 Ingress 的概念。Kubernetes 原來只有一個 Service 來實現服務發現和負載均衡,service 是四層的負載均衡,它作不到基於規則的轉發。在 Kubernetes 裏 Ingress 是屬於七層 HTTP 的實現,固然 Kubernetes 自己不去作七層的負載均衡,它是經過 Ingress Controller 實現的,Traefik 在 Kubernetes 裏就是一種 Ingress Controller。它能夠動態加載 Kubernetes 裏的 Ingress 所定義的路由規則,Ingress 裏也定義了一個路由規則所對應的 Service,而 Service 又和具體的 Pod 相關,Traefik 據此能夠將請求直接發送給目標 Pod,而無需經過 Service 所維護的 iptables 來作轉發。Pod列表是根據 Pod 的 Liveness 和 Readiness 狀態作動態的調整。
圖 15 是新發布的一個流程或者是開發的流程。咱們有三個環節:一個是開發階段,一個是集成測試,一個是上線。
開發階段,開發者在迭代開始時生成一個 Feature 分支,之後的每次更新都將這個 Feature 分支推送到 GitLab 。GitLab 裏配置的 Webhook 觸發一個 Jenkins job,這個 job 作單元測試和鏡像構建,構建成一個 Feature 分支的鏡像,給這個鏡像一個特定的 tag。生成新的鏡像以後,觸發 Spinnaker 的部署,這個部署只在開發環境裏。
開發者怎麼訪問剛剛部署的開發環境呢?若是這是個 HTTP 應用,假設應用叫作 APP1,而分支名稱叫 A,那開發者就經過 APP1-A.dev.xiaohongshu.com 就能夠訪問到 Feature A 的代碼。在整個週期裏能夠不斷的迭代,最後開發者以爲完成了這個 Feature 了,就能夠推送到 release。一旦把代碼推往 release 就觸發另外一個構建,基本上和前面的過程差很少。最後會有一個自動化的測試,基本上是由測試團隊提供的自動化測試的工具,用 Spinnaker 調用它,看結果是什麼樣。
若是今天頗有信心了,決定往生產發了,能夠在 Git 上生成一個 tag,好比這個 tag 是 0.1.1,今天要發 0.1.1 版了,一樣也會觸發一個鏡像的構建。這三個不一樣的階段構建的鏡像 tag 不同,每生成一個新 tag, Spinnaker 會根據 tag 的命名規則觸發不一樣的 Pipeline,作不一樣環境的部署。
最重要的是咱們有一個 Canary 的發佈過程,咱們在 Spinnaker 的基礎上,開發了一套 Canary 的機制。Canary 和 Beta 差很少,但 Canary 是真實引入流量,它把線上用戶分爲兩組:一是穩定版的流量用戶;二是 Canary 版的用戶,他們會率先使用新版本,咱們的具體策略是先給公司、先給咱們本身辦公室的人來用,這個灰度若是沒問題了,用戶反饋 OK,看看監控數據也以爲沒有問題,再按照 1%-10%-20%-50%-100% 的階段隨機挑選線上用戶繼續灰度,在這整個過程都有監控數據能夠看, 任什麼時候候若是有異常均可以經過 Spinnaker 進行回退。
這個是 Canary 的示意圖,線上用戶被分紅兩組,大部分用戶訪問老版本,特定用戶經過負載均衡轉發到特定的版本里,後臺有監控數據方便去比較兩個版本之間的差別。
這是咱們在容器環境裏實現的 Canary 的架構(圖 17),用戶請求從前面進來,首先打到 Traefik,若是沒有作 Canary 的過程,Traefik 是直接把請求打到組實例。若是要發佈一個新的版本,有一個 HTTP 的 API 控制 project service,決定把什麼樣的流量能夠打到這個裏面版本。咱們的策略多是把辦公室用戶,能夠經過 IP 看到 IP,或者把線上的安卓用戶,或者線上 1% 的安卓用戶打給它,這些都是能夠定義的。
如圖 18 所示是線上真實的部署流程。首先是要設置一個 Canary 策略,這個策略能夠指定徹底隨機仍是根據用戶的特定來源。好比說是一個辦公室用戶,或者全部上海的用戶等等,而後去調整參數,是 1% 的上海用戶,仍是全部的上海用戶。而後開始部署服務。接下來把這個 Canary 實例作擴展,在流量進來以前,實例的容量必定要先準備好。進來以後把流量作從新定向,把流量從原來直接打給後端的 Pod 改爲打到 Canary 代理服務上,由 Canary 代理服務根據策略和用戶來源作進一步的流量分發。整個過程不斷的迭代,有 1% 的線上用戶開始慢慢到到 100%。在達到 100% 後,就採用紅黑的策略替換掉全部舊版本:先把全部的新版本實例生成出來,等全部的新版本經過健康檢查,都在線了,舊的版本再批量下線,這樣完成一個灰度。若是中途發現問題不能繼續,立刻就能夠回退,所謂的回退就是把 把流量打回到線上版本去。
圖上(圖 19)是咱們的 Canary 策略。這是咱們本身實現的一套東西。圖中的例子是把來自指定網段一半的 iPhone 用戶進行 Canary。用戶分組的維度還能夠有其它規則,如今咱們支持的是徹底隨機/特定 IP/特定設備類型,這些規則能夠組合起來。
咱們的用戶分組是有一致性保證的,一旦爲某個用戶分組了,那在當前灰度期間,這個用戶的分組不會變,不然會影響用戶體驗。
ACA——自動灰度分析
自動容量管理
下一步咱們打算作兩件事情:第一,咱們想作自動灰度分析,叫 ACA,如今 AIOps 概念很熱門,我我的認爲自動灰度分析能夠說是一個具體的 AIOps 落地。在灰度的過程當中,人肉判斷新版本是否正常,其實若是日誌採集夠完整的話,這個判斷能夠由機器來作,機器根據全部數據來爲新版本作評分,而後發佈系統根據評分結果自動繼續發佈或者終止發佈並回退。第二,再往下能夠作自動的容量管理,固然是基於 Kubernetes 的基礎上,作自動容量管理,以便更好的善用計算資源。
最後總結一下:一個好的 CD 系統應該可以控制發佈帶來的風險;咱們在人力資源有限的狀況下傾向於採用開源的方法解決問題,若是開源不知足的話,咱們再開發一些適配的功能。