Table of Contents
(6) freezer – 暫停/恢復cgroup中的task
systemd-nspawn 在輕量級容器中運行命令或操作系統
Docker背後的內核知識——cgroups資源限制
摘要
當我們談論Docker時,我們常常會聊到Docker的實現方式。很多開發者都會知道,Docker的本質實際上是宿主機上的一個進程,通過namespace實現了資源隔離,通過cgroup實現了資源限制,通過UnionFS實現了Copy on Write的文件操作。但是當我們再深入一步的提出,namespace和cgroup實現細節時,知道的人可能就所剩無幾了。本文在docker基礎研究工作中着重對內核的cgroup技術做了細緻的分析和梳理,希望能對讀者深入理解Docker有所幫助
正文
上一篇中,我們瞭解了Docker背後使用的資源隔離技術namespace,通過系統調用構建一個相對隔離的shell環境,也可以稱之爲一個簡單的“容器”。本文我們則要開始講解另一個強大的內核工具——cgroups。他不僅可以限制被namespace隔離起來的資源,還可以爲資源設置權重、計算使用量、操控進程啓停等等。在介紹完基本概念後,我們將詳細講解Docker中使用到的cgroups內容。希望通過本文,讓讀者對Docker有更深入的瞭解。
1. cgroups是什麼
cgroups(Control Groups)最初叫Process Container,由Google工程師(Paul Menage和Rohit Seth)於2006年提出,後來因爲Container有多重含義容易引起誤解,就在2007年更名爲Control Groups,並被整合進Linux內核。顧名思義就是把進程放到一個組裏面統一加以控制。官方的定義如下{![引自:https://www.kernel.org/doc/Documentation/cgroups/cgroups.txt]}。
cgroups是Linux內核提供的一種機制,這種機制可以根據特定的行爲,把一系列系統任務及其子任務整合(或分隔)到按資源劃分等級的不同組內,從而爲系統資源管理提供一個統一的框架。
通俗的來說,cgroups可以限制、記錄、隔離進程組所使用的物理資源(包括:CPU、memory、IO等),爲容器實現虛擬化提供了基本保證,是構建Docker等一系列虛擬化管理工具的基石。
對開發者來說,cgroups有如下四個有趣的特點:
* cgroups的API以一個僞文件系統的方式實現,即用戶可以通過文件操作實現cgroups的組織管理。
* cgroups的組織管理操作單元可以細粒度到線程級別,用戶態代碼也可以針對系統分配的資源創建和銷燬cgroups,從而實現資源再分配和管理。
* 所有資源管理的功能都以“subsystem(子系統)”的方式實現,接口統一。
* 子進程創建之初與其父進程處於同一個cgroups的控制組。
本質上來說,cgroups是內核附加在程序上的一系列鉤子(hooks),通過程序運行時對資源的調度觸發相應的鉤子以達到資源追蹤和限制的目的。
2. cgroups的作用
實現cgroups的主要目的是爲不同用戶層面的資源管理,提供一個統一化的接口。從單個進程的資源控制到操作系統層面的虛擬化。Cgroups提供了以下四大功能{![參照自:http://en.wikipedia.org/wiki/Cgroups]}。
- 資源限制(Resource Limitation):cgroups可以對進程組使用的資源總額進行限制。如設定應用運行時使用內存的上限,一旦超過這個配額就發出OOM(Out of Memory)。
- 優先級分配(Prioritization):通過分配的CPU時間片數量及硬盤IO帶寬大小,實際上就相當於控制了進程運行的優先級。
- 資源統計(Accounting): cgroups可以統計系統的資源使用量,如CPU使用時長、內存用量等等,這個功能非常適用於計費。
- 進程控制(Control):cgroups可以對進程組執行掛起、恢復等操作。
過去有一段時間,內核開發者甚至把namespace也作爲一個cgroups的subsystem加入進來,也就是說cgroups曾經甚至還包含了資源隔離的能力。但是資源隔離會給cgroups帶來許多問題,如PID在循環出現的時候cgroup卻出現了命名衝突、cgroup創建後進入新的namespace導致脫離了控制等等{![詳見:https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=a77aea92010acf54ad785047234418d5d68772e2]},所以在2011年就被移除了。
3. 術語表
- task(任務):cgroups的術語中,task就表示系統的一個進程。
- cgroup(控制組):cgroups 中的資源控制都以cgroup爲單位實現。cgroup表示按某種資源控制標準劃分而成的任務組,包含一個或多個子系統。一個任務可以加入某個cgroup,也可以從某個cgroup遷移到另外一個cgroup。
- subsystem(子系統):cgroups中的subsystem就是一個資源調度控制器(Resource Controller)。比如CPU子系統可以控制CPU時間分配,內存子系統可以限制cgroup內存使用量。
- hierarchy(層級樹):hierarchy由一系列cgroup以一個樹狀結構排列而成,每個hierarchy通過綁定對應的subsystem進行資源調度。hierarchy中的cgroup節點可以包含零或多個子節點,子節點繼承父節點的屬性。整個系統可以有多個hierarchy。
4. 組織結構與基本規則
大家在namespace技術的講解中已經瞭解到,傳統的Unix進程管理,實際上是先啓動init
進程作爲根節點,再由init
節點創建子進程作爲子節點,而每個子節點由可以創建新的子節點,如此往復,形成一個樹狀結構。而cgroups也是類似的樹狀結構,子節點都從父節點繼承屬性。
它們最大的不同在於,系統中cgroup構成的hierarchy可以允許存在多個。如果進程模型是由init
作爲根節點構成的一棵樹的話,那麼cgroups的模型則是由多個hierarchy構成的森林。這樣做的目的也很好理解,如果只有一個hierarchy,那麼所有的task都要受到綁定其上的subsystem的限制,會給那些不需要這些限制的task造成麻煩。
瞭解了cgroups的組織結構,我們再來了解cgroup、task、subsystem以及hierarchy四者間的相互關係及其基本規則{![參照自:https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Resource_Management_Guide/sec-Relationships_Between_Subsystems_Hierarchies_Control_Groups_and_Tasks.html]}。
- 規則1: 同一個hierarchy可以附加一個或多個subsystem。如下圖1,cpu和memory的subsystem附加到了一個hierarchy。
圖1 同一個hierarchy可以附加一個或多個subsystem -
規則2: 一個subsystem可以附加到多個hierarchy,當且僅當這些hierarchy只有這唯一一個subsystem。如下圖2,小圈中的數字表示subsystem附加的時間順序,CPU subsystem附加到hierarchy A的同時不能再附加到hierarchy B,因爲hierarchy B已經附加了memory subsystem。如果hierarchy B與hierarchy A狀態相同,沒有附加過memory subsystem,那麼CPU subsystem同時附加到兩個hierarchy是可以的。
圖2 一個已經附加在某個hierarchy上的subsystem不能附加到其他含有別的subsystem的hierarchy上 -
規則3: 系統每次新建一個hierarchy時,該系統上的所有task默認構成了這個新建的hierarchy的初始化cgroup,這個cgroup也稱爲root cgroup。對於你創建的每個hierarchy,task只能存在於其中一個cgroup中,即一個task不能存在於同一個hierarchy的不同cgroup中,但是一個task可以存在在不同hierarchy中的多個cgroup中。如果操作時把一個task添加到同一個hierarchy中的另一個cgroup中,則會從第一個cgroup中移除。在下圖3中可以看到,
httpd
進程已經加入到hierarchy A中的/cg1
而不能加入同一個hierarchy中的/cg2
,但是可以加入hierarchy B中的/cg3
。實際上不允許加入同一個hierarchy中的其他cgroup野生爲了防止出現矛盾,如CPU subsystem爲/cg1
分配了30%,而爲/cg2
分配了50%,此時如果httpd
在這兩個cgroup中,就會出現矛盾。
圖3 一個task不能屬於同一個hierarchy的不同cgroup -
規則4: 進程(task)在fork自身時創建的子任務(child task)默認與原task在同一個cgroup中,但是child task允許被移動到不同的cgroup中。即fork完成後,父子進程間是完全獨立的。如下圖4中,小圈中的數字表示task 出現的時間順序,當
httpd
剛fork出另一個httpd
時,在同一個hierarchy中的同一個cgroup中。但是隨後如果PID爲4840的httpd
需要移動到其他cgroup也是可以的,因爲父子任務間已經獨立。總結起來就是:初始化時子任務與父任務在同一個cgroup,但是這種關係隨後可以改變。
圖4 剛fork出的子進程在初始狀態與其父進程處於同一個cgroup
5. subsystem簡介
subsystem實際上就是cgroups的資源控制系統,每種subsystem獨立地控制一種資源,目前Docker使用如下八種subsystem,還有一種net_cls
subsystem在內核中已經廣泛實現,但是Docker尚未使用。他們的用途分別如下。
- blkio: 這個subsystem可以爲塊設備設定輸入/輸出限制,比如物理驅動設備(包括磁盤、固態硬盤、USB等)。
- cpu: 這個subsystem使用調度程序控制task對CPU的使用。
- cpuacct: 這個subsystem自動生成cgroup中task對CPU資源使用情況的報告。
- cpuset: 這個subsystem可以爲cgroup中的task分配獨立的CPU(此處針對多處理器系統)和內存。
- devices 這個subsystem可以開啓或關閉cgroup中task對設備的訪問。
- freezer 這個subsystem可以掛起或恢復cgroup中的task。
- memory 這個subsystem可以設定cgroup中task對內存使用量的限定,並且自動生成這些task對內存資源使用情況的報告。
- perf_event 這個subsystem使用後使得cgroup中的task可以進行統一的性能測試。{![perf: Linux CPU性能探測器,詳見https://perf.wiki.kernel.org/index.php/Main_Page]}
- *net_cls 這個subsystem Docker沒有直接使用,它通過使用等級識別符(classid)標記網絡數據包,從而允許 Linux 流量控制程序(TC:Traffic Controller)識別從具體cgroup中生成的數據包。
6. cgroups實現方式及工作原理簡介
(1)cgroups實現結構講解
cgroups的實現本質上是給系統進程掛上鉤子(hooks),當task運行的過程中涉及到某個資源時就會觸發鉤子上所附帶的subsystem進行檢測,最終根據資源類別的不同使用對應的技術進行資源限制和優先級分配。那麼這些鉤子又是怎樣附加到進程上的呢?下面我們將對照結構體的圖表一步步分析,請放心,描述代碼的內容並不多。
圖5 cgroups相關結構體一覽
Linux中管理task進程的數據結構爲task_struct
(包含所有進程管理的信息),其中與cgroup相關的字段主要有兩個,一個是css_set *cgroups
,表示指向css_set
(包含進程相關的cgroups信息)的指針,一個task只對應一個css_set
結構,但是一個css_set
可以被多個task使用。另一個字段是list_head cg_list
,是一個鏈表的頭指針,這個鏈表包含了所有的鏈到同一個css_set
的task進程(在圖中使用的迴環箭頭,均表示可以通過該字段找到所有同類結構,獲得信息)。
每個css_set
結構中都包含了一個指向cgroup_subsys_state
(包含進程與一個特定子系統相關的信息)的指針數組。cgroup_subsys_state
則指向了cgroup
結構(包含一個cgroup的所有信息),通過這種方式間接的把一個進程和cgroup聯繫了起來,如下圖6。
圖6 從task結構開始找到cgroup結構
另一方面,cgroup
結構體中有一個list_head css_sets
字段,它是一個頭指針,指向由cg_cgroup_link
(包含cgroup與task之間多對多關係的信息,後文還會再解釋)形成的鏈表。由此獲得的每一個cg_cgroup_link
都包含了一個指向css_set *cg
字段,指向了每一個task的css_set
。css_set
結構中則包含tasks
頭指針,指向所有鏈到此css_set
的task進程構成的鏈表。至此,我們就明白如何查看在同一個cgroup中的task有哪些了,如下圖7。
圖7 cglink多對多雙向查詢
細心的讀者可能已經發現,css_set
中也有指向所有cg_cgroup_link
構成鏈表的頭指針,通過這種方式也能定位到所有的cgroup,這種方式與圖1中所示的方式得到的結果是相同的。
那麼爲什麼要使用cg_cgroup_link
結構體呢?因爲task與cgroup之間是多對多的關係。熟悉數據庫的讀者很容易理解,在數據庫中,如果兩張表是多對多的關係,那麼如果不加入第三張關係表,就必須爲一個字段的不同添加許多行記錄,導致大量冗餘。通過從主表和副表各拿一個主鍵新建一張關係表,可以提高數據查詢的靈活性和效率。
而一個task可能處於不同的cgroup,只要這些cgroup在不同的hierarchy中,並且每個hierarchy掛載的子系統不同;另一方面,一個cgroup中可以有多個task,這是顯而易見的,但是這些task因爲可能還存在在別的cgroup中,所以它們對應的css_set
也不盡相同,所以一個cgroup也可以對應多個·css_set
。
在系統運行之初,內核的主函數就會對root cgroups
和css_set
進行初始化,每次task進行fork/exit時,都會附加(attach)/分離(detach)對應的css_set
。
綜上所述,添加cg_cgroup_link
主要是出於性能方面的考慮,一是節省了task_struct
結構體佔用的內存,二是提升了進程fork()/exit()
的速度。
圖8 css_set與hashtable關係
當task從一個cgroup中移動到另一個時,它會得到一個新的css_set
指針。如果所要加入的cgroup與現有的cgroup子系統相同,那麼就重複使用現有的css_set
,否則就分配一個新css_set
。所有的css_set
通過一個哈希表進行存放和查詢,如上圖8中所示,hlist_node hlist
就指向了css_set_table
這個hash表。
同時,爲了讓cgroups便於用戶理解和使用,也爲了用精簡的內核代碼爲cgroup提供熟悉的權限和命名空間管理,內核開發者們按照Linux 虛擬文件系統轉換器(VFS:Virtual Filesystem Switch)的接口實現了一套名爲cgroup
的文件系統,非常巧妙地用來表示cgroups的hierarchy概念,把各個subsystem的實現都封裝到文件系統的各項操作中。有興趣的讀者可以在網上搜索並閱讀VFS的相關內容,在此就不贅述了。
定義子系統的結構體是cgroup_subsys
,在圖9中可以看到,cgroup_subsys
中定義了一組函數的接口,讓各個子系統自己去實現,類似的思想還被用在了cgroup_subsys_state
中,cgroup_subsys_state
並沒有定義控制信息,只是定義了各個子系統都需要用到的公共信息,由各個子系統各自按需去定義自己的控制信息結構體,最終在自定義的結構體中把cgroup_subsys_state
包含進去,然後內核通過container_of
(這個宏可以通過一個結構體的成員找到結構體自身)等宏定義來獲取對應的結構體。
圖9 cgroup子系統結構體
(2)基於cgroups實現結構的用戶層體現
瞭解了cgroups實現的代碼結構以後,再來看用戶層在使用cgroups時的限制,會更加清晰。
在實際的使用過程中,你需要通過掛載(mount)cgroup
文件系統新建一個層級結構,掛載時指定要綁定的子系統,缺省情況下默認綁定系統所有子系統。把cgroup文件系統掛載(mount)上以後,你就可以像操作文件一樣對cgroups的hierarchy層級進行瀏覽和操作管理(包括權限管理、子文件管理等等)。除了cgroup文件系統以外,內核沒有爲cgroups的訪問和操作添加任何系統調用。
如果新建的層級結構要綁定的子系統與目前已經存在的層級結構完全相同,那麼新的掛載會重用原來已經存在的那一套(指向相同的css_set)。否則如果要綁定的子系統已經被別的層級綁定,就會返回掛載失敗的錯誤。如果一切順利,掛載完成後層級就被激活並與相應子系統關聯起來,可以開始使用了。
目前無法將一個新的子系統綁定到激活的層級上,或者從一個激活的層級中解除某個子系統的綁定。
當一個頂層的cgroup文件系統被卸載(umount)時,如果其中創建後代cgroup目錄,那麼就算上層的cgroup被卸載了,層級也是激活狀態,其後代cgoup中的配置依舊有效。只有遞歸式的卸載層級中的所有cgoup,那個層級纔會被真正刪除。
層級激活後,/proc
目錄下的每個task PID文件夾下都會新添加一個名爲cgroup
的文件,列出task所在的層級,對其進行控制的子系統及對應cgroup文件系統的路徑。
一個cgroup創建完成,不管綁定了何種子系統,其目錄下都會生成以下幾個文件,用來描述cgroup的相應信息。同樣,把相應信息寫入這些配置文件就可以生效,內容如下。
tasks
:這個文件中羅列了所有在該cgroup中task的PID。該文件並不保證task的PID有序,把一個task的PID寫到這個文件中就意味着把這個task加入這個cgroup中。cgroup.procs
:這個文件羅列所有在該cgroup中的線程組ID。該文件並不保證線程組ID有序和無重複。寫一個線程組ID到這個文件就意味着把這個組中所有的線程加到這個cgroup中。notify_on_release
:填0或1,表示是否在cgroup中最後一個task退出時通知運行release agent
,默認情況下是0,表示不運行。release_agent
:指定release agent執行腳本的文件路徑(該文件在最頂層cgroup目錄中存在),在這個腳本通常用於自動化umount
無用的cgroup。
除了上述幾個通用的文件以外,綁定特定子系統的目錄下也會有其他的文件進行子系統的參數配置。
在創建的hierarchy中創建文件夾,就類似於fork中一個後代cgroup,後代cgroup中默認繼承原有cgroup中的配置屬性,但是你可以根據需求對配置參數進行調整。這樣就把一個大的cgroup系統分割成一個個嵌套的、可動態變化的“軟分區”。
7. cgroups的使用方法簡介
(1)安裝cgroups工具庫
本節主要針對Ubuntu14.04版本系統進行介紹,其他Linux發行版命令略有不同,原理是一樣的。不安裝cgroups工具庫也可以使用cgroups,安裝它只是爲了更方便的在用戶態對cgroups進行管理,同時也方便初學者理解和使用,本節對cgroups的操作和使用都基於這個工具庫。
apt-get install cgroup-bin
安裝的過程會自動創建/cgroup
目錄,如果沒有自動創建也不用擔心,使用 mkdir /cgroup
手動創建即可。在這個目錄下你就可以掛載各類子系統。安裝完成後,你就可以使用lssubsys
(羅列所有的subsystem掛載情況)等命令。
說明:也許你在其他文章中看到的cgroups工具庫教程,會在/etc目錄下生成一些初始化腳本和配置文件,默認的cgroup配置文件爲/etc/cgconfig.conf
,但是因爲存在使LXC無法運行的bug,所以在新版本中把這個配置移除了,詳見:https://bugs.launchpad.net/ubuntu/+source/libcgroup/+bug/1096771。
(2)查詢cgroup及子系統掛載狀態
在掛載子系統之前,可能你要先檢查下目前子系統的掛載狀態,如果子系統已經掛載,根據第4節中講的規則2,你就無法把子系統掛載到新的hierarchy,此時就需要先刪除相應hierarchy或卸載對應子系統後再掛載。
- 查看所有的cgroup:
lscgroup
- 查看所有支持的子系統:
lssubsys -a
- 查看所有子系統掛載的位置:
lssubsys –m
- 查看單個子系統(如memory)掛載位置:
lssubsys –m memory
(3)創建hierarchy層級並掛載子系統
在組織結構與規則一節中我們提到了hierarchy層級和subsystem子系統的關係,我們知道使用cgroup的最佳方式是:爲想要管理的每個或每組資源創建單獨的cgroup層級結構。而創建hierarchy並不神祕,實際上就是做一個標記,通過掛載一個tmpfs{![基於內存的臨時文件系統,詳見:http://en.wikipedia.org/wiki/Tmpfs]}文件系統,並給一個好的名字就可以了,系統默認掛載的cgroup就會進行如下操作。
mount -t tmpfs cgroups /sys/fs/cgroup
其中-t
即指定掛載的文件系統類型,其後的cgroups
是會出現在mount
展示的結果中用於標識,可以選擇一個有用的名字命名,最後的目錄則表示文件的掛載點位置。
掛載完成tmpfs
後就可以通過mkdir
命令創建相應的文件夾。
mkdir /sys/fs/cgroup/cg1
再把子系統掛載到相應層級上,掛載子系統也使用mount命令,語法如下。
mount -t cgroup -o subsystems name /cgroup/name
其中 subsystems 是使用,
(逗號)分開的子系統列表,name 是層級名稱。具體我們以掛載cpu和memory的子系統爲例,命令如下。
mount –t cgroup –o cpu,memory cpu_and_mem /sys/fs/cgroup/cg1
從mount
命令開始,-t
後面跟的是掛載的文件系統類型,即cgroup
文件系統。-o
後面跟要掛載的子系統種類如cpu
、memory
,用逗號隔開,其後的cpu_and_mem
不被cgroup代碼的解釋,但會出現在/proc/mounts裏,可以使用任何有用的標識字符串。最後的參數則表示掛載點的目錄位置。
說明:如果掛載時提示mount: agent already mounted or /cgroup busy
,則表示子系統已經掛載,需要先卸載原先的掛載點,通過第二條中描述的命令可以定位掛載點。
(4)卸載cgroup
目前cgroup
文件系統雖然支持重新掛載,但是官方不建議使用,重新掛載雖然可以改變綁定的子系統和release agent
,但是它要求對應的hierarchy是空的並且release_agent會被傳統的fsnotify
(內核默認的文件系統通知)代替,這就導致重新掛載很難生效,未來重新掛載的功能可能會移除。你可以通過卸載,再掛載的方式處理這樣的需求。
卸載cgroup非常簡單,你可以通過cgdelete
命令,也可以通過rmdir
,以剛掛載的cg1爲例,命令如下。
rmdir /sys/fs/cgroup/cg1
rmdir
執行成功的必要條件是cg1下層沒有創建其它cgroup,cg1中沒有添加任何task,並且它也沒有被別的cgroup所引用。
cgdelete cpu,memory:/
使用cgdelete
命令可以遞歸的刪除cgroup及其命令下的後代cgroup,並且如果cgroup中有task,那麼task會自動移到上一層沒有被刪除的cgroup中,如果所有的cgroup都被刪除了,那task就不被cgroups控制。但是一旦再次創建一個新的cgroup,所有進程都會被放進新的cgroup中。
(5)設置cgroups參數
設置cgroups參數非常簡單,直接對之前創建的cgroup對應文件夾下的文件寫入即可,舉例如下。
- 設置task允許使用的cpu爲0和1.
echo 0-1 > /sys/fs/cgroup/cg1/cpuset.cpus
使用cgset
命令也可以進行參數設置,對應上述允許使用0和1cpu的命令爲:
cgset -r cpuset.cpus=0-1 cpu,memory:/
(6)添加task到cgroup
- 通過文件操作進行添加
echo [PID] > /path/to/cgroup/tasks
上述命令就是把進程ID打印到tasks中,如果tasks文件中已經有進程,需要使用">>"
向後添加。 -
通過
cgclassify
將進程添加到cgroupcgclassify -g subsystems:path_to_cgroup pidlist
這個命令中,subsystems
指的就是子系統(如果使用man命令查看,可能也會使用controllers表示),如果mount了多個,就是用","
隔開的子系統名字作爲名稱,類似cgset
命令。 -
通過
cgexec
直接在cgroup中啓動並執行進程cgexec -g subsystems:path_to_cgroup command arguments
command
和arguments
就表示要在cgroup中執行的命令和參數。cgexec
常用於執行臨時的任務。
(7)權限管理
與文件的權限管理類似,通過chown
就可以對cgroup文件系統進行權限管理。
chown uid:gid /path/to/cgroup
uid
和gid
分別表示所屬的用戶和用戶組。
8. subsystem配置參數用法
(1)blkio – BLOCK IO資源控制
- 限額類
限額類是主要有兩種策略,一種是基於完全公平隊列調度(CFQ:Completely Fair Queuing )的按權重分配各個cgroup所能佔用總體資源的百分比,好處是當資源空閒時可以充分利用,但只能用於最底層節點cgroup的配置;另一種則是設定資源使用上限,這種限額在各個層次的cgroup都可以配置,但這種限制較爲生硬,並且容器之間依然會出現資源的競爭。- 按比例分配塊設備IO資源
- blkio.weight:填寫100-1000的一個整數值,作爲相對權重比率,作爲通用的設備分配比。
- blkio.weight_device: 針對特定設備的權重比,寫入格式爲
device_types:node_numbers weight
,空格前的參數段指定設備,weight
參數與blkio.weight
相同並覆蓋原有的通用分配比。{![查看一個設備的device_types:node_numbers
可以使用:ls -l /dev/DEV
,看到的用逗號分隔的兩個數字就是。有的文章也稱之爲major_number:minor_number
。]}
- 控制IO讀寫速度上限
- blkio.throttle.read_bps_device:按每秒讀取塊設備的數據量設定上限,格式
device_types:node_numbers bytes_per_second
。 - blkio.throttle.write_bps_device:按每秒寫入塊設備的數據量設定上限,格式
device_types:node_numbers bytes_per_second
。 - blkio.throttle.read_iops_device:按每秒讀操作次數設定上限,格式
device_types:node_numbers operations_per_second
。 - blkio.throttle.write_iops_device:按每秒寫操作次數設定上限,格式
device_types:node_numbers operations_per_second
- blkio.throttle.read_bps_device:按每秒讀取塊設備的數據量設定上限,格式
- 針對特定操作(read, write, sync, 或async)設定讀寫速度上限
- blkio.throttle.io_serviced:針對特定操作按每秒操作次數設定上限,格式
device_types:node_numbers operation operations_per_second
- blkio.throttle.io_service_bytes:針對特定操作按每秒數據量設定上限,格式
device_types:node_numbers operation bytes_per_second
- blkio.throttle.io_serviced:針對特定操作按每秒操作次數設定上限,格式
- 統計與監控
以下內容都是隻讀的狀態報告,通過這些統計項更好地統計、監控進程的 io 情況。- blkio.reset_stats:重置統計信息,寫入一個int值即可。
- blkio.time:統計cgroup對設備的訪問時間,按格式
device_types:node_numbers milliseconds
讀取信息即可,以下類似。 - blkio.io_serviced:統計cgroup對特定設備的IO操作(包括read、write、sync及async)次數,格式
device_types:node_numbers operation number
- blkio.sectors:統計cgroup對設備扇區訪問次數,格式
device_types:node_numbers sector_count
- blkio.io_service_bytes:統計cgroup對特定設備IO操作(包括read、write、sync及async)的數據量,格式
device_types:node_numbers operation bytes
- blkio.io_queued:統計cgroup的隊列中對IO操作(包括read、write、sync及async)的請求次數,格式
number operation
- blkio.io_service_time:統計cgroup對特定設備的IO操作(包括read、write、sync及async)時間(單位爲ns),格式
device_types:node_numbers operation time
- blkio.io_merged:統計cgroup 將 BIOS 請求合併到IO操作(包括read、write、sync及async)請求的次數,格式
number operation
- blkio.io_wait_time:統計cgroup在各設備中各類型IO操作(包括read、write、sync及async)在隊列中的等待時間(單位ns),格式
device_types:node_numbers operation time
- blkio.*_recursive:各類型的統計都有一個遞歸版本,Docker中使用的都是這個版本。獲取的數據與非遞歸版本是一樣的,但是包括cgroup所有層級的監控數據。
(2) cpu – CPU資源控制
CPU資源的控制也有兩種策略,一種是完全公平調度 (CFS:Completely Fair Scheduler)策略,提供了限額和按比例分配兩種方式進行資源控制;另一種是實時調度(Real-Time Scheduler)策略,針對實時進程按週期分配固定的運行時間。配置時間都以微秒(µs)爲單位,文件名中用us
表示。
- CFS調度策略下的配置
- 設定CPU使用週期使用時間上限
- cpu.cfs_period_us:設定週期時間,必須與
cfs_quota_us
配合使用。 - cpu.cfs_quota_us :設定週期內最多可使用的時間。這裏的配置指task對單個cpu的使用上限,若
cfs_quota_us
是cfs_period_us
的兩倍,就表示在兩個核上完全使用。數值範圍爲1000 – 1000,000(微秒)。 - cpu.stat:統計信息,包含
nr_periods
(表示經歷了幾個cfs_period_us
週期)、nr_throttled
(表示task被限制的次數)及throttled_time
(表示task被限制的總時長)。
- 按權重比例設定CPU的分配
- cpu.shares:設定一個整數(必須大於等於2)表示相對權重,最後除以權重總和算出相對比例,按比例分配CPU時間。(如cgroup A設置100,cgroup B設置300,那麼cgroup A中的task運行25%的CPU時間。對於一個4核CPU的系統來說,cgroup A 中的task可以100%佔有某一個CPU,這個比例是相對整體的一個值。)
- RT調度策略下的配置
實時調度策略與公平調度策略中的按週期分配時間的方法類似,也是在週期內分配一個固定的運行時間。- cpu.rt_period_us :設定週期時間。
- cpu.rt_runtime_us:設定週期中的運行時間。
(3) cpuacct – CPU資源報告
這個子系統的配置是cpu
子系統的補充,提供CPU資源用量的統計,時間單位都是納秒。
1. cpuacct.usage:統計cgroup中所有task的cpu使用時長
2. cpuacct.stat:統計cgroup中所有task的用戶態和內核態分別使用cpu的時長
3. cpuacct.usage_percpu:統計cgroup中所有task使用每個cpu的時長
(4)cpuset – CPU綁定
爲task分配獨立CPU資源的子系統,參數較多,這裏只選講兩個必須配置的參數,同時Docker中目前也只用到這兩個。
1. cpuset.cpus:在這個文件中填寫cgroup可使用的CPU編號,如0-2,16
代表 0、1、2和16這4個CPU。
2. cpuset.mems:與CPU類似,表示cgroup可使用的memory node
,格式同上
(5) device – 限制task對device的使用
- **設備黑/白名單過濾 **
- devices.allow:允許名單,語法
type device_types:node_numbers access type
;type
有三種類型:b(塊設備)、c(字符設備)、a(全部設備);access
也有三種方式:r(讀)、w(寫)、m(創建)。 - devices.deny:禁止名單,語法格式同上。
- devices.allow:允許名單,語法
- 統計報告
- devices.list:報告爲這個 cgroup 中的task設定訪問控制的設備
(6) freezer – 暫停/恢復cgroup中的task
只有一個屬性,表示進程的狀態,把task放到freezer所在的cgroup,再把state改爲FROZEN,就可以暫停進程。不允許在cgroup處於FROZEN狀態時加入進程。
* **freezer.state **,包括如下三種狀態:
– FROZEN 停止
– FREEZING 正在停止,這個是隻讀狀態,不能寫入這個值。
– THAWED 恢復
(7) memory – 內存資源管理
- 限額類
- memory.limit_in_bytes:強制限制最大內存使用量,單位有
k
、m
、g
三種,填-1
則代表無限制。 - memory.soft_limit_in_bytes:軟限制,只有比強制限制設置的值小時纔有意義。填寫格式同上。當整體內存緊張的情況下,task獲取的內存就被限制在軟限制額度之內,以保證不會有太多進程因內存捱餓。可以看到,加入了內存的資源限制並不代表沒有資源競爭。
- memory.memsw.limit_in_bytes:設定最大內存與swap區內存之和的用量限制。填寫格式同上。
- memory.limit_in_bytes:強制限制最大內存使用量,單位有
- 報警與自動控制
- memory.oom_control:改參數填0或1,
0
表示開啓,當cgroup中的進程使用資源超過界限時立即殺死進程,1
表示不啓用。默認情況下,包含memory子系統的cgroup都啓用。當oom_control
不啓用時,實際使用內存超過界限時進程會被暫停直到有空閒的內存資源。
- memory.oom_control:改參數填0或1,
- 統計與監控類
- memory.usage_in_bytes:報告該 cgroup中進程使用的當前總內存用量(以字節爲單位)
- memory.max_usage_in_bytes:報告該 cgroup 中進程使用的最大內存用量
- memory.failcnt:報告內存達到在
memory.limit_in_bytes
設定的限制值的次數 - memory.stat:包含大量的內存統計數據。
- cache:頁緩存,包括 tmpfs(shmem),單位爲字節。
- rss:匿名和 swap 緩存,不包括 tmpfs(shmem),單位爲字節。
- mapped_file:memory-mapped 映射的文件大小,包括 tmpfs(shmem),單位爲字節
- pgpgin:存入內存中的頁數
- pgpgout:從內存中讀出的頁數
- swap:swap 用量,單位爲字節
- active_anon:在活躍的最近最少使用(least-recently-used,LRU)列表中的匿名和 swap 緩存,包括 tmpfs(shmem),單位爲字節
- inactive_anon:不活躍的 LRU 列表中的匿名和 swap 緩存,包括 tmpfs(shmem),單位爲字節
- active_file:活躍 LRU 列表中的 file-backed 內存,以字節爲單位
- inactive_file:不活躍 LRU 列表中的 file-backed 內存,以字節爲單位
- unevictable:無法再生的內存,以字節爲單位
- hierarchical_memory_limit:包含 memory cgroup 的層級的內存限制,單位爲字節
- hierarchical_memsw_limit:包含 memory cgroup 的層級的內存加 swap 限制,單位爲字節
8. 總結
本文由淺入深的講解了cgroups的方方面面,從cgroups是什麼,到cgroups該怎麼用,最後對大量的cgroup子系統配置參數進行了梳理。可以看到,內核對cgroups的支持已經較爲完善,但是依舊有許多工作需要完善。如網絡方面目前是通過TC(Traffic Controller)來控制,未來需要統一整合;資源限制並沒有解決資源競爭,在各自限制之內的進程依舊存在資源競爭,優先級調度方面依舊有很大的改進空間。希望通過本文幫助大家瞭解cgroups,讓更多人蔘與到社區的貢獻中。
參考資料
- https://sysadmincasts.com/episodes/14-introduction-to-linux-control-groups-cgroups
- https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Resource_Management_Guide/index.html
- http://www.cnblogs.com/lisperl/archive/2013/01/14/2860353.html
- https://www.kernel.org/doc/Documentation/cgroups
- http://www.sel.zju.edu.cn/?p=573
libvirt虛擬化API
libvirt項目:
- 是用於管理虛擬化平臺的工具包
- 可從C,Python,Perl,Java等訪問
- 根據開源許可證獲得許可
- 支撐KVM, QEMU,Xen的, Virtuozzo的, VMware ESX的, LXC, BHyve和 更
- 針對Linux,FreeBSD,Windows和OS-X
- 被許多應用程序使用
最近/即將發佈的版本更改
Linux 容器工具 LXC
LXC 項目由一個 Linux 內核補丁和一些 userspace 工具組成。這些 userspace 工具使用由補丁增加的內核新特性,提供一套簡化的工具來維護容器。
容器可以提供輕量級的虛擬化,以便隔離進程和資源,而且不需要提供指令解釋機制以及全虛擬化的其他複雜性。本文循序漸進地介紹容器工具 Linux Containers(LXC)。
容器有效地將由單個操作系統管理的資源劃分到孤立的組中,以更好地在孤立的組之間平衡有衝突的資源使用需求。與虛擬化相比,這樣既不需要指令級模 擬,也不需要即時編譯。容器可以在覈心 CPU 本地運行指令,而不需要任何專門的解釋機制。此外,也避免了準虛擬化(paravirtualization)和系統調用替換中的複雜性。
通 過提供一種創建和進入容器的方式,操作系統讓應用程序就像在獨立的機器上運行一樣,但又能共享很多底層的資源。例如,可以有效地共享公共文件(比如 glibc)的頁緩存,因爲所有容器都使用相同的內核,而且所有容器還常常共享相同的 libc 庫(取決於容器配置)。這種共享常常可以擴展到目錄中其他不需要寫入內容的文件。
容器在提供隔離的同時,還通過共享這些資源節省開銷,這意味着容器比真正的虛擬化的開銷要小得多。
容 器技術早就出現。例如,Solaris Zones 和 BSD jails 就是非 Linux 操作系統上的容器。用於 Linux 的容器技術也有豐富的遺產,例如 Linux-Vserver、OpenVZ 和 FreeVPS。雖然這些技術都已經成熟,但是這些解決方案還沒有將它們的容器支持集成到主流 Linux 內核。
systemd-nspawn 在輕量級容器中運行命令或操作系統
版權聲明
本文譯者是一位開源理念的堅定支持者,所以本文雖然不是軟件,但是遵照開源的精神發佈。
- 無擔保:本文譯者不保證譯文內容準確無誤,亦不承擔任何由於使用此文檔所導致的損失。
- 自由使用:任何人都可以自由的閱讀/鏈接/打印此文檔,無需任何附加條件。
- 名譽權:任何人都可以自由的轉載/引用/再創作此文檔,但必須保留譯者署名並註明出處。
其他作品
本文譯者十分願意與他人分享勞動成果,如果你對我的其他翻譯作品或者技術文章有興趣,可以在如下位置查看現有的作品集:
聯繫方式
由於譯者水平有限,因此不能保證譯文內容準確無誤。如果你發現了譯文中的錯誤(哪怕是錯別字也好),請來信指出,任何提高譯文質量的建議我都將虛心接納。
- Email(QQ):70171448在QQ郵箱
名稱
systemd-nspawn — 在輕量級容器中運行命令或操作系統
大綱
systemd-nspawn
[OPTIONS...] [COMMAND
[ARGS...] ]
systemd-nspawn
--boot [OPTIONS...] [ARGS...]
描述
systemd-nspawn 可用於在一個輕量級的名字空間容器中運行一條命令、甚至一個操作系統。 它與 chroot(1) 很相似, 但是功能更爲強大:它能夠完全的虛擬化文件系統層次結構、進程樹、各種進程間通信(IPC)子系統、 主機名與域名。
systemd-nspawn 能夠 通過 --directory=
選項運行在任何包含操作系統的目錄樹上。通過使用 --machine=
選項, 可以自動在特定的位置(特別是保存容器鏡像的默認目錄 /var/lib/machines
) 搜索操作系統目錄樹。
與 chroot(1) 相比, systemd-nspawn 的強大之處還體現在它能夠在容器內啓動一個完整的Linux操作系統。
systemd-nspawn 會對容器內部的進程作如下限制:(1)僅允許以只讀方式訪問例如 /sys
, /proc/sys
, /sys/fs/selinux
這樣的內核接口。 (2)禁止修改主機的網絡接口以及系統時鐘。(3)禁止創建設備節點。 (4)禁止重啓主機操作系統。 (5)禁止加載內核模塊。
可以使用例如 dnf(8), debootstrap(8), pacman(8) 這樣的工具爲 systemd-nspawn 容器安裝一個操作系統目錄樹。 詳見後文的"例子"小節以瞭解更多有關如何使用這些命令的示例。
出於安全考慮, systemd-nspawn 會在啓動容器之前,首先檢查容器中是否存在 /usr/lib/os-release
或 /etc/os-release
文件(參見 os-release(5))。 因此對於那些不包含 os-release
文件的容器鏡像來說, 有必要手動添加 os-release
文件。
systemd-nspawn 既可以在交互式命令行上直接調用,也可以作爲系統服務在後臺運行。 當作爲後臺服務運行時,每一個容器實例就是一個以容器自己的名字命名的服務實例。 爲了簡化容器實例的創建,提供了一個默認的 [email protected]
模版單元, 它以容器的名字作爲單元實例的名字。注意,當 systemd-nspawn 被模版單元調用時, 命令行選項的默認值與從交互式命令行調用時的默認值是不一樣的。最重要的差別在於:模版單元會默認使用 --boot
選項, 而交互式命令行默認並不使用它。 其他的差別在後文對每一個命令行選項的詳細解釋中會有所說明。
可以使用 machinectl(1) 工具來管理容器。 它提供了許多方便的命令來基於 [email protected]
模版 以系統服務的方式運行容器。
對於每一個容器,都可以存在一個同名的 .nspawn
配置文件, 用於針對個別容器做特別的設置(詳見 systemd.nspawn(5) 手冊)。 配置文件中的設置可以覆蓋 [email protected]
模版的默認設置, 從而可以避免直接對模版進行修改。
注意, systemd-nspawn 會掛載專屬於容器內部私有的例如 /dev
, /run
之類的虛擬文件系統。 它們在容器之外不可見,並且隨着容器的退出而自動消亡。
注意,即使在同一個目錄上運行兩個 systemd-nspawn 容器,這兩個容器內的進程也是互相不可見的。 兩個容器的 PID 名字空間是完全互相隔離的,除了共享相同的底層文件系統之外, 兩個容器之間幾乎不再共享其他對象(除了幾個極個別的運行時對象)。 machinectl(1) 的 login 或 shell 命令可以 在一個正在運行的容器中打開一個額外的登錄會話。
systemd-nspawn 遵守 Container Interface 規範。
由 systemd-nspawn 啓動的容器將會被註冊到 systemd-machined.service(8) 服務中(如果它正在運行的話),並且被該服務持續跟蹤,此外,該服務還提供了與這些容器交互的編程接口。
選項
如果使用了 --boot
選項,那麼命令行參數(ARGS)將被原封不動的傳遞給 init 程序。 否則,將創建一個容器並在其中啓動 COMMAND
程序, 同時,命令行參數(ARGS)將被原封不動的傳遞給此程序。 如果既沒有使用 --boot
選項, 也沒有設置命令行參數(ARGS), 那麼將創建一個容器並在其中啓動一個 shell 。
可以使用的命令行選項(OPTIONS)如下:
-D
, --directory=
容器的 根文件系統目錄。
如果 --directory=
與 --image=
都沒有設置, 那麼將會搜索與容器名稱(也就是 --machine=
選項的值)同名的目錄, 並將其用作容器的根目錄。參見 machinectl(1) 手冊的"文件與目錄"小節,以瞭解具體的搜索路徑。
如果 --directory=
, --image=
, --machine=
都沒有設置,那麼將會把當前目錄用作容器的根目錄。 此選項不可與 --image=
同時使用。
--template=
容器根目錄的模版。必須是一個目錄(或 "btrfs
" 子卷)。 如果指定了此選項並且容器的根目錄(--directory=
)尚不存在, 那麼將會首先創建一個空的根目錄(或 "btrfs
" 快照), 然後將模版目錄中的內容原封不動的複製一份填充進去。 如果模版是一個 "btrfs
" 子卷的根, 那麼只需創建一個COW(copy-on-write)快照,即可瞬間完成容器根目錄的填充。 如果模版不是一個 "btrfs
" 子卷的根(甚至不是 "btrfs
" 文件系統), 那麼必須使用低效的複製操作(如果文件系統支持的話,也可能實際上是通過 copy-on-write 操作), 這樣就有可能需要花費大量的時間才能完成容器根目錄的填充。 此選項不可與 --image=
或 --ephemeral
同時使用。
注意, 模版中 "hostname", "machine ID" 之類的系統標識設置, 將會被原封不動的複製到容器根目錄去。
-x
, --ephemeral
以無痕模式運行容器。使用此選項之後,容器將會運行在文件系統的臨時快照之上, 當容器結束運行時,這個臨時快照將會被立即刪除,因此不會留下任何痕跡。 此選項不可與 --template=
同時使用。
注意,使用此選項啓動的容器, 原有的 "hostname", "machine ID" 之類的系統標識設置, 將會原封不動的保持不變。
-i
, --image=
容器的鏡像。 參數必須是一個普通文件或者塊設備節點。 指定的鏡像(普通文件或塊設備)將被掛載爲容器的根文件系統。 用作鏡像的文件或塊設備必須滿足如下條件:
-
僅包含一個 MBR 分區表, 其中僅含有一個 0x83 類型的分區(Linux), 並被標記爲引導分區(bootable)。
-
僅包含一個 GPT 分區表, 其中僅含有一個 0fc63daf-8483-4772-8e79-3d69d8477de4 類型的分區(Linux數據)。
-
僅包含一個 GPT 分區表, 其中僅含有一個根分區(將被掛載爲容器的根文件系統)。 可選的,還可以含有一個HOME分區(將被掛載到 /home 目錄)、 一個服務器數據分區(將被掛載到 /srv 目錄)。 所有這些分區的類型標記(GUID)必須遵守 Discoverable Partitions Specification 規範。
-
不包含任何分區表,整個文件或塊設備本身就是一個單純的文件系統(直接掛載爲容器的根文件系統)。
對於GPT鏡像,如果存在ESP(EFI System Partition)分區,並且根文件系統上的 /efi
目錄存在且爲空, 那麼ESP分區將會被自動掛載到 /efi
目錄。若 /efi
目錄不存在或不爲空, 並且根文件系統上的 /boot
目錄存在且爲空,那麼ESP分區將會被自動掛載到 /boot
目錄。
LUKS加密分區將會被自動解密。如果使用 --root-hash=
給出了GPT鏡像內受 dm-verity(數據完整性校驗技術)保護的分區的"root hash", 那麼將在掛載該分區之前自動校驗分區數據的正確性。
其他無關的分區(例如交換分區)將不會被掛載。 此選項不可與 --directory=
或 --template=
同時使用。
--root-hash=
用於校驗分區數據完整性(dm-verity)的"root hash"(十六進制字符串)。 如果容器鏡像中包含基於 dm-verity 技術的分區數據完整性元數據,此選項的值將用於校驗分區數據的正確性。 例如在使用 SHA256 算法的情況下,"root hash"一般是一個256位的二進制數(表現爲一個64字符的十六進制字符串)。 如果沒有使用此選項,但是容器的鏡像文件含有 "user.verity.roothash
" 擴展屬性(參見 xattr(7)), 那麼將從此擴展屬性中提取分區的"root hash"(十六進制字符串),並將其用於校驗分區數據的正確性。 如果容器的鏡像文件不含此擴展屬性,但是在鏡像文件的所在目錄中存在文件名相同且後綴名爲 .roothash
的文件, 那麼將會從此文件中讀取分區的"root hash"(十六進制字符串), 並將其用於校驗分區數據的正確性。
-a
, --as-pid2
以 PID=2 運行指定的程序。 如果既沒有使用此選項,也沒有使用 --boot
選項,那麼將以 PID=1 運行指定的程序(一般是 init 或 shell)。 注意,在UNIX系統上,PID=1 的進程(init)必須滿足一些特殊的要求, 例如,它必須能夠收集所有孤兒進程,還必須要實現與 sysvinit 兼容的信號處理器(特別是在收到 SIGINT 信號時重啓、 收到 SIGTERM 信號時重新執行、收到 SIGHUP 信號時重新加載配置、等等)。使用了 --as-pid2
選項之後, 將會以 PID=1 運行一個極度簡化的 init 進程,同時以 PID=2 運行指定的程序(不需要滿足 init 進程的特殊要求)。 這個極度簡化的 init 進程,僅能夠滿足UNIX系統對 init 進程的最低要求(收集孤兒進程以及處理 sysvinit 信號)。 強烈建議使用此選項在容器中運行絕大多數普通程序。 換句話說,除非運行的是能夠滿足UNIX系統 PID=1 進程的特殊要求的 init 或者 shell 程序, 否則應該明確使用此選項。 此選項不可與 --boot
同時使用。
-b
, --boot
自動搜索 init 程序並以 PID=1 運行它(而不是 shell 或用戶指定的程序)。 使用此選項之後,命令行上的參數(ARGS)將會被原封不動的傳遞給 init 程序。 此選項不可與 --as-pid2
同時使用。
下面的表格解釋了 不同調用模式之間的差異:
表 1. 調用模式
選項 | 解釋 |
---|---|
--as-pid2 與 --boot 都沒有使用 |
在容器中以 PID=1 運行 COMMAND 進程,並將 ARGS 原封不動的作爲命令行參數傳遞給 COMMAND 進程。 |
僅使用了 --as-pid2 |
首先在容器中以 PID=1 運行一個極度簡化的 init 進程,然後以 PID=2 運行 COMMAND 進程,並將 ARGS 原封不動的作爲命令行參數傳遞給 COMMAND 進程。 |
僅使用了 --boot |
自動搜索 init 程序並在容器中以 PID=1 運行它,同時將 ARGS 原封不動的作爲命令行參數傳遞給此 init 程序。 |
注意,在使用 [email protected]
模版的情況下, 將默認以 --boot
模式運行 systemd-nspawn 。
--chdir=
在容器內啓動進程之前,首先將工作目錄切換到此選項指定的目錄。 必須設爲一個以容器內的文件系統名字空間爲基準的絕對路徑。
--pivot-root=
在容器內將根目錄切換爲此選項指定的目錄,同時將容器內原來的根目錄卸載或掛載到其他目錄。 如果將此選項設爲一個單獨的路徑,那麼表示將容器的根目錄切換爲此選項指定的目錄,同時卸載原來的根目錄。 如果將此選項設爲冒號分隔的兩個路徑("新目錄:舊目錄"), 那麼表示將容器的根目錄切換爲"新目錄", 同時將容器內原來的根目錄掛載到"舊目錄"。 注意,所有的路徑都必須是以容器內的文件系統名字空間爲基準的絕對路徑。
此選項僅可用於包含多個不同啓動目錄的容器(例如包含多個 OSTree 部署的容器)。 此選項模擬了 initrd(initial RAM disk) 的功能:選取特定的目錄作爲根目錄, 並在完成根目錄的切換之後,再啓動 PID=1 的 init 進程。
-u
, --user=
進入容器之後, 將進程的用戶身份切換爲此選項指定的用戶(必須是在容器內確實存在的用戶)。 注意,此選項僅能用於預防某些粗心大意的操作可能造成的破壞, 它並非是一個特別堅固的安全特性, 可能無法抵擋某些精心設計的破壞。
-M
, --machine=
設置容器的名稱。 此名稱可以用於 在運行時引用該容器(例如 machinectl(1) 之類的工具)。 此外,該名稱還被用作容器的初始主機名(hostname)(容器啓動之後可以修改)。 如果未指定此選項,那麼將使用容器根目錄路徑的末尾部分, 同時, 如果使用了 --ephemeral
模式的話, 還可能會再加上一個隨機字符串後綴。 如果容器的根目錄就是主機的根目錄, 那麼容器的初始主機名將使用主機的主機名。
--hostname=
設置容器的初始主機名, 默認使用 --machine=
的值。 容器名(--machine=
)用於從外部標識容器, 而主機名(--hostname=
)則用於從內部標識容器自身。 爲了避免不必要的混淆,明智的做法是將兩者始終保持一致。 所以應該儘量避免使用此選項,僅使用 --machine=
即可。 注意,無論容器的初始主機名是 --hostname=
還是 --machine=
, 容器內的進程都可以在運行中對主機名進行修改。
--uuid=
設置容器的UUID("machine ID")。 容器的初始化系統將會使用此處的設置填充 /etc/machine-id
文件(如果確實不存在)。 注意,僅在容器中的 /etc/machine-id
確實不存在的情況下, 此選項纔有意義。
-S
, --slice=
將此容器添加到指定的 slice 單元中,而不是默認的 machine.slice
單元。 僅當此容器運行在自己的 scope 單元內時(也就是未使用 --keep-unit
選項), 此選項纔有意義。
--property=
爲容器所屬的 scope 單元設置一個單元屬性 僅當此容器運行在自己的 scope 單元內時(也就是未使用 --keep-unit
選項),此選項纔有意義。 屬性的賦值語法與 systemctl set-property 命令完全相同。 此選項主要用於修改容器的資源控制(例如內存限制)。
--private-users=
控制容器的用戶名字空間。啓用之後,容器將擁有自己私有的用戶與組(UID與GID), 容器內的私有 UID/GID(從 root 的 UID=0,GID=0 開始向上遞增) 將會被映射到宿主系統上一段未使用的 UID/GID 範圍(通常是高於 65536 的範圍)。 此選項可以接受如下幾種設置:
-
設爲一個或兩個正整數(冒號分隔),表示開啓用戶名字空間。 第一個數字表示宿主系統上分配給容器的 UID/GID 起點,第二個數字表示宿主系統上分配給容器的 UID/GID 數量。 如果省略第二個數字,那麼表示分配 65536 個。
-
使用了此選項但是未設置選項值,或者設爲布爾值 yes ,表示開啓用戶名字空間。 在這種情況下,爲容器分配的 UID/GID 數量是固定的 65536 個, 而 UID/GID 範圍的起點則是容器根目錄自身的 UID/GID 。 要使用此設置,必須確保:(1)在啓動容器之前先準備好完整的容器目錄樹內容; (2)目錄樹中的所有文件與目錄的 UID/GID 都沒有超出允許的範圍; (3)所有 ACL 中涉及的 UID/GID 都沒有超出允許的範圍; (4)容器根目錄的 UID/GID 必須是 65536 的整數倍。
-
未使用此選項,或者設爲布爾值 no ,表示徹底關閉用戶名字空間。
-
設爲特殊值 "
pick
" ,表示開啓用戶名字空間。 在這種情況下,爲容器分配的 UID/GID 數量是固定的 65536 個, 而 UID/GID 範圍的起點則按如下規則確定: 如果容器根目錄自身的 UID/GID 既未被宿主系統使用也未被其他容器使用, 那麼 UID/GID 範圍的起點就是容器根目錄自身的 UID/GID (這與上文的"yes"類似), 否則(也就是已被宿主或其他容器佔用), 將會自動在宿主系統 UID/GID 的 524288-1878982656 範圍內隨機選擇一個未被佔用的值作爲起點(必須是 65536 的整數倍)。 此設置隱含的設置了下文的--private-users-chown
選項, 從而可以確保容器中的文件和目錄的 UID/GID 不會超出自動選擇的範圍。 此設置讓用戶名字空間的行爲變得完全自動化。 在這種情況下,第一次啓動一個先前從未使用過的容器鏡像,可能會導致爲該容器重新分配 UID/GID 範圍, 從而導致非常耗時的調整 UID/GID 的操作。 不過以後再次啓動此容器就很輕鬆了(除非又遇到需要重新分配 UID/GID 範圍的情況)。
建議爲每個容器都分配 65536 個 UID/GID ,以完整覆蓋常用的16位 UID/GID 範圍。 出於安全考慮,切不可爲不同的容器分配重疊的 UID/GID 範圍。 應該將32位 UID/GID 的高16位用作容器標識符、低16位用作容器內的用戶標識符。 事實上, --private-users=pick
就隱含了這個規則。
當開啓了用戶名字空間之後,分配給每個容器的 GID 範圍將始終保持與 UID 範圍完全相同。
在絕大多數場合, --private-users=pick
都是首選的設置。 因爲它增強了容器的安全性並且在絕大多數場合都能自動完成必要的操作。
注意,被選中的 UID/GID 範圍並不會被記錄在 /etc/passwd
或 /etc/group
文件中(事實上根本不會記錄在任何地方)。 這個範圍僅在啓動容器的當時,根據容器根目錄的 UID/GID 進行推算。
注意,當啓用了用戶名字空間之後, 文件系統將會遵循容器與宿主之間的 UID/GID 映射規則。 這就意味着在容器與宿主之間來回複製文件的時候, 需要根據映射規則自動修改文件的 UID/GID 。
--private-users-chown
調整容器內所有文件與目錄的 UID/GID 與 ACL 以確保遵循容器與宿主之間的 UID/GID 映射規則(見上文)。 注意, 使用此選項有可能會導致巨大的性能損失。
--private-users=pick
隱含的設置了此選項。 如果關閉了用戶名字空間,那麼此選項將被忽略(不起任何作用)。
-U
如果內核支持用戶名字空間,那麼此選項等價於 --private-users=pick --private-users-chown
,否則等價於 --private-users=no
注意,在使用 [email protected]
模版的情況下, -U
是默認設置。
注意,如果想要在文件系統上撤銷 --private-users-chown
(或 -U
) 造成的影響,可以通過將容器的 UID/GIU 起點重置爲"0"來實現:
systemd-nspawn … --private-users=0 --private-users-chown
--private-network
將容器的網絡從宿主的網絡斷開。 這樣,在容器中,除了 loopback 設備、 --network-interface=
指定的設備、 --network-veth
配置的設備, 其他所有網絡接口都將變爲不可見。 使用此選項之後, CAP_NET_ADMIN capability 將被添加到容器現有的 capabilities 集合中。 當然,你也可以明確的使用 --drop-capability=
去掉它。 如果沒有明確設置此選項(但可能被其他選項隱含的設置了), 那麼該容器將能夠完全訪問宿主機的全部網絡。
--network-namespace-path=
接受一個內核網絡名字空間的文件路徑, 表示將該容器運行在指定的網絡名字空間中。 路徑應該指向一個 (可能是綁定掛載的)網絡名字空間文件(位於 /proc/$PID/ns/net
)。 一個典型的用法是指向 /run/netns/
目錄下一個由 ip-netns(8) 創建的網絡名字空間文件(例如 --network-namespace-path=/run/netns/foo
)。 注意,此選項不能與其他網絡選項(例如 --private-network
或 --network-interface=
)一起使用。
--network-interface=
爲容器分配指定的網絡接口。 這將會從宿主系統刪除指定的網絡接口, 並將其轉移到容器中。 當容器終止之後,此接口將會被交還給宿主系統。 注意, --network-interface=
隱含的設置了 --private-network
選項。 可以多次使用此選項 以分配多個網絡接口。
--network-macvlan=
爲指定的以太網接口創建一個 "macvlan
" 接口, 並將其添加到容器中。 所謂 "macvlan
" 接口, 是指在一個現有的物理以太網接口上添加一個新的MAC地址而創建的虛擬接口。 添加到容器內的這個 "macvlan
" 接口, 其名稱將由宿主系統內對應的以太網接口名稱再加上 "mv-
" 前綴組成。 注意,--network-macvlan=
隱含的設置了 --private-network
選項。 可以多次使用此選項以添加多個 "macvlan
" 接口。
--network-ipvlan=
爲指定的以太網接口創建一個 "ipvlan
" 接口, 並將其添加到容器中。 所謂 "ipvlan
" 接口, 是指在一個現有的物理以太網接口上添加一個新的IP地址而創建的虛擬接口。 添加到容器內的這個 "ipvlan
" 接口, 其名稱將由宿主系統內對應的以太網接口名稱再加上 "iv-
" 前綴組成。 注意,--network-ipvlan=
隱含的設置了 --private-network
選項。 可以多次使用此選項以添加多個 "ipvlan
" 接口。
-n
, --network-veth
在容器與宿主之間創建一個虛擬以太網連接("veth
")。 宿主端看到的以太網連接的名稱就是容器的名稱(也就是 --machine=
的值)再加上 "ve-
" 前綴。 容器端看到的以太網連接的名稱則是 "host0
" 。注意, --network-veth
隱含的設置了 --private-network
選項。
注意, systemd-networkd.service(8) 默認包含 /usr/lib/systemd/network/80-container-ve.network
, 此文件匹配所有通過該選項創建的虛擬以太網連接的宿主端接口, 此文件不但爲這些接口啓用了 DHCP 功能,而且還爲這些接口設置了通向宿主機外部網絡的路由(從而可以連通外網)。 該服務還默認包含 /usr/lib/systemd/network/80-container-host0.network
, 此文件匹配所有通過該選項創建的虛擬以太網連接的容器端接口,並且爲這些接口啓用了 DHCP 功能。 如果在宿主與容器內同時運行了 systemd-networkd
服務, 那麼無須額外的配置,即可自動實現在容器與宿主之間進行 IP 通信, 並且可以連接到外部網絡。
注意, --network-veth
是使用 [email protected]
模版時的默認選項。
--network-veth-extra=
在容器與宿主之間添加一個額外的虛擬以太網連接。 接受一對冒號分隔的網絡接口名稱("宿主網絡接口名稱:容器網絡接口名稱")。 如果省略了後一個名稱, 那麼表示使用同一個名稱來命名宿主和容器的網絡接口。 此選項與 --network-veth
沒有任何關係。 此選項不但可以多次使用,而且還可以明確的指定網絡連接的名稱。 注意, --network-bridge=
對於 --network-veth-extra=
創建的網絡連接沒有任何作用。
--network-bridge=
把 --network-veth
創建的虛擬以太網連接("veth
")的宿主端添加到此選項指定的以太網橋上。 接受一個有效的網橋設備的網絡接口名稱。 注意,--network-bridge=
隱含的設置了 --network-veth
選項。 如果使用了此選項,那麼太網連接的宿主端名稱將使用 "vb-
" 前綴(而不是 "ve-
" 前綴)。
--network-zone=
在容器中創建一個虛擬以太網連接("veth
"),並將其添加到一個自動管理的以太網橋上。 以太網橋的接口名稱就是此選項的值再加上 "vz-
" 前綴。 此網橋接口將在當第一個配置了此名稱的容器啓動時自動啓動, 並在最後一個配置了此名稱的容器退出時自動移除。 因此,使用此選項配置的網橋接口,只會在至少有一個引用了此網橋的容器處於運行狀態時纔會存在。 此選項與 --network-bridge=
很相似, 不同之處在於此選項所配置的網橋會被自動創建與刪除(無須人爲干預)。
使用此選項,可以方便的將一組相關的本地容器,添加到基於虛擬以太網的同一個廣播域(也就是同一子網)之中。 這樣的廣播域就被稱爲"區域"(zone)。每一個容器都只能是某個 zone 的一部分,而每個 zone 則可以包含多個容器。 zone 的名稱(也就是 --network-zone=
的值)可以自由選擇, 但是必須確保加上 "vz-
" 前綴之後,可以構成一個合法的網絡接口名稱。 使用了相同 --network-zone=
值的多個運行中的容器, 將會自動加入到同一個 zone 當中。
注意, systemd-networkd.service(8) 默認包含 /usr/lib/systemd/network/80-container-vz.network
, 此文件匹配所有通過該選項創建的網橋接口,此文件不但爲這些接口啓用了 DHCP 功能, 而且還爲這些接口設置了通向宿主機外部網絡的路由(從而可以連通外網)。 因此,在絕大多數情況下,通過使用 --network-zone=
選項,無須額外的配置, 即可自動實現將多個本地容器加入同一個廣播域(也就是同一子網)、並與宿主之間互相連通, 而且可以通過宿主機連接到外部網絡。
-p
, --port=
如果爲容器開啓了私有網絡, 那麼可以使用此選項在宿主的一個IP端口與容器的一個IP端口之間建立映射。 選項值必須滿足"[協議:]宿主端口[:容器端口]"格式。 其中的"協議"必須是 "tcp
" 或 "udp
" 、 "宿主端口"與"容器端口"都必須是一個 1-65535 之間的某個端口號。 如果省略"協議:"部分, 那麼等價於設爲"tcp:"; 如果省略":容器端口"部分, 那麼等價於使用與"宿主端口"相同的端口號。 注意,僅在容器確實使用了私有網絡的情況下,纔可以使用此選項。 也就是說僅在確實使用了例如 --network-veth
, --network-zone=
, --network-bridge=
這些選項的時候,纔可以使用此選項。
-Z
, --selinux-context=
用於容器內進程的 SELinux 安全上下文標籤。
-L
, --selinux-apifs-context=
用於容器內 虛擬內核文件系統中的文件的 SELinux 安全上下文標籤。
--capability=
給容器額外賦予指定的 capabilities 。 接受一個逗號分隔的 capabilities(7) 列表。容器默認擁有的 capabilities 集合包括: CAP_AUDIT_CONTROL, CAP_AUDIT_WRITE, CAP_CHOWN, CAP_DAC_OVERRIDE, CAP_DAC_READ_SEARCH, CAP_FOWNER, CAP_FSETID, CAP_IPC_OWNER, CAP_KILL, CAP_LEASE, CAP_LINUX_IMMUTABLE, CAP_MKNOD, CAP_NET_BIND_SERVICE, CAP_NET_BROADCAST, CAP_NET_RAW, CAP_SETFCAP, CAP_SETGID, CAP_SETPCAP, CAP_SETUID, CAP_SYS_ADMIN, CAP_SYS_BOOT, CAP_SYS_CHROOT, CAP_SYS_NICE, CAP_SYS_PTRACE, CAP_SYS_RESOURCE, CAP_SYS_TTY_CONFIG 。 此外,如果使用了 --private-network
選項,那麼將會自動包含 CAP_NET_ADMIN 。 特殊值 "all
" 表示賦予全部的 capabilities 。
--drop-capability=
從容器中 刪除指定的 capabilities 。 這將導致容器以少於默認 capabilities 集合(見上文)的方式 運行。
--no-new-privileges=
接受一個布爾值。爲容器中的進程設置 PR_SET_NO_NEW_PRIVS
標記的值。 默認值爲 no 。設爲 yes 表示禁止容器中的進程獲取新特權, 也就是,文件的 "setuid" 位以及文件系統 capabilities 將會失效。 詳見 prctl(2) 手冊。
--system-call-filter=
設置容器的系統調用過濾器。接受一個空格分隔的列表, 列表中的每一項都是一個系統調用名稱或系統調用組名稱(組名稱帶有 "@
" 前綴,可以使用 systemd-analyze(1) 的 syscall-filter 命令列出所有系統調用組名稱)。 如果列表前面帶有 "~
" 前綴,那麼表示黑名單列表(禁止容器使用列表中的系統調用), 否則表示白名單列表(允許容器使用列表中的系統調用)。 如果多次使用此選項,那麼表示將多個列表組合在一起。 如果某個系統調用既在白名單中又在黑名單中,那麼以黑名單爲準(也就是黑名單的優先級更高)。 注意,systemd-nspawn 始終隱含着一個默認的系統調用白名單, 此選項僅僅是在這個隱含的默認白名單基礎上利用白名單添加或利用黑名單刪除某些系統調用。 注意,容器的系統調用過濾器還隱含的受到 --capabilities= 命令行選項的影響。
--rlimit=
爲容器設置特定的 POSIX 資源限制。 可以接受兩種格式: (1) "
" (2) "LIMIT
=SOFT
:HARD
" 。其中, LIMIT
=VALUE
LIMIT
是一個資源限制類型(例如 RLIMIT_NOFILE
或 RLIMIT_NICE
)、 SOFT
與 HARD
分別是軟限制與硬限制的數值。 如果使用(2)格式,那麼 VALUE
將同時表示軟限制與硬限制的數值。 特殊值 "infinity
" 表示取消該資源類型的限制。 可以多次使用此選項,從而對多種類型的資源進行限制。 如果多次對同一種資源類型設置限制,那麼以最後一個設置爲準。 有關資源限制的更多詳情,參見 setrlimit(2) 手冊。 默認情況下,容器中 init 進程(PID=1) 的資源限制將被設置爲與 Linux 內核最初傳遞給宿主 init 進程的值相同。 注意,針對單個用戶設置的資源限制(特別是 RLIMIT_NPROC
),仍然會作用於該用戶的容器。這就意味着,除非使用 --private-users=
開啓了用戶名字空間, 否則,針對同一用戶設置的任何資源限制,都將作用於該用戶的所有本地容器與宿主進程。 因此,必須特別注意這種限制,因爲它們可能由不可信任的代碼設置。例如: "--rlimit=RLIMIT_NOFILE=8192:16384
"
--oom-score-adjust=
設置該容器的 OOM ("Out Of Memory") 計分調整值(也就是 /proc/self/oom_score_adj
的值)。 該值會影響因爲內存不足而需要殺死進程時,殺死該容器的優先級。詳見 proc(5) 手冊。 取值範圍是 -1000…1000 之間的一個整數(數值越大越容易被優先殺死)。
--cpu-affinity=
設置該容器的CPU關聯性。 接受一個逗號分隔的CPU編號與CPU範圍("編號下限-編號上限")的列表。詳見 sched_setaffinity(2) 手冊。
--kill-signal=
當 systemd-nspawn 自身收到 SIGTERM
信號時,要給容器內的 init 進程(PID=1)發送什麼信號(以觸發容器的有序關閉)。 如果使用了 --boot
選項, 那麼默認爲 SIGRTMIN+3
信號(對於與 systemd 兼容的 init 來說,表示正常有序的關閉)。 如果沒有使用 --boot
選項,那麼默認爲 SIGKILL
信號(粗暴的強制關閉)。 更多有關可用信號的解釋,可參見 signal(7) 手冊。
--link-journal=
通過軟連接或綁定掛載控制容器內的日誌對宿主系統的可見性。 若開啓,則表示允許從宿主系統查看容器內的日誌文件。 注意,宿主系統的日誌在容器內是永遠不可見的。 可將此選項設爲 "no
", "host
", "try-host
", "guest
", "try-guest
", "auto
" 之一。 "no
" 表示不作任何連接(不可見)。 "host
" 表示將容器的日誌文件直接存儲在宿主系統的 /var/log/journal/
目錄中, 也就是,將宿主系統上對應的日誌目錄綁定掛載到容器內對應的日誌目錄上。 "machine-id
guest
" 表示將容器的日誌文件直接存儲在容器自身的 /var/log/journal/
目錄中, 同時,在宿主系統的相同路徑上創建一個軟連接,並將其指向容器內對應的日誌目錄。 "machine-id
try-host
"/"try-guest
" 與 "host
"/"guest
" 類似, 不同之處在於:當宿主系統沒有啓用持久日誌時(/var/log/journal
不存在或不可寫),不會導致容器啓動失敗。 默認值 "auto
" 表示如果宿主系統的 /var/log/journal
目錄下與該容器對應的日誌目錄確實存在, 那麼就將其綁定掛載到容器內對應的日誌目錄上, 否則不作任何連接(相當於設爲 "no
")。 實際上,只要成功使用 "guest
" 或 "host
" 啓動過一次容器, 並且後來又使用 "auto
" 再次啓動該容器, 那麼就會在宿主系統的日誌目錄中創建該容器專用的持久日誌目錄。
注意,在使用 [email protected]
模版的情況下, --link-journal=try-guest
是默認設置。
-j
等價於 --link-journal=try-guest
--resolv-conf=
如何處理容器內的 /etc/resolv.conf
文件(也就是如何處理宿主系統與容器之間的DNS同步)。 可設爲 "off
", "copy-host
", "copy-static
", "bind-host
", "bind-static
", "delete
", "auto
" 之一。 "off
" 表示保持容器內的 /etc/resolv.conf
文件原封不動, 就好像此文件是容器鏡像內的固有文件一樣(既不修改它、也不使用綁定掛載覆蓋它)。 "copy-host
" 表示把宿主系統的 /etc/resolv.conf
直接複製到容器中。 "bind-host
" 表示把宿主系統的 /etc/resolv.conf
綁定掛載到容器中。 "copy-static
"/"bind-static
" 表示把 systemd-resolved.service(8) 提供的靜態 resolv.conf
文件直接複製/綁定掛載到容器中。 "delete
" 表示刪除容器內的 /etc/resolv.conf
文件(若存在)。 "auto
" 表示:(1)如果開啓了私有網絡(--private-network
),那麼設爲 "off
" ; (2)否則如果 systemd-resolved.service
可用,那麼設爲 "copy-static
"(可寫鏡像) 或 "bind-static
"(只讀鏡像); (3)否則設爲 "copy-host
"(可寫鏡像) 或 "copy-static
"(只讀鏡像)。 如果容器有可能自己修改 /etc/resolv.conf
文件,那麼應該使用 "copy-*
" 以確保與宿主系統分離(從而允許在容器內修改),否則應該使用 "bind-*
" 以確保無法在容器內修改 /etc/resolv.conf
文件(因爲使用了只讀綁定掛載)(不過如果容器具有足夠的特權, 有可能卸載掉已綁定的掛載)。注意,無論綁定掛載還是複製, 在完成一次性的早期初始化之後, 通常不會再進一步傳播配置(因爲該文件通常已經被複制與重命名)。 默認值爲 "auto
"
--timezone=
如何處理容器內的 /etc/localtime
文件(也就是如何處理宿主系統與容器之間的本地時區同步)。 可設爲 "off
", "copy
", "bind
", "symlink
", "delete
", "auto
" 之一。 "off
" 表示保持容器內的 /etc/localtime
文件原封不動, 就好像此文件是容器鏡像內的固有文件一樣(既不修改它、也不使用綁定掛載覆蓋它)。 "copy
" 表示把宿主系統的 /etc/localtime
直接複製到容器中。 "bind
" 表示把宿主系統的 /etc/localtime
綁定掛載到容器中。 "symlink
" 表示在容器內創建 /etc/localtime
軟連接, 並將其指向容器內對應於宿主時區的時區文件。 "delete
" 表示刪除容器內的 /etc/localtime
文件(若存在)。 "auto
" 表示如果宿主系統的 /etc/localtime
是軟連接,那麼設爲 "symlink
" ,否則設爲 "copy
"(可寫鏡像) 或 "bind
"(只讀鏡像)。 默認值爲 "auto
"
--read-only
以只讀模式 掛載容器的根文件系統。
--bind=
, --bind-ro=
將宿主機上的一個文件或目錄綁定掛載到容器內指定的目錄上。 如果參數是一個單獨的路徑,那麼它必須是一個目錄,表示從宿主系統綁定掛載到容器內的相同路徑上。 如果參數是冒號分隔的一對路徑("源路徑:目標路徑"),那麼第一個源路徑表示宿主系統上的源路徑(既可以是文件也可以是目錄)、 第二個目標路徑表示在容器內的掛載目錄(必須是目錄)。如果還想指定掛載選項,那麼可以使用"源路徑:目標路徑:掛載選項"這樣格式的參數。 如果在源路徑前加上 "+
" 字符前綴,那麼表示此源路徑是容器內的源路徑, 從而可以實現容器內部的綁定掛載。 如果源路徑是一個空字符串,那麼表示此源路徑實際上是宿主系統 /var/tmp
目錄下的一個臨時目錄,並且在容器關閉之後,此臨時目錄將會被自動刪除。 "掛載選項"是一系列逗號分隔的掛載選項,但實際上,目前僅允許使用 rbind
與 norbind
兩個選項,用於控制是否以遞歸的方式綁定掛載(默認是"rbind")。 如果要在路徑中包含冒號,那麼必須使用 "\:
" 進行轉義。 可以多次使用此選項以創建多個互不相關的綁定掛載點。 使用 --bind-ro=
表示僅創建只讀的綁定掛載點。
注意,當與 --private-users
一起使用時, 產生的掛載點將被 nobody
用戶所擁有。 這是因爲掛載點下的文件和目錄的用戶與組仍然位於宿主系統中,而非位於容器中, 因此在容器內就會呈現爲 UID 65534 (nobody)。 推薦使用 --bind-ro=
以只讀方式創建這種掛載點。
--tmpfs=
在容器內掛載一個 tmpfs 內存文件系統。 如果設爲一個單獨的絕對路徑, 那麼表示容器內的掛載點。 如果還想同時指定掛載選項, 那麼可以使用"掛載路徑:掛載選項"這樣格式的參數。 除非在掛載選項中專門進行了設置, 否則掛載點默認被 root/root 擁有,並且默認的訪問權限是 0755 。 此選項在啓動無狀態容器(也就是將 /var
掛載到內存),特別是與 --read-only
組合使用時,非常有用。 如果要在路徑中包含冒號, 那麼必須使用 "\:
" 進行轉義。
--overlay=
, --overlay-ro=
將多個目錄樹依次組合成一個 overlay 文件系統,並將其掛載到容器中。 接受一系列冒號分隔的目錄路徑列表,除了最後一個路徑表示容器內的掛載點之外, 前面的所有路徑都表示宿主機上用於組成 overlay 文件系統的一個個目錄樹(從左到右依次疊加)。
如果要在路徑中包含冒號, 那麼必須使用 "\:
" 進行轉義。
如果設置了三個或更多路徑, 那麼最後一個路徑表示容器內的掛載點, 前面的所有路徑都表示宿主機上用於組成 overlay 文件系統的一個個目錄樹(從左到右依次疊加)。 最左邊的路徑位於 overlay 文件系統最底層, 倒數第二個路徑位於 overlay 文件系統最上層。 使用 --overlay-ro=
表示創建只讀的 overlay 文件系統。 如果使用 --overlay=
創建了可讀寫的 overlay 文件系統, 那麼所有對 overlay 文件系統的寫入都將僅作用於最上層的目錄, 也就是倒數第二個路徑所在的目錄。
如果僅設置了兩個路徑, 那麼第二個路徑既是宿主機上 overlay 文件系統的最上層目錄, 同時又是容器內 overlay 文件系統的掛載點。 此二選項都要求必須設置至少兩個路徑。
如果在組成 overlay 文件系統的源路徑前面加上 "+
" 字符前綴,那麼表示此源路徑是容器內的源路徑。 如果將組成 overlay 文件系統的最上層目錄的源路徑設爲空字符串, 那麼表示此源路徑實際上是宿主系統 /var/tmp
目錄下的一個臨時目錄, 並且在容器關閉之後,此臨時目錄將會被自動刪除。 這個特性經常用於讓只讀的容器目錄在運行時變爲可寫。 例如,使用 "--overlay=+/var::/var
" 可以自動在只讀的 /var
目錄上疊加一個可寫的臨時目錄。
更多關於 overlay 文件系統的介紹,可參見 overlayfs.txt 文檔。 注意,overlay 文件系統與常規的文件系統在基本語義上有着很大的不同, 例如,進程在寫入一個文件的時候, 可能會看到該文件的所屬設備與 inode 信息在寫入前後發生了變化, 或者有時候進程會看到某個文件已經過期的老舊版本。 注意,因爲此選項會從最上層的目錄樹自動獲得 "workdir=
" 目錄的 overlay 文件系統掛載選項(原封不動的搬運過來), 再加上 "workdir=
" 必須與最上層的目錄樹位於同一個文件系統上, 所以,最上層的目錄樹本身其實並不是一個真正的掛載點。 還需要注意的是,"lowerdir=
" 目錄的掛載選項 來自於 此選項中的路徑棧(以反向順序排列)。
-E
, NAME
=VALUE
--setenv=
NAME
=VALUE
向容器內的 init 進程傳遞一個環境變量(以 "NAME=VALUE
" 格式)。 此選項既可以用於覆蓋一個已有環境變量的默認值,也可以用於添加一個新的環境變量。 可以多次使用此選項以添加多個環境變量。
--register=
控制是否將該容器註冊到 systemd-machined.service(8) 服務中。 接受一個布爾值,默認值爲 "yes
" 。 當容器中運行的是一個完整操作系統的時候(準確的說是當 PID=1 的進程是系統與服務管理器的時候),應該開啓此選項。 開啓此選項之後,該容器即可被 machinectl(1) 工具控制,並且被 ps(1) 工具顯示出來。 如果該容器內的 PID=1 進程不是服務管理器,那麼應該將此選項設爲 "no
" 。
--keep-unit
不是在一個臨時創建的 scope 單元中運行此容器, 而是直接在調用 systemd-nspawn 命令的 service 或 scope 單元中運行此容器。 如果同時還設置了 --register=yes
,那麼該 service 或 scope 單元將被註冊到 systemd-machined.service(8) 服務中。 如果從一個服務單元內部調用 systemd-nspawn 命令,那麼必須使用此選項, 並且該服務單元的唯一作用必須是僅僅只運行一個單獨的 systemd-nspawn 容器。 禁止在用戶會話環境中使用此選項。
注意,使用了 --keep-unit
選項之後, --slice=
與 --property=
將會失效。同時使用 --keep-unit
與 --register=no
可以防止將調用 systemd-nspawn 命令的 service 或 scope 單元註冊到 systemd-machined 中。
--personality=
設置 容器內 uname(2) 所報告的體系架構。目前僅支持 "x86
" 與 "x86-64
" 兩個值。 此選項對於在64位宿主機上運行32位容器來說纔有實際意義。 如果未設置此選項, 那麼將使用與宿主機相同的值。
-q
, --quiet
靜默模式。 也就是關閉狀態報告。 使用此選項之後, 將僅輸出容器內操作系統自身的控制檯內容。
--volatile
, --volatile=
MODE
控制以何種"易失"模式啓動容器。 --volatile
(等價於 --volatile=yes
) 表示以完全無狀態模式(完全隱私模式)啓動容器, 也就是將內存("tmpfs
")用作容器的根文件系統, 同時僅以只讀模式掛載容器鏡像內的 /usr
目錄, 這樣,將以只讀模式使用容器鏡像, 所有對容器內文件系統的修改都將在容器關閉後丟失。 --volatile=state
表示以常規方式掛載容器鏡像內的根目錄, 但是僅將 /var
掛載到內存中("tmpfs
"), 這樣,容器將以用戶自定義的配置啓動, 但是運行過程中的狀態都將在容器關閉之後丟失。 --volatile=no
是默認值, 表示以常規的讀寫模式掛載容器鏡像內的文件系統。
此選項爲容器提供了類似於宿主機 "systemd.volatile=
" 內核引導選項的功能。詳見 kernel-command-line(7) 手冊。
注意,要使用此選項,容器內的操作系統必須滿足以下條件:(1)可以在僅掛載 /usr
的條件下啓動;(2)可以自動填充 /var
目錄;(3)當 "--volatile=yes
" 時, 還必須能夠自動填充 /etc
目錄。
--settings=
MODE
控制 systemd-nspawn 是否搜索並使用專門針對每個容器的 .nspawn
配置文件。 接受一個布爾值或者特殊值 override
或 trusted
。
默認值 yes 表示在 /etc/systemd/nspawn/
與 /run/systemd/nspawn/
目錄中搜索與容器名稱(來自於 --machine=
選項或者容器鏡像目錄/文件的名稱)相同且後綴名爲 .nspawn
的配置文件並應用這些配置文件。 如果沒有找到對應的配置文件, 那麼將會進一步在容器鏡像文件的所在目錄、或容器根目錄的所在父目錄中搜索。 如果找到了對應的配置文件,那麼將會僅應用其中的非特權指令, 而所有特權指令都將被忽略。 注意,命令行上的設置比容器配置文件的優先級更高, 也就是 .nspawn
文件中的配置將會被命令行上的設置所覆蓋。 這裏所說的特權指令, 是指有可能造成權限提升或者要求訪問主機資源(例如主機的文件或目錄)的配置指令。 更多關於 .nspawn
文件的說明,參見 systemd.nspawn(5) 手冊。
override
與 yes 類似, 不同之處在於,容器配置文件的優先級將會高於命令行上的設置。 也就是 .nspawn
文件中的配置將會 覆蓋命令行上的設置。
trusted
與 yes 類似, 不同之處在於,將會搜索所有位於 /etc/systemd/nspawn/
目錄、 /run/systemd/nspawn/
目錄、 容器鏡像文件的所在目錄、容器根目錄的所在父目錄 中的 .nspawn
文件,並應用這些配置文件。 注意,這些文件中的配置依然會被命令行上的設置所覆蓋。
設爲 no 表示 完全忽略 .nspawn
文件。
--notify-ready=
配置容器內的 init 進程是否支持通知機制, 僅接受一個布爾值(no
與 yes
)。 設爲 no
表示 systemd-nspawn 將會在啓動容器內 init 進程的同時, 向 systemd 發送一條 "READY=1
" 消息。 設爲 yes
表示 systemd-nspawn 將會等待容器內 init 進程主動發送 "READY=1
" 消息,然後再將此消息轉發給 systemd 。 有關通知機制的詳情, 參見 sd_notify(3) 手冊。
-h
, --help
--version
例子
例 1. 下載 Fedora 鏡像並在其中啓動 shell
# machinectl pull-raw --verify=no \ https://mirrors.163.com/fedora/releases/30/Cloud/x86_64/images/Fedora-Cloud-Base-30-1.2.x86_64.raw.xz # systemd-nspawn -M Fedora-Cloud-Base-30-1.2.x86_64.raw
這將使用 machinectl(1) 下載鏡像並在其中啓動一個 shell
例 2. 創建一個最小化的 Fedora 系統並在容器中啓動它
# dnf -y --releasever=30 --installroot=/var/lib/machines/f30 \ --disablerepo='*' --enablerepo=fedora --enablerepo=updates install \ systemd passwd dnf fedora-release vim-minimal # systemd-nspawn -bD /var/lib/machines/f30
這將在 /var/lib/machines/f30
目錄中安裝一個最小化的 Fedora 系統,並在名字空間容器中啓動該系統。 因爲此 Fedora 系統安裝在標準目錄 /var/lib/machines/
之中,所以還可以使用 systemd-nspawn -M f30 命令來啓動。
例 3. 創建一個最小化的 Debian unstable 系統並在容器中啓動一個 shell
# debootstrap unstable ~/debian-tree/ # systemd-nspawn -D ~/debian-tree/
這將在 ~/debian-tree/
目錄中安裝一個最小化的 Debian unstable 系統, 然後在一個容器中啓動此係統中的 shell 。
debootstrap 支持 Debian, Ubuntu, Tanglu 開箱即用,所以對於這三種發行版可以使用相同的安裝命令。 對於其他 Debian 系發行版,必須明確指定一個鏡像,詳見 debootstrap(8) 手冊。
例 4. 創建一個最小化的 Arch Linux 系統並在容器中啓動它
# pacstrap -c -d ~/arch-tree/ base # systemd-nspawn -bD ~/arch-tree/
這將在 ~/arch-tree/
目錄中安裝一個最小化的 Arch Linux 系統, 然後在一個容器中啓動此係統。
例 5. 安裝 OpenSUSE Tumbleweed 滾動發行版
# zypper --root=/var/lib/machines/tumbleweed ar -c \ https://download.opensuse.org/tumbleweed/repo/oss tumbleweed # zypper --root=/var/lib/machines/tumbleweed refresh # zypper --root=/var/lib/machines/tumbleweed install --no-recommends \ systemd shadow zypper openSUSE-release vim # systemd-nspawn -M tumbleweed passwd root # systemd-nspawn -M tumbleweed -b
例 6. 在容器中啓動一個宿主系統的臨時快照
# systemd-nspawn -D / -xb
這將在容器中運行當前宿主系統的一個臨時快照,並在容器退出後立即銷燬此快照。 在容器運行期間對文件系統所做的任何更改都將在容器關閉後丟失。
例 7. 運行帶有 SELinux 沙盒安全上下文的容器
# chcon system_u:object_r:svirt_sandbox_file_t:s0:c0,c1 -R /srv/container # systemd-nspawn -L system_u:object_r:svirt_sandbox_file_t:s0:c0,c1 \ -Z system_u:system_r:svirt_lxc_net_t:s0:c0,c1 -D /srv/container /bin/sh
例 8. 運行含有一個 OSTree 部署的容器
# systemd-nspawn -b -i ~/image.raw \ --pivot-root=/ostree/deploy/$OS/deploy/$CHECKSUM:/sysroot \ --bind=+/sysroot/ostree/deploy/$OS/var:/var
退出狀態
等於 在容器中執行的程序的退出狀態
參見
systemd(1), systemd.nspawn(5), chroot(1), dnf(8), debootstrap(8), pacman(8), zypper(8), systemd.slice(5), machinectl(1), btrfs(8)
Linux Namespaces機制
...
SELinux:Linux安全子系統
...