Docker在B站的實施之路
B站一直在關注Docker的發展,去年成功在核心SLB(Tengine)集群上實施了Docker。今年我們對比了各種Docker實施方案后選擇了Mesos。結合CI&CD,打通了整個業務的Docker上線流程,并實現了全自動擴縮容。這次結合我們的實施之路,分享一下遇到的重點與難點:
-
自研Docker網絡插件的介紹;
-
Bili PaaS平臺中的CD實現與優化;
-
應用全自動擴縮容的實現方案;
-
Nginx動態Upstream跟Docker的配合使用。
B站一直在關注Docker的發展,去年成功在核心SLB(Tengine)集群上實施了Docker,規模不大但訪問量大、沒有CI & CD的工作。隨著業務量的增長,應用擴縮需求越來越多。但在沒有Docker標準化的情況下,應用擴容需要擴服務器,操作繁重。同時為了減少因測試、線上環境不一致導致的問題,我們計劃將業務全部Docker化,并配合CI & CD,打通整個業務上線流程,達到秒級動態擴縮容。
下面是我們的實施之路,整體架構圖如下:
為什么選擇Mesos?
Kubernetes太重,功能繁多。我們主要看中Mesos的調度功能,且輕量更易維護。另外和我們選擇了Macvlan的網絡有關。
Docker網絡選擇
Docker自帶的網絡都不能滿足我們的需求。
Bridger:Docker分配私有IP,本機通過bridge跟容器通信。不同宿主機如果要通信需要iptables映射端口。隨著容器的增多,端口管理會很混亂,iptables規則也越來越多。
Host:使用宿主機的網絡,不同容器不能監聽相同端口。
None:Docker不給容器分配網絡,手動分配。
正當我們無法選定Docker的網絡方案時,發現最新的Docker 1.12版本提供了另外兩種網絡驅動:Overlay和Macvlan。
Overlay:在原來的TCP/IP數據包基礎上再封裝成UDP的數據包傳輸。當網絡出現問題需要抓包時,會比較麻煩。而且,Overlay依靠服務器的CPU來解UDP數據包,會導致Docker網絡性能非常不穩定,性能損耗比較嚴重,在生產環境中難以使用。
Macvlan:在交換機上配置VLAN,然后在宿主機上配置物理網卡,使其接收對應的VLAN。Docker在創建Network時driver指定Macvlan。對Docker的Macvlan網絡進行壓測,跑在Macvlan網絡的容器比跑在host網絡的容器性能損失10~15%左右,但總體性能很穩定,沒有抖動。這是能接受的。
基于Macvlan,我們開發了自己的IPAM Driver Plugin—底層基于Consul。
Docker在創建Macvlan網絡時,驅動指定為自己研發的Consul。Consul中會記錄free和used的IP。如下圖:
IPAM Driver在每臺宿主機上都存在,通過Socket的方式暴露給Docker調用。Docker在創建容器時,IPAM Plugin會從Consul申請一個free的IP地址。刪除容器時,IPAM Plugin會釋放這個IP到Consul。因為所有宿主機上的IPAM Plugin連接到的是同一個Consul,就保證了所有容器的IP地址唯一性。
我們用IPAM Plugin遇到的問題:
1) Consul IPAM Plugin在每臺宿主機上都存在,通過Socket方式調用,目前使用容器啟動。
當Docker daemon重啟加載Network時,因為容器還未啟動,會找不到Consul IPAM Plugin的Socket文件,導致Docker daemon會去重試請求IPAM,延長daemon的啟動時間,報錯如下:
level=warning msg="Unable to locate plugin: consul, retrying in 1s" level=warning msg="Unable to locate plugin: consul, retrying in 2s" level=warning msg="Unable to locate plugin: consul, retrying in 4s" level=warning msg="Unable to locate plugin: consul, retrying in 8s" level=warning msg="Failed to retrieve ipam driver for network \"vlan1062\"
解決方案:Docker識別Plugin的方式有三種:
-
sock files are UNIX domain sockets.
-
spec files are text files containing a URL, such as unix:///other.sock or tcp://localhost:8080.
-
json files are text files containing a full json specification for the plugin.
最早我們是通過.sock的方式識別IPAM Plugin。現在通過.spec文件的方式調用非本地的IPAM Plugin。這樣Docker daemon在重啟時就不受IPAM Plugin的影響。
2) 在通過Docker network rm 刪除用Consul IPAM創建的網絡時,會把網關地址釋放給Consul,下次創建容器申請IP時會獲取到網關的IP,導致網關IP地址沖突。
解決方案:在刪除容器釋放IP時,檢測下IP地址,如果是網關IP,則不允許添加到Consul的free列表。
基于以上背景,我們剛開始選型的時候,測試過Docker 1.11 + Swarm 和Docker 1.12集成的SwarmKit。Docker 1.11 + Swarm網絡沒有Macvlan驅動,而Docker 1.12集成的SwarmKit只能使用Overlay網絡,Overlay的性能太差。最終我們采用了Docker 1.12 + Mesos。
CI & CD
對于CI,我們采用了目前公司中正在大量使用的Jenkins。 Jenkins通過Pipeline分為多個step,step 1 build出要構建war包。Step 2 build Docker 鏡像并push到倉庫中。
第一步: build出想要的war,并把war包保存到固定的目錄。第二步:build docker鏡像,會自動發現前面build出的war包,并通過寫好的Dockerfile build鏡像,鏡像名即為應用名。鏡像構建成功后會push到我們的私有倉庫。每次鏡像構建都會打上一個tag,tag即為發布版本號。后續我們計劃把CI從jenkins獨立出來,通過自建的Paas平臺來build war包和鏡像。
我們自研了基于Docker的PaaS平臺(持續開發中)。該平臺的功能主要包括信息錄入、應用部署、監控、容器管理、應用擴縮等。CD就在PaaS上。
當要部署一個新的業務系統時,要先在PaaS系統上錄入應用相關信息,比如基礎鏡像地址、容器資源配置、容器數量、網絡、健康檢查等
CD時,需要選擇鏡像的版本號,即上文提到的tag
我們同時支持控制迭代速度,即迭代比例的設置
這個設置是指,每次迭代20%的容器,同時存活的容器不能低于迭代前的100%。
我們遇到的問題:控制迭代比例。
Marathon有兩個參數控制迭代比例:
-
minimumHealthCapacity(Optional. Default: 1.0)處于health狀態的最少容器比例;
-
maximumOverCapacity(Optional. Default: 1.0)可同時迭代的容器比例。
假如有個Java應用通過Tomcat部署,分配了四個容器,默認配置下迭代,Marathon可以同時啟動四個新的容器,啟動成功后刪除四個老的容器。四個新的Tomcat容器在對外提供服務的瞬間,因為請求量太大,需要立即擴線程數預熱,導致剛進來的請求處理時間延長甚至超時(B站因為請求量大,請求設置的超時時間很短,在這種情況下,請求會504超時)。
解決方法:
對于請求量很大需要預熱的應用,嚴格控制迭代比例,比如設置maximumOverCapacity為0.1,則迭代時只能同時新建10%的容器,這10%的容器啟動成功并刪除對應老的容器后才會再新建10%的容器繼續迭代。
對于請求量不大的應用,可適當調大maximumOverCapacity,加快迭代速度。
動態擴縮容
節假日或做活動時,為了應對臨時飆高的QPS,需要對應用臨時擴容。或者當監控到某個業務的平均資源使用率超過一定限制時,自動擴容。我們的擴容方式有兩種:1、手動擴容;2、制定一定的規則,當觸發到規則時,自動擴容。我們的Bili PaaS平臺同時提供了這兩種方式,底層是基于Marathon的Scale API。這里著重講解下基于規則的自動擴縮容。
自動擴縮容依賴總架構圖中的幾個組件:Monitoring Agent、Nginx+UpSync+Consul、Marathon Hook、Bili PaaS。
Monitor Agent:我們自研了Docker的監控Agent,封裝成容器,部署在每臺Docker宿主機上,通過docker stats的接口獲取容器的CPU、內存、IO等信息,信息錄入InfluxDB,并在Grafana展示。
Bili PaaS:應用在錄入PaaS平臺時可以選擇擴縮容的規則,比如:平均CPU > 300% OR MEM > 4G。PaaS平臺定時輪詢判斷應用的負載情況,如果達到擴容規則,就按一定的比例增加節點。本質上是調用Marathon的API進行擴縮。
Marathon Hook:通過Marathon提供的/v2/events接口監聽Marathon的事件流。當在Bili PaaS平臺手動擴容或觸發規則自動擴容時,Bili Paas平臺會調用Marathon的API。Marathon的每個操作都會產生事件,通過/v2/events接口暴露出來。Marathon Hook程序會把所有容器的信息注冊到Consul中。當Marathon刪除或創建容器時,Marathon Hook就會更新Consul中的Docker容器信息,保證Consul中的信息和Marathon中的信息是一致的,并且是最新的。
Nginx+UpSync+Consul:當Marathon擴容完成時,新容器的IP:PORT一定要加到SLB(Tengine/Nginx)的Upstream中,并reload SLB后才能對外提供服務。但Tengine/Nginx reload時性能會下降。為了避免頻繁reload SLB導致的性能損耗,我們使用了動態Upstream:Nginx + UpSync + Consul。Upsync是Weibo開源的一個模塊,使用Consul保存Upstream的server信息。Nginx啟動時會從Consul獲取最新的Upstream server信息,同時Nginx會建立一個TCP連接hook到Consul,當Consul里的數據有變更時會立即通知到Nginx,Nginx的worker進程更新自己的Upstream server信息。整個過程不需要reload nginx。注意:UpSync的功能是動態更新upstream server,當有vhost的變更時,還是需要reload nginx。
我們遇到的問題:
1) Nginx + UpSync 在reload時會產生shutting down。因為Nginx Hook到Consul的鏈接不能及時斷開。曾在GitHub上因這個問題提過issue,作者回復已解決。個人測試發現shuttding down還是存在。并且UpSync和Tengine的http upstream check模塊不能同時編譯。
解決方案:Tengine + Dyups。我們正在嘗試把Nginx + Dyups替換為Tengine + Dyups。Dyups的弊端就是Upstream信息是保存在內存里的。Reload/Restart Tengine時就會丟失。需要自己同步Upstream信息到磁盤中。基于此,我們對Tengine + Dyups做了封裝,由一個代理進程Hook Consul,發現有更時則主動更新Tengine,并提供了Web管理界面。目前正在內部測試中。
2)Docker Hook —> Marathon Hook,最早我們是去Hook Docker的events。這需要在每臺宿主機上起一個Hook服務。當應用迭代時,Docker會立即產生一個create container的事件。Hook程序監控到后去更新Consul,然后Consul通知Nginx去更新。導致問題就是:容器里的服務還沒啟動成功(比如Tomcat),就已經對外提供服務了。這會導致很多請求失敗,產生重啟請求。
解決方案:Marathon Hook。Marathon中有一個health check的配置。如下圖
我們規定所有的Web服務必須提供一個Health Check接口,這個接口隨著服務一同起來,這個接口任何非200的http code都代表應用異常。Marathon剛啟動容器時,顯示此容器的Health狀態是uknow。當Health Check成功時,Marathon顯示此容器的Health狀態Healthy,并會產生一個事件。Marathon Hook程序通過Hook這個事件,就能準確捕獲容器中應用啟動成功的時間,并更新Consul,同步Nginx,對外提供服務。
3)Marathon Failover后會丟失command health check,通過Marathon給容器添加Health Check時,有三種方式可以選擇:HTTP TCP COMMAND
當使用HTTP TCP時,Check是由Marathon發起的,無法選擇Check時的端口,Marathon會用自己分配的PORT進行Check。實際上我們并未使用marathon映射的端口。我們選擇了COMMAND方式,在容器內部發起curl請求來判斷容器里的應用狀態。當Marathon發生failover后,會丟失COMMAND health check,所有容器狀態都顯示unknow。需要重啟或者迭代應用才能恢復。
Q&A
Q:你好 問下貴公司的自動擴容是針對應用的吧 有沒有針對Mesos資源池監控并做Mesos Agent的擴容?
A:目前的自動擴容是針對應用的。Mesos Agent擴容時,先把物理機信息錄入PaaS平臺,手動在PaaS平臺點擊擴容,后臺會調用Ansible,分鐘快速級擴Mesos Agent。
Q:現在是確定Nginx+UpSync+Upsteam check是無法一起用的么?貴公司的Nginx版本是多少哇?
A:測試過Nginx 1.8和1.10,確認無法一同編譯。我們用的最多的Nginx(SLB)是Tengine 2.1.1,部署在Docker上。
Q:既然是封裝, 那底層用Mesos比Kubernets并沒有太大的靈活性吧?
A:對于PaaS平臺,目前我們希望的只需要資源調度這個功能,其他功能我們還是希望可以自己實現,而Mesos是專注于調度資源,而且已經歷經了大量級的考驗。而Kubernetes目前提供的很多服務,我們并不需要,所以選擇了Mesos。
Q:容器是采用Monitor Agent監控,那容器內的呢?也還是內部埋點?還是EFK嗎?監控是采用Prometheus嗎?
A:Prometheus沒有使用,我們是用自己的監控Agent -> InfluxDB。容器內有多種監控方式。有用ELK,也有其他埋點,比如StatsD,基于Dapper論文實現的全鏈路追蹤。
Q:網絡選型這塊,還調研過其他網絡方案嗎?譬如Calico、Weave等,為什么會選用Macvlan?
A:我們的選型第一步是先選擇標準的,要從CoreOS主導的cni還是Docker官方主導cnm里面選擇,目前由于我們容器方案還是走的Docker,所以選擇了cnm,那從cnm的標準里面的選擇基本是:1. 基于XVLAN的Overlay;2. 基于三層路由的Calico;3. 基于二層隔離的Macvlan,實際以上的方案我們都調研使用過,基于希望盡量簡單的原則最終選型還是Macvlan。
Q:Bili PaaS平臺,自動擴容和手動擴容,應用多的是哪種方式?自動擴容后,資源會重調度么?是否會中斷已有業務呢?
A:用的更多的是根據制定好的策略,自動擴容。通過Nginx 動態Upstream對外提供服務,不會中斷業務。
Q:關于日志收集每個容器里都跑一個Logstash嗎?好像ELK不能搜索展示上下文的啊?
A:容器里面沒有跑Logstash。目前是在遠端的Logstash集群上監聽一個UDP端口,應用直接把日志推送到Logstash的UDP端口,然后Logstash把日志推送到Kafka,Kafka的消費者有兩個,一個是Elasticsearch,一個是HDFS。一般用ELK足以。需要詳細日志時,由運維通過HDFS查詢。
Q:我想請教下Nginx的一些動態配置文件是封裝在容器內部了?還是通過volume的方式掛載了?有沒有配置中心類似的服務?這塊想了解下是怎么實現的?
A:Nginx的Upstream是從Consul動態獲取生成在本地的,通過Volume掛載,持久化到宿主機。有配置中心。業務Docker化時,就會推動業務配置接配置中心,Docker中不在保存業務依賴的配置。
來自:http://mp.weixin.qq.com/s?__biz=MzA5OTAyNzQ2OA==&mid=2649692067&idx=1&sn=a944922e5adbd4c0fa05cd8091ce0c7e&chksm=889328c0bfe4a1d66a276ecdf88e32acc4ea3bc054e9af56769c4d3dc094e94b764b761c2bbe&mpshare=1&scene=1&srcid=1009FDaiOLLfveQDyg9a8gb8#rd