Docker容器實戰(五) - 特殊的進程!

  • 容器起於PaaS
  • Docker項目具備里程碑意義
  • Docker項目經過「容器鏡像」,解決應用打包這個根本難題

容器自己沒有價值,有價值的是「容器編排」

正由於如此,容器技術生態才爆發了一場關於「容器編排」的「戰爭」
而此次戰爭,最終以Kubernetes項目和CNCF社區的勝利而了結。docker

因此會以Docker和Kubernetes項目爲核心,爲你詳細介紹容器技術的各項實踐與其中的原理。網絡

容器,究竟是怎麼一回事兒?

容器實際上是一種沙盒技術
就是可以像一個集裝箱同樣,把你的應用「裝」起來的技術。這樣,應用與應用之間,就由於有了邊界而不至於相互干擾
而被裝進集裝箱的應用,也能夠被方便地搬來搬去,這不就是PaaS最理想的狀態嘛。spa

這兩個能力提及來簡單,但要用技術手段去實現它們,可能大多數人就無從下手了。
就先來講說這個操作系統

「邊界」的實現手段

如今要寫一個計算加法的程序
輸入來自於一個文件
輸出到另外一個文件中。命令行

因爲計算機只認識0和1,因此不管用哪一種語言編寫這段代碼,最後都須要經過某種方式翻譯成二進制文件,才能在計算機操做系統中運行起來。
而爲了可以讓這些代碼正常運行,咱們每每還要給它提供數據,好比加法程序所須要的輸入文件
這些數據加上代碼自己的二進制文件,放在磁盤上,就是咱們日常所說的一個「程序」,也叫代碼的可執行鏡像(executable image)
而後,咱們就能夠在計算機上運行這個「程序」了。線程

首先OS從「程序」中發現輸入數據保存在一個文件中,因此這些數據就被會加載到內存中待命
同時OS又讀取到了計算加法的指令,這時,它就須要指示CPU完成加法操做。而CPU與內存協做進行加法計算,又會使用寄存器存放數值、內存堆棧保存執行的命令和變量
同時,計算機裏還有被打開的文件,以及各類各樣的I/O設備在不斷地調用中修改本身的狀態翻譯

一旦「程序」被執行起來,它就從磁盤上的二進制文件,變成了計算機內存中的數據、寄存器裏的值、堆棧中的指令、被打開的文件,以及各類設備的狀態信息的一個集合
像這樣一個程序運起來後的計算機執行環境的總和,就是進程code

進程的靜態表現就是程序,日常都安安靜靜地待在磁盤上
而一旦運行起來,它就變成了計算機裏的數據和狀態的總和,這就是它的動態表現。blog

而容器技術的核心功能,就是經過約束和修改進程的動態表現,從而爲其創造出一個「邊界」
對於Docker等大多數Linux容器來講進程

  • Cgroups技術製造約束的主要手段
  • Namespace技術修改進程視圖的主要方法。

你可能會以爲Cgroups和Namespace這兩個概念很抽象,別擔憂,接下來咱們一塊兒動手實踐一下,你就很容易理解這兩項技術了。

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

接下來,讓咱們首先建立一個容器來試試。

$ docker run -it busybox /bin/sh

-it告訴了Docker項目在啓動容器後,須要給咱們分配一個文本輸入/輸出環境,也就是TTY,跟容器的標準輸入相關聯,這樣咱們就能夠和這個Docker容器進行交互了。而/bin/sh就是咱們要在Docker容器裏運行的程序。
請幫我啓動一個容器,在容器裏執行/bin/sh,而且給我分配一個命令行終端跟這個容器交互。

這樣機器就變成了一個宿主機,而一個運行着/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號員工

如今,咱們要經過Docker把這個/bin/sh程序運行在一個容器當中,Docker就會在這個第100號員工入職時給他施一個「障眼法」,讓他永遠看不到前面的其餘99個員工
這樣,他就會錯誤地覺得本身就是公司裏的第1號員工。

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

這種技術,就是Linux裏面的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容器最基本的實現原理

因此Docker容器是在建立容器進程時,指定了這個進程所須要啓用的一組Namespace參數
這樣,容器就只能「看」到當前Namespace所限定的資源、文件、設備、狀態,或者配置
而對於宿主機以及其餘不相關的程序,它就徹底看不到了。

因此容器,實際上是一種特殊的進程而已。

總結

談到爲「進程劃分一個獨立空間」的思想,相信你必定會聯想到虛擬機
你應該還看過一張虛擬機和容器的對比圖。


左邊虛擬機的工做原理
名爲Hypervisor的軟件是虛擬機最主要的部分,它經過硬件虛擬化功能,模擬出了運行一個操做系統須要的各類硬件,好比CPU、內存、I/O設備等等
而後,它在這些虛擬的硬件上安裝了一個新的操做系統,即Guest OS。

這樣,用戶的應用進程就能夠運行在這個虛擬的機器中,它能看到的天然也只有Guest OS的文件和目錄,以及這個機器裏的虛擬設備。這就是爲何虛擬機也能起到將不一樣的應用進程相互隔離的做用。

右邊,名爲Docker Engine的軟件替換了Hypervisor
這也是爲何,不少人會把Docker項目稱爲「輕量級」虛擬化技術的緣由
實際上就是把虛擬機的概念套在了容器

但是這樣的說法,卻並不嚴謹
跟真實存在的虛擬機不一樣,在使用Docker的時候,並無一個真正的「Docker容器」運行在宿主機裏面
Docker項目幫助用戶啓動的,仍是原來的應用進程,只不過在建立這些進程時,Docker爲它們加上了各類各樣的Namespace參數
這些進程就會以爲本身是各自PID Namespace裏的第1號進程,只能看到各自Mount Namespace裏掛載的目錄和文件,只能訪問到各自Network Namespace裏的網絡設備,就彷彿運行在一個個「容器」

參考

  • Github
  • docker官網
  • Docker實戰
  • 深刻剖析Kubernetes
相關文章
相關標籤/搜索