快速理解容器技術的實現原理

libinfs發表於2023-02-10
Photo by Elaine Casap on Unsplash
本文核心內容整理自 Brian Holt 的 workshop 《Complete-Intro-Containers》 。

與 Docker 類似的容器技術並不是作業系統與生俱來的能力,而是透過組合一些 Linux 特性,實現程式組隔離的一種技術。

本篇文章將從介紹容器技術的發展開始,進而說明哪些 Linux 特性組成了容器技術的核心部分。希望您能夠藉由閱讀本篇文章,對 Docker 等容器技術有更深刻的認識。

1. 為什麼我們需要容器

容器技術並不是憑空出現的,它來源自時代發展中人們對於如何更高效地利用計算機資源的思考和工程實踐,在本章中,我將遍歷容器技術出現之前的各時代,幫助您理解容器技術究竟解決了什麼樣的問題。

1.1 裸機時代

網際網路服務早期,想要架設 Web 伺服器,就需要租用某個地方的伺服器裝置,執行程式程式碼。只要有充足且稱職的人員維護,就能最大限度的發揮伺服器效能。

裸機時代的問題在於擴充套件服務極度缺乏靈活性:如果想要新增裝置,就需要找伺服器供應商(Dell 或 IBM)購買新的物理裝置,並指派專業人員進行安裝,除錯,啟動,這大概需要一兩個月的時間。

並且,當部署好一個伺服器叢集,作業系統與驅動的升級,硬體的替換與維修,網路問題的修復,線材的整理,機房管理許可權的設定,資料中心溫度的控制以及電費與 IPS 費用的支付...等等這些都需要專業的團隊去處理。

1.2 虛擬機器時代

於是我們進入了虛擬機器時代,虛擬機器是介於使用者與硬體裝置之間的一層抽象。一開始,相較於裸機時代,一臺計算機服務於單一的使用者主體,現在一臺計算機允許多個使用者主體登入,使用計算資源執行彼此的服務。只要裝置效能充足,使用者便可以在需要時快速新增新服務。這使得我們獲得了一些服務擴充套件的靈活性。

但在這種模式下存在著一些問題:

  1. 任何使用者都有許可權獲取其他使用者服務儲存的資料;
  2. 使用者可以透過投放 Fork Bomb(見下方說明) 的方式,掠奪伺服器資源;
  3. 一臺物理裝置上的任何租戶都可能無意間使整個伺服器崩潰;

為瞭解決這一問題,出現了虛擬機器技術:即當使用者建立服務時,在計算機的主作業系統上安裝新的作業系統排程硬體資源以達成資料隔離的目標。並且當一個服務崩潰時,最多導致服務所屬的作業系統崩潰,伺服器裝置上的其餘租戶將不受影響。

虛擬機器技術的弊端在於在主作業系統中執行其他作業系統所帶來的效能損耗。但只要計算機擁有充足的算力和記憶體,這些效能損耗都可以被接受。

Fork Bomb 是一種透過不斷生成子程式,以達到佔用大量系統資源的目的,從而導致系統無法正常工作,甚至停止響應的攻擊手段。它通常透過在作業系統中建立大量程式,以消耗系統記憶體和處理器資源,並導致系統崩潰。

1.3 公有云時代

透過 Microsoft Azure,Amazon Web Services 或阿里雲等公有云服務提供商提供的虛擬機器服務,使用者不再需要管理昂貴且複雜的資料中心,只需要管理自己的應用程式。雲服務廠商雖然不會幫助使用者更新作業系統,但是會定期更新伺服器裝置。

但在這種模式下,虛擬機器提供商向使用者提供的,本質上仍然是計算機的硬體裝置(CPU 和記憶體),使用者仍然需要支付排程,維護整個作業系統的開銷(例如網路管理,安裝與更新軟體等),這又需要專業的技術人員負責。

如果能夠幫助使用者節省掉維護作業系統的開銷,讓應用程式直接執行,那就太棒了。這種需求催生了下個時代的到來。

1.4 容器時代

容器技術為使用者提供了許多虛擬機器安全和資源管理的功能,但節省掉了執行整個作業系統的成本。它透過以下三個 Linux 命令成功將程式組之間彼此隔離:

  • chroot:實現目錄級別的資源隔離;
  • unshare:實現程式級別的資源隔離;
  • cgroup:限制隔離環境中可排程的資源大小;

下面我們將詳細介紹這三個命令。

2. 實現容器技術的三個關鍵 Linux 命令

2.1 chroot命令

chroot 是一個 Linux 命令,允許為一個新程式建立根目錄。當為一個容器設定一個新目錄後,容器內的程式將無法訪問到任何根目錄外的資料,這就消除了資料洩露的安全隱患。

執行以下命令開始實踐:

  1. docker run -it --name docker-host --rm --privileged ubuntu:bionic

命令解析:

  • docker run:在容器中執行一些命令;
  • -it:令 shell 保持可互動狀態;
  1. 建立新目錄並進入: mkdir /my-new-root && cd $_
  2. 建立一些秘密檔案:echo "my super secret thing" >> /my-new-root/secret.txt
  3. 執行命令:chroot /my-new-root bash

此時,程式會報錯:

chroot: failed to run command 'bash': No such file or directory

這是因為新的根目錄 /my-new-root 內並未包含 bash程式,執行以下命令修復:

  1. mkdir /my-new-root/bin
  2. cp /bin/bash /bin/ls /my-new-root/bin/
  3. chroot /my-new-root bash

此時程式依然會報錯,因為我們尚未安裝 bash的依賴。(透過 ldd命令可檢視):

linux-vdso.so.1 (0x00007ffe5705a000)
libtinfo.so.5 => /lib/x86_64-linux-gnu/libtinfo.so.5 (0x00007fb89f047000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fb89ee43000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fb89ea52000)
/lib64/ld-linux-x86-64.so.2 (0x00007fb89f58b000)

接著執行以下命令:

  1. mkdir /my-new-root/lib{,64}
  2. 將 bash 的依賴項複製至新建的根目錄:

    • cp /lib/x86_64-linux-gnu/libtinfo.so.5 /lib/x86_64-linux-gnu/libdl.so.2 /lib/x86_64-linux-gnu/libc.so.6 /my-new-root/lib
    • cp /lib64/ld-linux-x86-64.so.2 /my-new-root/lib64
  3. ls 的依賴項如法炮製的安裝:cp /lib/x86_64-linux-gnu/libselinux.so.1 /lib/x86_64-linux-gnu/libpcre.so.3 /lib/x86_64-linux-gnu/libpthread.so.0 /my-new-root/lib

此時,執行 chroot /my-new-root bash命令將成功執行。在 bash shell 中使用 pwd命令可見,當前根目錄為 /。至此,我們完成了目錄級別的資源隔離。

2.2 unshare命令

chroot命令使作業系統可以使使用者彼此無法訪問目錄下的檔案,但使用者依然可以透過檢視系統程式瞭解計算機的執行情況。透過殺死程式,解除安裝檔案系統等手段,惡意使用者依然會對計算機的安全造成威脅。

2.2.1 chroot 命令的問題

  1. 開啟一個新的終端,並執行 docker exec -it docker-host bash 命令進入作業系統;
  2. 執行 tail -f /my-new-root/secret.txt & 命令,持久化一個後臺程式;
  3. 執行 ps 命令檢視程式 ID(PID);
  4. 在原先的終端中執行 kill <PID> 命令,可見終端 2 的持久化程式已經被殺死了;

由此可見,僅僅做到檔案系統的隔離是不夠的,因此需要透過 unshare命令,隱藏程式,讓程式之間彼此不透明。

2.2.2 unshare 命令

unshare命令將從父程式中建立一個獨立的名稱空間。程式碼操作如下:

exit # from our chroot'd environment if you're still running it, if not skip this

# install debootstrap
apt-get update -y
apt-get install debootstrap -y
debootstrap --variant=minbase bionic /better-root

# head into the new namespace'd, chroot'd environment
unshare --mount --uts --ipc --net --pid --fork --user --map-root-user chroot /better-root bash # this also chroot's for us
mount -t proc none /proc # process namespace
mount -t sysfs none /sys # filesystem
mount -t tmpfs none /tmp # filesystem

再重複一次我們剛才的實驗會發現,此時終端 #1 已經無法再訪問和殺死終端 #2 的持久化程式了。

2.3 cgroups命令

即使透過 chroot 命令隔離檔案系統,透過 unshare隔離程式,每個隔離環境依然可以訪問伺服器的所有物理資源,這使得當伺服器中的一個租戶執行大量計算佔滿計算資源時,其他租戶的服務將無以為繼。

這時候就需要用到 cgroups(control groups) 命令。它使得每個隔離單元只能夠有限地使用系統資源。

具體操作如下:

# outside of unshare'd environment get the tools we'll need here
apt-get install -y cgroup-tools htop

# create new cgroups
cgcreate -g cpu,memory,blkio,devices,freezer:/sandbox

# add our unshare'd env to our cgroup
ps aux # grab the bash PID that's right after the unshare one
cgclassify -g cpu,memory,blkio,devices,freezer:sandbox <PID>

# list tasks associated to the sandbox cpu group, we should see the above PID
cat /sys/fs/cgroup/cpu/sandbox/tasks

# show the cpu share of the sandbox cpu group, this is the number that determines priority between competing resources, higher is is higher priority
cat /sys/fs/cgroup/cpu/sandbox/cpu.shares

# kill all of sandbox's processes if you need it
# kill -9 $(cat /sys/fs/cgroup/cpu/sandbox/tasks)

# Limit usage at 5% for a multi core system
cgset -r cpu.cfs_period_us=100000 -r cpu.cfs_quota_us=$[ 5000 * $(getconf _NPROCESSORS_ONLN) ] sandbox

# Set a limit of 80M
cgset -r memory.limit_in_bytes=80M sandbox
# Get memory stats used by the cgroup
cgget -r memory.stat sandbox

# in terminal session #2, outside of the unshare'd env
htop # will allow us to see resources being used with a nice visualizer

# in terminal session #1, inside unshared'd env
yes > /dev/null # this will instantly consume one core's worth of CPU power
# notice it's only taking 5% of the CPU, like we set
# if you want, run the docker exec from above to get a third session to see the above command take 100% of the available resources
# CTRL+C stops the above any time

# in terminal session #1, inside unshare'd env
yes | tr \n x | head -c 1048576000 | grep n # this will ramp up to consume ~1GB of RAM
# notice in htop it'll keep the memory closer to 80MB due to our cgroup
# as above, connect with a third terminal to see it work outside of a cgroup

3. 小結

透過綜合使用 chrootunsharecgroups 命令,我們能夠基於作業系統,有效地建立一個隔離單元,隔離檔案系統,程式並設定計算機資源的使用上限。這就是容器技術的核心。它幫助使用者節省掉了維護作業系統的開銷,可以使使用者專注於應用程式的開發和部署。

希望各位讀者有所收穫,後會有期:)

相關文章