Linux 容器在 v2.6.29版本之后就加入到内核之中了, 之前虽然也听说过, 但一直没有太留心, 一直使用 KVM 来创建虚拟机.
直至最近 Docker 大出风头, 才开始关注. 想了解一下 Linux 容器究竟是什么? 与现有虚拟机技术(Xen, KVM等)有什么区别?
Linux 容器技术出现的很早, 其实也是一直虚拟化技术, 但似乎一直没有 Xen, KVM 这些来的出名.
同时, 在实现原理上, 和Xen, KVM之类的也是有很大区别的.
下面简单说明下目前4类虚拟技术的区别: (下面说明中, VM:虚拟机, HOST:主机, 即安装虚拟机的机器)
传统的虚拟化技术 (VirtualBox, VMware)
通过在Linux上安装虚拟化软件, 然后通过虚拟化软件来安装虚拟机系统, 大致结构如下:
VM1 VM2 VM3 ... ... |
VirtualBox or VMWare or ... |
Linux Kernel |
硬件 |
VM是由虚拟化软件(VirtualBox, VMWare…)来管理的, Linux Kernel不能直接管理到各个VM.
Xen (半虚拟化)
Xen是Linux上历史比较长的虚拟化技术, 它的虚拟化结构大致如下:
Linux Kernel VM1 VM2 VM3 ... ... |
Xen |
硬件 |
Xen的虚拟化原理是在 Linux Kernel和硬件之间加入一层 Xen代码, 有Xen来管理Linux Kernel和其它的VM.
KVM (最新的虚拟化技术)
相比其它的虚拟化技术, KVM是比较新的, 它需要CPU的支持. 它的虚拟化结构大致如下:
VM1 VM2 VM3 ... ... |
KVM (由内核管理) |
Linux Kernel |
硬件 |
这个结构和传统的虚拟化技术很类似, 有一点不同的是, KVM和Linux Kernel是紧密结合的,
所以Linux Kernel能够更好的管理 VMs, VM的性能会比传统的虚拟化技术更好.
Linux 容器 (LXC - linux container)
LXC 是非常轻量级的, 它将 VM 的进程也伪装成 HOST 的进程. 大致的结构如下:
p1(HOST), p2(VM), p3(VM), p4(HOST)...... |
Linux Kernel |
硬件 |
那么, 对于某些系统进程, PID是固定的, 比如 init进程的PID=1, VM中的 init进程的PID是如何处理的呢?
原来, VM的 init进程的PID在 HOST的进程表中会显示成其它PID(>1).
从上面可以看出, LXC这种虚拟化, VM的进程就像HOST的进程一样运行, 管理, 所以创建和销毁都是非常快速的.
注: 参考 http://veck.logdown.com/posts/200566-compare-of-kvm-and-lxc
Linux容器功能是基于 cgroups 和 Namespace 来实现的. 所以要了解 Linux 容器必须先了解 cgroup 和 Namespace.
cgroups 是将任意进程进行分组化管理的 Linux 内核功能.
通过cgroups可以有效的隔离各类进程, 同时还可以控制进程的资源占用(CPU, 内存等等)情况.
使用示例: (debian v7.6 x86_64)
mount -t tmpfs cgroup_root /sys/fs/cgroup
mkdir /sys/fs/cgroup/test
mount -t cgroup -ocpuset test /sys/fs/cgroup/test
此时, test目录就是一个 cgroup, 这里 -o 指定了 cpuset, cpuset是Linux中既定的一种cgroup, 后面有时间重新写博客详细介绍.
test 目录有cgroup必须的各个文件
cd /sys/fs/cgroup/test
ls -l
total 0
-rw-r--r-- 1 root root 0 Aug 14 14:34 cgroup.clone_children
--w--w--w- 1 root root 0 Aug 14 14:34 cgroup.event_control
-rw-r--r-- 1 root root 0 Aug 14 14:34 cgroup.procs
-rw-r--r-- 1 root root 0 Aug 14 14:34 cpuset.cpu_exclusive
-rw-r--r-- 1 root root 0 Aug 14 14:34 cpuset.cpus
-rw-r--r-- 1 root root 0 Aug 14 14:34 cpuset.mem_exclusive
-rw-r--r-- 1 root root 0 Aug 14 14:34 cpuset.mem_hardwall
-rw-r--r-- 1 root root 0 Aug 14 14:34 cpuset.memory_migrate
-r--r--r-- 1 root root 0 Aug 14 14:34 cpuset.memory_pressure
-rw-r--r-- 1 root root 0 Aug 14 14:34 cpuset.memory_pressure_enabled
-rw-r--r-- 1 root root 0 Aug 14 14:34 cpuset.memory_spread_page
-rw-r--r-- 1 root root 0 Aug 14 14:34 cpuset.memory_spread_slab
-rw-r--r-- 1 root root 0 Aug 14 14:34 cpuset.mems
-rw-r--r-- 1 root root 0 Aug 14 14:34 cpuset.sched_load_balance
-rw-r--r-- 1 root root 0 Aug 14 14:34 cpuset.sched_relax_domain_level
-rw-r--r-- 1 root root 0 Aug 14 14:34 notify_on_release
-rw-r--r-- 1 root root 0 Aug 14 14:34 release_agent
-rw-r--r-- 1 root root 0 Aug 14 14:34 tasks
其中部分文件介绍.
文件名 | R/W | 用途 |
---|---|---|
release_agent | RW | 删除分组时执行的命令. 这个文件只存在于根分组 |
notify_on_release | RW | 设置是否执行 release\_agent. 为1时执行 |
tasks | RW | 属于分组的线程 TID 列表 |
cgroup.procs | R | 属于分组的进程 PID 列表. 仅包括多线程进程的线程leader的TID, 这点与 tasks 不同 |
cgroup.event_control | RW | 监视状态变化的分组删除事件的配置文件 |
在cgroup中还可以建立子cgroup, 建立的方法很简单, 只要创建文件夹即可.
cd /sys/fs/cgroup/test
mkdir test-child
ls -l test-child # 创建了文件夹之后, 自动生成cgroup需要的文件
total 0
-rw-r--r-- 1 root root 0 Aug 14 15:10 cgroup.clone_children
--w--w--w- 1 root root 0 Aug 14 15:10 cgroup.event_control
-rw-r--r-- 1 root root 0 Aug 14 15:10 cgroup.procs
-rw-r--r-- 1 root root 0 Aug 14 15:10 cpuset.cpu_exclusive
-rw-r--r-- 1 root root 0 Aug 14 15:10 cpuset.cpus
-rw-r--r-- 1 root root 0 Aug 14 15:10 cpuset.mem_exclusive
-rw-r--r-- 1 root root 0 Aug 14 15:10 cpuset.mem_hardwall
-rw-r--r-- 1 root root 0 Aug 14 15:10 cpuset.memory_migrate
-r--r--r-- 1 root root 0 Aug 14 15:10 cpuset.memory_pressure
-rw-r--r-- 1 root root 0 Aug 14 15:10 cpuset.memory_spread_page
-rw-r--r-- 1 root root 0 Aug 14 15:10 cpuset.memory_spread_slab
-rw-r--r-- 1 root root 0 Aug 14 15:10 cpuset.mems
-rw-r--r-- 1 root root 0 Aug 14 15:10 cpuset.sched_load_balance
-rw-r--r-- 1 root root 0 Aug 14 15:10 cpuset.sched_relax_domain_level
-rw-r--r-- 1 root root 0 Aug 14 15:10 notify_on_release
-rw-r--r-- 1 root root 0 Aug 14 15:10 tasks
注意, 删除子cgroup的时候, 要用 rmdir 来删除文件夹, 用 rm -rf 的方法无法删除
cd /sys/fs/cgroup/test
rmdir test-child
注: 参考内核文档 Documentation/cgroups/cgroups.txt
使用Namespace, 可以让每个进程组有独立的PID, IPC和网络空间.
Namespace的生效主要是通过 clone系统调用来实现的.
clone系统调用的第3个参数flags就是通过设置Namespace来划分资源的.
参数种类如下:
名称 | 说明 |
---|---|
CLONE_NEWIPC | 划分IPC(进程间通信)命名空间, 信号量(semaphore), 共享内存, 消息队列等进程间通信用的资源 |
CLONE_NEWNET | 划分网络命名空间. 分配网络接口 |
CLONE_NEWNS | 划分挂载的命名空间. 与chroot同样分配新的根文件系统 |
CLONE_NEWPID | 划分 PID 命名空间. 分配新的进程ID空间 |
CLONE_NEWUTS | 划分 UTS(Universal Time sharing System)命名空间. 分配新的 UTS 空间 |
安装 LXC
apt-get install lxc
lxc-checkconfig # 安装完成后, 用这个命令检查系统是否可以使用 lxc
# 我的debian系统上有个 missing
Cgroup namespace: CONFIG_CGROUP_NSmissing
# 对于这个missing, 可能是由于系统中没有挂载cgroup导致的, 挂载一个cgroup即可
mount -t cgroup cgroup /mnt/cgroup
创建容器
从现有模板创建容器, 比较慢, 需要下载
# 创建一个 debian 系统
lxc-create -n test -t debian
这样创建的容器默认在 /var/lib/lxc/test 中, 为了将容器创建在我们指定的位置, 可以写个简单的配置文件
lxc.conf, 里面只需要一句
lxc.rootfs = /home/lxc/test
然后,
lxc-create -n test -t debian -f /path/to/lxc.conf
这样, 就把容器创建在了 /home/lxc/test 中了, /var/lib/lxc/test 中只有一个 config文件(这个config文件可以作为 lxc-create 命令 -f 参数对应配置文件的参考)
启动容器
启动后就进行入了虚拟机的控制台了. (果然像传说一样, 几秒就启动完成了 ^_^)
lxc-start -n test
停止容器
在主机中输入停止的命令.
lxc-stop -n test
销毁容器
销毁之前, 可以通过 lxc-ls 来查看有几个容器
lxc-ls
test
lxc-destroy -n test
lxc-ls
注: 参考URL - http://obdnmagazine.blogspot.com/2013/07/tested-lxc-080-rc1-debian-wheezyax3a6.html
尝试在容器配置一次开发环境, 然后通过复制容器, 形成多个虚拟机.
# 主机中
root@debian-113:~# uliweb # 主机中没有安装uliweb 软件包
-bash: uliweb: command not found
root@debian-113:~# lxc-start -n test
# 虚拟机登录界面, 输入用户名和密码
# 虚拟机中
root@test:~# apt-get install python
root@test:~# apt-get install python-pip
root@test:~# pip install Uliweb
root@test:~# uliweb --version
Uliweb version is 0.3.1
主机中设置网桥, 虚拟机用桥接方式上网, 确保每个虚拟机有独立的IP
# 主机中
root@debian-113:~# lxc-stop -n test
root@debian-113:~# apt-cache search bridge-utils
root@debian-113:~# brctl addbr br0
# 配置主机的网桥
root@debian-113:/var/lib/lxc/test# cat /etc/network/interfaces
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).
# The loopback network interface
auto lo
#auto eth0
iface lo inet loopback
# 追加的网桥配置
auto br0
iface br0 inet static
address 192.168.1.113
netmask 255.255.255.0
gateway 192.168.1.1
bridge_ports eth0
bridge_stp on
bridge_fd 0
root@debian-113:/var/lib/lxc/test# /etc/init.d/networking restart
配置容器的网络(也是在主机中修改容器的配置文件)
root@debian-113:/var/lib/lxc/test# cat /var/lib/lxc/test/config
... ... (很多默认生成的配置)
# network <-- 这个 network 相关的是要追加的
lxc.network.type = veth
lxc.network.flags = up
lxc.network.link = br0
lxc.network.name = eth0
启动Linux容器, 进入虚拟机
root@debian-113:/var/lib/lxc/test# lxc-start -n test
# 登录进入虚拟机, 确认虚拟机的IP
root@test:~# cat /etc/network/interfaces <-- 默认是自动获取IP
auto lo
iface lo inet loopback
auto eth0
iface eth0 inet dhcp
root@test:~# ifconfig <-- 我的机器自动分配的 192.168.1.167
# 创建一个简单的uliweb工程
root@test:~# cd /home/
root@test:/home# mkdir CM-web
root@test:/home# cd CM-web/
root@test:/home/CM-web# uliweb makeproject test
root@test:/home/CM-web# cd test/
root@test:/home/CM-web/test# uliweb makeapp first_app
root@test:/home/CM-web/test# uliweb runserver -h 0.0.0.0
启动Web服务后, 就可以在主机的浏览器中 通过 http://192.168.1.167:8000/ 来访问虚拟机中的web服务了.
最后, 复制一个新的容器, 也就是再重新生成一个上面的 python uliweb 开发环境
# 在主机中
root@debian-113:~# cd /var/lib/lxc
root@debian-113:/var/lib/lxc# cp -r test test2
# 修改 test2/config 如下
lxc.utsname = test2 <-- 修改名称
xc.rootfs = /home/lxc/test2 <-- 修改 rootfs位置
... ... <-- 其它部分不用修改, 和 test 一样就行
root@debian-113:/var/lib/lxc# cd /home/lxc/
root@debian-113:/home/lxc# cp -r test test2 <-- 重新复制一份 rootfs
root@debian-113:/home/lxc# lxc-start -n test2 <-- 启动 test2 虚拟机, 其中环境和 test一样, IP会不一样, 自动获取的
# 进入 test2 虚拟机中, 可以直接启动之前的 uliweb 测试工程, 也可以从主机中访问其web服务.