Funky's NoteBook

Views about Namespace & Cgroups of Linux Kernel

字数统计: 2,597阅读时长: 12 min
2018/08/20 Share

浅谈 Linux 内核中的 Namespace 与 Cgroups

我是在学习 Docker 的时候接触的这两个概念,这两个内核功能也贯穿整个 Docker 容器的运作。那么我们下面简单的谈一谈这两个概念。本章使用自己开发的donkey runtime 与 docker 同步做对比。

Namespace

Namespace 也叫命名空间,是 Linux kernel 的一个功能,以下简称NS,他可以隔离一系列的系统资源,比如 PID、UserID、Network等。在 Docker 中,NS 更多的用于做系统的隔离,比如让不同的用户只能访问自己的服务,让每个容器在自己的 root 的环境下运行自己的程序,使用NS,可以做到 UID 级别的隔离,也就是说以 UID 为 n 的用户,虚拟出来的 NS,可以在自己的 NS 里面拥有 root 权限。但是在真实的物理机器上他还是那个 UID为 n 的用户,并没有 宿主机的 root 权限,这样就解决了用户隔离的问题。

除了 User Namespace,PID也是可以被虚拟的,NS 建立系统的不同视图,从用户角度来看,他拥有自己的 init 进程 (PID == 1),而在外部只是一个普通的进程(PID == N)。

举例说明:

example_PID

子Namespace A、B 都有自己 PID 为 1 的 init 进程,而它在父 NS 看来只是普通的进程,子NS的进程映射到父 NS 进程上,父 NS 可以知道每个子 NS 的运行状态,而子 NS 之间是隔离的,子 NS 中并不知道 父 NS 中有哪些进程,从上面的关系图可以看出,进程 2334 在父 NS 中的PID为 2334,而在子 NS 中的 PID 为 1。也就是说用户从 NS A中看,它就像 init 进程一样,是自己的初始化进程,而在整个宿主机中,NS A只不过是由PID 2334初始化出来的一个隔离空间而已,同理 PID 2593 也是如此。

当前 Linux 一共实现了 6 种不同类型的 Namespace。

Namespace 类型 系统调用参数 内核版本
Mount Namespace CLONE_NEWNS 2.4.19
UTS Namespace CLONE_NEWUTS 2.6.19
IPC Namespace CLONE_NEWIPC 2.6.19
PID Namespace CLONE_NEWPID 2.6.24
Network Namespace CLONE_NEWNET 2.6.29
User Namespace CLONE_NEWUSER 3.8

NS 的 API 常用的三个系统调用有:

  • clone() 创建新进程
  • unshare() 将进程移出某个 NS
  • setns() 将进程加入某个 NS

下面就对这几个NS做简单的介绍。

UTS Namespace

它主要用来隔离 nodename 与 domainname 两个系统标识。在 UTS NS 中,每个NS 允许有自己的 hostname。

1
2
3
4
5
6
7
8
9
10
11
$ hostname
funky-mac
$ docker run -ti --hostname ubuntu123 ubuntu /bin/sh
# hostname
ubuntu123
$ donkey run -ti --name hello busybox sh #内部修改hostname 外部不受影响
/ # hostname hello
/ # hostname
hello
$ hostname
funky-mac

IPC Namespace

它用于隔离 System V IPC 和 POSIX message queues。每一个 IPC NS都有自己的 System V IPC 和 POSIX message queues。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ ipcs -q
IPC status from <running system> as of Mon Aug 20 19:00:05 CST 2018
T ID KEY MODE OWNER GROUP
Message Queues:

$ docker run -ti ubuntu bash
root@443da4f613ba:/# ipcs -q

------ Message Queues --------
key msqid owner perms used-bytes messages
root@443da4f613ba:/# ipcmk -Q
Message queue id: 0

$ donkey run -ti --name hello busybox sh
/ # ipcs -q

------ Message Queues --------
key msqid owner perms used-bytes messages

PID Namespace

刚刚已经详细讨论了下,他主要用于隔离进程 ID。

1
2
3
4
5
6
7
8
9
10
11
12
$ echo $$
48123
$ donkey run -ti --name hello busybox sh
echo $$
1
$ docker run -ti ubuntu bash
root@428cf6604a90:/# ps
PID TTY TIME CMD
1 pts/0 00:00:00 bash
12 pts/0 00:00:00 ps
root@428cf6604a90:/# echo $$
1

Mount Namespace

用于隔离各个进程看到的挂载点视图。在不同的 NS 中,看到的文件系统层次是不一样的。在Mount NS中使用mount 与 umount 只会影响当前的 NS,对全局文件系统没有影响。你肯定会想到 chroot,它可以将一个目录变成根节点,Mount NS 不仅仅可以实现这个功能,而且可以更加灵活与安全。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ df
Filesystem Size Used Avail Capacity iused ifree %iused Mounted on
/dev/disk1s1 932Gi 152Gi 774Gi 17% 1275908 9223372036853499899 0% /
devfs 193Ki 193Ki 0Bi 100% 666 0 100% /dev
/dev/disk1s4 932Gi 5.5Gi 774Gi 1% 5 9223372036854775802 0% /private/var/vm
map -hosts 0Bi 0Bi 0Bi 100% 0 0 100% /net
map auto_home 0Bi 0Bi 0Bi 100% 0 0 100% /home
/dev/disk1s3 932Gi 495Mi 774Gi 1% 16 9223372036854775791 0% /Volumes/Recovery
$ docker run -ti ubuntu bash
root@428cf6604a90:/# df -Th
Filesystem Type Size Used Avail Use% Mounted on
overlay overlay 59G 3.5G 52G 7% /
tmpfs tmpfs 64M 0 64M 0% /dev
tmpfs tmpfs 1000M 0 1000M 0% /sys/fs/cgroup
/dev/sda1 ext4 59G 3.5G 52G 7% /etc/hosts
shm tmpfs 64M 0 64M 0% /dev/shm
tmpfs tmpfs 1000M 0 1000M 0% /proc/acpi
tmpfs tmpfs 1000M 0 1000M 0% /sys/firmware
$ donkey run -ti --name hello busybox sh
/ # df -Th
Filesystem Type Size Used Available Use% Mounted on
none aufs 39.3G 2.6G 35.1G 7% /
tmpfs tmpfs 244.9M 0 244.9M 0% /dev

User Namespace

User NS 主要用于隔离用户的用户组ID,一个进程的 User ID 个 Group IP 在 User NS 内外可以不同。

1
2
3
4
5
6
7
8
9
10
$ id
uid=501(funky) gid=20(staff)

$ docker run -ti ubuntu bash
root@752b77b90707:/# id
uid=0(root) gid=0(root) groups=0(root)

$ donkey run -ti --name hello busybox sh
/ # id
uid=0(root) gid=0(root) groups=0(root)

Network Namespace

Network NS用于隔离网络设备、IP等网络栈的NS,它可以让每个容器都拥有自己独立的网络设备。并且每个NS 的端口不会互相冲突。在宿主机上搭建网桥后可以很方便的实现容器直接的相互通信。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ ifconfig
lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> mtu 16384
options=1203<RXCSUM,TXCSUM,TXSTATUS,SW_TIMESTAMP>
inet 127.0.0.1 netmask 0xff000000
inet6 ::1 prefixlen 128
inet6 fe80::1%lo0 prefixlen 64 scopeid 0x1
nd6 options=201<PERFORMNUD,DAD>
gif0: flags=8010<POINTOPOINT,MULTICAST> mtu 1280
stf0: flags=0<> mtu 1280
XHC20: flags=0<> mtu 0
XHC1: flags=0<> mtu 0
XHC0: flags=0<> mtu 0
en1: flags=8963<UP,BROADCAST,SMART,RUNNING,PROMISC,SIMPLEX,MULTICAST> mtu 1500
options=60<TSO4,TSO6>
ether ca:00:XX
media: autoselect <full-duplex>
status: inactive


$ donkey run -ti --name hello busybox sh
/ # ifconfig
/ # ip addr #不存在设备,隔离
1: lo: <LOOPBACK> mtu 65536 qdisc noop
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

Cgroups

Linux Cgroups 即 Linux Control Groups,它提供了一组进程以及将来子进程的资源限制、控制和统计能力,这些资源包括CPU、内存、存储等,通过Cgroups,可以方便限制某个进程占用的资源,并且实时监控和统计信息。

Cgroups 三大组件

  • cgroup 是对进程分组管理的一种机制,一个cgroup包含一组进程,并可以在整个cgroup上增加各种参数配置,将一组进程和一组subsystem的系统参数关联起来。

    这里请注意区分cgroup 与 cgroups

  • subsystem 是一组资源控制块,一般包含以下几项。

    • blkio 设置块设备IO访问控制
    • cpu 设置cpu调度策略
    • devices 控制设备在cgroup中的访问
    • memory 用于控制内存占用

    Linux 系统可以通过命令lssubsys -a查看更多支持的 subsystem

  • hierarchy 的功能是把一组cgroup串联成一个树状结构,这样的树便是一个 hierarchy ,通过树状结构Cgroups可以做到继承。比如,系统通过cgroup1限制CPU,有个服务还需要同时限速内存,那么可以创建cgroup2继承于cgroup1并限制内存。同时cgroup2的限制只在cgroup2内部,cgroup1中的服务并不受cgroup2限制内存的影响。

三大组件相互关系

  • 系统创建新的 hierarchy 后,系统中所有进程都会加入这个 hierarchy 的cgroup根节点,这个根节点是 hierarchy 默认创建。

    p1

  • 一个 subsystem 只能附加在 一个 hierarchy 上面。

  • 一个 hierarchy 附加多个 subsystem。

    p2

  • 一个进程可以作为多个 cgroup 的成员,但这些cgroup必须在不同的 hierarchy 中。

    p3

  • 一个进程 fork出子进程时,子进程与父进程是在一个cgroup中的,也可以根据需要移动到其他cgroup里。

    p4

Kernel 接口

Cgroups 中的 hierarchy 是一种树状的组织结构,Kernel为了使Cgroup配置更加直观,是通过一个虚拟树状文件系统配置Cgroups的,通过层级的目录虚拟出cgroup树。

下面演示如何操作cgroups。

1.首先创建并挂载一个hierarchy (cgroup树)

1
2
3
4
$ mkdir cgroup-test #创建一个 hierarchy 挂载点
$ sudo mount -t cgroup -o none,name=cgroup-test cgroup-test ./cgroup-test # 挂载 hierarchy
$ ls ./cgroup-test #查看cgroup生成文件
cgroup.clone_children cgroup.event_control cgroup.procs cgroup.sane_behavior notify_on_release release_agent tasks
  • cgroup.clone_children cpuset的subsystem会读取这个配置文件,改文件值为1(默认0),子cgroup会继承父cgroup的cpuset的配置。
  • cgroup.procs 是树中当前cgroup中的进程组ID。
  • notify_on_release 与 release_agent 一起使用,前者用于标识当这个cgroup最后一个进程退出后是否执行了后者,后者是一个路径,通常用作进程退出后自动清理不在使用的cgroup。
  • tasks 标注 该cgroup下面的进程ID,如果把一个进程ID写到tasks文件中,便会把相应的进程加入到这个cgroup中。

2.接着刚刚创建好的hierarchy上的cgroup根节点中扩展出两个子cgroup。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
$ cd cgroup-test
$ sudo mkdir cgroup-1
$ sudo mkdir cgroup-2
tree
.
|-- cgroup-1
| |-- cgroup.clone_children
| |-- cgroup.event_control
| |-- cgroup.procs
| |-- notify_on_release
| `-- tasks
|-- cgroup-2
| |-- cgroup.clone_children
| |-- cgroup.event_control
| |-- cgroup.procs
| |-- notify_on_release
| `-- tasks
|-- cgroup.clone_children
|-- cgroup.event_control
|-- cgroup.procs
|-- cgroup.sane_behavior
|-- notify_on_release
|-- release_agent
`-- tasks

2 directories, 17 files

可以看出在一个cgroup的目录下创建文件夹时,kernel会把这个文件夹记为这个cgroup的子cgroup,他们会继承cgroup的属性。

3.在cgroup中添加移动进程

我们刚刚说过,一个进程在一个Cgroups的hierarchy 中,只能在一个cgroup节点存在,系统所有进程都会默认在根节点存在,可以将进程移动到其他cgroup节点,只需要将进程ID写到移动到的cgroup节点的tasks文件即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ cd cgroup-1
$ sudo sh -c "echo $$">> tasks
$ cat /proc/$$/cgroup
12:name=cgroup-test:/cgroup-1
11:hugetlb:/user/500.user/17.session
10:perf_event:/user/500.user/17.session
9:blkio:/user/500.user/17.session
8:freezer:/user/500.user/17.session
7:devices:/user/500.user/17.session
6:memory:/user/500.user/17.session
5:cpuacct:/user/500.user/17.session
4:cpu:/user/500.user/17.session
3:cpuset:/
2:name=systemd:/user/500.user/17.session

发现进程已经被加入到cgroup-test:/cgroup-1中了。

Docker中,引擎通过为每个container 创建 cgroup,并通过cgroup 去配置资源限制和资源监控。

CATALOG
  1. 1. 浅谈 Linux 内核中的 Namespace 与 Cgroups
    1. 1.1. Namespace
      1. 1.1.1. UTS Namespace
      2. 1.1.2. IPC Namespace
      3. 1.1.3. PID Namespace
      4. 1.1.4. Mount Namespace
      5. 1.1.5. User Namespace
      6. 1.1.6. Network Namespace
    2. 1.2.
    3. 1.3. Cgroups
      1. 1.3.1. Cgroups 三大组件
      2. 1.3.2. 三大组件相互关系
      3. 1.3.3. Kernel 接口