tft每日頭條

 > 圖文

 > 容器是什麼概念

容器是什麼概念

圖文 更新时间:2024-07-28 21:05:22

容器是什麼概念(到底是怎麼一回事兒)1

在文章開始之前十分想和大家介紹一些“容器”的來龍去脈,但受限于篇幅,你能先理解如下幾個事實就好:

  • 容器技術的興起源于 PaaS 技術的普及;
  • Docker 公司發布的 Docker 項目具有裡程碑式的意義;
  • Docker 項目通過“容器鏡像”,解決了應用打包這個根本性難題。

在極客時間的「深入剖析 Kubernetes」專欄裡,詳細介紹了容器技術圈在過去五年裡的“風雲變幻”,這篇文章也出自于這個專欄。

接下來我們就一起來看一看容器到底是怎麼一回事兒?

容器是什麼概念(到底是怎麼一回事兒)2

容器本身沒有價值,有價值的是“容器編排”,也正因為如此,容器技術生态才爆發了一場關于“容器編排”的“戰争”。在看這篇文章之前,你還需要搞清楚一個更為基礎的問題:

容器,到底是怎麼一回事兒?

我們知道容器其實是一種沙盒技術,能夠像一個集裝箱一樣,把應用“裝”起來的技術。這樣,應用與應用之間,就因為有了邊界而不至于相互幹擾;而被裝進集裝箱的應用,也可以被方便地搬來搬去,這不就是 PaaS 最理想的狀态嘛。

不過,這兩個能力說起來簡單,但要用技術手段去實現它們,可能大多數人就無從下手了。

所以,我就先來跟你說說這個“邊界”的實現手段。

假如,現在你要寫一個計算加法的小程序,這個程序需要的輸入來自于一個文件,計算完成後的結果則輸出到另一個文件中。

由于計算機隻認識 0 和 1,所以無論用哪種語言編寫這段代碼,最後都需要通過某種方式翻譯成二進制文件,才能在計算機操作系統中運行起來。

而為了能夠讓這些代碼正常運行,我們往往還要給它提供數據,比如我們這個加法程序所需要的輸入文件。這些數據加上代碼本身的二進制文件,放在磁盤上,就是我們平常所說的一個“程序”,也叫代碼的可執行鏡像(executable image)。

然後,我們就可以在計算機上運行這個“程序”了。

首先,操作系統從“程序”中發現輸入數據保存在一個文件中,所以這些數據就被會加載到内存中待命。同時,操作系統又讀取到了計算加法的指令,這時,它就需要指示 CPU 完成加法操作。而 CPU 與内存協作進行加法計算,又會使用寄存器存放數值、内存堆棧保存執行的命令和變量。同時,計算機裡還有被打開的文件,以及各種各樣的 I/O 設備在不斷地調用中修改自己的狀态。

就這樣,一旦“程序”被執行起來,它就從磁盤上的二進制文件,變成了計算機内存中的數據、寄存器裡的值、堆棧中的指令、被打開的文件,以及各種設備的狀态信息的一個集合。像這樣一個程序運起來後的計算機執行環境的總和,就是我們今天的主角:進程。

所以,對于進程來說,它的靜态表現就是程序,平常都安安靜靜地待在磁盤上;而一旦運行起來,它就變成了計算機裡的數據和狀态的總和,這就是它的動态表現。

容器技術的核心功能,就是通過約束和修改進程的動态表現,從而為其創造出一個“邊界”。

對于 Docker 等大多數 Linux 容器來說,Cgroups 技術是用來制造約束的主要手段,而Namespace 技術則是用來修改進程視圖的主要方法。

你可能會覺得 Cgroups 和 Namespace 這兩個概念很抽象,别擔心,接下來我們一起動手實踐一下,你就很容易理解這兩項技術了。

假設你已經有了一個 Linux 操作系統上的 Docker 項目在運行,比如我的環境是 Ubuntu 16.04 和 Docker CE 18.05。

接下來,讓我們首先創建一個容器來試試。

複制代碼

$ docker run -it busybox /bin/sh / #

這個命令是 Docker 項目最重要的一個操作,即大名鼎鼎的docker run

而 -it 參數告訴了 Docker 項目在啟動容器後,需要給我們分配一個文本輸入 / 輸出環境,也就是 TTY,跟容器的标準輸入相關聯,這樣我們就可以和這個 Docker 容器進行交互了。而 /bin/sh 就是我們要在 Docker 容器裡運行的程序。

所以,上面這條指令就是:請幫我啟動一個容器,在容器裡執行 /bin/sh,并給我分配一個命令行終端跟這個容器交互。

這樣,我的 Ubuntu 16.04 機器就變成了一個宿主機,一個運行着 /bin/sh 的容器,就跑在了這個宿主機裡面。

此時,如果我們在容器裡執行一下 ps 指令,就會發現一些更有趣的事情:

複制代碼

/ # ps PID USER TIME COMMAND 1 root 0:00 /bin/sh 10 root 0:00 ps

可以看到,我們在 Docker 裡最開始執行的 /bin/sh,就是這個容器内部的第 1 号進程(PID=1),而這個容器裡一共隻有兩個進程在運行。這就意味着,前面執行的 /bin/sh,以及我們剛剛執行的 ps,已經被 Docker 隔離在了一個跟宿主機完全不同的世界當中。

本來,每當我們在宿主機上運行了一個 /bin/sh 程序,操作系統都會給它分配一個進程編号,比如 PID=100。這個編号是進程的唯一标識,就像員工的工牌一樣。所以 PID=100,可以粗略地理解為這個 /bin/sh 是我們公司裡的第 100 号員工,而第 1 号員工就自然是比爾 · 蓋茨這樣統領全局的人物。

而現在,我們要通過 Docker 把這個 /bin/sh 程序運行在一個容器當中。這時候,Docker 就會在這個第 100 号員工入職時給他施一個“障眼法”,讓他永遠看不到前面的其他 99 個員工,更看不到比爾 · 蓋茨。這樣,他就會錯誤地以為自己就是公司裡的第 1 号員工。

這種機制,其實就是對被隔離應用的進程空間做了手腳,使得這些進程隻能看到重新計算過的進程編号,比如 PID=1。可實際上,他們在宿主機的操作系統裡,還是原來的第 100 号進程。

這種技術,就是 Linux 裡面的 Namespace 機制。而 Namespace 的使用方式也非常有意思:它其實隻是 Linux 創建新進程的一個可選參數。我們知道,在 Linux 系統中創建線程的系統調用是 clone(),比如:

複制代碼

int pid = clone(main_function, stack_size, SIGCHLD, NULL);

這個系統調用就會為我們創建一個新的進程,并且返回它的進程号 pid。

而當我們用 clone() 系統調用創建一個新進程時,就可以在參數中指定 CLONE_NEWPID 參數,比如:

複制代碼

int pid = clone(main_function, stack_size, CLONE_NEWPID | SIGCHLD, NULL);

這時,新創建的這個進程将會“看到”一個全新的進程空間,在這個進程空間裡,它的 PID 是 1。之所以說“看到”,是因為這隻是一個“障眼法”,在宿主機真實的進程空間裡,這個進程的 PID 還是真實的數值,比如 100。

當然,我們還可以多次執行上面的 clone() 調用,這樣就會創建多個 PID Namespace,而每個 Namespace 裡的應用進程,都會認為自己是當前容器裡的第 1 号進程,它們既看不到宿主機裡真正的進程空間,也看不到其他 PID Namespace 裡的具體情況。

除了我們剛剛用到的 PID Namespace,Linux 操作系統還提供了 Mount、UTS、IPC、Network 和 User 這些 Namespace,用來對各種不同的進程上下文進行“障眼法”操作。

比如,Mount Namespace,用于讓被隔離進程隻看到當前 Namespace 裡的挂載點信息;Network Namespace,用于讓被隔離進程看到當前 Namespace 裡的網絡設備和配置。

這,就是 Linux 容器最基本的實現原理了。

容器是什麼概念(到底是怎麼一回事兒)3

所以,Docker 容器這個聽起來玄而又玄的概念,實際上是在創建容器進程時,指定了這個進程所需要啟用的一組 Namespace 參數。這樣,容器就隻能“看”到當前 Namespace 所限定的資源、文件、設備、狀态,或者配置。而對于宿主機以及其他不相關的程序,它就完全看不到了。

所以說,容器,其實是一種特殊的進程而已。

談到為“進程劃分一個獨立空間”的思想,相信你一定會聯想到虛拟機。而且,你應該還看過一張虛拟機和容器的對比圖。

容器是什麼概念(到底是怎麼一回事兒)4

這幅圖的左邊,畫出了虛拟機的工作原理。其中,名為 Hypervisor 的軟件是虛拟機最主要的部分。它通過硬件虛拟化功能,模拟出了運行一個操作系統需要的各種硬件,比如 CPU、内存、I/O 設備等等。然後,它在這些虛拟的硬件上安裝了一個新的操作系統,即 Guest OS。

這樣,用戶的應用進程就可以運行在這個虛拟的機器中,它能看到的自然也隻有 Guest OS 的文件和目錄,以及這個機器裡的虛拟設備。這就是為什麼虛拟機也能起到将不同的應用進程相互隔離的作用。

而這幅圖的右邊,則用一個名為 Docker Engine 的軟件替換了 Hypervisor。這也是為什麼,很多人會把 Docker 項目稱為“輕量級”虛拟化技術的原因,實際上就是把虛拟機的概念套在了容器上。

可是這樣的說法,卻并不嚴謹。

在理解了 Namespace 的工作方式之後,你就會明白,跟真實存在的虛拟機不同,在使用 Docker 的時候,并沒有一個真正的“Docker 容器”運行在宿主機裡面。Docker 項目幫助用戶啟動的,還是原來的應用進程,隻不過在創建這些進程時,Docker 為它們加上了各種各樣的 Namespace 參數。

這時,這些進程就會覺得自己是各自 PID Namespace 裡的第 1 号進程,隻能看到各自 Mount Namespace 裡挂載的目錄和文件,隻能訪問到各自 Network Namespace 裡的網絡設備,就仿佛運行在一個個“容器”裡面,與世隔絕。

不過,相信你此刻已經會心一笑:這些不過都是“障眼法”罷了。

,

更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!

查看全部

相关圖文资讯推荐

热门圖文资讯推荐

网友关注

Copyright 2023-2024 - www.tftnews.com All Rights Reserved