聚美优品云平台实践
項目背景
聚美優品的云平臺項目是從2017年上半年開始調研的,當時在面對基礎設施管理這塊我們主要面臨兩個選擇,一個是業界比較成熟的OpenStack私有云架構,另一個是剛出來不久的Kubernetes容器方案。由于之前我們的運維團隊有OpenStack相關的技術沉淀,而對于Docker方面的技術棧幾乎為0,所以早期云平臺選型時的方案是基于OpenStack Mitaka來做的。雖然云平臺第一版做出來上線后確實為我們節省了一部分計算資源的交付時間,但是對于線上業務系統的交付運維仍然需要花大量時間去配置和調試。
云平臺第二版
得益于Docker鏡像環境的一致性和Kubernetes調度的靈活性,所以很快我們在2018年初開始對云平臺進行重構,主要為其引入了Kubernetes來解決我們電商大促期間的彈性資源調度問題和日常物理資源的管理問題。聚美云平臺第二版又名Galaxy,直譯過來代表星系,寓意群星閃耀,翻譯成中文也可理解為一群出色的人。在Galaxy中,目前我們已經完成了對Kubernetes,OpenStack和公有云的資源對接,并有統一的管理頁面提供給運維和研發團隊使用。對于Galaxy的定位,它更像是一個公共的基礎設施服務,讓運維從傳統繁瑣的基礎操作中解脫出來,將重心傾向于check業務層面的東西。
Galaxy主要支持如下功能:
統一的管理界面,同時支持多機房,多種資源的接入和管理;
支持對Kubernetes內部資源的管理(命名空間,Deployment,DaemonSet、Service等);
支持應用的健康檢查和容器生命周期配置管理;
支持應用的服務注冊與發現;
集成內部CI/CD平臺,提高了運維效率;
提供帶權限管理的WebConsole;
支持API,提供外部服務調用。
容器結構
為了保證Docker中的進程足夠輕量和簡單,我們摒棄了使用傳統的Sysvinit或Supervisor的方式來管理容器內部的1號進程。取而代之的是采用dumb-init這個工具來作為容器的1號PID。dumb-init是一個天生為輕量級容器而設計的進程管理器,它能夠很好向下傳遞信號量,并且還能夠接管因子進程退出而無法找到其父進程的僵尸進程。對于容器運行時的掛起,我們采用是pause這個工具,通過Linveness探針來監控容器內進程的存活性。在處理容器內部子進程優雅退出的流程時,是通過trap這個命令來捕獲terminal信號量的。一個基礎的容器啟動腳本如下:
容器內部調用
由于容器內進程做的足夠輕量,所以當一個Pod內的多個Container服務之間有內部調用時,我們優先使用網絡套接字的方式處理。如果有的業務只能使用套接字文件的話,在Kubernetes的編排中,我們添加了一個emptyDir來臨時存放套接字文件,然后通過掛載Volume的形式讓每個容器能夠正常發現套接字文件的路徑的形式來實現服務間通訊,當然套接字文件的名稱和路徑需要和研發同事協商一致。例如下面這個樣例主要就實現了,容器A和B中的服務通過讀取/var/run/service目錄下的套接字文件來實現過程調用。
容器啟動與停止
因為Kubernetes對于Pod內容器的啟動和停止是無序化的管理方式,這對于我們在管理Container起停之間的依賴也來了一些挑戰。以目前我們PHP-FPM類的容器來舉個例子,每個容器都會在啟動階段去check自己依賴的容器是否已經正常運行,如果檢查失敗則退出重新運行,直到依賴的從容器啟動完成后自己才會進行接下來的啟動邏輯。同理,容器在停止的時候,利用preStop這個特性在停止容器前sleep不同的時間來控制容器停止的順序。
代碼容器
聚美優品的持續構建交付平臺由于是自研的,和業界利用Jenkins生態構建的平臺有很大差別,所以這里我僅舉一個比較有代表性的例子來跟大家分享我們是如何在容器平臺下解決代碼發布的問題。首先,我們在線上的deployment編排里面大量運用了Kubernetes中的initContainer容器,它能夠在正常容器起來之前預處理一些東西,例如配置下載、環境的設置等等。我們通過initContainer提前將業務打包好的代碼下載解壓到code volume中,等到業務容器啟動時,code volume會隨著容器的啟動掛載到指定的代碼存放路徑。code volume我們使用的emptyDir來存放代碼,它會隨著容器的生命周期消失而銷毀,所以這里我們會與研發協商好代碼目錄中不會有任何需要持久化到本地的數據。
容器網絡
云平臺的容器網絡插件是采用IPVLAN來實現內部通信和外部通信。IPVLAN是Linux內核4.2+版本新支持的一個網絡虛擬化方案,其工作原理類似于Macvlan。IPVLAN主要提供三種工作模式L2、L3和L3S。我們主要采用的是L2模式,簡單來說,在L2模式下,操作系統內核會在master網卡上虛擬出多塊網卡分給Docker容器使用,而每一塊虛擬網卡都共享master網卡上的Mac地址,但是每塊虛擬網卡是可以有獨立的IP地址的。一個簡單的基于IPVLAN構建的虛擬網絡拓撲結構如下圖:
IPAM
對于IPAM我們是通過host-local的方式來管理的,也就是說我們在宿主機上會預配置好一個固定給容器使用的IP地址段,而對于容器的網絡規劃主要是通過自研的容器網絡管理平臺來實現統一分配和管理。使用的邏輯如下,首先我們會在IDC機房提前規劃好一批容器使用的網絡,并在接入層交換機上配置好路由和訪問規則,其次將容器的網絡和宿主機的元數據錄入到管理平臺,平臺會自動根據錄入的元數據為宿主機分配一個全局唯一的IP地址段,同時存入到Consul的KV當中。當新增加kubelet節點宿主機在初始化CNI的時候,就會從Consul中讀取到自己所對應的值去渲染CNI的配置文件。
服務和配置管理
對于容器中的應用配置管理這塊,我們采用的是聚美自研的配置管理系統(Dove),它能為各業務系統(不限于業務系統)提供統一的配置管理平臺和方案。主要解決目前我們各項目配置維護困難、容易失誤、同步效率低、管理成本太高等問題。
Dove主要的技術特性如下:
極度松散耦合,不需改動原業務代碼即可集成或棄用配置管理系統。
高效,無需額外開銷,配置即本地開發語言對象。
多環境統一管理
配置分組權限控制
變更日志:可追溯歷史內容及修改者。
離線運行:配置獲取后,即使配置服務下線也不會影響應用當前版本的運行。
離線恢復:在無配置服務器在線的情況下,應用或其服務器重啟可以自動回復上次的配置,繼續正常運行。
調試模式:使用調試的客戶端(DEV環境),開發者可以在本地編寫自己的配置內容來忽略服務端進行調試。
SOA服務治理
隨著核心服務往容器化方向遷移,傳統的通過填寫IP地址管理服務上下線的運維方式也得跟著改變。首先我們面臨的問題是,容器IP地址是隨著容器的生命周期而存在,如果容器重啟后IP發生變化,要求運維將新的IP配置到應用當中已經變得不再現實。所以在18年初,我們花了大量時間投入到SOA服務框架(Lark)的研發和落地當中,其中重點解決的問題之一,就是服務發現的問題。
lark-agent以Sidecar模式和業務容器共同組成一個Pod實例,當發起一個服務調用時,客戶端會將RPC協議的請求數據發送至lark-agent,Lark根據協議中的服務名稱將請求轉發到后端狀態正常的服務實例中去。經過一年時間的沉淀,我們已經將線上所有核心業務接入到lark平臺當中,而帶來的收益也非常明顯,其中主要就包含:
為客戶端和服務端增加Lark代理,將復雜的長連接協議隱藏起來,降低了不同語言間協議的適配成本;
Lark主動向云端上報心跳,根據自定義策略報告服務器負載情況,把運維成本降到最低;
實現與云端定時通訊,獲取當前集群運行參數,根據定制化的策略,智能選擇最優節點,拒絕“雪崩”情況的發生;
自動剔除無法與云端進行心跳保持的節點,讓上下線服務器,業務升級更加平滑;
實現故障節點自動懲罰機制,連接類錯誤容錯重試,將大大減少服務器“抖動”類問題。
對于非SOA架構的服務,因為kube-proxy使用iptables轉發的效率問題,我們并沒有直接采用Kubernetes的Service的方案,而是選擇了聚美已有的一套API服務注冊平臺。這套平臺主要是基于Consul集群、Consul-Template和Tengine/Haproxy來實現服務的管理。Consul集群本身支持多數據中心模式,我們將consul-agent容器和業務容器共同組成一個Pod實例。當容器服務器被拉起來時,consul-agent會根據ConfigMap定義好的健康檢查接口對內部服務發起請求,只有服務狀態正常的容器才會被渲染至Upstream或Backend配置當中去。
服務注冊與銷毀
運行在容器中的服務注冊和銷毀主要依賴Kubernetes的Pod生命周期管理的postStart和preStop鉤子來實現的。在容器上線前,利用postStart調用我們預先定義好的health腳本去檢查業務狀態,當退出碼為0時,我們認為容器服務正常,便會將服務注冊到服務管理平臺。同理,在容器銷毀前,利用preStop去調用health腳本觸發服務下線。在Kubernetes的編排中如下示例:
容器日志與監控
容器內日志采集的問題,業界一直存在兩個大方向,一種是日志落盤后通過log-agent方式將日志發往服務端,另一種是日志不落盤,通過異步方式發送日志到服務端。這兩個方案各有各的好處,這里我們不做討論。聚美云平臺使用的是第一種方案,即業務代碼通過聚美自研的日志組件MNLogger將正常日志和exception日志全部落地到服務器本地硬盤上,再由客戶端發往至Logstash做日志過濾后轉發至Kafka集群。日志采集客戶端采用的是log-courier,實例是跟隨著業務容器在一個Pod里面運行。
容器Mount目錄
對于在容器中,如何將日志持久化到硬盤上,我們采用的是hostPath的方式,將掛載一個宿主機的日志目錄給容器使用,而在容器里面主要通過創建軟連接的方式將業務日志寫入Volume當中。
監控服務
聚美在云平臺之前就已經有了自己完善的監控體系,其中就包含鏈路追蹤、日志監控、性能監控和統一告警幾大要素。而對于云平臺的接入,我們除了要收集業務的異常日志外,還需要對容器性能和狀態的指標有一個全面的收集。
這里我簡單列舉兩個點跟大家分享。
容器的性能數據的采集,云平臺主要利用Prometheus聯邦配合服務發現來實現的。對于不同業務邏輯的我們劃分了三種服務類型的Promethues實例,最終由上層Prometheus實例負責匯總所有Metrics。這里需要著重強調的是,大家在自己在開發和設計Metrics的Labels時,應保證遵循一個統一的命名規范,最好能夠結合被采集實例的元數據命名。這樣有助于后期大家在出監控圖時候利用Label關聯多項Metrics。
對更新正在運行時日志容器的配置文件,我們自己研發了日志采集管理平臺,用戶只需通過在平臺上去更新日志采集路徑、日志類型、kafaka topic_id等參數。參數提交成功后,平臺會自動將其渲染成業務所需要的配置文件,并通過HTTP服務生成一個全局唯一的URL提供給客戶端下載。當客戶端檢查到配置文件更新就會下載最新的配置文件,并觸發進程的reload機制。
聚美優品是一家以主打化妝品限時特賣的網上電商平臺。眾所周知,國內的電商平臺每年都會有那么幾天會推出"剁手"季的活動,例如剛剛過去的雙11/雙12購物節。那么該如何去滿足公司在大促活動期間對資源的彈性需求呢,我這里簡單發幾條我們遇到的需求場景,希望起到拋磚引玉的作用引發大家思考:
擴容的資源需靈活調度容器,并支持跨機房的調度;
容器大規模上線或鏡像更新,帶來的鏡像pull洪峰問題;
大促前需要業務承載能力進行壓測評估,擴容資源會經歷多次創建和銷毀;
電商活動一般會在整點迎來處理請求的峰值,甚至在某個時間點前端請求出現翻數倍的場景;
對擴容資源的穩定和成本之間平衡出一個最優的結果。
其實聚美云平臺內部,機房的網絡環境與三方云提供商已經做了VPC網絡專線的互通,對于大促期間擴容的計算資源,我們通過定義內部的元數據服務來確定在云主機啟動之后自己該注冊到哪個Region的Kubernetes Master。如果資源元數據發生變動,可以很快的通過下發腳本的方式重新對資源進行注冊。
關于第2點,鏡像洪峰問題,我們會提前將docker鏡像植入到云主機鏡像當中,這樣當云主機在pull鏡像時只需拉取鏡像更新的差異層,可以節省大量流量,縮短容器上線時間。
對于第3、4、5點,我們主要利用Kubernetes的DaemonSet類型來滿足容器的創建,調度和銷毀需求。目前我們對于大促期間的容器使用策略是一主機一項目的機制,也就是說擴容的云主機上,我們只會調度一個項目的容器在上面運行,同時Pod的網絡采用hostNetwork。這樣主要有兩個好處,一是調度足夠簡單,通過對Node添加項目標簽的方式快速實現一個項目容器的上線。二是單容器實例可以有效避免因高并發來臨時多容器間資源爭搶問題引起滾雪球式的報錯。另外還有一個不得不考慮的問題是云上容器網絡選型問題。對于聚美私有云來說,公有云上的資源運行不是常態,所以我們會直接讓容器使用主機網絡與外部通信以降低網絡的開銷。
大數據Yarn彈性資源
聚美優品大數據團隊的服務經常需要在凌晨跑各種業務統計數據和其它離線數據,受限于硬件資源的擴展,導致可用資源非常緊張,經常會出現數據出不來或者很晚才出來的情況,嚴重影響了業務同學的進度和工作。與大數據業務場景相反,業務的資源在凌晨的使用率是很低的,所以在今年年中,我們嘗試和大數據團隊合作,將大數據現有Yarn服務容器化后接入到的聚美私有云平臺來彌補資源緊張的狀況。
這里著重強調的是在Yarn容器下線過程中如果有任務正在執行,就可能導致這些任務失敗。具體來說,當AM失敗并且在配置的重試次數內,整個Application可以重新開始執行;如果是分配給Application(MR類型)的非AM用的Container掛掉了,AM會為其重新申請Container在其他機器上運行。所以我們修改Yarn源碼讓除了MR任務的Spark、Flink等任務不要分配到Docker容器中的Node上。還禁止了AppMaster分配到Docker節點中,避免因為下線Docker導致的任務異常。
Q&A
Q:為啥采用Consul作為容器的服務發現與配置管理,而不采用默認的etcd?A:這個問題主要是聚美對于Web類服務已經存在了基于Consul的發現機制,所以云平臺在推廣時就利用了現有的架構。
Q:請問IPVLAN網絡會不會導致容器內無法訪問本地物理網卡?或者說宿主機無法訪問本地的容器?A:這個問題很好,最初我們采用IPVLAN時也遇到了kubelet在通過Liveness探針時與容器網絡不通。后來我們通過將主機網絡和容器網絡分開,解決了這個問題。
Q:服務間的Socket通訊原理是通過掛載Volume方式進行訪問,這個/var/run/service內容能透露一下嗎?是聚美優品的RPC框架通信協議文件嗎?A:/var/run/service其實就是一個臨時目錄,里面就保存了Pod的各個Container中運行進程的Sockets文件。至于服務間(不同Pod間)通訊是聚美這邊自己實現的一個私有的RPC框架通信協議。
Q:問一下,你那邊的不同業務分配資源的時候是每個節點都打上labels嗎?A:是的,我們主要還是通過label的的形式來做容器的調度。另外每個deployment都會通過resourcelimit來做資源限制。目前對于高資源利用的容器還是通過平臺kill容器的方式,重新拉起新的容器。
Q:Prometheus是什么運行方式,拉取數據會有什么問題注意嗎,單節點夠用嗎?A:目前我們Prometheus是部署在Kubernetes當中,根據不通過的服務發現模式做了邏輯切分。上層通過Prometheus Federation來統一匯聚數據。
Q:那不同的業務之間訪問有狀態應用是通過定義ExternalName嗎?比如訪問MySQL或Redis等?A:目前我們大部分跑在云平臺上的容器都是無狀態的服務,對于有狀態的服務,還是建議通過傳統的方式運行。
Q:請問你那邊Pod是直接跑在虛擬機上嗎,還是做了一層虛擬化分配使用虛擬機呢?A:Pod的運行主要分兩塊類型。對于DaemonSet方式運行的容器,我們大都通過虛擬機或者云主機來支撐,分享里面也提到我們通過DaemonSet方式來解決我們大促期間快速擴容的需求
Q:Pod跨節點的訪問是怎么做到的,如果兩個節點不在同一個swtich下,需要跨路由器的情況下?A:IPVLAN的網絡是提前規劃好的,并保存在網絡配置管理平臺上。對于IPVLAN不同網段的之間的通信,還是在三層交換機上做路由來實現的。
總結
- 上一篇: js进度条功能
- 下一篇: 1813 方块游戏(枚举)