隨着集羣規模的擴大,您是否曾經因鏡像分發問題而困擾過?根據不一樣的場景,咱們利用不一樣的鏡像分發方法:java
你會發現第二種方式依賴於網絡穩定性。本文將總結如何根據鏡像內容讀請求動態加載從遠程到本地存儲的鏡像做爲權衡,以及如何選擇適合鏡像分佈的方式。mysql
在業務很小的時候,能夠單臺機器部署單個容器;但當業務量大了,而且分爲多個應用時,就須要將多個應用部署在同一臺機器上;此時多個應用的依賴是一個比較難解決的問題。且多個應用同時運行,相互之間也有所幹擾。linux
一開始,可使用虛擬機的方式來實現,這樣每一個應用一個虛擬機,基本不存在依賴衝突,可是虛擬機仍是會有啓動慢,空間佔用大等一系列問題。nginx
後面隨着技術的發展,有了 lxc 技術,它能讓應用跑在一個單獨的命名空間上,實現了應用的運行態隔離。golang
這裏能夠看到,虛擬機解決了靜態隔離的問題,lcx 解決了運行態隔離的問題,而 Docker 則同時解決了運行態和靜態隔離兩個問題,所以獲得親睞。算法
Docker 的三大核心:鏡像、容器、倉庫。容器在 Docker 以前已經有了不一樣的實現,好比 lxc。鏡像和倉庫是 Docker 的很大創新。鏡像是一個實體,倉庫是它的集中存儲中心,而容器則是鏡像的運行時。sql
Docker 經過鏡像,能夠很神奇地實現好比:開發的同窗在本身喜歡的平臺上面作開發測試(例如 ubuntu),而真正部署的節點,多是 redhat/centos 的服務器。docker
Docker 是以鏡像爲基礎,因此容器運行時必需要有鏡像,而容器又能夠經過 docker commit 生成一個鏡像。可是第一個鏡像從哪裏來呢?能夠經過 docker import/load 的方式導入。而且能夠經過 docker push/pull 的方向將鏡像上傳到鏡像中心,或從鏡像中心下載鏡像。ubuntu
鏡像製做centos
Docker 提供了一套 docker build 機制,方便來製做鏡像。想像一下,沒有 docker build 機制,製做鏡像的步驟以下:
有了 docker build 機制,只須要一個 dockerfile,而後 Docker 就會把上述步驟自動化了,最後生成一個目標鏡像。
容器
詳細看一下是如何使用鏡像的,鏡像是一層一層的,設計上,鏡像全部層是隻讀的,linux 經過 aufs/overlayfs 的方式將一個鏡像 mount 到主機上後,從上往下看,最上面層是容器的可讀寫層,容器運行時的全部寫操做都寫到這一層上面,而下面的層都是鏡像的只讀層。
在 Docker 的 registry 或其它網站上看到,像 busybox, nginx, mysql 等鏡像都不大,也就是幾十 M 到幾百 M 的樣子。
可是阿里巴巴的鏡像廣泛有 3~4G,鏡像這麼大緣由有許多,好比:
這就致使一個問題:鏡像中心頗有可能被打爆。
這種狀況在集團出現屢次,在打爆的狀況下,當前的鏡像拉取被阻塞,同時又有更多的鏡像拉取請求上來,致使鏡像中心嚴重超負荷運行,而且導致問題愈來愈嚴重,可能會致使整個系統奔潰。所以,必須解決鏡像下載致使的問題。
集團開發了蜻蜓(Dragonfly )這套 P2P 下載系統。上面是蜻蜓的架構圖,蜻蜓能夠分爲三層,最上層是一個配置中心,中間是超級節點,下面就是運行容器的物理節點。
配置中心管理整個蜻蜓集羣,當物理節點上有鏡像要下載時,它經過 dfget 命令向超級節點發出請求,而向哪些超級節點發出請求是由配置中心配置的。
如下圖爲例:
當下載一個文件時,物理節點發送了一個 dfget 請求到超級節點,超級節點會檢查自身是否有這部分數據,若是有,則直接把數據傳送給物理節點;不然會向鏡像中心發出下載請求,這個下載請求是分塊的,這樣,只有下載失敗的塊須要從新下載,而下載成功的塊,則不須要。
更爲重要的是,若是是兩個或更多物理節點請求同一份數據,那麼蜻蜓收到請求後,只會往鏡像中心發一個請求,極大地下降了鏡像中心的壓力。
關於 P2P,也就是說若是一個節點有了另外一個節點的數據,爲了減小超級節點的壓力,兩個節點能夠完成數據的互傳。這樣更高效地利用了網絡帶寬。
從上面的蜻蜓效果圖能夠看到:
隨着鏡像拉取的併發量愈來愈大,傳統方式所消耗的時間會愈來愈多,後面的線條愈來愈陡;可是蜻蜓模式下,雖然耗時有增長,但只是輕微增長。效果仍是很是明顯的。
應該說,集團內是先有蜻蜓,後面纔有 docker 的。緣由是集團在全面 docker 以前,使用了 t4 等技術,無論哪一種技術,都須要在物理機上面下載應用的包,而後把應用跑起來。
因此,從里程碑上看,2015 年 6 月就已經有了蜻蜓 p2p 了,隨着集團全面 Docker 化,在 2015 年 9 月,蜻蜓提供了對 Docker 鏡像的支持,後面的幾個時間點上,能夠看到蜻蜓經受了幾回雙 11 的考驗,而且通過軟件迭代,在 2017 年 11 月的時候實現了開源。
如今蜻蜓還在不斷演化,好比說所有使用 golang 重寫,解決 bug,提供更多能力集,相信蜻蜓的明天更美好。
蜻蜓在集團取得了極好的效果,可是並不能解決全部問題,好比咱們就遇到了如下比較突出的問題:
在實踐中,上線的業務運行常常有抖動的現象,分析其中緣由,有部分業務抖動時,都在作鏡像下載。
分析緣由發現:鏡像層採用的是 gzip 壓縮算法。這一古老的算法,是單線程運行,而且獨佔一個 CPU,更要命的是解壓速度也很慢,若是幾個鏡像層同時展開的話,就要佔用幾個 CPU,這搶佔了業務的 CPU 資源,致使了業務的抖動。
平時應用發佈、升級時,只須要選擇在業務低峯,而且經過必定的算法,好比只讓 10% 左右的容器作發佈、升級操做,其它 90% 的容器還給用戶提供服務。這種不影響業務、不影響用戶體驗的操做,對於時效性要求不高,只要在業務高峯來臨前完成操做便可,因此耗時一兩個小時也無所謂。
假設遇到大促場景,原計劃 10 個容器能夠服務 100 萬用戶,可是,忽然來了 300 萬用戶,這時全部用戶的體驗將會降低,這時就須要經過擴容手段來增長服務器數量,此時對容器擴出來有着極高的時效要求。
如今全部公司都在上雲,集團也在上雲階段,但云上服務器的一些特性與物理機仍是有些差異,對鏡像來說感覺最深的就是磁盤 IO 了,原來物理機的 SSD 磁盤,IO 能夠達到 1G 以上,而使用 ECS 後,標準速度是 140M,速度只有原來的十分之一。
Gzip 優化
對於 gzip 的問題,經過實測及數據分析,如上圖所示:
1 與 2 下載的是同一個鏡像層,只是 1 下載的是一個 gzip 格式的,而 2 下載的是一個沒有壓縮的鏡像層。能夠看到 2 由於下載的數據量要大不少,因此下載的時間要長許多,可是 1 中將 400M 的 gzip 還原成 1G 的原始數據卻又消耗了太多時間,因此最終二者整體時效是差很少的。
而且因爲去掉 gzip,鏡像下載不會搶佔業務的 CPU 資源。自從上了這一方案後,業務抖動的次數明顯減小了許多。
在蜻蜓的章節,鏡像下載是鏡像層從超級節點到物理機,在這一過程當中,蜻蜓有一個動態壓縮能力,好比使用更好的 lz4 算法,即達到了數據壓縮的效果,又不會形成業務抖動。這就是圖 3,總體上這一點在集團的應用效果很不錯。
解決了鏡像下載對業務的干擾後,擴容、雲環境的問題尚未解決。這時遠程盤就派上用場了。
從上面的架構圖看,集團有一套鏡像轉換機制,將原始的鏡像層放在遠端服務器上,第一個層都有一個惟一的遠程盤與之對應。而後鏡像中保存的是這個遠程盤的 id,這樣作下來,遠程盤的鏡像能夠作到 kB 級別。對於 kB 級別的鏡像,下載耗時在 1~2 秒之間。
經過遠程盤,解決了鏡像下載的問題,同時因爲遠程盤放在物理機同一個機房,容器運行時讀取鏡像數據,至關於從遠程盤上面讀取數據,由於在同一個機房,固然不能跟本地盤比,可是效果能夠與雲環境的雲盤性能相媲美。
遠程盤雖然解決了鏡像下載的問題,可是全部鏡像的數據都是從遠程盤上讀取,消耗比較大的網絡帶寬。當物理機上環境比較複雜時,遠程盤的數據又不能緩存在內存時,全部數據都要從遠端讀取,當規模上來後,就會給網絡帶來不小的壓力。
另一個問題是,若是遠程盤出現問題,致使 IO hang,對於容器進程來說,就是殭屍進程,而殭屍進程是沒法殺死的。對於應用來說,一個容器要麼是死,要麼是活,都好辦,死的了容器上面的流量分給活着的容器便可,但對於一個殭屍容器,流量無法摘除,致使這部分業務就受損了。
最後,遠程盤由於要服務多臺物理機,必然要在磁盤 IO 上面有比較好的性能,這就致使了成本較高。
針對上述問題,集團採用的 DADI 這一項技術,都是對症下藥:
遠程盤是物理機全部數據都要從遠程盤上讀,這樣會致使對遠程盤機器的配置較高的要求,而且壓力也很大。
而經過 P2P 手段,能夠將一部分壓力分擔掉,當一臺機器已經有另外一臺須要的數據時,它倆之間能夠完成數據互傳。
一樣是解決網絡帶寬的問題,遠程盤對應的數據都是沒有壓縮的,傳輸會佔用比較多的帶寬。
而 DADI 則在傳輸過程當中,跟蜻蜓同樣,對數據進行壓縮,減小網絡壓力。
這個是核心買點了,當容器啓動後,DADI 會把整個鏡像數據拉取到本地,這樣即便網絡有問題,也不會致使容器進程殭屍,解決了業務受損的問題。