Mesos在去哪兒網的實踐之路
背景
業務線開發環境的困擾
年初的時候機票的同事向我們反饋,希望可以提供Docker環境幫助他們快速構建開發環境,加速功能的迭代。正好我們OpsDev團隊也在為容器尋找試點,雙方一拍即合,立即開始了前期的調研工作。
隨著交流的深入,我們發現對于一個包含了幾十個模塊,快速迭代的系統,開發團隊想要建立一個相對穩定的,能覆蓋周邊模塊的開發和自測環境是非常困難的,除了要申請虛擬機外,還要新增profile,創建jenkins job,發布,服務依賴等一系列的流程。
即使解決了以上問題,運維這套環境又是個大麻煩:項目之間的依賴關系寫在配置文件中,切換環境時需要手工修改;多套不同版本的環境維護起來費時費力;對于涉及面較廣的聯調,需要其他組的同事配合完成,更不用說這些模塊間的版本如何有效的保證一致了。。
整理問題
經過多次的討論和調研,最終雙方團隊確認出幾個業務線最關心的功能,優先解決:
- 版本一致,即代碼版本,配置版本和數據庫schema一致,減少聯調時不必要的適配和調整。
- 快速切換多套環境。
- 服務依賴,開發新人也可以輕松部署整套復雜的環境。
- 維護簡單,例如新增項目時,自動加入到整套環境中。
- 低學習成本,節約時間去開發業務。
- 環境隔離,最好每個人一套完整環境,不互相影響。
暫時性解決0和1的問題
業務線的同事用docker-compose臨時搭建了一套開發環境,但是需要手工維護版本以及nginx的轉發,同時也暴露出了更多的問題:
- 能支撐如此多模塊的compose,只能是實體機,資源限制較大。
- 擴容模塊時的端口沖突問題。
- 數據庫持續集成
- 容器固定IP
需要找到一個治標又治本的方案解決業務線的問題。
尋求解決之道
參考了現有的容器集群方案后,最終焦點集中在了Apache Mesos(后簡稱Mesos)和Google Kubernetes上。Kubernetes的pod和service概念更貼近業務線的訴求,同時,Mesos在資源管理和調度靈活性上顯然經得起生 產的考驗。最終團隊決定兩者并行測試,在各自的優勢方向尋找試點項目做驗證。
項目試點
仔細考量后,我們選擇了基于ELK構建的日志平臺作為驗證Mesos + Docker的切入點,積累相關的開發和運維經驗。
圖一 典型的Mesos + Docker結構(source from google)
首先容器化的是Logstash和Kibana,Kibana本身作為ElasticSearch的數據聚合展示層,自身就是無狀態化 的,Logstash對SIGTERM有專門的處理,docker stop的時候可以從容處理完隊列中的消息再退出。而ElasticSearch部署在Mesos集群外,主要考慮到數據持久化的問題以及資源消耗。采用 Marathon和Chronos調度Logstash和Kibana,以及相關的監控、統計和日志容器。
圖二 日志平臺的結構
數據來自多種方式,針對不同的日志類型,采取不同的發送策略。系統日志,比如mail.log、sudo.log、dmesg等通過rsyslog 發送。業務日志采用flume,容器日志則使用heka和fluentd。匯總到各個機房的Kafka集群后,粗略的解析后匯總到中央Kafka,再通過 Logstash集群解析后存入ElasticSearch。同時,監控數據通過statsd發送到內部的監控平臺,便于后續的通知和報警。
隨著業務線日志的逐步接入,這個平臺已經增長成為單日處理60億條日志/6TB數據的龐大平臺。
問題和經驗總結
1. Daemon OOM
最初我們使用的Docker版本是1.6,docker attach接口存在內存泄露,容器的stdout輸出較多日志時,比較容易造成daemon的OOM。
{code} fatal error: runtime: out of memory runtime stack: runtime.SysMap(0xc2c9760000, 0x7f310000, 0x7f453c96b000, 0x13624f8) /usr/local/go/src/runtime/mem_linux.c:149 +0x98 runtime.MHeap_SysAlloc(0x1367be0, 0x7f310000, 0x43b8f2) /usr/local/go/src/runtime/malloc.c:284 +0x124 runtime.MHeap_Alloc(0x1367be0, 0x3f986, 0x10100000000, 0x0) /usr/local/go/src/runtime/mheap.c:240 +0x66 ...... {code}
這個問題是比較嚴重的,daemon掛掉后容器跟著都宕機了,雖說上層的Marathon會重新部署應用,但是頻率較高的話容易造成集群不穩定。
首先想到的辦法就是用runsv啟動daemon,保證進程宕掉后可以重新被拉起。其次,參考了Kubernetes的做法,在daemon啟動后修改oom_adj的值為-15,防止daemon被最先kill掉。
最治標的辦法還是升級Docker的版本,或者自己patch這個bug(https://github.com/docker/docker/issues/9139)。
2. Heka的DockerEventInput不釋放socket
DockerEventInput使用的go-dockerclient有bug,heka異常推出后不會關閉socket,容易導致文件句柄泄露,最終導致daemon不再接受任何命令,這個BUG在v0.10.0b1仍然還存在。
{code} time="2015-09-30T15:25:00.254779538+08:00" level=error msg="attach: stdout: write unix @: broken pipe" time="2015-09-30T15:25:00.254883039+08:00" level=error msg="attach: stdout: write unix @: broken pipe" time="2015-09-30T15:25:00.256959458+08:00" level=error msg="attach: stdout: write unix @: broken pipe" {code}
相關問題:https://github.com/fsouza/go-dockerclient/issues/202。
3. 對新加入集群的slave“預熱”
同在局域網內,第一次下載鏡像也是比較慢的,推薦在slave部署完畢后,主動pull一批常用的鏡像,減少第一次啟動的時間。這個工作我們放在 salt、ansible腳本里自動部署。另外,對于基礎監控類的容器,Marathon目前還未支持自動scale,需要自己實現。
相關討論:https://github.com/mesosphere/marathon/issues/846
4. Distribution引起的daemon宕機
升級1.7.1后發現的問題,起因是一個手誤導致Marathon的配置沒有帶上自己的registry,daemon去pull了官方的鏡像。這 個坑幸好發生在我們的registry準備遷移V2的之前,相關的代碼還沒有patch到我們自己的docker上,暫時還是使用V1。
相關問題:https://github.com/docker/docker/issues/15724
(點擊放大圖像)
5. Mesos的資源搶占
資源搶占是在Mesos 0.23.0版本引入的,官方還不建議在生產環境使用,如何有效的搶占資源一直是我們在使用過程中比較關注的。
Mesos的資源是直接映射到role上的,我們以此為切入點,提前劃分多個role,每個role分配靜態資源。比如,ops的role運行基礎 服務,每個slave上最多占用4個CPU,logstash則在每臺機器上可以占用32個CPU,以這種方式變相超售CPU資源。
MESOS_resources="cpus(logstash):32;" MESOS_resources="${MESOS_resources}cpus(common):4;" MESOS_resources="${MESOS_resources}cpus(kibana):4;" MESOS_resources="${MESOS_resources}cpus(ops):4;" MESOS_resources="${MESOS_resources}cpus(spark):16;" MESOS_resources="${MESOS_resources}cpus(storm):16;" MESOS_resources="${MESOS_resources}cpus(rebuild):32;" MESOS_resources="${MESOS_resources}cpus(mysos):16;" MESOS_resources="${MESOS_resources}cpus(others):16;" MESOS_resources="${MESOS_resources}cpus(universe):1;" MESOS_resources="${MESOS_resources}cpus(test):8;" MESOS_resources="${MESOS_resources}mem(*):126976;ports(*):[8000-32000]"
在使用時,不再根據容器的資源使用情況動態調整實例數量,而是交替發布任務搶占CPU。比如凌晨2點至6點是業務低峰,日志量少,許多logstash容器并未滿負荷工作,正適合發布Spark的job。這種調度方式實現簡單,基于時間調度,更容易監控。
缺點也是顯而易見的,需要提前規劃role,盡量對每種資源消耗大戶都分配到一個對應的role,擴展性較差,適合上層應用較穩定的系統。等MESOS-3791合并后,就可以動態的管理role,那么Mesos的資源的管理就會更加靈活了。
6. 版本升級
主要是Mesos、Docker的版本升級,由于眾所周知的原因,Docker的升級是比較痛苦的,需要停止所有的容器后再升級daemon。我們 的線上環境經歷了Mesos 0.22.0到0.25.0,Docker 1.4.1到1.7.1的演進,總結出了一套比較有效的升級策略,上層服務無感知。首先Mesos要開啟白名單(--whitelist)功能:
1) 先將要升級的機器踢出白名單,這一步保證了上層的Framework在收到statusUpdate不會調度到這臺機器上;
2) 然后逐個stop容器,容器內的應用建議處理SIGTERM信號做清理工作;
3) 接著停止docker daemon和mesos slave;
4) 升級docker和mesos版本;
5) 重啟docker和mesos并將機器重新加入到白名單。
開發環境快速rebuild
有了日志平臺的經驗,我們的工作中心開始向實際需求傾斜,盡快滿足業務線的環境要求。共經歷了三次比較大的變更,主要從兼容性,公司內的發布流程和開發人員易用性的角度考量,逐步演進:
1) OpenStack + nova-docker + VLAN
2) Mesos + Marathon + Docker(--net=host) + 隨機端口
3) Mesos + Marathon + Docker + Calico
第一階段:容器當作虛擬機用
容器的使用和行為盡量模擬虛擬機是我們第一階段考慮的重點,同時還要考慮到發布系統改造的成本,OpenStack提供的nova-docker自 然成了首選。再此基礎上,為容器提供外部可訪問的獨立IP(VLAN)。nova-docker和nova-network已經提供了大部分功能,整合的 速度也比較快。
容器啟動后會有多個進程,比如salt-minion和sshd,這樣使用者可以ssh到容器內debug,而部署的工作則交給salt統一管理。
第二階段:以服務為核心
逐漸強化以服務為核心的應用發布和管理流程,向統一的服務樹靠攏。在第一階段的成果的基礎上,完善服務樹的結構和規則,為后面打通監控樹,應用樹等模塊做好充分的準備。
(點擊放大圖像)
同時,容器開始從OpenStack + nova-docker的結構向Mesos + Marathon + Docker遷移,整套環境的發布壓縮到了7~9分鐘,其中還包含了healthcheck的時間,還有深入優化的空間。
- 依賴放在QAECI中維護,發布時根據拓撲排序后的結果選擇自動切換并行,串行發布。
- 代碼和配置在容器啟動后再拉取,減少維護鏡像的成本,方便升級運行環境,比如升級JDK或Tomcat。
- 服務端口全部隨機生成,并通過環境變量注入到依賴的容器中并替換配置,這樣就解決了--net=host模式下端口分配的問題。dubbo服務注冊的是宿主機的IP和PORT,如果是bridge模式的話,記得要注冊宿主機的IP和映射的PORT。
- 適當緩存編譯后的代碼,減少重復構建的時間浪費。
- Openresty + lua腳本動態proxy_pass 到集群內的Tomcat,外部即可通過泛域名的方式訪問Marathon發布的應用,例如app1.marathon.corp.qunar.com即可訪問到app1對應的WEB服務。
- 修改logback和tomcat的配置,所有日志都輸出到stdout和stderr,并附帶文件名前綴做區分。并通過heka,配合fields_from_env區分是哪一個Mesos task的日志,統一發向日志平臺匯總和監控。
第三階段
為容器分配固定IP,打通集群內外的服務通信,讓開發人員無障礙的訪問容器。為此我們引入了Calico作為解決方案。Calico整合Mesos比較簡單,通過Mesos slave啟動時指定--modules和-isolation即可使用:
{code} ./bin/mesos-slave.sh --master=master_ip:port --namespaces='network' \ --modules=file://path/to/slave_gssapi.json \ --isolation="com_mesosphere_mesos_MetaswitchNetworkIsolator" \ --executor_environment_variables={“DOCKER_HOST”: “localhost:2377”} { "libraries": [ { "file": "/path/to/libmetaswitch_network_isolator.so "modules": [ { "name": "com_mesosphere_mesos_MetaswitchNetworkIsolator", "parameters": [ { "key": "initialization_command", "value": "python /path/to/initialization_script.py arg1 arg2" }, { "key": "cleanup_command", "value": "python /path/to/cleanup_script.py arg1 arg2" } ] } ] } ] } {code}
這樣Mesos在執行Docker命令的時候,所有的請求都被calico容器劫持并轉發給docker daemon,同時給容器分配IP,上層的Marathon只需要額外添加兩個env配置:
- CALICO_IP=auto|ip
- CALICO_PROFILE=test
結合我們自身的網絡結構,我們在交換機上預留了一個IP段,全部指向了calico的兩臺gateway,轉發到Mesos集群內部:
(點擊放大圖像)
同時整合公司內的DNSDB服務,將容器的名稱和IP自動注冊到DNSDB內,這樣全公司的人都可以訪問到這個容器,打通集群內外的通信。對于一些有特殊要求的情況,如開發機的名稱必須符合一定命名規則,通過傳入--hostname就可以模擬一臺開發機。
總結
經過近1年來的使用和運維,在Docker和Mesos上踩了不少的坑,多虧了社區的貢獻者們,積累了許多經驗。Mesos表現出的穩定性、可用性和擴展性足夠擔當生產環境的資源管理者,美中不足的是調度策略略顯單一,依賴上層Framework的二次調度。
后續我們將考慮在第四階段調研Swarm on Mesos,利用Docker公司原生的集群方案配合Mesos的資源管理,為業務線提供更加穩定,便利的容器環境。
來自:http://www.infoq.com/cn/articles/practice-of-mosos-in-qunar