进一步了解LXC

LXC是什么

LXC is the well known set of tools, templates, library and language bindings. It’s pretty
low level, very flexible and covers just about every containment feature supported by the
upstream kernel ——linuxcontainers.org

LXC是Linux Containers的简称,是一种基于容器操作系统级轻量级的虚拟化技术,相对于其他的虚拟化方案(如VMWare、Xen等硬件抽象级的),LXC的虚拟化开销小的多,能更大程度地利(zha)用(gan)宿主服务器的资源,因此得到许多公司的青睐,笔者当前就职公司就是其中之一

另外,当前比较流行的Docker就是基于LXC(Docker被用来管理LXC环境)

关于本文

本文源于14年年初笔者在部门内做的一次分享——《LXC与虚拟化》,重新整理了一下,发上来充个数,欢迎各位看官留言或邮件交流指点

本文不是一篇介绍LXC如何安装与使用的文章,而是介绍LXC背后的一些东西(rootfs、cgroup、namespace)、这些东西在linux系统中的使用以及LXC中如何实现的

结构与组成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
+---------+
| LXC |
+----+----+
|
+-----------+----+------+----------+
| | | |
+--+---+ +--+---+ +---+---+ +--+---+
| p1 | | p2 | | ... | | pn |
+--+---+ +------+ +-------+ +------+
|
+------------------------+
| | |
+--+---+ +---+---+ +----+--+
|config| | fstab | | rootfs|
+------+ +-------+ +---+---+
|
+--------+----+---+--------+
| | | |
+-+-+ +--+--+ +--+-+ +---+--+
|bin| | etc | |... | | home |
+---+ +-----+ +----+ +------+

p1、p2、pn为创建的容器的名字

文件系统的隔离(chroot/rootfs)

rootfs

Root FileSystem,Linux系统中的根文件系统,是Linux系统的基本结构,能让操作系统正常运行的文件、文件夹的集合

建立rootfs可通过如下几种方式:

  • 手动创建(麻烦)
  • 使用debootstrap(ubuntu/debian)、febootstrap(fedora)等工具创建

LXC下templates目录下有一些模板:

1
2
$ ls /usr/local/lib/lxc/templates/
lxc-busybox lxc-debian lxc-fedora lxc-lenny lxc-opensuse lxc-sshd lxc-ubuntu

我们可以使用模板(-t)来创建容器

1
lxc-create –t ubuntu –n p1

如果你仔细观察创建过程,便会发现如下信息:

Download complete.
Copy /var/cache/lxc/lucid/rootfs-amd64 to /usr/local/var/lib/lxc/p1/rootfs …

创建成功后查看rootfs的目录结构:

1
2
$ ls /usr/local/var/lib/lxc/p1/rootfs/
bin boot dev etc home lib lib64 media mnt opt proc root sbin selinux srv sys tmp usr var

chroot初探

chroot,即更改root目录(change root directory)。众所周知,在linux系统中,root目录即根目录(/),整个文件系统的”入口”,而通过chroot这种技术,可以指定任意路径为根目录。我想你已经有些眉目了——无论什么样的虚拟化技术,首先得实现磁盘的虚拟化。也难怪chroot被称为容器虚拟化的鼻祖

先来看一下chroot的威力

这里为了简单,我手动创建了一些目录,并拷贝了bash、busybox以及bash所依赖的库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
~/chroot# tree
.
├── bin
│   ├── bash
│   └── busybox
├── lib
│   └── x86_64-linux-gnu
│   ├── libc-2.15.so
│   ├── libc.so.6 -> libc-2.15.so
│   ├── libdl-2.15.so
│   ├── libdl.so.2 -> libdl-2.15.so
│   ├── libtinfo.so.5 -> libtinfo.so.5.9
│   └── libtinfo.so.5.9
└── lib64
└── ld-linux-x86-64.so.2
4 directories, 9 files

接着,像变戏法似的把/root/chroot目录变为根目录(/)

1
2
3
4
5
6
7
~/chroot# pwd
/root/chroot
~/chroot# chroot .
bash-4.2# pwd
/
bash-4.2# busybox ls
bin lib lib64

在LXC中,是通过系统调用(pivot_root)来实现的

setup_rootfs_pivot_root - conf.c
1
2
3
4
5
6
7
8
9
if (pivot_root(".", path)) {
SYSERROR("pivot_root syscall failed");
return -1;
}
if (chdir("/")) {
SYSERROR("can't chdir to / after pivot_root");
return -1;
}

资源限制(cgroup)

  • 简介

cgroup是linux内核的一个功能,用来限制、记录、隔离进程组所使用的物理资源(cpu、memory、io等)的机制。最初由google的工程师提出,后来被整合进linux内核

  • 思想

将任意进程进行分组化管理

  • 概念
    • 任务(task):在cgroup中,任务就是系统的一个进程
    • 控制组(control group):cgroup中控制资源的单位
    • 层级(hierarchy):control group可以组织成hierarchy的形式,即一个control group tree,树上的子节点继承父节点的特定的属性
    • 子系统(subsystem):一个子系统是一个资源控制器(如cpu子系统就是控制cpu时间分配的一个控制器)。子系统必须附加到一个层级上才能起作用,附加到某个层级上后,这个层级上的所有控制组都受这个子系统的控制
  • 实例(限制内存使用量)

挂载cgoup(memory子系统)

1
2
3
4
5
6
7
# mkdir /cgroup
# mount -t cgroup -o memory memcg /cgroup
# ls /cgroup
cgroup.clone_children memory.force_empty memory.memsw.limit_in_bytes memory.numa_stat memory.swappiness release_agent
cgroup.event_control memory.limit_in_bytes memory.memsw.max_usage_in_bytes memory.oom_control memory.usage_in_bytes tasks
cgroup.procs memory.max_usage_in_bytes memory.memsw.usage_in_bytes memory.soft_limit_in_bytes memory.use_hierarchy
memory.failcnt memory.memsw.failcnt memory.move_charge_at_immigrate memory.stat notify_on_release

创建控制组

1
2
3
4
5
6
# mkdir /cgroup/GroupA
# ls /cgroup/GroupA
cgroup.clone_children memory.force_empty memory.memsw.limit_in_bytes memory.numa_stat memory.swappiness tasks
cgroup.event_control memory.limit_in_bytes memory.memsw.max_usage_in_bytes memory.oom_control memory.usage_in_bytes
cgroup.procs memory.max_usage_in_bytes memory.memsw.usage_in_bytes memory.soft_limit_in_bytes memory.use_hierarchy
memory.failcnt memory.memsw.failcnt memory.move_charge_at_immigrate memory.stat notify_on_release

限制内存的使用大小

1
2
3
4
5
# cat /cgroup/GroupA/memory.limit_in_bytes
9223372036854775807
# echo 10M > /cgroup/GroupA/memory.limit_in_bytes
# cat /cgroup/GroupA/memory.limit_in_bytes
10485760

添加任务

1
2
3
4
5
6
7
# echo $$ > /cgroup/GroupA/tasks
# cat /cgroup/GroupA/tasks
2177
2197
# cat /cgroup/GroupA/tasks
2177
2198

通过echo $$将当前shell的进程id添加到tasks后,所有由当前shell打开的进程也会默认添加到tasks中(上面的2197、2198分别是两次cat命令的pid)

  • LXC与cgroup

可以查看cgroup.h中定义的接口与cgroup.c中的实现

1
2
3
4
5
int lxc_cgroup_create(const char *name, pid_t pid);
int lxc_cgroup_destroy(const char *name);
int lxc_cgroup_path_get(char **path, const char *subsystem, const char *name);
int lxc_cgroup_nrtasks(const char *name);
int lxc_ns_is_mounted(void);

从上面的实例中可以看到,cgroup是linux内核的一个功能,使用的方式是挂载到某个目录上(需要在shell中完成),然后在其下面创建相应的目录(控制组)即可

lxc_one_cgroup_create() -- cgroup.c
1
2
3
4
5
6
7
8
9
10
11
12
/* Let's create the cgroup */
if (mkdir(cgname, 0700)) {
SYSERROR("failed to create '%s' directory", cgname);
return -1;
}
/* Let's add the pid to the 'tasks' file */
if (cgroup_attach(cgname, pid)) {
SYSERROR("failed to attach pid '%d' to '%s'", pid, cgname);
rmdir(cgname);
return -1;
}

资源隔离(namespace)

简介

在linux系统中,每个namespace下的资源对于其他namespace下的资源都是透明的。因此在操作系统层面上看,就会出现多个相同pid的进程,多个相同的uid等。由于属于不同的namespace,所以它们之间并不冲突,而在用户层面上只能看到属于用户自己namespace的资源

namespace还拥有层次关系。如:一个parent namespace下有两个child namespace。parent和两个child都有三个进程号为1、2、3的进程,同时child的每个进程被映射到了parent中的4、5、6、7、8、9。虽然只有9个进程,但需要15个进程号来表示他们

种类

  • User Namespace
  • Pid Namespace
  • Net Namespace
  • IPC Namespace
  • UTS Namespace
  • Mount Namespace

实例

  • 更改UTS Namespace
1
2
3
4
5
6
7
8
9
# uname -n
vagrant-ubuntu
# unshare -u /bin/bash
# hostname newhostname
# uname -n
newhostname
# ssh -P2222 vagrant@127.0.0.1
# uname -n
vagrant-ubuntu

LXC与Namespace

启动时调用lxc_clone来创建命名空间

lxc_spawn -- start.c
1
2
3
4
5
6
7
8
clone_flags = CLONE_NEWUTS|CLONE_NEWPID|CLONE_NEWIPC|CLONE_NEWNS;
if (!lxc_list_empty(&handler->conf->network)) {
clone_flags |= CLONE_NEWNET;
}
/* Create a process in a new set of namespaces */
handler->pid = lxc_clone(do_start, handler, clone_flags);

其中clone_flags是要创建的命名空间,lxc_clone主要通过系统调用clone__clone2来实现

lxc_clone -- namespace.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
pid_t lxc_clone(int (*fn)(void *), void *arg, int flags)
{
struct clone_arg clone_arg = {
.fn = fn,
.arg = arg,
};
long stack_size = sysconf(_SC_PAGESIZE);
void *stack = alloca(stack_size) + stack_size;
pid_t ret;
#ifdef __ia64__
ret = __clone2(do_clone, stack,
stack_size, flags | SIGCHLD, &clone_arg);
#else
ret = clone(do_clone, stack, flags | SIGCHLD, &clone_arg);
#endif
if (ret < 0)
ERROR("failed to clone(0x%x): %s", flags, strerror(errno));
return ret;
}

参考

坚持原创技术分享,您的支持将鼓励我继续创作!