12.19 容器化技術實現原理

容器的本質

容器的本質是什麼?

容器的本質是一個進程

容器的本質是一個進程

容器的本質是一個進程


容器化技術實現原理

運行容器


進程與進程之間相互隔離造就了容器與容器互不影響的特性。在啟動一個容器(即創建一個進程時),通過 Namespace 技術實現容器的隔離、通過 Cgroups 來實現容器的資源控制。


Namespace

容器進程的創建通過 Linux 平臺下的 clone 方法創建,在調用該方法創建進程時,通過指定額外的 Namespace 參數,使得剛創建的進程屬於一個獨立的空間。int pid = clone(main_function, stack_size, CLONE_NEWPID | SIGCHLD, NULL)指定額外參數 CLONE_NEWPID 創建的新進程,有一個自己的 獨立進程空間,在這個空間裡,它的進程 ID 為 1。它既看不到其在宿主機的真正進程、也看不到其他容器的進程。其實這都是假象,創建的該進程在宿主機上是真實存在的,並且也受宿主機的管理和控制,也享受宿主機的資源。但在該進程內部,它處於一個獨立的空間,只看得到該進程一個資源,讓其誤以為自己處於一個密閉的 盒子 內。這這,就是 Linux 容器最基本的實現原理了!你可能也注意到了,雖然通過 Linux Namespace 技術實現了進程的相互隔離,但這種隔離機制只是為不同的容器進程指定不同的 namespace,但不同的容器進程其實在宿主機上是真實存在的,並且也使用相同的宿主機內核。這也是容器在和虛擬化技術相比下,隔離得不夠徹底的原因。

Cgroups

容器化技術實現原理

容器進程創建好後,若不進行其他處理,該進程運行時所消耗及佔用的資源(如 CPU、內存)等;是可以被其他宿主機進程或其他容器進程享用的。為了解決這個問題,Linux 容器設計中引入了 Cgroups 的概念。Linux Cgroups 的全稱是 Linux Control Group,它的主要作用就是限制一個進程能夠使用的資源上限(如 cpu、內存、網絡等)。在 Linux 中,Cgroups 給用戶暴露出來的操作接口是文件系統,即它以文件和目錄的方式組織在操作系統的 /sys/fs/cgroup 路徑下。 若你的系統中沒有掛載該目錄,那你需要自行 google 掛載。在該目錄下,你可以看到很多諸如 cpuset、cpu、 memory 這樣的子目錄,也叫子系統。每個目錄下面又有很多配置文件。如你可能在 cpu 目錄下看到諸如 cfs_period 和 cfs_quota 這樣的配置文件。而這兩個配置文件,是限制 CPU 使用率的關鍵配置項。這兩個參數需要組合使用,可以用來限制進程在長度為 cfs_period 的一段時間內,只能被分配到總量為 cfs_quota的 CPU 時間。其中 period 的默認值為 100 ms(毫秒),quota 的默認值為 -1,即不做限制。當修改 quota 的值為 20 ms 時,表示在 100ms 的時間範圍內,cpu 只能使用 20 ms。也就是說,cpu 的使用率最大為 20%。 上面說到,容器啟動後的進程我們需要用 Linux Cgroups 來限制其資源的訪問。接下來我們看看到底是如何進行控制的。我們在 /sys/fs/cgroup/cpu 目錄下創建一個文件夾:可以看到,我們創建的文件夾裡面已經被系統默認創建了一些配置文件。我們把這樣一個目錄叫做控制組,接下來修改這個控制組的 cpu使用率為 20% 。我們在命令行用一個死循環來測試,也使得該進程能佔用全部的 CPU。通過 top 參數,我們看到該進程 5451 的 cpu 使用率已經達到 99.3%;由於我們創建該進程時,沒有為其指定一個控制組時,他默認將享用全部的 cpu 及內存配置。接下老我們將該進程通過下面的命令加入到剛剛創建的 container 組中去。$ echo 5451 > /sys/fs/cgroup/cpu/container/tasks將改進程加入控制組後,改進程的 cpu 使用率一下就從原來的 100% 降到了 20% 左右。在 docker 中,我們可以在啟動容器的時候,指定 cpu 參數來控制該容器的資源佔用,如下所示,這樣啟動的 docker 容器,只能使用 20% 的 cpu。Docker run -it --cpu-period=100000 --cpu-quota=20000 ubuntu /bin/bash當然,通過 Linux Cgroups 還可以限制其他參數,如網絡、掛載點、內存等等。

Cgroups 允許對可用資源設置限制和約束。例如,您可以在一臺擁有 16 G 內存的計算機上創建一個 Namespace ,限制其內部進程可用內存為 1 GB。

到這,您可能已經猜到 Docker 的工作原理了。當您請求 Docker 運行容器時,Docker 會在您的計算機上設置一個資源隔離的環境。然後 Docker 會將打包的應用程序和關聯的文件複製到 Namespace 內的文件系統中,此時環境的配置就完成了。之後 Docker 會執行您指定的命令運行應用程序。

簡而言之,Docker 通過使用 Linux namespace 和 cgroup(以及其他一些命令)來協調配置容器,將應用程序文件複製到為容器分配的磁盤,然後運行啟動命令。Docker 還附帶了許多其他用於管理容器的工具,例如:列出正在運行的容器,停止容器,發佈容器鏡像等許多其他工具。

與虛擬機相比,容器更輕量且速度更快,因為它利用了 Linux 底層操作系統在隔離的環境中運行。虛擬機的 hypervisor 創建了一個非常牢固的邊界,以防止應用程序突破它,而容器的邊界不那麼強大。另一個區別是,由於 Namespace 和 Cgroups 功能僅在 Linux 上可用,因此容器無法在其他操作系統上運行。此時您可能想知道 Docker 如何在 macOS 或 Windows 上運行? Docker 實際上使用了一個技巧,並在非 Linux 操作系統上安裝 Linux 虛擬機,然後在虛擬機內運行容器。

讓我們利用目前為止學到的所有內容,從頭開始創建和運行 Docker 容器。如果你還沒有將 Docker 安裝在你的機器上,可以參考這裡[1]安裝 Docker。在這個示例中,我們將創建一個 Docker 容器,下載一個用 C語言寫的 Web 服務,編譯並運行它,然後使用瀏覽器訪問這個 Web 服務。

我們將從所有 Docker 項目開始的地方從創建一個 Dockerfile 開始。此文件描述瞭如何創建用於運行容器的 Docker 鏡像。既然我們還沒有聊到鏡像,那麼讓我們看一下鏡像的官方定義[2]:

鏡像是一個可執行包,其包含運行應用程序所需的代碼、運行時、庫、環境變量和配置文件,容器是鏡像的運行時實例。

簡單的講,當你要求 Docker 運行一個容器時,你必須給它一個包含如下內容的鏡像:

包含應用程序及其所有依賴的文件系統快照。


在我們的示例中,我們選擇 Alpine Linux 為基礎鏡像。當您在 Docker 中看到 “alpine” 時,它通常意味著一個精簡的基本鏡像。 Alpine Linux 鏡像大小隻有約為5 MB!

在您的計算機創建一個新目錄(例如 dockerprj),然後新建一個 Dockerfile 文件。

<code>umermansoor:dockerprj$ touch Dockerfile
1/<code>

將如下內容粘貼到 Dockerfile:

<code># Use Alpine Linux rootfs tarball to base our image on
FROM alpine:3.9

# Set the working directory to be '/home'
WORKDIR '/home'

# Setup our application on container's file system
RUN wget http://www.cs.cmu.edu/afs/cs/academic/class/15213-s00/www/class28/tiny.c \\
&& apk add build-base \\
&& gcc tiny.c -o tiny \\
&& echo 'Hello World' >> index.html

# Start the web server. This is container's entry point
CMD ["./tiny", "8082"]

# Expose port 8082
EXPOSE 8082
1234567891011121314151617/<code>

這個 Dockerfile 包含創建鏡像的內容說明。我們創建鏡像基於 Alpine Linux(rootfs tarball),並將工作目錄設置為 /home 。接下來下載,編譯並創建了一個用 C 編寫的簡單 Web 服務器的可執行文件,然後指定在運行容器時要執行的命令,並將容器端口 8082 暴露給主機。

現在,我們就可以構建鏡像了。在 Dockerfile 的同級目錄運行 docker build 命令:

<code>umermansoor:dockerprj$ docker build -t codeahoydocker .
/<code>

如果這個命令成功了,您將看到:

<code>Successfully tagged codeahoydocker:latest
/<code>

此時我們的鏡像就創建成功了,該鏡像主要包括:

  • 文件系統快照(Alpine Linux 和 我們安裝的 Web 服務)
  • 啟動命令(./tiny 8092)


既然成功構建了鏡像,那麼我們可以使用如下命令運行容器。

<code>umermansoor:dockerprj$ docker run -p 8082:8082 codeahoydocker:latest/<code>

讓我們瞭解下這裡發生了什麼。

通過 docker run 命令,我們請求 Docker 基於 codeahoydocker:latest 鏡像創建和啟動一個容器。-p 8082:8082 將本地的 8082 端口映射到容器的 8082 端口(容器內的 Web 服務器正在監聽 8082 端口上的連接)。打開你的瀏覽器並訪問 localhost:8082/index.html 。你將可以看到 Hello World 信息。


分享到:


相關文章: