DockOne微信分享(一八八):唯品會雲平臺實現內幕披露
【編者的話】Noah雲平臺從2017年初調研開發到現在,已經一年多時間了,雖然背靠開源技術框架,但在此基礎上結合唯品會的技術體系,做了很多重要的功能開發和二次開發,本次分享想給大家介紹下我們的實現細節,從面到點,慢慢的介紹唯品會雲平臺的發展和壯大的過程,也會給大家介紹下我們的經驗和踩過的一些坑。
唯品會Noah雲平臺的構建歷程
發展歷程
Noah雲平臺從2017年初開始調研,3月份確定選型和架構,7月份已經開始接入業務,到現在已經研發了1年半時間,現在部署了5個IDC,共9個Kubernetes叢集(有些IDC部署了兩套Kubernetes叢集),其中兩套Kubernetes叢集供AI使用。
雲平臺的目標
這裡講些我們建設雲平臺的目標,主要從資源利用率提升,開發測試運維一致性和對DevOps的進化三個目標,其實就是提高人效和機器效率。
Noah雲平臺整體架構
Noah雲平臺整體架構按架構層次,分為主機層,容器層,雲平臺層(後面說的Noah Server),其中容器排程使用了業界開源的Kubernetes 1.9.8版本,容器層使用Docker 1.13.1版本,容器網路使用Contiv+Netplugin方案。
Noah雲平臺層(Noah Server),後面會說到,它是整個Noah雲平臺的粘合層,對外提供容器Lifecycle管理和叢集管理,網路管理等API和UI。
Noah雲平臺也包括了CI/CD流水線,負責業務映象的構建,提供功能聯調環境(Pandora)測試,它支援業務開發快速建立自己的測試環境,把依賴的服務快速拉起。目的是提高業務測試的效率。
Noah雲平臺也提供了基礎映象和映象儲存。
運維的釋出系統,CMDB,ITIL(變更系統),都會對接Noah雲平臺相關API或UI做叢集和容器的相關操作。
Noah雲平臺也部署了一套跟生產完全一樣的Noah Staging環境,只是規模不一樣而已。它提供了業務映象上線前的整合測試。
雲平臺層(Noah Server)詳解
Kubernetes 不是萬能
Noah雲平臺是基於Kubernetes+Docker技術框架構造的,雖Kubernetes已經成為容器編排的勝出者,但真正使用過程中,還是需要結合公司的實際情況使用。比如:
過於Cloud Native化:
- Kubernetes,包括CNCF下的開源專案,都是以Cloud Native為方向,而對於企業來說,都有歷史包袱,比如物理機/VM 和容器混布一段時間,比如有自己的服務化框架和日誌監控系統,容器化必須與公司的基礎設施打通。
功能複雜,偏分散式應用開發者:
- 提供基於yaml檔案的宣告式API,運維和開發使用起來比較複雜,容易出錯。
- Kubernetes提供豐富的功能,但都需要深入瞭解才能用好,對運維和開發要求高。
功能很完美,但現實很骨感:
- Kubernetes Deployment的rolling upgrade策略不適用唯品會的釋出流程。
- 多叢集管理Federation還不完善,Federation現在還處於Alpha階段,不太穩定。依賴CoreDNS和ETCD,增加系統部署的複雜度。不支援Pod資訊的聚合,不方便做watch。
Noah Server的定位
擺在我們面前兩條路,一個是修改Kubernetes原始碼支援我們的需求,但這會跟社群分岔路會走得越來越遠,享受不了開源社群給我們帶來的好處。因此我們在Kubernetes上構建一套Noah Server,來標準化和簡化Kubernetes的使用。我們定義它為Noah雲平臺的粘合層。
我們的思路,跟“張磊”說的有點像,走擴充套件,組合機制,而不是硬改程式碼的機制。
Noah Server主要解決以下問題:
- UI支援,標準化使用流程
- 多機房多叢集管理,同機房兩套叢集
- 灰度分批分部署池釋出(每套Kubernetes叢集相當於一個部署池,為了減少新版本釋出導致的問題,使用分批發布分批驗證)
- 與公司的釋出系統和變更系統深度整合,提高運維操作的效率
- 容器生命週期管理(摘流量,隔離,Debug)
- 提供統一的註冊發現機制,對業務透明
- 支援多種應用型別的Health Check
- 支援容器部署的高可用
- 支援一鍵容災遷移(若某個叢集不可用,可快速把該機房的容器遷移到其他機房)
Noah Server的標配功能
基礎映象
Noah雲平臺提供了唯品會所有應用型別的基礎映象,OSP(唯品會服務化框架)應用,Tomcat應用,PHP應用等,雲平臺提供CI流水線讓業務自己構建映象,CI流水線自動根據應用型別選擇最新發布的基礎映象來構建。
基礎映象的初始化腳步基於my_init開發了vip_init指令碼,來管理基礎映象執行的Service。以下是容器啟動和銷燬的流程。包括了容器啟動和關閉的關鍵步驟。
Noah映象都是按照Docker的映象分層來構建,基礎映象+業務映象+配置 = 業務容器,Noah雲平臺呼叫唯品會運維的crab系統獲取容器的執行時配置資訊[環境變數配置]。
下圖簡單介紹了基礎映象層的分層結構:
映象倉庫
我們在開源的Harbor上做了二次開發,支援多機房同步,支援唯品會的分散式儲存VOS儲存映象,支援映象同步監控等功能。
映象釋出流程是這樣的:
- 測試環境部署Harbor A,準備release的映象釋出到Harbor A,Harbor A也會儲存測試的映象
- 各機房部署Harbor B,但網路安全只開通gd9機房的Harbor B與測試環境的Harbor A相通
- 其他機房通過分散式儲存VOS做映象同步
灰度分批發布
灰度釋出是減少釋出故障的主要手段,Kubernetes管理的資源物件,如Deployment,StatefulSet等都提供了滾動升級的策略,但Kubernetes暫時不支援分批的滾動,因為每釋出批次都需要驗收測試。舉個例子,比如說某個域有10個容器,我們希望能夠分3批來發布,第一批發布容器個數為1,第二批為4,第三批為5,每批次都需要業務方確認沒問題,在繼續執行下一批操作。
我們也從京東瞭解過,他們修改了Kubernetes程式碼,增加了group controller來支援這種分批暫停的驗證。但我們考慮以後Merge程式碼的複雜性,所以對於這個功能沒有修改Kubernetes的程式碼,而是使用了下面介紹的方式。同時我們使用Kubernetes的另一個原因是想享受開源社群帶來的紅利:)
下面我們介紹下我們灰度分批的方案,其實很簡單,就是兩個Deployment,一個Running Deployment,一個新版本Cannary Deployment。Running Deployment做scale down,Cannary Deployment做scale up,最後釋出完成,刪除舊的Running Deployment。若在釋出中發現有問題,需要回滾,那做反操作即可。
灰度分批發布流程:
容器Auto Scaling
使用容器後的另一個好處是容器可以很方便的做伸縮,但其實有更高階的實現是Auto Scaling。
Kubernetes提供了HPA功能,是Kubernetes Controller Manager的一個元件,但使用HPA有些Limitation:
- HPA依賴Heapster服務來獲取容器的資源使用情況,而我們已經有自己的監控系統。
- 提供Custom Metric的接入方式,但引入了Custom Metric Server後,需要在Kubernetes Controller Manager和Kubernetes API Server直接部署kube-aggregator(Reserve Proxy)元件, 對 /apis/* 請求,轉發到 API Server 上。對於apis/custom-metrics.metrics.k8s.io/v1alpha1 請求, 轉發到後端的 Custom Metric Server,這樣增加了部署的複雜度
由於HPA有以上的Limitation,然後調研Kubernetes的HPA演算法,我們決定自己做容器的HPA。
Noah雲平臺的HPA實現:
- 支援多種策略(CPU,IO)
- 多種策略同時計算,選最優的策略執行
- 支援多種因子
- Cold down window(擴容冷卻時間為3分鐘,縮容為5分鐘)
- Tolerable factor(容忍Metric值上下波動5%)
- Frustrated level(以Target值的1.4倍作為警戒線,若超出警戒線,忽略Cold down window繼續擴容)
- 計算公式:TargetNumOfPods = ceil(sum(CurrentPodsCPUUtilization) / Target) ,Target為HPA規則設定的目標觸發值。比如cpu usage > 50%
叢集節點管理
叢集管理很多節點,所有操作多為批量操作。
- Node Label自動同步
- 每臺節點都需要打上node label,如rack資訊,machine-type等。雲平臺從CMDB系統同步資訊自動打上rack,machine-type等node label
- 批量查詢/調整 節點上所有容器的流量權重
- 批量上/下線節點
多叢集事件監控
Noah雲平臺管理了多套Kubernetes叢集,因此需要watch每個叢集的Kubernetes事件,監控這些事件做什麼?因為唯品會的其他運維繫統需要容器的實時資訊,比如容器的IP,容器個數等。
我們參考了Kubernetes Informer的實現,自研了多叢集watch的機制。只有Leader角色的Noah Server才做事件監控。Leader watch多個Kubernetes叢集的事件,然後publish到唯品會的訊息系統平臺,其他系統統一到訊息系統消費這些事件。
比如Noah Server會上報容器的新增或刪除的IP到CMDB系統,容器IP衝突會告警等。
容器Web Console
唯品會業務在使用Noah雲平臺前,程式碼都是部署到物理機或者VM上,業務人員需要通過堡壘機登陸到機器上查詢日誌等資訊。但容器後,就沒有固定的機器了,業務人員怎樣辦,因此Noah雲平臺必須實現容器的Web Console,直接登入到業務容器。
Kubernetes提供了exec介面, 允許使用者登入容器執行命令。 exec介面底層是通過呼叫docker exec來執行的, 網路協議是使用web socket, 能支援客戶端和容器之間雙向推送訊息。基於此,我們開發了登陸容器Web Console功能。
但Kubernetes的exec介面只能針對單個Pod進行操作, 這對於運維人員來說是不足夠的。 在日常的工作中,他們經常要對多個容器批量下發命令, 例如他們需要同時檢視deployment下所有Pod的某個檔案的狀態。
為了滿足這方面的需求, 我們提供了對多個Pod批量執行命令的功能。 這個功能的實現可以歸納為2個部分:
- 使用websocket分別連線多個Pod下發命令
- Noah對命令的輸出,按容器名稱進行聚合並返回
此外Kubernetes的exec命令是無法指定執行使用者的, 而在生產環境中,我們往往需要對開發和運維人員, 劃分不同的使用者許可權進行操作的。幸好,Docker的exec命令是支援使用-u引數指定執行使用者的; 所以我們對Kubernetes的exec命令做了一定改造,把登入使用者的引數最終透傳給了Docker命令。
服務註冊發現
容器化後,必須要支援動態的服務註冊與發現。Kubernetes提供了Service機制來實現,但Service剛開始使用iptables,但iptables的規則匹配是線性的,匹配的時間複雜度是O(N),規則更新是非增量式的,哪怕增加/刪除一條規則,也是整體修改Netfilter規則表。另外一個問題是效能問題,當iptables資料量很大的時候,更新會非常慢。
由於Kubernetes Service存在的一些問題,加上唯品會大部分業務都服務化了,Noah雲平臺考慮支援HTTP服務就可以,因此Noah雲平臺暫時沒有使用Kubernetes Service。
OSP服務
唯品會也有自研的服務化框架OSP(Open Service Platform),所有核心業務,在16年就做了大重構--服務化。因此對OSP應用,服務註冊發現已經支援,從物理機遷移到容器是非常簡單的。
對OSP有興趣話,請參考江南白衣的blog:《 ofollow,noindex" target="_blank">唯品會的Service Mesh三年進化史 》。
OSP Proxy容器化
其實OSP是類似現在一直在推崇的Service Mesh,也就是有一個OSP Proxy,如果用sidecar模式,由於OSP Proxy是Java的,堆內和堆外記憶體吃得有點多,而且OSP Proxy升級,必須依賴業務域釋出。
所以,我們選擇了DaemonSet的形式,每臺宿主機上只執行一個Proxy,Proxy啟動時把自己的IP寫在一個共享檔案裡,這個檔案也Mount進各個容器裡面,各個客戶端會監聽這個檔案的變化。
大家可能會想到,一臺OSP Proxy頂多個業務容器的請求轉發,會不會某個業務域的bug,把OSP Proxy壓死啊?其實在做這方案前我們也考慮到,所以OSP Proxy做了些改造。Proxy加了個來源IP的限流,效果就是單個容器的呼叫高於2萬QPS時,第二萬零一個請求開始就把它臨時重定向到Remote Proxy叢集。 十秒鐘後再重試本地Proxy,如果還是高,又繼續轉到Remote Proxy叢集。
HTTP 服務
其他非OSP服務(Http服務)怎麼辦?其實業界上有很多解決方案,比如Confd,Bamboo動態更新HAProxy或Nginx等。唯品會選擇了etcd + Confd+HAProxy。
何時註冊/銷燬:
- 我們使用了Kubernetes 的poststart和prestop的鉤子
- 容器啟動時,會呼叫我們定義的poststart指令碼,該指令碼定期檢查容器Health Check,若通過後,就通過Confd更新HaProxy和reload,上線容器
- 容器銷燬時,會呼叫prestop指令碼,該指令碼通過Confd更新HaProxy和reload,下線容器
節點資源優化
Kubernetes在容器排程是最核心的一個功能,Kubernetes的Scheduler是通過Plugin的方式編寫,我們可以很靈活編寫自己的排程演算法,而對Kubernetes原始碼沒有侵入性,其中排程演算法分為兩個階段,Predicate(過濾)和 Priorities(優選),Noah雲平臺使用了Kubernetes的Node Selector,Node Affinity/anti-affinity,Pod Affinity/anti-affinity來對容器進行排程,使用request和limit對容器資源進行資源限定。
但實際執行過程中,資源並沒有達到充分利用,我們需要更高效的利用率。因此我們考慮應用畫像的排程演算法。
應用畫像
大家對使用者畫像並不陌生,其實應用畫像也是類似,實現原理是分析應用N天的效能資料,計算出兩個維度的資料。
兩個應用畫像維度:
- 一個維度是計算應用屬於計算密集型、記憶體密集型、IO密集型,這一維度的資料是供我們做容器與節點(Node Selector)排程使用
- 另一個維度上應用跟應用直接的親和度,這裡舉個例子,A,B兩個應用都屬於計算密集型應用,理論上它們應該部署到不同的節點,但A應用是白天忙,B應用是晚上忙,那其實它們是可以部署在一起,這樣資源的使用率會更高
應用畫像實現演算法:
- 計算應用屬於哪種型別
- 雲平臺從唯品會容器監控系統獲取容器的不同Metric,然後使用正態釋出演算法,計算得出應用的型別
- 使用Kubernetes的Node Affinity來完成最後的容器排程
- 計算應用與應用間的親和度
- 通過歐式距離(常用於機器學習中聚類演算法的相似性度量)計算應用與應用間的相似度,越相似的應用越不能部署在一起
- 使用Kubernetes的Pod affinity和anti-affinity來完成最後的容器排程
- Pod Affinity實現上有效能瓶頸,因此在設定Pod affinity的value值的時候只取排名靠前N個域名。e.g.
{{{podAffinity:
preferredDuringSchedulingIgnoredDuringExecution: - weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- b.api.vip.com
- key: business_domain
operator: In
values:- c.api.vip.com
topologyKey: kubernetes.io/hostname}}}
超配
我們根據線上執行的資料,發現機器的CPU使用率只有30%,經過分析因為Noah雲平臺採用核心域與非核心域混布的方式,也就是說一臺物理機上會部署多個非核心域和核心域。這樣部署的目的是想核心域能瞬時使用非核心域的資源,所以核心域的limit cpu是request cpu的1.5倍。但這樣還不能達到資源使用率的提升。
根據下面的公式,我們推算出超配係數為3,也就是每臺機器可以超配3倍。我們修改了kubelet,增加了--cpu-overcommit-times 啟動引數,kubelet 上報cpu核數會乘以超賣係數。
如果大家有Cgroup的知識背景,瞭解cpu share和cpu quota的區別,就會懂的配置超配係數後,如果不是極端情況(機器上跑的所有容器都100% cpu usage),業務域容器承諾的limit cpu是可以保證的。
資料定義:
M:非核心域的CPU數 N:核心域的CPU數 Q:總CPU數 Tm:非核心域CPU利用率 Tn:核心域CPU利用率 T:整個叢集的CPU利用率
計算超配係數:
資源分配公式:M+N=Q 利用率公式:Tm*M+Tn*N=Q*T 如果:Tm=0.1,Tn=0.7,M=2N(同一臺機器,非核心域是核心域的兩倍) 計算: 3N=Q 0.1*2N+0.7*N=Q*T 0.9N=Q*T, 0.9=3*T,T=0.3 說明,不超配情況下,叢集CPU利用率只能達到30%,與實際情況一樣 引入超配係數k 則公式為: M+N=Q*k Tm*M+Tn*N=Q*T 其他條件不變的情況下計算 0.9N=3N/k*T k=3/0.9*T,如果目標全叢集利用率0.9,則k=3
注意:使用超配係數,就不能用cpuset特性,因為cpuset是真的繫結CPU的。
BTW,我們還在嘗試使用應用畫像,計算業務容器的request cpu,這樣比超配方案更精確。因為超配方案是針對所有域的容器,粗粒度,應變快,而應用畫像是根據歷史資料計算,細粒度,但需要業務重新發布。
資源優化助手
為了更快捷更頻繁的優化我們的容器雲資源,我們提供了6種操作建議給運維。
垂直擴容是增大容器的資源規格,垂直縮容是減少容器的資源規格,垂直或水平擴縮容由運維與業務方協定。
- 垂直/水平擴容容器CPU規格,一週內CPU最高使用超過70%的域
- 垂直/水平縮容CPU規格,一週內CPU最高使用低於10%的域,按總閒置CPU排序,總閒置CPU=(CPU核數*(free百分比-70%)*容器數量)
- 垂直擴容容器記憶體規格,一週內CPU最高使用超過70%的域
- 垂直縮容容器記憶體規格,一週內CPU最高使用低於60%的域,按總閒置記憶體排序,總閒置記憶體=(記憶體規格*(free百分比-20%)*容器數量)
容器隔離性
Disk/Network IO使用限制
容器的磁碟和網路隔離是大家最頭疼的一個問題,暫時只能限制使用量,防止某些業務容器有bug,導致狂寫磁碟或打滿網絡卡,影響同一臺機器的其他容器。
Noah雲平臺是這樣做的:
- 使用Kubernetes ConfigMap 配置docker IO options和network options,docker options在Kubernetes啟動Docker容器的時候傳遞引數,network options在Kubernetes呼叫CNI介面時傳遞
- 支援全域性/按機器型別/按應用型別/按域 四種級別的配置。e.g. 不同機器型別,docker IO options不一樣
- 修改Kubernetes程式碼支援從ConfigMap讀取配置並傳遞options引數
該方案的限制:
- 只能限制單個容器最大值,肯定是超賣的
- Disk IO 只能限制Direct IO,不能限制Buffer IO
容器日誌不落盤
日誌採集方案在業界大部分都是考慮先寫檔案,然後通過Agent收集到中央,這樣應用可以解耦。但這種方式,花費了Disk IO和網路IO,而且容器化後,日誌量比物理機/VM的時代多了很多,對IO造成了一定的壓力。因此業界也開始使用Log Appender直接傳送的方式。
業界阿里雲的例子:
Noah雲平臺使用了類似的方式,開發日誌不落盤的Log Appender,支援logback,log4j和log4j2三種log框架,支援兩種模式,模式1:是先落盤,超過rate limit(限流)後才傳送到kafka,模式2:是先發送到Kafka,超過rate limit後再落盤,預設使用模式2。
為啥發到Kafka,這跟唯品會的日誌收集框架有關,請參考後面<<日誌與監控>>章節。
因為日誌不落盤Log Appender是以jar包形式給業務域使用,因此如何動態變更Log Appender是必須要做的,我們通過watch yaml格式的配置檔案[容器通過mount物理機上的這個yaml檔案],動態調整引數,比如rate limit值,Kafka地址,kafka topic名稱,message壓縮演算法,partition key等引數。
容器高可用
容器機架反親和
Noah雲平臺在容器部署的時候,Kubernetes的Pod Anti-Affinity來實現同一個域的機架反親和。避免同一個業務域的容器都部署到同一個機架上,防止由於機架網路問題或者掉電,導致服務不可用。
如何實現:
- 自動對節點打上rack的Node Label
- 在排程中使用Pod Anti-Affinity來對同域容器做反親和
podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - podAffinityTerm: labelSelector: matchExpressions: - key: business_domain operator: In values: - osp-noah-demo.jdk17.com topologyKey: rack weight: 100
單機房多叢集
為了實現叢集的高可用,Noah雲平臺提供多個IDC部署,業務可以同時部署到不同的IDC的Kubernetes叢集,但核心業務對延時要求非常敏感,業務容器依賴的第三方服務還沒有做到MHA,如資料庫SQL/">MySQL,Redis。這樣容器多機房部署後,容器呼叫就變成跨機房呼叫。
為了提高叢集的高可用,同時也防止跨機房呼叫,Noah雲平臺把一個IDC的大叢集,拆成兩個小叢集,業務容器只需部署到同機房的兩個叢集,這樣不單可以解決跨機房呼叫問題,也可以防止Kubernetes叢集過大導致的排程效能問題。
聰明的你也會問,如果整個IDC都不可用怎麼辦?Noah雲平臺其實也提供瞭解決辦法。
- 多IDC網路形成環路,避免光纖被挖斷的尷尬
- 提供一鍵容災遷移功能,快速遷移業務域的容器到其他機房(有幾分鐘的服務不可用)
容器網路
容器網路也是Noah雲平臺最重要的一環了,容器網路的互通性,效能是保證容器化推進順利催化劑。因此我們的網路方案也做了很多調研和優化。容器網路方案使用Kubernetes CNI模型。
對比不同的容器網路型別:隧道方案 vs VLAN方案 vs 路由方案,我們最後選型的是Contiv Netplugin。
Contiv Netplugin:
這裡說下我們網路方案優化的地方,為了讓新增容器業務不增加現有核心ARP表壓力,閘道器下移至TOR交換機,核心/匯聚交換機無需承載ARP,僅需執行路由協議或靜態路由,可增加核心可靠性。
閘道器下移到TOR:
各元件的職責:
- Netmaster
- 統一分配,管理子網與IP地址池,管理Docker網路,配置資料同步更新etcd
- Netplugin
- 通過CNI介面與kubelet交互向netmaster申請IP、MAC等網路資源
- 建立veth pair ,為容器設定nic,ip addr,ip路由資訊
- 管理OVS端,設定port\vlan\qos\流規則等資訊
- 監聽etcd,實時更新網路資訊快取
- Endpoint 和 IP 地址池清理
- OpenvSwitch
- 橋接容器與物理網口,承載來自容器業務流量轉發
- ACL訪問控制和QoS限流
- etcd
- 儲存contiv網路資料模型,包括網路名,endpoint,ip地址池等資訊,傳遞網路狀態更新事件
各元件的職責:
日誌與監控
唯品會早就已經有自己的日誌監控(Dragonfly),業務監控(Mercury)和物理機監控(Falcon)系統。但它們也需要監控容器,因此也做不少工作。
我們在filebeat的基礎上,開發了vfilebeat agent,日誌採集效能比Logstash提高5倍。因為vfilebeat部署在宿主機上收集多個容器的日誌,因此Logstash不能滿足日誌採集效能需求。vfilebeat把日誌上報到kafka叢集,elasticsearch做日誌索引,最後在dragonfly UI上供業務查詢。
容器指標,我們開發了smart agent,它除了收集業務的trace log日誌外,還收集業務自定義的metric指標,容器效能指標等。同時它也會觸發告警,我們在告警時增加了命令執行鉤子,這樣可以在發生告警時做一些action,比如收集當時的dstat,收集容器程序的vjdump和火焰圖等。
由於本文關注的是Noah雲平臺,因此這裡就不詳細展開日誌和監控系統的架構了。
完整的監控體系指標:
一些小技巧
容器重啟保留現場
Kubernetes提供了Liveness Prob,如果Health Check不過,容器會自動重啟,但這樣就沒有現場了,業務就比較困難定位問題,所以Noah雲平臺在容器Health Check不過導致的容器重啟,會自動執行唯品會開源的vjtools工具的vjdump命令,抓取當前的snapshot。
如何實現:
- 還是使用prestop的鉤子,在prestop的指令碼檢查下容器的Health Check介面,如果不過,則執行vjdump。
日誌Mount路徑
唯品會有日誌收集系統Dragonfly,在物理機/VM的時代,Dragonfly的vfilebeat agent會收集業務域目錄下指定檔案,如/apps/logs/log_receiver/{domain-name}/xxxx.log,但容器化後,同一個業務容器可能跑到同一臺宿主機上,如果按照原來的log路徑mount到宿主機的話,會導致兩個容器同時寫同一個日誌問題,導致log錯亂等問題。
如何實現:
- 容器日誌mount到宿主機上,增加PodName做為Path的一部分,如: /apps/logs/logreceiver/{domain-name}/{pod_name}/trace/trace.out
- 在容器初始化指令碼通過軟連結的方式,把容器/apps/logs目錄link到/docker/logs/${PODNAME}, 其中PODNAME環境變數是kubernetes設定到容器裡面的,是唯一的名稱
if [[ ! "${!SKIP_LOG_SETUP[@]}" && -e /docker/logs ]]; then mkdir -p /docker/logs/${POD_NAME} chown xxx:xxx -R /docker/logs if [ -e /apps/logs ]; then rm -rf /apps/logs fi ln -s /docker/logs/${POD_NAME} /apps/logs fi
-
容器釋出的時候,把容器裡面的/docker/logs目錄mount到宿主機的/apps/logs目錄
{{{- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
volumeMounts:
- mountPath: /docker/logs/
name: smartgagent
volumes:
- hostPath:
path: /apps/logs/
name: smartagent}}}
隔離某個容器定位問題
如果業務需要線上容器來定位問題,可以先把某個容器的流量摘掉就可以了,但若想保證容器的instance數量,怎麼做呢?Noah雲平臺使用了K8S ReplicaSet Selctor的特性,如RS有selector:[a=b, c=d],則含有且不限於label:[a=b, c=d]的pod,且pod的metadata.ownerReferences有指向此RS的引用,那麼這個pod就被視為被RS管理。
鑑於此特性,如果想將某個容器隔離出當前的RS,只需要修改此容器的label即可,為了能夠方便查詢被隔離的容器,Noah雲平臺把label修改為[name-bk=deployName,pod-status=isolate]的方式。
巧用Pod中斷預算(Pod Disruption Budget)
Pod中斷預算是Kubernetes用來保證應用的高可用的,對那些Voluntary(自願的)Disruption做好Budgets(預算方案),這裡說到PDB是解決Voluntary Disruption,不解決Involuntary的場景。
官方文件已經有說什麼叫Voluntary和Involuntary場景,我這裡就不在多說了: Kubernetes Pod Disruption Budget 。
我這裡說下Noah雲平臺怎樣巧用PDB。在我們做叢集機器的核心升級過程中(需重啟機器),為了保證升級過程不中斷業務,運維必須按機櫃來重啟機器,因為如果多機櫃同時操作的話,如果某個業務域的容器剛好都在這批重啟的機櫃上,那這服務就中斷了。
這時,我們使用PDB。在升級前,為Kubernetes叢集中每個Deployment建立PDB[一個指令碼搞定],然後運維升級核心重啟機器就不需要按機櫃逐個做了,就把大叢集的機器分多批來做,在執行kubectl drain命令的時候,PDB會產生效果,保證業務容器的最低Running Instance個數,如果少於最低Running Instance,則drain命令會block,直到符合PDB要求,才繼續。
我們使用這種方式,以前幾百臺機器都要升級一個下午,現在基本1小時能夠完成上千臺機器的核心升級重啟。當然這是我們使用PDB的例子,但其實也有很多地方可以使用的,比如ZooKeeper,etcd要保證最少容器數等。
PDB例子:
apiVersion: policy/v1beta1 kind: PodDisruptionBudget metadata: name: $deploymentname-pdb spec: maxUnavailable: 50% selector: matchLabels: name: $deploymentname
解決過的問題
作業系統引數調整
CPU開啟Performance模式
生產伺服器預設都調整為Performance模式的,但在CPU是E5-2630 v4這個型號的華為機器不生效。這裡分享下我們的經驗。
- 系統交付時,檢查/proc/cpuinfo的cpu主頻是否跟硬體主頻一致
- 監控容器的cpu throttled number和cpu throttled time指標,如發生throttling,要double check一下機器的CPU主頻
設定Dirty Backgroup Bytes
在系統高IO的情況下,如果不設定dirtybackgroundbytes,預設使用dirtybackgroundratio的設定,預設是10,在現在動不動幾十G記憶體的機器,值非常大,當把這麼大量的page cache資料刷到磁碟上的時候會超過普通磁碟的iops。因此我們設定這個引數,滿100M就刷盤。
vm.dirtybackgroundbytes = 104857600
高IO的場景,也會影響JVM進入Stop the world的時間,因為JVM經常會默默的在/tmp/hperf 目錄寫上一點statistics資料,如果剛好遇到PageCache刷盤,把檔案阻塞了,就不能結束這個Stop the World的安全點了。因此我們在JVM啟動引數增加了-XX:+PerfDisableSharedMem。
高IO的場景,也會Block GC Log列印,從而Block Stop the World的過程,因為列印GC log是用VM Thread來做的,JVM虛擬機器只有一條VM Thread,所以當這條執行緒在安全點內被BLOCK住,Stop the World問題就比較嚴重。因此我們把JVM的gc log列印到/dev/shm/ 目錄下,如果程序重啟,stop指令碼會把ram disk的gc log move到磁碟儲存。
設定swapniess
作業系統OS的swapniess預設是60,這是Linux在很久的時候設定的預設值,可能當時記憶體容量沒現在這麼大吧,但現在這個預設值已經不合適了。如果不修改,那記憶體剩下很多就開始用swap了,用了swap,各種超時就出來了。因為容器的記憶體是根據JVM Heap計算出來的,通常都比JVM Heap要大,因此我們把swapiness修改為0。
vm.swappiness = 0
設定fs.inotify
Kubernetes kubelet會watch一些檔案,如果不設定該值,當節點容器數量比較多的情況下,會報no space left on device。
當然我們的節點不但只有kubelet,還有一些agent,比如日誌收集agent,監控agent都會watch 檔案,也會用到inotify的watch數量。
fs.inotify.maxuserwatches = 24576
容器執行緒數過多的問題
業務容器化後,執行時執行緒數暴漲,後來分析後,根本原因是很多框架或第三方庫,都是通過Runtime.getRuntime().availableProcessors()獲取CPU核數來計算執行緒數,很悲劇的是,JDK 1.9以下版本都只能獲取物理機的核數,這樣導致執行緒數超多,由於容器的CPU資源受限,因此這麼多執行緒數,導致Context Switch增大,從而消耗CPU和影響效能。
發現JDK已經有個bug跟進JDK-6515172。但業務大部分都是JDK7 和 JDK8的。我們使用了libsysconfcpus去攔截_SC_NPROCESSORSCONF和 _SC_NPROCESSORSONLN 系統呼叫,返回容器分配的CPU值。
在容器啟動指令碼 export LDPRELOAD="/usr/local/lib/libsysconfcpus.so:$LDPRELOAD",CONTAINERCORELIMIT是Noah雲平臺上每個容器都有設定的容器CPU Limit值。
if [ "x$CONTAINER_CORE_LIMIT" != "x" ]; then LIBSYSCONFCPUS="$CONTAINER_CORE_LIMIT" export LIBSYSCONFCPUS fi export LD_PRELOAD="/usr/local/lib/libsysconfcpus.so:$LD_PRELOAD"
系統呼叫:
從Kubernetes 1.6.4升級到1.9.8版本後遇到的問題
我們用的CentOS的核心版本是3.10.0-862.9.1。 但升級到1.9.8後,容器銷燬會導致cgroup memory沒有釋放,最終導致啟動新容器時報“no space left on device”。
根本原因是1.9.8預設打開了OS的Kernel Memory,而我們用的核心版本的Kernel Memory是不穩定的。回想起問題定位過程,真的非常艱鉅,連續幾天披星戴月啊……當然我們也總結了踩坑過程:《 Kubernetes 1.9與CentOS 7.3核心相容問題 》。
解決方法:
升級核心到4.x版本風險大,最後折衷,通過修改了Kubernetes一行程式碼,暫時關閉Kernel Memory功能,暫時解決這個問題。
我們在Kubernetes GitHub上提的 issue 61937 ,然後發現很多人都遇到相同的問題。
On The Way
這裡說說我們正在做和準備做的一些事情,希望能引起一些大家的討論。
CRD/Operator的應用
Operator是由CoreOS開發的,用來擴充套件Kubernetes API,特定的應用程式控制器,它用來建立、配置和管理複雜的有狀態應用。有些叢集需要一些特殊操作才能構建起來,如etcd,Redis Cluster,Kubernetes提供的StatefulSet不能滿足這些需求。因此Kubernetes提供了CRD(自定義控制器)的方式,讓我們可以擴充套件,其中Operator就是一系列應用程式特定的自定義控制器。
Noah雲平臺使用Operator技術,構建了redis cluster,mysql cluster供測試環境使用。
Operator構建方式:
- 使用 Kubebuilder 來構建Operator framework。
Operator的工作流程:
- Operator與其他controller manager工作原理一樣,以leader模式執行。(建議每個Kubernetes叢集部署3個Operator)
- Operator使用Informer元件,監聽資源狀態。當資源發生變化時,根據事件型別呼叫對應的callback函式。
- Operator的任務是使Custom Resource的狀態,和Spec定義保持一致。
- Custom Resource以json的格式儲存在Kubernetes etcd中
籃色部分為Kubernetes已提供的開發元件,紅色部分即我們需要實現的模組:
基於Local Stroage的容器排程
MySQL Operator的儲存是使用了分散式Ceph儲存,效能方面不能滿足生產需求,DBA希望MySQL容器可以使用本地的SSD,因此我們需要基於Local Stroage的容器排程。
Kubernetes 1.9提供了VolumeScheduling,就是基於Local Storage的排程。
CheckVolumnBinding的邏輯:
- 已繫結PVC:對應PV.NodeAffinity需匹配候選Node,否則排除該節點
- 未繫結PVC:該PVC是否需要延時繫結,如需要,遍歷未繫結PV,其NodeAffinity是否匹配候選Node,如滿足,記錄PVC和PV的對映關係到快取bindingInfo中,留待節點最終選出來之後進行最終的繫結
容器本地rebuild & 容器固定IP
有些業務希望容器能夠在本地做patch重啟,比如第一次釋出根據排程規則把容器部署到不同的節點上,後面的新版本釋出,他們希望新容器能夠在以前的節點上重啟容器。該需求的目的是容器與物理機相對固定,業務就可以做一些事情,比如一些降級檔案可以只下載一次,不需要每次釋出都下載一次降級檔案(降級檔案比較大),還有一些目的是加快容器啟動速度,鎖定容器資源,重用資料卷等。
當然容器本地rebuild會喪失容器排程的能力,因此只會對某些域開放。
容器本地rebuild實現後,容器的IP相對就固定了,因為patch容器的時候,Kubernetes pause容器沒有被重啟,只重啟業務容器,因此容器的IP是不變的。我們在此基礎上,結合我們容器網路拓撲的特殊性(因為閘道器下沉到機櫃上,所以容器IP與機架必須對應),開發了容器固定IP。
我們也是使用Operator框架,開發了ReusableSet Controller。
容器本地rebuild:
結束語
以上是唯品會Noah雲平臺在總體架構,它構建與開源的生態框架,但又做了一些二次開發來滿足唯品會雲平臺的需求,本文通過雲平臺標配功能的實現細節,服務註冊發現實現原理,資源優化方法,容器隔離方案,容器高可用性方案,容器網路方案,一些實現的小技巧和解決過的問題等維度做了比較詳細的介紹,希望這些方案和實現細節,能幫助大家在實現自己的雲平臺有所幫助。
Q&A
Q:灰度釋出時,兩個應用前要加負載均衡嗎?
A:我在服務註冊發現章節提到唯品會有自研的服務化框架,是通過服務化框架的Proxy做LB的,LB是服務治理的一個重要功能。對於HTTP服務,最後還是註冊到HAProxy的,因此還是通過它做LB的。
Q: 有狀態的服務比如IP固定,不知道你們有沒有這種服務,是怎麼解決的?
A:我們是有寫有狀態的服務,如Redis和MySQL,是通過CentOS Operator框架,自己編寫Operator解決的。固定IP我們正在開發中,因為要結合唯品會的網路拓撲,實現起來稍微複雜點。還有,我們在做的rebuild方案,IP也是相對固定的,如果沒有觸發Kubernetes的scheduler排程的話,比如node evict。
Q:請問,外部請求如何路由到Kubernetes叢集內,是使用的Ingress嗎?
A:外部流量的接入,唯品會有VGW的Gateway,通過APP上的智慧路由找到最優機房的VGW,然後一層一層到容器。
Q:超配的情況下,如果各個pod load都增大,驅逐策略是怎樣的?
A:這裡我沒有講細,你的問題很仔細啊,贊,我們開發了熱點遷移容器的API,監控系統如果收到告警(比如CPU過高,IO過高),會呼叫我們API ,我們API獲取實時的監控資料,根據某個演算法,遷移走部分熱點容器。
Q:自動縮容的時候是如何選擇Pod,如何保證資料不丟失呢?
A:自動縮容之針對無狀態應用的,而且我們要求所有上雲平臺的應用,都支援Graceful Shutdown,由業務保證。
Q:Tomcat類應用容器Xmx記憶體分配多少比例合適,就是Xmx使用百分多少容器記憶體合適?
A:JVM記憶體的計算包括了Heap+Permgen+執行緒數的stack(1M/per執行緒)+堆外記憶體,所以我們監控容器的RSS資料,這是容器真實的記憶體佔用。
Q:叢集空閒率多少合適?我們的叢集超過60%上面的容器就不穩定了。
A:我們為了提高資源利用率,做了很多事情,上面有說到,你說的60%就不穩定,需要具體分析下,因為我們也踩過一些Kubernetes和Docker的坑,同時也需要優化好系統引數,有時候問題也跟核心版本有關。
以上內容根據2018年9月18日晚微信群分享內容整理。分享人 王志雄,唯品會雲平臺架構師,參與工作15+,其中10多年在億訊(中國電信),愛立信參與電信領域產品開發研究工作,4年前加入唯品會基礎架構部,主要負責服務化平臺(唯品會OSP)的研發和推廣落地工作,OSP現已經是唯品會主流的服務化框架。17年開始雲平臺產品相關工作,現是唯品會雲平臺架構師,主要負責唯品會Noah雲平臺的產品研發和推廣落地工作,Noah雲平臺已經接入了大部分核心域和其他業務域,並順利承載了公司的多次大促 。DockOne每週都會組織定向的技術分享,歡迎感興趣的同學加微信:liyingjiesd,進群參與,您有想聽的話題或者想分享的話題都可以給我們留言。