王成昌,,唯品會PaaS平臺高級開發(fā)工程師 主要工作內(nèi)容包括:平臺DevOps方案流程優(yōu)化,持續(xù)部署,,平臺日志收集,,Docker以及Kubernetes研究。 大家好,,我是唯品會PaaS團隊的王成昌,,與大家分享一下PaaS在Kubernetes的實踐?;?014年底或2015年初PaaS沒有推廣的現(xiàn)狀,,唯品會PaaS部門目前已經(jīng)做了兩年的時間。 PaaS 主要工作將分為三個部分進行介紹,,首先,,PaaS定義的標準構(gòu)建流程,持續(xù)集成和持續(xù)部署的架構(gòu)以及已有組建上的功能定制,;第二部分,,基于Kubernetes實現(xiàn)的網(wǎng)絡(luò)方案,以及根據(jù)網(wǎng)絡(luò)方案做的擴展定制,;第三部分,,PaaS如何做日志收集和監(jiān)控方案,最后列一下唯品會目前為止所遇到的問題和總結(jié),。 唯品會目前線上有一千多個域,,每個域之間相互的依賴比較復(fù)雜,每次的部署發(fā)布困難,。線下有多套的測試環(huán)境,,每套測試環(huán)境都要去維護單獨的應(yīng)用升級和管理。公司層面也沒有統(tǒng)一的持續(xù)集成和部署的流程,,大家各自去維護一個Jenkins或者一個Jenkins slave,,看工程師的個人追求是否能夠?qū)懸粋€完整的從源代碼、到打包,、最后到部署的腳本,。 唯品會線上全部用物理機在跑,,之前Openstack方式?jīng)]有在線上,只是在測試環(huán)境跑,,物理機的使用效率還是比較低的,。即使在7周年大促的高峰時段,60~80%的物理機利用率也均低于10%,。 基于前面提到的現(xiàn)狀,,唯品會的PaaS定義了一個構(gòu)建流程,整個流程不是一蹴而就,,這是目前為止的定義,,首先從源代碼的角度出發(fā),即Git,,所有的7個Phase全部包括在Jenkins Pipeline里,,由于是基于Kubernetes,所以Jenkins Pipeline的執(zhí)行是通過Jenkins k8s Plugin去調(diào)度后臺的k8s Cluster,,由k8s產(chǎn)生的Pod去運行Pipeline,。整個Pipeline的幾個階段,除了傳統(tǒng)的編譯單元測試和打包之外,,加入了烘焙鏡像,、部署以及集成公司的集成測試(即VTP),打包和鏡像完成后會正常上傳到公司統(tǒng)一的包管理系統(tǒng)Cider和平臺維護的Docker registry,。 部署完成后會觸發(fā)集成測試,,如果通過測試的話,會把這個包或者是鏡像標記為可用的狀態(tài),,一般先從測試環(huán)境標記,,然后通過到staging環(huán)境。目前PaaS 平臺主要是維護測試環(huán)境和staging環(huán)境,,線上還沒有,,但是已經(jīng)定義了一個審批的流程,如果標記了這個包為可用的狀態(tài),,需要一個審批來決定它是否可以上線,。部署后通過k8s client,,由另外一套k8s的集群來管理部署里面所有的節(jié)點,。 這是唯品會的PaaS架構(gòu),主要包含持續(xù)集成和持續(xù)部署,。首先由一個統(tǒng)一UI的入口Dashboard,,使用Nginx和Tomcat作為服務(wù)的網(wǎng)關(guān)。其背后有兩套系統(tǒng)——CPMS和API server,,CPMS主要管理持續(xù)集成的各個流程,,API server主要管理應(yīng)用部署,,在CPMS背后是使用多個Jenkins server統(tǒng)一連到一個Kubernetes集群上產(chǎn)生Pod作為Jenkins slave去運行,不同的構(gòu)建有多種語言也有不同的模板,,這里會提供各種方案讓不同的Jenkins Pipeline運行在不同的Kubernetes node里面,。 在部署實現(xiàn)一個Cloud Framework,可以接入各種cloud provider,,目前使用的是k8s provider,,背后的服務(wù)發(fā)現(xiàn)也是k8s推薦使用的Skydns。為了兼容公司基于包發(fā)布的這樣一套模式,,鏡像管理這部分會把包管理系統(tǒng)Cider接入進入,,平臺的Docker Registry,以及應(yīng)公司安全方面的要求,,通過Clair對鏡像的內(nèi)容進行檢查,。 在日志收集方面,使用fluentd+ELK的組合,,采用Prometheus做監(jiān)控,。在PaaS架構(gòu)里,安全是通過接入公司的CAS做認證的動作,,有一個Oauth組件做鑒權(quán)機制,,通過Gnats做消息傳輸?shù)南到y(tǒng)。配額的問題在構(gòu)建和部署中都會有所體現(xiàn),,包括用戶對于Pipeline的個數(shù)控制或者Pipeline觸發(fā)的個數(shù),,以及對應(yīng)用上的物理配額或者邏輯資源配額等。 Docker Registry改造,,主要在Middleware做了一些工作,,做了一個接入公司的CAS和Oauth做的驗證和授權(quán)。也接入了當(dāng)有新的鏡像Push進來的時候,,會自動觸發(fā)應(yīng)用的部署,。Docker Registry本身對所有的repository不同的tag索引還是比較慢的,所以會針對push進來所有的鏡像信息存入數(shù)據(jù)庫做一個索引,,方便查找鏡像,。用戶行為記錄主要針對pull和push的動作,把它記錄下來,。鏡像安全通過接入Clair做掃描,,push鏡像layer完成之后在push鏡像的manifest時,會將鏡像layer信息發(fā)送到Clair做鏡像的安全掃描,。 原來的Jenkins Kubernetes Plugin,,默認把Jenkins slave調(diào)度在所有Kubernetes node上,我們做了一個node selecter,,針對不同的Pipeline類型,,需要跑在不同的節(jié)點上,。調(diào)度上加入了node selecter,此外在每個Pipeline要去run的時候申請資源,,加入了資源的request limit,,防止單個的Pipeline在運行的時候占用過多的資源,導(dǎo)致其他的應(yīng)用或者是構(gòu)建任務(wù)受影響,。在掛載方面,,像傳統(tǒng)的maven項目在下載過一個包之后,如果是同一個主機上會把.m2文件會掛載在主機上,,不同的Jenkins Pool在跑的時候,,可以共享已經(jīng)下載過的資源文件。 最后,,實現(xiàn)了Container slave pool的策略,。當(dāng)要run一個Pipeline的時候,每次告訴k8s要起一個Jenkins slave Pod,,當(dāng)需要執(zhí)行一個job的時候,,等待時間比較長,這里會定一個池的策略,,就是一個預(yù)先準備的過程,,當(dāng)有一個新的任務(wù)要run的時候,立刻就可以拿到一個可用的containerslave,。 這是PaaS的功能點,,包含三個主要的部分,構(gòu)建,,部署和測試集,。構(gòu)建是關(guān)于用戶定義Pipeline以及對Pipeline觸發(fā)的record的管理,以及Pipeline各個phase的管理,。部署主要對應(yīng)用配置的管理,,這個應(yīng)用包括服務(wù)的配置如何、資源的申請如何,,以及應(yīng)用實例的一些管理,。測試集對接公司的集成測試環(huán)境,和平臺的應(yīng)用進行關(guān)聯(lián),。 空間管理和鏡像管理,,空間主要提供不同的隔離空間,提供應(yīng)用快速的復(fù)制,,比如你有一個測試環(huán)境,,我也有一個測試環(huán)境,為了大家環(huán)境之間相互不干擾可以提供應(yīng)用的快速復(fù)制,。鏡像管理主要分三種,,即平臺提供的基礎(chǔ)鏡像,業(yè)務(wù)部門一些特殊的需求會基于基礎(chǔ)鏡像做一些定制,,以及具體業(yè)務(wù)鏡像,。 PaaS采用的網(wǎng)絡(luò)方案,網(wǎng)絡(luò)方案最開始直接使用的k8s 1.0的版本加flannel的一套工作模式,,后來由于業(yè)務(wù)需求,,用戶需求能夠直接訪問到實例IP,而flannel當(dāng)時是封閉的子網(wǎng),。目前采用Contiv這套網(wǎng)絡(luò)模式,,由公司統(tǒng)一分配Pod的IP網(wǎng)段。這里做了一個kube-HAProxy,,替換了節(jié)點上kube-proxy這個組件,,用kube-HAProxy來做Service IP到end point的一個轉(zhuǎn)發(fā)。 在kube2sky,,完成域名和服務(wù)IP的注冊,。傳統(tǒng)的模式下,域名是短域名,,Service的名字作為短域名,,還有Service本身的IP會注冊到Skydns上。這里做了一些定制,,因為公司的應(yīng)用比如兩個業(yè)務(wù)域A和B都有本身的域名——a.vip.com和b.vip.com,,A如果要訪問B,不能讓這個訪問跑到線上或者其他環(huán)境去,,于是通過kube-sky去解析規(guī)則,,把b.vip.com加入到里面,再加一個subdomain作為擴展的domain search,,最終找到平臺內(nèi)部部署的B域,。 goroute 主要是平臺內(nèi)部的應(yīng)用,每個應(yīng)用都會提供一個平臺的域名,,這個域名主要是有一個組件叫做state aggregator,,會watch k8s apiserver發(fā)出來的Service和end point的變化,最終通過Service的名字和end point的地址,,把它寫到gorouter的route注冊表信息中,,當(dāng)我們訪問平臺域名時就可以找到真正的end point地址。這里也有定制,,采用HAproxy和KeepAlived替換了kube-proxy,,之前從Service IP到end point IP的轉(zhuǎn)化,通過每個節(jié)點部署的kube-proxy,它會檢測到 Service和end point的變化,,去寫IPtables的規(guī)則,,來找到最終end point的地址的IP。 現(xiàn)在統(tǒng)一使用的HAproxy加上KeepAlived,,有一個kube2HAproxy組件,,功能和kube-proxy前面一部分相似,都要watch kube-apiserver的Service和 end point的event來動態(tài)的生成一個HAproxy最新的配置,。KeepAlived主要為了高可用,。有一個值得注意的細節(jié),kube2HAproxy所在機器的IP,,要和Service IP的網(wǎng)段在同一個網(wǎng)段里,,用戶在訪問真正的應(yīng)用的時候直接使用Service IP是公共可見的,而不是隨便定義的Service IP,。 對外應(yīng)用訪問是由平臺提供的域名,,后綴均為*.PaaS.vip.com,解析到之后會有公司的DNS統(tǒng)一轉(zhuǎn)發(fā)到gorouter這臺機器上,,因為gorouter會監(jiān)聽到Service和end point的變化,,route表里面會存儲每個域名對應(yīng)的end point的地址,通過round robin的方式找到最終的Pod來完成http訪問,。 最后一個定制關(guān)于Pod的IP固定,,為什么要做PodIP固定?因為之前的測試環(huán)境很多應(yīng)用都是部署在VM甚至在物理機上,,IP都是固定的,,有一些應(yīng)用是需要白名單訪問的,應(yīng)用在這個部署機上,,需要將IP提供給相應(yīng)的調(diào)用方或者是公司的某個部門來告訴他加入白名單,。Docker 默認情況下,每次銷毀和重建的過程中,,IP都會隨機申請和釋放,,所以IP有可能變化。IP固定主要在k8s apiserver做,,加了兩個對象,,即Pod IP Allocator和IP recycler,Pod IP Allocator是一個大的Pod的網(wǎng)段,,可以認為它是Pod的IP池,, IP recycler主要記錄一些臨時回收的IP,或者叫臨時暫存區(qū),,IP不是一直存在,,否則是一種IP浪費,,有一個TTL時效性的。 當(dāng)應(yīng)用重新部署的時候,,原本的Pod會被刪掉,,刪掉的過程中會先放在IP recycler中,當(dāng)一個新的Pod啟動的時候,,會通過namespace+RC name的規(guī)則去找是否有可用的IP,,如果找到優(yōu)先用這樣的IP記錄在Pod里,,這個Pod對象最終會交由kubelet去啟動,,kubelet 啟動的時候會讀取這個Pod IP,然后告訴Docker 啟動的IP是什么,。最終有新的Pod啟動之后,,它的IP是之前已經(jīng)被銷毀的Pod IP,達到的效果就是Pod IP固定,。在kubelet因為修改了Pod對象的結(jié)構(gòu),,增加了Pod IP記錄使用IP的情況,根據(jù)Pod的IP告訴Docker run的時候執(zhí)行剛剛的IP來啟動,。kubelet 在刪除Pod的時候會告訴k8s去release這個IP,。 日志收集主要分三種類型:首先是平臺自身的服務(wù)組件的收集,比如像jenkins,、Docker 或者Kubernetes 相關(guān)組件的日志收集,,另一個是所有部署在平臺里面應(yīng)用的收集,最后還有一些域,,因為公司一些已有系統(tǒng)(dragonfly)也是做日志收集和監(jiān)控的,,有一些特定的規(guī)則對接公司。 平臺自身日志收集的規(guī)則,,包含系統(tǒng)組件還有平臺應(yīng)用兩種設(shè)計,。系統(tǒng)組件比較簡單,無外乎通過systemd或者是指定日志文件的路徑做日志的收集,,應(yīng)用收集主要在k8snode上,,k8s會把每一個Pod日志link在一個特定的文件路徑下,因為Docker會記錄每一個容器的日志,,可以從這個地方讀取應(yīng)用的日志,,但是只拿到namespace和Pod name這樣的結(jié)構(gòu),我們會通過fluentd里的filter反向去k8s拿Pod所對應(yīng)的meta data,,最終發(fā)送到kafka,,通過logstash達到elastic search。 Kibana的展現(xiàn)做了一些定制,,因為平臺的展現(xiàn)主要基于namespace和應(yīng)用名稱的概念查看日志的,,定制能夠展現(xiàn)特定的namespace下的特定應(yīng)用的日志,同時把自定義的告警加在了這里,因為告警是通過elastalert來做的,,在Kibana上做一個自定義告警的UI入口,,由用戶來指定想要監(jiān)聽什么樣的日志內(nèi)容的告警,去配置監(jiān)聽的間隔或者出現(xiàn)的次數(shù),,以及最終的郵件接收人,。 有一個組件是當(dāng)用戶創(chuàng)建了自定義告警的規(guī)則時會發(fā)送到后面的elastalert ruler,ruler解析前臺UI的信息,,生成elastalert能夠識別的configure文件,,動態(tài)地讀取configure的加入。 對接公司的業(yè)務(wù)系統(tǒng)比較簡單,,特定的收集規(guī)則都是特定的,,比如具體的目錄規(guī)則,一般來講都是通過掛載容器的目錄到主機目錄上,,在主機上統(tǒng)一部署一個agent去run,,主要體現(xiàn)在k8s node上,所以仍然使用Daemonset的方式去跑agent,。 監(jiān)控有兩種,,一種是對單個Pod的實例監(jiān)控,在頁面上是可以直接看這樣的實例的,,單個Pod是一個應(yīng)用實例了,,然后通過node agent去包裝了cAdviser,前端去統(tǒng)一訪問,,獲取對應(yīng)的CPU和Memory使用信息,。cAdviser對Network收集到的數(shù)據(jù)是不正確的,通過node agent 讀取Linux file獲取Network的信息,。Websocket Server 是為了提供網(wǎng)頁上直接對容器進行網(wǎng)頁控制臺的登錄,。 另一個是看整個的應(yīng)用,因為應(yīng)用是有多個實例的,,通過Graphana去定制,,去展現(xiàn)namespace的應(yīng)用,有多個實例,,就把多個實例的監(jiān)控都展現(xiàn)出來,。此處有一個promethus plugin的定制,之前有一些Swarm的節(jié)點加入,,持續(xù)部署提到過它是一個多個cloud framework都可以接入的,。唯品會接入了一些Swarm的信息,針對Swarm創(chuàng)建的容器的話,,也要能夠監(jiān)控到它的容器監(jiān)控信息的數(shù)據(jù),。在Promethus plugin通過Docker info獲取不同的Swarm node的信息,,在每個Swarm node上部署cAdviser,獲取由Swarm創(chuàng)建的容器的監(jiān)控信息,。 遇到的問題非常多,,到現(xiàn)在為止將近兩年的時間,有很多都是可以在GitHub找到的問題,,以及通過升級可以解決的問題,。最開始采用的是Docker 1.6以及Kubernetes 1.0,中間經(jīng)歷兩次的升級,,現(xiàn)在主要使用Docker 1.10和Kubernetes 1.2,。 Devicemapper loopback 性能問題 production使用direct-lvm做Devicemapper存儲。 Pod實例處于 pending狀態(tài)無法刪除 目前發(fā)現(xiàn)一些Pod一直處于pending的狀態(tài),,使用kubectl或者Kubernetes API沒有辦法直接對Pod做任何操作,,目前只能通過手動Docker的方式刪除容器,。 k8s 僵死容器太多,,占用太多空間 k8s僵死容器是以前碰到的問題,現(xiàn)在最新版的Kubelet是支持這樣的參數(shù),,允許每一個節(jié)點上最大僵死容器個數(shù),,交由Kubelet自己做清理的工作。 Kubernetes1.1.4上ResourceQuota更新比較慢 因為以前發(fā)現(xiàn)用戶來告訴配額不足的時候,,調(diào)整配額并不會立馬的生效,,升級以后就沒有這種問題了。 k8s batch job 正常退出會不斷重啟 這個是應(yīng)用的問題,,不是傳統(tǒng)理解的跑一次性任務(wù),,k8s batch job要求一定要exit 0。也有一些Job的類型,,目前是直接對應(yīng)的背后k8s的Pod方式,。 Skydns ping get wrong IP 這是最新遇到的問題,上下文的話,,稍微有一點復(fù)雜,,有兩個應(yīng)用部署在唯品會的平臺上,每個應(yīng)用都有自己的legacy域名,,比如有一個域名叫user.vip.com,,另一個叫info.user.vip.com,這時候ping user.vip.com,,有可能會拿到info.user.vip.com的IP,,由于kube-sky本身在寫Skydns record時候有問題,所以會添加一個group做一個唯一性的識別,,這樣在ping子域名(user.vip.com)的時候就不會讀到info.user.vip.com,。Skydns本身目錄結(jié)構(gòu)的問題,,加上group就不會再去讀到下面的子路徑。 Overlayfs issue: can’t doing mv operation 唯品會一直使用Devicemapper,,中間嘗試一些節(jié)點用Overlayfs的存儲,,但是在目錄操作時會file not found,官方也有一個issue說當(dāng)Overlayfs 在不同的layer的時候,,牽扯到刪除的操作,,會出現(xiàn)這個問題??梢陨壗鉀Q,,但是系統(tǒng)是固定的,升級很麻煩,,所以沒有做升級而是切回了Devicemapper,。 Devicemapper空間滿,需要改造cAdvisor監(jiān)控容器空間使用 由于容器占用的磁盤空間過多,,導(dǎo)致了整個k8s node的磁盤空間被占滿的問題,,唯品會是在Cadviser做一些改造,可以監(jiān)控到每一個容器所占用的磁盤空間,,對磁盤空間做一些限制,。 Kubernetes namespaces stuck in terminating state 在刪除一個namespace的時候,namespace是可以動態(tài)創(chuàng)建和刪除的,,在刪除的時候會看到namespace一直處于terminating的狀態(tài),,這個在GitHub上有一些解決方法,因為它本身是由于namespace的finalizer會進入一個死循環(huán),,有一個work around,,可以手動的置空finalizer,把這個namespace update回去,,就可以正常刪除了,。 以上就是我要分享的主要內(nèi)容,謝謝大家,。 |
|