作容器的研究和容器化幾年了,從最初對於容器的初步認識,到積攢了大量的容器遷移經驗,並和客戶解釋了容器技術以後,發現原來對於容器的理解有大量的誤解,並且容器並不是虛擬機的替代,而是有十分具體的應用場景的。mysql
第一部分:容器的理解誤區nginx
誤區一:容器啓動速度快,秒級啓動
spring
這是不少人佈道容器的時候常常說的一句話,每每人們會啓動一個nginx之類的應用,的確很快就可以啓動起來了。sql
容器爲啥啓動快,一是沒有內核,二是鏡像比較小。數據庫
然而容器是有主進程的,也即Entrypoint,只有主進程徹底啓動起來了,容器纔算真正的啓動起來,一個比喻是容器更像人的衣服,人站起來了,衣服才站起來,人躺下了,衣服也躺下了。衣服有必定的隔離性,可是隔離性沒那麼好。衣服沒有根(內核),可是衣服能夠隨着人處處走。緩存
因此按照一個nginx來評判一個容器的啓動速度有意義麼?對於Java應用,裏面安裝的是tomcat,而tomcat的啓動,加載war,而且真正的應用啓動起來,若是你盯着tomcat的日誌看的話,仍是須要一些時間的,根本不是秒級。若是應用啓動起來要一兩分鐘,僅僅談容器的秒級啓動是沒有意義的。tomcat
如今OpenStack中的VM的啓動速度也優化的愈來愈快了,啓動一個VM的時候安全
,原來須要從Glance下載虛擬機鏡像,後來有了一個技術,是的Glance和系統盤共享Ceph存儲的狀況下,虛擬機鏡像無需下載,啓動速度就快不少。服務器
並且容器之因此啓動速度快,每每建議使用一個很是小的鏡像,例如alpine,裏面不少東西都裁剪掉了,啓動的速度就更快了。微信
OpenStack的虛擬機鏡像也能夠通過大量的裁剪,實現快速的啓動
咱們能夠精細的衡量虛擬機啓動的每個步驟,裁剪掉相應的模塊和啓動的過程,大大下降虛擬機的啓動時間。
例如在UnitedStack的一篇博客裏面https://www.ustack.com/blog/build-block-storage-service,咱們能夠看到這樣的實現和描述
「使用原生的OpenStack建立虛擬機須要1~3分鐘,而使用改造後的OpenStack僅須要不到10秒鐘時間。這是由於nova-compute再也不須要經過HTTP下載整個鏡像,虛擬機能夠經過直接讀取Ceph中的鏡像數據進行啓動。」
因此對於虛擬機的總體啓動時間,如今優化的不錯的狀況下,通常可以作到十幾秒到半分鐘之內。這個時間和Tomcat的啓動時間相比較,其實不算是負擔,和容器的啓動速度相比,沒有質的差異,可能有人會說啓動速度快一點也是快,尤爲是對於在線環境的掛掉自修復來說,不是分秒必爭麼?關於自修復的問題,咱們下面另外說。
然而虛擬機有一個好處,就是隔離性好,若是容器是衣服,虛擬機就是房子,房子立在那裏,裏面的人不管站着仍是躺着,房子老是站着的,房子也不會跟着人走。使用虛擬機就像人們住在公寓裏面同樣,每人一間,互補干擾,使用容器像你們穿着衣服擠在公交車裏面,看似隔離,誰把公交弄壞了,誰都走不了。
綜上所述,容器的啓動速度不足以構成對OpenStack虛擬機的明顯優點,然而虛擬機的隔離性,則秒殺容器。
誤區二:容器輕量級,每一個主機會運行成百上千個容器
不少人會作實驗,甚至會跟客戶說,容器平臺多麼多麼牛,你看咱們一臺機器上能夠運行成百上千個容器,虛擬機根本作不到這一點。
可是一個機器運行成百上千個容器,有這種真實的應用場景麼?對於容器來說,重要的是裏面的應用,應用的核心在於穩定性和高併發支撐,而不在於密度。
我在不少演講的會議上遇到了不少知名的處理雙十一和618的講師,廣泛反饋當前的Java應用基本上4核8G是標配,若是碰見容量不足的狀況,少部分經過縱向擴容的方式進行,大部分採用橫向擴容的方式進行。
若是4核8G是標配,不到20個服務就能夠佔滿一臺物理服務器,一臺機器跑成百上千個nginx有意思麼? 這不是一個嚴肅的使用場景。
固然如今有一個很火的Serverless無服務架構,在無服務器架構中,全部自定義代碼做爲孤立的、獨立的、經常細粒度的函數來編寫和執行,這些函數在例如AWS Lambda之類的無狀態計算服務中運行。這些計算服務能夠是虛擬機,也能夠是容器。對於無狀態的函數來說,須要快速的建立可刪除,並且極可能執行一個函數的時間自己就很是短,在這種狀況下容器相比於虛擬機仍是有必定優點的。
目前無服務架構比較適用於運行一些任務型批量操做,利用進程級別的橫向彈性能力來抵消進程建立和銷燬帶來的較大的代價。
在spark和mesos的集成中,有一個Fine-Grained模式,同一般大數據的執行的時候,任務的執行進程早就申請好了資源,等在那裏分配資源不一樣,這種模式是當任務分配到的時候才分配資源,好處就是對於資源的彈性申請和釋放的能力,壞處是進程的建立和銷燬仍是粒度太大,因此這種模式下spark運行的性能會差一些。
spark的這種作法思想相似無服務架構,你會發現咱們原來學操做系統的時候,說進程粒度太大,每次都建立和銷燬進程會速度太慢,爲了高併發,後來有了線程,線程的建立和銷燬輕量級的多,固然仍是以爲慢,因而有了線程池,事先建立在了那裏,用的時候不用現建立,不用的時候交回去就行,後來仍是以爲慢,由於線程的建立也須要在內核中完成,因此後來有了協程,所有在用戶態進行線程切換,例如AKKA,Go都使用了協程,你會發現趨勢是爲了高併發,粒度是愈來愈細的,如今不少狀況又須要進程級別的,有種風水輪流轉的感受。
誤區三:容器有鏡像,能夠保持版本號,能夠升級和回滾
容器有兩個特性,一個是封裝,一個是標準。有了容器鏡像,就能夠將應用的各類配置,文件路徑,權限封裝起來,而後像孫悟空說「定」,就定在了封裝好的那一刻。鏡像是標準的,不管在哪一個容器運行環境,將一樣的鏡像運行起來,都能還原當時的那一刻。
容器的鏡像還有版本號,咱們能夠根據容器的版本號進行升級,一旦升級有錯,能夠根據版本號進行回滾,回滾完畢則可以保證容器內部仍是原來的狀態。
可是OpenStack虛擬機也是有鏡像的,虛擬機鏡像也是能夠打snapshot的,打snapshot的時候,也會保存當時的那一刻全部的狀態,並且snapshot也能夠有版本號,也能夠升級和回滾。
彷佛容器有的這些特性OpenStack虛擬機都有,兩者有什麼不一樣呢?
虛擬機鏡像大,而容器鏡像小。虛擬機鏡像動不動就幾十個G甚至上百G,而容器鏡像多幾百M。
虛擬機鏡像不適合跨環境遷移。例如開發環境在本地,測試環境在一個OpenStack上,開發環境在另外一個OpenStack上,虛擬機的鏡像的遷移很是困難,須要拷貝很是大的文件。而容器就好的多,由於鏡像小,能夠很快的從不一樣的環境之間遷移。
虛擬機鏡像不適合跨雲遷移。當前沒有一個公有云平臺支持虛擬機鏡像的下載和上傳(安全的緣由,盜版的緣由),於是一個鏡像在不一樣的雲之間,或者同一個雲不一樣的region直接,沒法進行遷移,只能從新作一個鏡像,這樣環境的一致性就得不到保障。而容器的鏡像中心是獨立於雲以外的,只要可以連上鏡像中心,到哪一個雲上均可如下載,而且由於鏡像小,下載速度快,而且鏡像是分層的,每次只須要下載差別的部分。
OpenStack對於鏡像方面的優化,基本上仍是在一個雲裏面起做用,一旦跨多個環境,鏡像方便的多。
誤區四:容器可使用容器平臺管理自動重啓實現自修復
容器的自修復功能是常常被吹噓的。由於容器是衣服,人躺下了,衣服也躺下了,容器平臺可以立刻發現人躺下了,因而能夠迅速將人從新喚醒工做。而虛擬機是房子,人躺下了,房子還站着,於是虛擬機管理平臺不知道里面的人能不能工做,因此容器掛了會被自動重啓,而虛擬機裏面的應用掛了,只要虛擬機不掛,極可能沒人知道。
這些說法都沒錯,可是人們慢慢發現了另外的場景,就是容器裏面的應用沒有掛,因此容器看起來還啓動着,可是應用以及不工做沒有反應了。當啓動容器的時候,雖然容器的狀態起來了,可是裏面的應用還須要一段時間才能提供服務。因此針對這種場景,容器平臺會提供對於容器裏面應用的health check,不光看容器在不在,還要看裏面的應用能不能用,若是不能,可自動重啓。
一旦引入了health check,和虛擬機的差異也不大了,由於有了health check,虛擬機也能看裏面的應用是否工做了,不工做也能夠重啓應用。
還要就是容器的啓動速度快,秒級啓動,若是可以自動重啓修復,那就是秒級修復,因此應用更加高可用。
這個觀點固然不正確,應用的高可用性和重啓的速度沒有直接關係。高可用性必定要經過多個副原本實現,在任何一個掛掉以後,不能經過這一個應用快速重啓來解決,而是應該靠掛掉的期間,其餘的副本立刻把任務接過來進行解決。虛擬機和容器均可以有多副本,在有多個副本的狀況下,重啓是一秒仍是20秒,就沒那麼重要了,重要的是掛掉的這段時間內,程序作了什麼,若是程序作的是可有可無的操做,那麼掛了20秒,也沒啥關係,若是程序正在進行一個交易和支付,那掛掉一秒也不行,也必須可以修復回來。因此應用的高可用性要靠應用層的重試,冪等去解決,而不該該靠基礎設施層重啓的快不快來解決。
對於無狀態服務,在作好重試的機制的狀況下,經過自動重啓修復是沒有問題的,由於無狀態的服務不會保存很是重要的操做。
對於有狀態服務,容器的重啓不但不是推薦的,並且多是災難的開始。一個服務有狀態,例如數據庫,在高併發場景下,一旦掛了,哪怕只有一秒,咱們必需要弄清楚這一秒都發生了什麼,哪些數據保存了,哪些數據丟了,而不能盲目的重啓,不然會極可能形成數據的不一致性,後期修都無法修。例如高頻交易下的數據庫掛了,按說DBA應該嚴格審覈丟了哪些數據,而不是在DBA不知情的狀況下,盲目的重啓了,DBA還以爲沒什麼事情發生,最終好久才能發現問題。
因此容器比較適合部署無狀態服務的,隨便重啓均可以。
而容器部署有狀態容器不是不能,而是要很是當心,甚至都是不推薦的。雖然不少的容器平臺都支持有狀態容器,然而平臺每每解決不了數據問題,除非你對容器裏面的應用很是很是很是熟悉,當容器掛了,你可以準確的知道丟了哪些,哪些要緊,哪些沒關係,並且要寫代碼處理這些狀況,而後才能支持重啓。網易這面的數據庫主備同步的狀況下,是經過修改mysql源代碼,保證主備之間數據徹底同步,纔敢在主掛了的狀況下,備自動切換主。
而宣傳有狀態容器的自動重啓,對於服務客戶來說是很不經濟的行爲,由於客戶每每沒有那麼清楚應用的邏輯,甚至應用都是買的,若是使用有狀態容器,任憑自動重啓,最終客戶發現數據丟失的時候,仍是會怪到你的頭上。
因此有狀態的服務自動重啓不是不可用,須要足夠專業才行。
誤區五:容器可使用容器平臺進行服務發現
容器平臺swarm, kubernetes,mesos都是支持服務發現的,當一個服務訪問另外一個服務,都會有服務名轉化爲VIP,而後訪問具體的容器。
然而人們會發現,基於Java寫的應用,服務之間的調用多不會用容器平臺的服務發現,而是用Dubbo或者spring cloud的服務發現。由於容器平臺層的服務發現,仍是作的比較基礎,基本是一個域名映射的過程,對於熔斷,限流,降級都沒有很好的支持,然而既然使用服務發現,仍是但願服務發現中間件可以作到這一點,於是服務之間的服務發現之間使用容器平臺的少,越是須要高併發的應用,越是如此。
那容器平臺的服務發現沒有用了麼?不是,慢慢你會發現,內部的服務發現是一方面,這些Dubbo和spring cloud可以搞定,而外部的服務發現就不一樣了,好比訪問數據庫,緩存等,究竟是應該配置一個數據庫服務的名稱,仍是IP地址呢?若是使用IP地址,會形成配置十分複雜,由於不少應用配置之因此複雜,就是依賴了太多的外部應用,也是最難管理的一方面。若是有了外部的服務發現,配置就會簡單不少,也只須要配置外部服務的名稱就能夠了,若是外部服務地址變了,能夠很靈活的改變外部的服務發現。
誤區六:容器能夠基於鏡像進行彈性伸縮
在容器平臺上,容器有副本數的,只要將副本數從5改到10,容器就基於鏡像進行了彈性伸縮。其實這一點虛擬機也能作到,AWS的Autoscaling就是基於虛擬機鏡像的,若是在同一個雲裏面,就沒有區別。
固然若是跨雲無狀態容器的彈性伸縮,容器方便不少,能夠實現混合雲模式,當高併發場景下,將無狀態容器擴容到公有云,這一點虛擬機是作不到的。
容器理解誤區總結
如圖,左面是常常掛在嘴邊的所謂容器的優點,可是虛擬機都能一一懟回去。
若是部署的是一個傳統的應用,這個應用啓動速度慢,進程數量少,基本不更新,那麼虛擬機徹底可以知足需求。
應用啓動慢:應用啓動15分鐘,容器自己秒級,虛擬機不少平臺能優化到十幾秒,二者幾乎看不出差異
內存佔用大:動不動32G,64G內存,一臺機器跑不了幾個。
基本不更新:半年更新一次,虛擬機鏡像照樣可以升級和回滾
應用有狀態:停機會丟數據,若是不知道丟了啥,就算秒級啓動有啥用,照樣恢復不了,並且還有可能由於丟數據,在沒有修復的狀況下,盲目重啓帶來數據混亂。
進程數量少:兩三個進程相互配置一下,不用服務發現,配置不麻煩
若是是一個傳統應用,根本沒有必要花費精去容器化,由於白花了力氣,享受不到好處。
第二部分:容器化,微服務,DevOps三位一體
什麼狀況下,才應該考慮作一些改變呢?
傳統業務忽然被互聯網業務衝擊了,應用總是變,三天兩頭要更新,並且流量增大了,原來支付系統是取錢刷卡的,如今要互聯網支付了,流量擴大了N倍。
沒辦法,一個字:拆
拆開了,每一個子模塊獨自變化,少相互影響。
拆開了,原來一個進程扛流量,如今多個進程一塊兒扛。
因此稱爲微服務。
微服務場景下,進程多,更新快,因而出現100個進程,天天一個鏡像。
容器樂了,每一個容器鏡像小,沒啥問題,虛擬機哭了,由於虛擬機每一個鏡像太大了。
因此微服務場景下,能夠開始考慮用容器了。
虛擬機怒了,老子不用容器了,微服務拆分以後,用Ansible自動部署是同樣的。
這樣說從技術角度來說沒有任何問題。
然而問題是從組織角度出現的。
通常的公司,開發會比運維多的多,開發寫完代碼就不用管了,環境的部署徹底是運維負責,運維爲了自動化,寫Ansible腳原本解決問題。
然而這麼多進程,又拆又合併的,更新這麼快,配置老是變,Ansible腳本也要常改,天天都上線,不得累死運維。
因此這如此大的工做量狀況下,運維很容易出錯,哪怕經過自動化腳本。
這個時候,容器就能夠做爲一個很是好的工具運用起來。
除了容器從技術角度,可以使得大部分的內部配置能夠放在鏡像裏面以外,更重要的是從流程角度,將環境配置這件事情,往前推了,推到了開發這裏,要求開發完畢以後,就須要考慮環境部署的問題,而不能當甩手掌櫃。
這樣作的好處就是,雖然進程多,配置變化多,更新頻繁,可是對於某個模塊的開發團隊來說,這個量是很小的,由於5-10我的專門維護這個模塊的配置和更新,不容易出錯。
若是這些工做量全交給少數的運維團隊,不但信息傳遞會使得環境配置不一致,部署量會大很是多。
容器是一個很是好的工具,就是讓每一個開發僅僅多作5%的工做,就可以節約運維200%的工做,而且不容易出錯。
然而原本原來運維該作的事情開發作了,開發的老大願意麼?開發的老大會投訴運維的老大麼?
這就不是技術問題了,其實這就是DevOps,DevOps不是不區分開發和運維,而是公司從組織到流程,可以打通,看如何合做,邊界如何劃分,對系統的穩定性更有好處。
因此微服務,DevOps,容器是相輔相成,不可分割的。
不是微服務,根本不須要容器,虛擬機就能搞定,不須要DevOps,一年部署一次,開發和運維溝通再慢都能搞定。
因此,容器的本質是基於鏡像的跨環境遷移。
鏡像是容器的根本性發明,是封裝和運行的標準,其餘什麼namespace,cgroup,早就有了。這是技術方面。
在流程方面,鏡像是DevOps的良好工具。
容器是爲了跨環境遷移的,第一種遷移的場景是開發,測試,生產環境之間的遷移。若是不須要遷移,或者遷移不頻繁,虛擬機鏡像也行,可是老是要遷移,帶着幾百G的虛擬機鏡像,太大了。
第二種遷移的場景是跨雲遷移,跨公有云,跨Region,跨兩個OpenStack的虛擬機遷移都是很是麻煩,甚至不可能的,由於公有云不提供虛擬機鏡像的下載和上傳功能,並且虛擬機鏡像太大了,一傳傳一天。
因此跨雲場景下,混合雲場景下,容器也是很好的使用場景。這也同時解決了僅僅私有云資源不足,扛不住流量的問題。
第三部分:容器的正確使用場景
根據以上的分析,咱們發現容器推薦使用在下面的場景下。
1. 部署無狀態服務,同虛擬機互補使用,實現隔離性
2. 若是要部署有狀態服務,須要對裏面的應用十分的瞭解
3. 做爲持續集成的重要工具,能夠順利在開發,測試,生產之間遷移
4. 適合部署跨雲,跨Region,跨數據中心,混合雲場景下的應用部署和彈性伸縮
5. 以容器做爲應用的交付物,保持環境一致性,樹立不可變動基礎設施的理念
6. 運行進程基本的任務類型的程序
7. 用於管理變動,變動頻繁的應用使用容器鏡像和版本號,輕量級方便的多
8. 使用容器必定要管理好應用,進行health check和容錯的設計
歡迎關注微信公衆號