【關於linux容器你所需要知道的一切系列】第一篇:Linux控制組以及程序隔離
【編者的話】這是關於Linux容器介紹的第一篇,主要介紹了Linux控制組 - control groups,也叫做cgroups,以及程序隔離。通過一個簡單的例子讓你很快學習到linux控制組是如何工作的。以及哪些庫可以讓你方便快捷的使用控制組。
每個人都聽說過容器,那麼容器到底是什麼呢?
軟體的發展使這項技術以多種方式得以實現,而Docker則是最流行的一種。因為容器的可移植性以及它隔離工作環境的特點可以限制它對底層計算的影響以及影響範圍,越來越多資料中心開始採用這項技術。為了全面瞭解這項技術,你首先需要了解是哪些技術實現了容器。
附註:人們總喜歡問容器和虛擬機器的區別。它們都有各自特定的目的,並沒有太多相似的地方。並且一個並不會淘汰掉另一個。一個容器指的是一個輕量級的環境,在這個環境中你可以啟動一個或者多個應用,它們都與外界隔離,並且這個環境的效能與裸機相當。但如果你需要執行一整個作業系統或者生態系統,又或者你需要執行與底層環境不相容的應用程式,那麼你需要選擇虛擬機器。
Linux 控制組
說實話,一些未知的軟體應用可能需要被控制或限制 - 至少是為了穩定性或者某種程度上的安全性。很多時候,一個bug或者僅僅只是爛程式碼就有可能破壞掉整個機器甚至可能削弱整個生態。幸運的是,有一種方式可以控制相同的應用程式,Linux控制組(cgroups)是一個核心功能,用於限制,記錄和隔離一個或多個程序對CPU, 記憶體,磁碟I/O,以及網路的使用量及訪問。
控制組技術最初由谷歌開發的,最終在2.6.24版本(2008年1月)中併入Linux核心主線。這項技術被部分重新設計,添加了kernfs(用於分割一些sysfs邏輯),這些改變被合併到3.15和3.16版本的核心中。
控制組的主要為了提供統一介面來管理程序或者整個作業系統級別的虛擬化,包括Linux 容器,或者LXC (將在之後的文章中詳細介紹這項技術)。控制組框架提供了以下功能:
- 資源限制 :一個控制組可以配置成不能超過指定的記憶體限制或是不能使用超過一定數量的處理器或限制使用特定的外圍裝置。
- 優先順序 :一個或者多個控制組可以配置成使用更少或者更多的CPUs或者磁碟I/O吞吐量。
- 記錄 :一個控制組的資源使用情況會被監督以及測量。
- 控制 :程序組可以被凍結,暫停或者重啟。
一個控制組可以包含一個或者多個程序,這些程序將全部綁定於同一組限制。控制組也可以繼承,這意味著一個子組可以繼承其父組限制。
Linux 核心為控制組技術的一系列控制器以及子系統提供了訪問。控制器將負責將特定型別的系統資源分配給一個控制組(包含一個或者多個程序)。例如,記憶體
控制器會限制記憶體使用而cpuacct
控制器會監控CPU的使用情況。
你可以直接或者間接(通過LXC, libvirt或者Docker)訪問及管理控制組,這裡我首先介紹使用sysfs以及libgroups
庫。接下來的示例需要你預先安裝一個必須的包。在Red Hat Enterprise Linux或者CentOS裡面,在命令列輸入以下命令:
$ sudo yum install libcgroup libcgroup-tools
如果是Ubuntu或者Debian, 輸入:
$ sudo apt-get install libcgroup1 cgroup-tools
我將使用一個簡單的shell指令碼檔案test.sh作為示例應用程式,它將會在無限while
迴圈中執行以下兩個命令。
$ cat test.sh
!/bin/sh
while [ 1 ]; do
echo "hello world"
sleep 60
done
手動方法
安裝必要的包後,你可以直接通過sysfs的目錄結構來配置你的控制組,例如,要在記憶體子系統中建立一個叫做foo
的控制組,只需要在/sys/fs/cgroup/memory
底下新建一個叫做foo
的目錄。
$ sudo mkdir /sys/fs/cgroup/memory/foo
預設情況下,每個新建的控制組將會繼承對系統整個記憶體池的訪問許可權。但對於某些應用程式,這些程式拒絕釋放已分配的記憶體並繼續分配更多記憶體,這種預設繼承方式顯然不是個好主意。要使程式的記憶體限制變得更為合理,你需要更新檔案memory.limit_in_bytes
。
限制控制組foo
下執行的任何應用的記憶體上限為50MB。
$ echo 50000000 | sudo tee ↪/sys/fs/cgroup/memory/foo/memory.limit_in_bytes
驗證設定:
$ sudo cat memory.limit_in_bytes 50003968
請注意,回讀的值始終是核心頁面大小的倍數(即4096位元組或4KB)。這個值是記憶體的最小可分配大小 。
啟動應用程式test.sh:
$ sh ~/test.sh
使用程序ID(PID),將應用程式移動到記憶體
控制器底下的控制組foo
:
$ echo 2845 > /sys/fs/cgroup/memory/foo/cgroup.procs
使用相同的PID,列出正在執行的程序並驗證它是否在正確的控制組下執行:
$ ps -o cgroup 2845 CGROUP 8:memory:/foo,1:name=systemd:/user.slice/user-0.slice/ ↪session-4.scope
你還可以通過讀取檔案來監控控制組正在使用的資源。在這種情況下,你可以檢視你的程序(以及生成的子程序)被分配的記憶體大小。
$ cat /sys/fs/cgroup/memory/foo/memory.usage_in_bytes 253952
當程序“迷路”時
現在讓我們重新建立相同的場景,但這次我們將控制組foo
的記憶體限制從50MB改為500 bytes
$ echo 500 | sudo tee /sys/fs/cgroup/memory/foo/ ↪memory.limit_in_bytes
注意:如果任務超出其定義的限制,核心將進行干預,並在某些情況下終止該任務 。
同樣,當您重新讀取值時,它將始終是核心頁面大小的倍數。因此,雖然您將其設定為500位元組,但它實際上被設定為4 KB:
$ cat /sys/fs/cgroup/memory/foo/memory.limit_in_bytes 4096
啟動應用程式test.sh,將其移動到控制組下並監視系統日誌:
$ sudo tail -f /var/log/messages Oct 14 10:22:40 localhost kernel: sh invoked oom-killer: ↪gfp_mask=0xd0, order=0, oom_score_adj=0 Oct 14 10:22:40 localhost kernel: sh cpuset=/ mems_allowed=0 Oct 14 10:22:40 localhost kernel: CPU: 0 PID: 2687 Comm: ↪sh Tainted: G OE------------3.10.0-327.36.3.el7.x86_64 #1 Oct 14 10:22:40 localhost kernel: Hardware name: innotek GmbH VirtualBox/VirtualBox, BIOS VirtualBox 12/01/2006 Oct 14 10:22:40 localhost kernel: ffff880036ea5c00 ↪0000000093314010 ffff88000002bcd0 ffffffff81636431 Oct 14 10:22:40 localhost kernel: ffff88000002bd60 ↪ffffffff816313cc 01018800000000d0 ffff88000002bd68 Oct 14 10:22:40 localhost kernel: ffffffffbc35e040 ↪fffeefff00000000 0000000000000001 ffff880036ea6103 Oct 14 10:22:40 localhost kernel: Call Trace: Oct 14 10:22:40 localhost kernel: [<ffffffff81636431>] ↪dump_stack+0x19/0x1b Oct 14 10:22:40 localhost kernel: [<ffffffff816313cc>] ↪dump_header+0x8e/0x214 Oct 14 10:22:40 localhost kernel: [<ffffffff8116d21e>] ↪oom_kill_process+0x24e/0x3b0 Oct 14 10:22:40 localhost kernel: [<ffffffff81088e4e>] ? ↪has_capability_noaudit+0x1e/0x30 Oct 14 10:22:40 localhost kernel: [<ffffffff811d4285>] ↪mem_cgroup_oom_synchronize+0x575/0x5a0 Oct 14 10:22:40 localhost kernel: [<ffffffff811d3650>] ? ↪mem_cgroup_charge_common+0xc0/0xc0 Oct 14 10:22:40 localhost kernel: [<ffffffff8116da94>] ↪pagefault_out_of_memory+0x14/0x90 Oct 14 10:22:40 localhost kernel: [<ffffffff8162f815>] ↪mm_fault_error+0x68/0x12b Oct 14 10:22:40 localhost kernel: [<ffffffff816422d2>] ↪__do_page_fault+0x3e2/0x450 Oct 14 10:22:40 localhost kernel: [<ffffffff81642363>] ↪do_page_fault+0x23/0x80 Oct 14 10:22:40 localhost kernel: [<ffffffff8163e648>] ↪page_fault+0x28/0x30 Oct 14 10:22:40 localhost kernel: Task in /foo killed as ↪a result of limit of /foo Oct 14 10:22:40 localhost kernel: memory: usage 4kB, limit ↪4kB, failcnt 8 Oct 14 10:22:40 localhost kernel: memory+swap: usage 4kB, ↪limit 9007199254740991kB, failcnt 0 Oct 14 10:22:40 localhost kernel: kmem: usage 0kB, limit ↪9007199254740991kB, failcnt 0 Oct 14 10:22:40 localhost kernel: Memory cgroup stats for /foo: ↪cache:0KB rss:4KB rss_huge:0KB mapped_file:0KB swap:0KB ↪inactive_anon:0KB active_anon:0KB inactive_file:0KB ↪active_file:0KB unevictable:0KB Oct 14 10:22:40 localhost kernel: [ pid ]uidtgid total_vm ↪rss nr_ptes swapents oom_score_adj name Oct 14 10:22:40 localhost kernel: [ 2687]0268728281 ↪3471200 sh Oct 14 10:22:40 localhost kernel: [ 2702]0270228281 ↪50700 sh Oct 14 10:22:40 localhost kernel: Memory cgroup out of memory: ↪Kill process 2687 (sh) score 0 or sacrifice child Oct 14 10:22:40 localhost kernel: Killed process 2702 (sh) ↪total-vm:113124kB, anon-rss:200kB, file-rss:0kB Oct 14 10:22:41 localhost kernel: sh invoked oom-killer: ↪gfp_mask=0xd0, order=0, oom_score_adj=0 [ ... ]
請注意,核心的Out-Of-Mempry Killer(也叫做oom-killer 記憶體不足殺手)在應用程式達到4kb限制時就會介入。它會殺死應用程式,應用程式將不再執行,你可以通過輸入以下命令進行驗證:
$ ps -o cgroup 2687 CGROUP
使用libcgroup
之前描述的許多早期步驟都可以通過libcgroup
包中提供的管理工具進行簡化。例如,使用cgcreate
二進位制檔案的單個命令即可建立sysfs條目和檔案。
輸入以下命令即可在記憶體
子系統下建立一個叫做foo
的控制組
$ sudo cgcreate -g memory:foo
注意:libcgroup提供了一種管理控制組中任務的機制。
使用與之前相同的方法,你就可以開始設定記憶體閾值:
$ echo 50000000 | sudo tee ↪/sys/fs/cgroup/memory/foo/memory.limit_in_bytes
驗證新配置的設定:
$ sudo cat memory.limit_in_bytes 50003968
使用cgexec
二進位制檔案在控制組foo
中執行應用程式:
$ sudo cgexec -g memory:foo ~/test.sh
使用它的程序ID - PID來驗證應用程式是否在控制組和子系統(記憶體
)下執行
$ps -o cgroup 2945 CGROUP 6:memory:/foo,1:name=systemd:/user.slice/user-0.slice/ ↪session-1.scope
如果您的應用程式不再執行,並且您想要清理並刪除控制組,則可以使用二進位制檔案cgdelete
來執行此操作。要從記憶體
控制器下刪除控制組foo
,請輸入:
$ sudo cgdelete memory:foo
持久組
您也可以通過一個簡單的配置檔案和服務的啟動來完成上述所有操作。您可以在/etc/cgconfig.conf
檔案中定義所有控制組名稱和屬性。以下為foo
組添加了一些屬性:
$ cat /etc/cgconfig.conf #
Copyright IBM Corporation. 2007
#
Authors:Balbir Singh <ofollow,noindex" target="_blank">[email protected] .com>
This program is free software; you can redistribute it
and/or modify it under the terms of version 2.1 of the GNU
Lesser General Public License as published by the Free
Software Foundation.
#
This program is distributed in the hope that it would be
useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE.
#
#
By default, we expect systemd mounts everything on boot,
so there is not much to do.
See man cgconfig.conf for further details, how to create
groups on system boot using this file.
group foo {
cpu {
cpu.shares = 100;
}
memory {
memory.limit_in_bytes = 5000000;
}
}
cpu.shares
選項定義了該組的CPU優先順序。預設情況下,所有組都繼承1024 shares(CPU share指的是控制組中的任務被分配到的CPU的 time的優先順序,即值越大,分配到的CPU time越多,這個值需大於等於2),即100%的CPU time(CPU time是CPU用於處理一個程式所花費的時間)。通過將cpu.shares
的值降低到更保守的值(如100),這個組將會被限制只能使用大概10%的CPU time。
就如之前討論的,在控制組中執行的程序也可以被限制它能訪問的CPUs(核心)的數量。將以下部分新增到同一個配置檔案cgconfig.conf
中組名底下。
cpuset { cpuset.cpus="0-5"; }
有了這個限制,這個控制組會將應用程式繫結到到0核到5核 - 也就是說,它只能訪問系統上的前6個CPU核。
接下來,您需要使用cgconfig
服務載入此配置。首先,啟用cgconfig以在系統啟動時能夠載入上述配置:
$ sudo systemctl enable cgconfig Create symlink from /etc/systemd/system/sysinit.target.wants/ ↪cgconfig.service to /usr/lib/systemd/system/cgconfig.service.
現在,啟動cgconfig
服務並手動載入相同的配置檔案(或者您可以跳過此步驟直接重啟系統):
$ sudo systemctl start cgconfig
在控制組foo
下啟動該應用程式並將其繫結到您設定的記憶體和CPU限制:
$ sudo cgexec -g memory,cpu,cpuset:foo ~/test.sh &
除了將應用程式啟動到預定義的控制組之外,其餘所有內容都將在系統重新啟動後持續存在。但是,您可以通過定義依賴於cgconfig
服務的啟動初始指令碼來啟動該應用程式,自動執行該過程。
總結
通常來說,限制一個機器上一個或者多個任務的許可權是必要的。控制組提供了這項功能,通過使用它,您可以對一些特別重要或無法控制的應用程式實施嚴格的硬體和軟體限制。如果一個應用程式沒有設定上限閾值或限制它可以在系統上消耗的記憶體量,cgroups可以解決這個問題。如果另一個應用程式沒有CPU上的限制,那麼cgroups可以再一次解決您的問題。您可以通過cgroup完成這麼多工作,只需花一點時間,您就可以使用你的作業系統環境恢復穩定性,安全性和健全性。
在這個系列的第二篇中,我將會把焦點從控制組轉移到Linux容器等技術是如何使用控制組的。
==============================================================================
譯者介紹
Grace,程式設計師,研究生畢業於SUNY at Stony Brook,目前供職於Linktime Cloud Company,對大資料技術以及資料視覺化技術感興趣。