關于容器的歷史、發展以及技術本質,在互聯網上已經有非常多的文章了。這里旨在結合自身的工作經驗和理解,通過一系列的文章,講清楚這項技術。
容器的歷史和發展 前世講到容器,就不得不提lxc(linux container),他是docker的前生,或者說docker是lxc的使用者。完整的lxc能力在2008年合入linux主線,所以容器的概念在2008年就基本定型了,并不是后面docker造出來的。關于lxc的介紹很多,大體都會說“lxc是linux內核提供的容器技術,能提供輕量級的虛擬化能力,能隔離進程和資源”,但總結起來,無外乎就兩大知識點cgroups(linux control group)和linux namespace。搞清楚他倆,容器技術就基本掌握了。
cgroups:重點在“限制”。限制資源的使用,包括cpu、內存、磁盤的使用,體現出對資源的管理能力。 namespace:重點在“隔離”。隔離進程看到的linux視圖。說大白話就是,容器和容器之間不要相互影響,容器和宿主機之間不要相互影響。 少年期起步艱難2009年,cloud foundry基于lxc實現了對容器的操作,該項目取名為warden。2010年,dotcloud公司同樣基于lxc技術,使用go語言實現了一款容器引擎,也就是現在的docker。那時,dotcloud公司還是個小公司,出生卑微的docker沒什么熱度,活得相當艱難。
成長為巨無霸2013年,dotcloud公司決定將docker開源。開源后,項目突然就火了。從大的說,火的原因就是docker的這句口號“build once,run anywhere”。呵呵,是不是似曾相識?對的,和java的write once,run anywhere一個道理。對于一個程序員來說,程序寫完后打包成鏡像就可以隨處部署和運行,開發、測試和生產環境完全一致,這是多么大一個誘惑。程序員再也不用去定位因環境差異導致的各種坑爹問題。
docker開源項目的異常火爆,直接驅動dotcloud公司在2013年更名為docker公司。docker也快速成長,干掉了coreos公司的rkt容器和google的lmctfy容器,直接變成了容器的事實標準。也就有了后來人一提到容器就認為是docker。
總結起來,docker為什么火,靠的就是docker鏡像。他打包了應用程序的所有依賴,徹底解決了環境的一致性問題,重新定義了軟件的交付方式,提高了生產效率。
被列強蠶食docker在容器領域快速成長,野心自然也變大了。2014年推出了容器云產品swarm(kubenetes的同類產品),想擴張事業版圖。同時docker在開源社區擁有絕對話語權,相當強勢。這種走自己的路,讓別人無路可走的行為,讓容器領域的其他大廠玩家很是不爽,為了不讓docker一家獨大,決定要干他。
2015年6月,在google、redhat等大廠的“運作”下,linux基金會成立了oci(open container initiative)組織,旨在圍繞容器格式和運行時制定一個開放的工業化標準,也就是我們常說的oci標準。同時,docker公司將libcontainer模塊捐給cncf社區,作為oci標準的實現,這就是現在的runc項目。說白了,就是現在這塊兒有個標準了,大家一起玩兒,不被某個特定項目的綁定。
講到docker,就得說說google家的kubernetes,他作為容器云平臺的事實標準,如今已被廣泛使用,儼然已成為大廠標配。kubernetes原生支持docker,讓docker的市場占有率一直居高不下。如圖是2019年容器運行時的市場占有率。
但在2020年,kubernetes突然宣布在1.20版本以后,也就是2021年以后,不再支持docker作為默認的容器運行時,將在代碼主干中去除dockershim。
如圖所示,kubenetes自身定義了標準的容器運行時接口cri(container runtime interface),目的是能對接任何實現了cri接口的容器運行時。在初期,docker是容器運行時不容置疑的王者,kubenetes便內置了對docker的支持,通過dockershim來實現標準cri接口到docker接口的適配,以此獲得更多的用戶。隨著開源的容器運行時containerd(實現了cri接口,同樣由docker捐給cncf)的成熟,kubenetes不再維護dockershim,僅負責維護標準的cri,解除與某特定容器運行時的綁定。當然,也不是kubenetes不支持docker了,只是dockershim誰維護的問題。 隨著kubenetes態度的變化,預計將會有越來越多的開發者選擇直接與開源的containerd對接,docker公司和docker開源項目(現已改名為moby)未來將會發生什么樣的變化,誰也說不好。
講到這里,不知道大家有沒有注意到,docker公司其實是捐獻了containerd和runc。這倆到底是啥東西。簡單的說,runc是oci標準的實現,也叫oci運行時,是真正負責操作容器的。containerd對外提供接口,管理、控制著runc。所以上面的圖,真正應該長這樣。
docker公司是一個典型的小公司因一個爆款項目火起來的案例,不管是技術層面、公司經營層面以及如何跟大廠纏斗,不管是好的方面還是壞的方面,都值得我們去學習和了解其背后的故事。
什么是容器按國際慣例,在介紹一個新概念的時候,都得從大家熟悉的東西說起。幸好容器這個概念還算好理解,喝水的杯子,洗腳的桶,養魚的缸都是容器。容器技術里面的“容器”也是類似概念,只是裝的東西不同罷了,他裝的是應用軟件本身以及軟件運行起來需要的依賴。用魚缸來類比,魚缸這個容器里面裝的應用軟件就是魚,裝的依賴就是魚食和水。這樣大家就能理解docker的logo了。大海就是宿主機,docker就是那條鯨魚,鯨魚背上的集裝箱就是容器,我們的應用程序就裝在集裝箱里面。
在講容器的時候一定繞不開容器鏡像,這里先簡單的把容器鏡像理解為是一個壓縮包,后續再詳細講解。壓縮包里包含應用的可執行程序以及程序依賴的文件(例如:配置文件和需要調用的動態庫等),接下來通過實際操作來看看容器到底是個啥。
宿主機視角看容器 1、首先,我們啟動容器。 dockerrun-d--name=@quot;aimar-1-container@quot;euleros_arm:2.0sp8spc306/bin/sh-c@quot;whiletrue;doechoaimar-1-container;sleep1;done@quot;這是docker的標準命令。意思是使用euleros_arm:2.0sp8spc306鏡像(鏡像名:版本號)創建一個新的名字為“aimar-1-container”的容器,并在容器中執行shell命令:每秒打印一次“aimar-1-container”。
參數說明:
-d:使用后臺運行模式啟動容器,并返回容器id。 --name:為容器指定一個名字。 dockerrun-d--name=@quot;aimar-1-container@quot;euleros_arm:2.0sp8spc306/bin/sh-c@quot;whiletrue;doechoaimar-1-container;sleep1;done@quot; 207b7c0cbd811791f7006cd56e17033eb430ec656f05b6cd172c77cf45ad093c從輸出中,我們看到一串長字符207b7c0cbd811791f7006cd56e17033eb430ec656f05b6cd172c77cf45ad093c。他就是容器id,能唯一標識一個容器。當然在使用的時候,不需要使用全id,直接使用縮寫id即可(全id的前幾位)。例如下圖中,通過docker ps查詢到的容器id為207b7c0cbd81。
aimar-1-container容器啟動成功后,我們在宿主機上使用ps進行查看。這時可以發現剛才啟動的容器就是個進程,pid為12280。
我們嘗試著再啟動2個容器,并再次在宿主機進行查看,你會發現又新增了2個進程,pid分別為20049和21097。
所以,我們可以得到一個結論。從宿主機的視角看,容器就是進程。
2、接下來,我們進入這個容器。 dockerexec-it207b7c0cbd81/bin/bashdocker exec也是docker的標準命令,用于進入某個容器。意思是進入容器id為207b7c0cbd81的容器,進入后執行/bin/bash命令,開啟命令交互。
參數說明:
-it其實是-i和-t兩個參數,意思是容器啟動后,要分配一個輸入/輸出終端,方便我們跟容器進行交互,實現跟容器的“對話”能力。
從hostname從kwephispra09909變化為207b7c0cbd81,說明我們已經進入到容器里面了。在容器中,我們嘗試著啟動一個新的進程。
[root@207b7c0cbd81/]#/bin/sh-c@quot;whiletrue;doechoaimar-1-container-embed;sleep1;done@quot;@amp;
再次回到宿主機進行ps查看,你會發現不管是直接啟動容器,還是在容器中啟動新的進程,從宿主機的角度看,他們都是進程。
容器視角看容器前面我們已經進入容器里面,并啟動了新的進程。但是我們并沒有在容器里查看進程的情況。在容器中執行ps,會發現得到的結果和宿主機上執行ps的結果完全不一樣。下圖是容器中的執行結果。
在container1容器中只能看見剛起啟動的shell進程(container1和container1-embed),看不到宿主機上的其他進程,也看不到container2和container3里面的進程。這些進程像被關進了一個盒子里面,完全感知不到外界,甚至認為我們執行的container1是1號進程(1號進程也叫init進程,是系統中所有其他用戶進程的祖先進程)。所以,從容器的視角,容器覺得“我就是天,我就是地,歡迎來到我的世界”。
但尷尬的是,在宿主機上,他們卻是普通得不能再普通的進程。注意,相同的進程,在容器里看到的進程id和在宿主機上看到的進程id是不一樣的。容器中的進程id分別是1和1859,宿主機上對應的進程id分別是12280和9775(見上圖)。
總結通過上面的實驗,對容器的定義就需要再加上一個定語。容器就是進程=@gt;容器是與系統其他部分隔離開的進程。這個時候我們再看下圖就更容易理解,容器是跑在宿主機os(虛機容器的宿主機os就是guest os)上的進程,容器間以及容器和宿主機間存在隔離性,例如:進程號的隔離。
在容器內和宿主機上,同一個進程的進程id不同。例如:container1在容器內pid是1,在宿主機上是12280。那么該進程真正的pid是什么呢?當然是12280!那為什么會造成在容器內看到的pid是1呢,造成這種幻象的,正是linux namespace。
linux namespace是linux內核用來隔離資源的方式。每個namespace下的資源對于其他namespace都是不透明,不可見的。
namespace按隔離的資源進行分類:
前面提到的容器內外,看到的進程id不同,正是使用了pid namespace。那么這個namespace在哪呢?在linux上一切皆文件。是的,這個namespace就在文件里。在宿主機上的proc文件中(/proc/進程號/ns)變記錄了某個進程對應的namespace信息。如下圖,其中的數字(例如:pid:[ 4026534312])則表示一個namespace。
對于container1、container2、container3這3個容器,我們可以看到,他們的pid namespace是不一樣的。說明他們3個容器中的pid相互隔離,也就是說,這3個容器里面可以同時擁有pid號相同的進程,例如:都有pid=1的進程。
在一個命名空間中,那這倆進程就相互可見,只是pid與宿主機上看到的不同而已。
至此,我們可以對容器的定義再細化一層。容器是與系統其他部分隔離開的進程=》容器是使用linux namespace實現與系統其他部分隔離開的進程。
揚州到南縣物流專線昆明到安順物流專線南寧到沁陽物流專線英文網站設計要關注的差異什么是RSS推廣新站沒有排名的問題,又該怎樣獲取排名【網站怎么優化】網站編輯是否需要考慮SEO問題北京到鄂爾多斯物流專線