本節將介紹Cgroup與Namespace以及經過這兩個功能實現的容器功能。
Cgroup
Cgroup(control group)是將任意進程進行分組化管理的Linux內核功能。Cgroup自己是提供將進程進行分組化管理的功能和接口的基礎結構,I/O或內存的分配控制等具體的資源管理功能是經過這個功能來實現的。這些具體的資源管理功能稱爲Cgroup子系統或控制器。
Cgroup子系統有控制內存的Memory控制器、控制進程調度的CPU控制器等。運行中的內核可使用的Cgroup子系統由/proc/cgroup來確認。
Cgroup提供了一個cgroup虛擬文件系統,做爲進行分組管理和各子系統設置的用戶接口。要使用Cgroup,必須掛載cgroup文件系統。這時經過掛載選項指定使用哪一個子系統。這裏指定debug這個沒有實質功能的調試用子系統來掛載。javascript
# mount -t cgroup -o debug cgroup /cgroup
注意事項:這裏所說的「虛擬文件系統」,是指procfs和sysfs這種不具備物理設備的文件系統。並非用來接納文件系統差別的內核內部層layerVFS。
小貼士:關於cgroup文件系統的標準化掛載要點,是由開發論壇進行討論的,但目前還沒有得出結論。這裏是掛載到/cgroup。
掛載後,在掛載位置下應該能夠看到下列幾個文件。這些是Cgroup呈現出來的特殊文件。css
# ls /cgroup cgroup.event_control debug.current_css_set debug.taskcount cgroup.procs debug.current_css_set_cg_links notify_on_release debug.cgroup_css_links debug.current_css_set_refcount release_agent debug.cgroup_refcount debug.releasable tasks
文件名前綴爲cgroup的以及沒有前綴的文件是由Cgroup的基礎結構提供的特殊文件。而前綴爲debug的文件是由debug子系統提供的特殊文件。Cgroup的子系統提供的特殊文件都會像這樣加上子系統的前綴。所以,根據掛載時指定的選項,即所使用的子系統不一樣,存在的特殊文件也不一樣。可是Cgroup的基礎結構所提供的特殊文件則是不管指定哪一種子系統都一直存在的。特殊文件分爲只讀文件和可讀寫文件。只讀文件是爲用戶提供信息的文件。可讀寫的特殊文件經過寫入值來更改Cgroup以及Cgroup子系統設置的文件。設置的值能夠經過讀入特殊文件來確認。在這些特殊文件中,最重要的是tasks特殊文件。其內容能夠顯示以下。html
# cat /cgroup/tasks
1 2 3 4 ...
雖然看上去只是一些數字的排列,但其實這些是屬於這個分組的線程的線程ID(TID)。在這時,系統上運行的全部線程的TID都包含在/cgroup/tasks中。這就表示所有線程都屬於這個分組。那麼這裏出現的「分組」又是什麼呢?分組,就是體現爲cgroup文件系統目錄的線程的集合。因爲/cgroup也是目錄,所以它也表示一個分組。像這樣位於掛載點最上層的目錄是自動生成的分組,稱爲根分組。在這個階段,只有/cgroup(即根分組)是系統上存在的惟一分組。
小貼士:英語中將經過Cgroup建立的分組稱作cgroup,容易與表示結構的「Cgroup」混淆,因此這裏僅稱爲「分組」。
下面嘗試建立一個分組,也就是在/cgroup下建立子目錄。其內容以下所示。java
# mkdir /cgroup/test # ls /cgroup/test cgroup.event_control debug.current_css_set debug.taskcount cgroup.procs debug.current_css_set_cg_links notify_on_release debug.cgroup_css_links debug.current_css_set_refcount tasks debug.cgroup_refcount debug.releasable
雖然是新生成的目錄,可是已經有文件存在。cgroup文件系統在目錄生成的同時就會在其中配置特殊文件。
/cgroup/test也和/cgroup同樣有tasks。其內容以下。linux
# cat /cgroup/test/tasks
tasks的內容彷佛是空的,這表示這個分組內一個線程也沒有。能夠將適當的線程添加到這個分組中。要將線程添加到分組中,能夠在tasks中寫入該線程的TID。這裏以添加shell自己爲例。shell
# echo
$$
2474 # echo $$ > /cgroup/test/tasks # cat /cgroup/test/tasks 2474 3821
tasks的內容中包含shell的TID(也是PID,即進程ID)—2474,能夠看出這個shell已經屬於test分組。除此之外,這個分組內還有另外一個TID爲3821的線程,這是什麼呢?咱們再來看一下tasks的內容。bootstrap
# cat /cgroup/test/tasks
2474 3822
結果竟然發生了變化。事實上這個改變的部分,是顯示了tasks內容的cat進程的TID。最初的cat(3821)和第二次的cat(3822)是不一樣的進程,TID也不一樣,因此結果發生了變化。可是彷佛並無將cat進程添加到test分組中。其實,屬於分組的進程一旦生成子進程,其子進程就會自動屬於母進程。因爲cat是shell的子進程,所以前者自動屬於test分組。你們應該還記得,在掛載cgroup文件系統後,系統上的全部線程是屬於根分組的。也就是說,除了將明確指定爲新生成分組內的進程爲祖先進程之外,生成的進程都屬於根分組。
這時,再顯示/cgroup/tasks的內容的話,應該不會顯示shell的TID(2474)。這是由於shell不屬於根分組,而是屬於test分組。而後,再將這個shell返回到根分組。bash
# echo 2474 > /cgroup/tasks
這樣,shell的TID(2474)就再次屬於/cgroup/tasks,而/cgroup/test/tasks就變空。若是分組中一個線程也沒有,能夠進行撤銷。刪除目錄就能夠撤銷分組。服務器
# rmdir /cgroup/test
表2-1是每一個子系統中Cgroup都會提供的特殊文件列表。
表2-1 Cgroup提供的文件種類網絡
這裏僅介紹了最基本的Cgroup使用方法,也就是分組的建立、撤銷和將線程添加到分組的方法。實際使用Cgroup時,應在將線程添加到分組後,在分組內的特殊文件中設置值,來控制系統的運行。
Namespace
使用Namespace(命名空間),可讓每一個進程組具備獨立的PID、IPC和網絡空間。
能夠向clone系統調用的第3個參數flags設置劃分命名空間的標誌,經過執行clone系統調用能夠劃分命名空間。
例如,劃分PID命名空間後,在新生成的PID命名空間內進程的PID是從1開始的。重新PID爲1的進程fork()分叉獲得的進程,被封閉到這個新的PID命名空間,與其餘PID命名空間分隔開。在新建立的PID命名空間中生成的進程,其PID有可能與存在於原PID命名空間中的進程相同,但因爲兩者的PID命名空間劃分開,就不存在相互影響。
一樣,也能夠用PID、網絡、文件系統的掛載空間、UTS(Universal Time sharing System)爲對象進行資源劃分。能夠在clone系統調用的第3個參數中設置資源劃分的種類,如表2-2所示。
表2-2 資源劃分
Linux 容器
使用Cgroup和Namespace就能夠實現容器。容器這個技術也稱爲操做系統虛擬化,是將一個內核所管理的資源劃分紅多個分組。
在容器中,CPU和內存資源是使用Cgroup來劃分的。PID、IPC、網絡等資源使用Namespace來劃分。
LXC
Linux中實際安裝的容器有LXC(Linux Container)。本節將以Fedora 14爲例介紹LXC的使用方法。
# yum install lxc
要使用網絡,還須要安裝bridge-utils。
# yum install bridge-utils
在使用LXC以前,必須啓用cgroup文件系統。使用下列命令掛載cgroup文件系統。
# mount -t cgroup cgroup /cgroup
另外,向/etc/fstab添加下列語句,就能夠在系統啓動時自動掛載cgroup文件系統。
cgroup /cgroup cgroup defaults 0 0
首先,將bash shell進程放進容器。
這裏要爲容器中使用的文件系統準備一個/lxc目錄。
# mkdir /lxc # cd /lxc
而後準備做爲容器內的根文件系統的目錄。
# mkdir rootfs # cd rootfs
還須要準備其餘必要的目錄(這些目錄主要使用bind mount)。
# mkdir bin dev etc lib lib64 proc sbin sys usr var
而後還要生成LXC的配置文件lxc.conf以及引用的fstab。
# vi /lxc/lxc.conf
lxc.utsname = lxc
lxc.rootfs = /lxc/rootfs lxc.mount = /lxc/fstab # vi /lxc/fstab /bin /lxc/rootfs/bin none ro,bind 0 0 /sbin /lxc/rootfs/sbin none ro,bind 0 0 /lib /lxc/rootfs/lib none ro,bind 0 0 /lib64 /lxc/rootfs/lib64 none ro,bind 0 0 /etc /lxc/rootfs/etc none ro,bind 0 0 /usr /lxc/rootfs/usr none ro,bind 0 0 /dev /lxc/rootfs/dev none rw,bind 0 0 /dev/pts /lxc/rootfs/dev/pts none rw,bind 0 0 /proc /lxc/rootfs/proc proc defaults 0 0 /sys /lxc/rootfs/sys sysfs defaults 0 0
準備工做完成後,使用lxc-create命令生成名爲lxc的容器。
# lxc-create -n lxc -f /lxc/lxc.conf
使用lxc-ls命令能夠確認容器列表。
# lxc-ls lxc
使用lxc-create命令在lxc容器內執行bash。
# lxc-execute -n lxc bash
小貼士:在筆者的環境下,出現了終端的按鍵輸入不顯示的狀況。發生這種狀況時能夠執行reset命令,終端的操做就會恢復。
bash 4.1# reset(不顯示在畫面上)
執行ps命令,就能夠發現PID是從1開始的,除lxc容器之外看不到其餘進程。
bash-4.1# ps aux USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 1 0.1 0.1 14688 664 pts/0 S 21:34 0:00 /usr/lib64/lxc/lxc-init -- bash root 2 0.3 0.3 108440 1760 pts/0 S 21:34 0:00 bash root 6 0.0 0.2 108120 1104 pts/0 R+ 21:34 0:00 ps aux
另外,生成的容器可使用lxc-destroy命令來撤銷。
# lxc-destroy -n lxc
接下來嘗試啓用網絡,並啓動sshd進程。而後,建立用來鏈接分配到容器的網絡接口的網橋(關於網橋請參考Hack #24)。
在這個例子中將IP地址設置爲192.168.20.254。
# brctl addbr br0
# ifconfig br0 192.168.20.254
而後,修改lxc.conf,添加網絡設置。
# vi /lxc/lxc.conf
lxc.utsname = lxc
lxc.rootfs = /lxc/rootfs lxc.mount = /lxc/fstab lxc.network.type = veth lxc.network.flags = up lxc.network.link = br0 lxc.network.name = eth0 lxc.network.ipv4 = 192.168.20.1/24
啓動sshd時須要下列目錄,要事先建立。當該目錄不存在時,從lxc-execute啓動sshd時就會失敗。
# mkdir -p rootfs/var/empty/sshd
接下來生成容器。
# lxc-execute -n lxc /usr/sbin/sshd
這時打開其餘終端,確認SSH服務器是否正在運行。
首先,使用ping命令確認網絡是否已鏈接。
# ping 192.168.20.1 PING 192.168.20.1 (192.168.20.1) 56(84) bytes of data. 64 bytes from 192.168.20.1: icmp_req=1 ttl=64 time=0.899 ms 64 bytes from 192.168.20.1: icmp_req=2 ttl=64 time=0.174 ms ^C
使用ssh命令,鏈接分配到容器的IP地址192.168.20.1。
# ssh 192.168.20.1 root@192.168.20.1's password:
SSH鏈接已創建,輸入密碼後就成功登陸。
經過ps命令所顯示的進程來確認資源是否已被容器隔離。
-bash-4.1# ps auxw USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 1 0.0 0.1 14688 660 pts/0 S+ 21:43 0:00 /usr/lib64/lxc/ lxc-init -- /usr/sbin/sshd root 3 0.0 0.2 75104 1116 ? Ss 21:43 0:00 /usr/sbin/sshd root 4 1.4 0.8 108816 4068 ? Ss 21:47 0:00 sshd: root@pts/3 root 6 1.6 0.3 108440 1904 pts/3 Ss 21:47 0:00 -bash root 19 0.0 0.2 108124 1104 pts/3 R+ 21:47 0:00 ps auxw -bash-4.1# ifconfig eth0 eth0 Link encap:Ethernet HWaddr 3A:A1:C2:A0:6F:1B inet addr:192.168.20.1 Bcast:192.168.20.0 Mask:255.255.255.0 inet6 addr: fe80::38a1:c2ff:fea0:6f1b/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:280 errors:0 dropped:0 overruns:0 frame:0 TX packets:259 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:31389 (30.6 KiB) TX bytes:31373 (30.6 KiB) -bash-4.1# exit logout
能夠在其餘終端執行lxc-stop,來關閉裝有sshd的容器。
# lxc-stop -n lxc
而後運行debian。
使用debootstrap建立用來啓動debian的根文件系統。deboot strap使用yum來安裝。
# yum install debootstrap
建立debian的根文件系統,須要準備/debian。
# mkdir /debian # cd /debian
如今執行debootstrap,生成debian lenny的文件系統。
# debootstrap - -arch=amd64 lenny lenny
而後,準備LXC用的設置。
# vi /debian/lenny.conf
lxc.utsname = lenny
lxc.network.type = veth
lxc.network.flags = up
lxc.network.link = br0
lxc.network.name = eth0
lxc.network.ipv4 = 192.168.20.2/24 lxc.rootfs = /debian/lenny lxc.mount = /debian/lenny.fstab # vi /debian/lenny.fstab devpts /debian/lenny/dev/pts devpts defaults 0 0 proc /debian/lenny/proc proc defaults 0 0 sysfs /debian/lenny/sys sysfs defaults 0 0
建立名稱爲lenny的容器。
# lxc-create -n lenny -f lenny.conf
所建立的容器能夠經過lxc-start來啓動。只到這一步的話,init雖然啓動,但不能進行任何操做。這是由於剛執行debootstrap後,安裝的僅是最低限度須要的數據包。這裏爲了讓外部可以鏈接到容器,須要安裝sshd。
# lxc-start -n lenny bash
debian環境的shell就會在容器內啓動。而後使用apt-get安裝openssh-server。
lenny:~# apt-get install openssh-server
另外,爲了在SSH上進行登陸,還須要設置root的密碼。
lenny:~# passwd
在容器內啓動lenny。
# lxc-start -n lenny
INIT: version 2.86 booting Setting the system clock. Cannot access the Hardware Clock via any known method. Use the --debug option to see the details of our search for an access method. Unable to set System Clock to: Fri Dec 10 22:59:45 UTC 2010 (warning). Activating swap...done. Setting the system clock. Cannot access the Hardware Clock via any known method. Use the --debug option to see the details of our search for an access method. Unable to set System Clock to: Fri Dec 10 22:59:46 UTC 2010 (warning). Cleaning up ifupdown.... Loading kernel modules...FATAL: Could not load /lib/modules/2.6.35.9-64.fc14.x86_64/modules.dep: No such file or directory Checking file systems...fsck 1.41.3 (12-Oct-2008) done. Setting kernel variables (/etc/sysctl.conf)...done. Mounting local filesystems...done. Activating swapfile swap...done. Setting up networking.... Configuring network interfaces...done. INIT: Entering runlevel: 2 Starting enhanced syslogd: rsyslogd. Starting OpenBSD Secure Shell server: sshd. Starting periodic command scheduler: crond.
咱們嘗試從其餘終端使用SSH來鏈接。
# ssh 192.168.20.2 root@192.168.20.2's password: lenny:~#
在debian環境下實施關閉(shutdown)的話,容器結束。
lenny:~# shutdown -h now
按照前面所述方法使用LXC就能夠簡單地建立容器。
小結
本節介紹了Linux內核的資源劃分功能:劃分CPU、內存空間、I/O等的Cgroup,以及劃分PID、IPC、網絡、mount命名空間的Namespace。另外,還介紹了實際安裝上述資源劃分功能的容器LXC。
參考文獻
man 2 clone
man 2 unshare
LXC
http://lxc.sourceforge.net/
http://www.ibm.com/developerworks/jp/linux/library/l-lxc-containers/
debootstrap
http://www.debian.org/releases/stable/i386/apds03.html.ja—Munehiro IKEDA, Hiroshi Shimamoto