1、Namespace
Linux内核中的namespace技术实现了各种资源的隔离。
最新的 Linux 5.6 内核中提供了 8 种类型的 Namespace:
Namespace 名称 | 作用 | 内核版本 |
---|---|---|
Mount(mnt) | 隔离挂载点 | 2.4.19 |
Process ID (pid) | 隔离进程 ID | 2.6.24 |
Network (net) | 隔离网络设备,端口号等 | 2.6.29 |
Interprocess Communication (ipc) | 隔离 System V IPC 和 POSIX message queues | 2.6.19 |
UTS Namespace(uts) | 隔离主机名和域名 | 2.6.19 |
User Namespace (user) | 隔离用户和用户组 | 3.8 |
Control group (cgroup) Namespace | 隔离 Cgroups 根目录 | 4.6 |
Time Namespace | 隔离系统时间 | 5.6 |
1.1、隔离挂载点
Mount Namespace 实现了不同进程可以看到不同的挂载信息。
通俗点说,容器内的挂载操作不会影响到主机。
使用unshare命令新建一个mount namespace
$ sudo unshare --mount --fork /bin/bash
创建临时挂载目录 [root@centos7 centos]# mkdir /tmp/tmpfs
使用tmpfs挂载一个目录 [root@centos7 centos]# mount -t tmpfs -o size=20m tmpfs /tmp/tmpfs
当前窗口查看挂载信息 [root@centos7 centos]# df -h Filesystem Size Used Avail Use% Mounted on /dev/vda1 500G 1.4G 499G 1% / devtmpfs 16G 0 16G 0% /dev tmpfs 16G 0 16G 0% /dev/shm tmpfs 16G 0 16G 0% /sys/fs/cgroup tmpfs 16G 57M 16G 1% /run tmpfs 3.2G 0 3.2G 0% /run/user/1000 tmpfs 20M 0 20M 0% /tmp/tmpfs
新开一个窗口,查看主机挂载信息
可看到挂载信息不同 [centos@centos7 ~]$ df -h Filesystem Size Used Avail Use% Mounted on devtmpfs 16G 0 16G 0% /dev tmpfs 16G 0 16G 0% /dev/shm tmpfs 16G 57M 16G 1% /run tmpfs 16G 0 16G 0% /sys/fs/cgroup /dev/vda1 500G 1.4G 499G 1% / tmpfs 3.2G 0 3.2G 0% /run/user/1000
1.2、隔离进程ID
此功能可实现不同的PID namespace内的进程拥有相同的ID。
创建一个pid namespace
$ sudo unshare --pid --fork --mount-proc /bin/bash 查看进程信息,可以发现1号进程是bash [root@centos7 centos]# ps aux USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 1 0.0 0.0 115544 2004 pts/0 S 10:57 0:00 bash root 10 0.0 0.0 155444 1764 pts/0 R+ 10:59 0:00 ps aux
1.3、隔离网络设备、端口号
net namespace实现网络设备的隔离。
查看主机网络信息
$ ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 link/ether 02:11:b0:14:01:0c brd ff:ff:ff:ff:ff:ff inet 172.20.1.11/24 brd 172.20.1.255 scope global dynamic eth0 valid_lft 86063337sec preferred_lft 86063337sec inet6 fe80::11:b0ff:fe14:10c/64 scope link valid_lft forever preferred_lft forever 3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default link/ether 02:42:82:8d:a0:df brd ff:ff:ff:ff:ff:ff inet 172.17.0.1/16 scope global docker0 valid_lft forever preferred_lft forever inet6 fe80::42:82ff:fe8d:a0df/64 scope link valid_lft forever preferred_lft forever 创建一个net namespace $ sudo unshare --net --fork /bin/bash [root@centos7 centos]# 查看此namespace的网络信息 [root@centos7 centos]# ip a 1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
1.4、隔离主机名
UTS Namespace 主要是用来隔离主机名的,它允许每个 UTS Namespace 拥有一个独立的主机名。
-
-
[root@centos7 centos]#
创建好 UTS Namespace 后,当前命令行窗口已经处于一个独立的 UTS Namespace 中,下面我们使用 hostname 命令(hostname 可以用来查看主机名称)设置一下主机名:
-
root@centos7 centos]# hostname -b lagoudocker
然后再查看一下主机名:
-
[root@centos7 centos]# hostname
-
lagoudocker
通过上面命令的输出,我们可以看到当前UTS Namespace 内的主机名已经被修改为 lagoudocker。然后我们新打开一个命令行窗口,使用相同的命令查看一下主机的 hostname:
-
[centos
-
centos7
可以看到主机的名称仍然为 centos7,并没有被修改。由此,可以验证 UTS Namespace 可以用来隔离主机名。
1.5、IPC Namespace
IPC Namespace 主要是用来隔离进程间通信的。例如 PID Namespace 和 IPC Namespace 一起使用可以实现同一 IPC Namespace 内的进程彼此可以通信,不同 IPC Namespace 的进程却不能通信。
同样我们通过一个实例来验证下IPC Namespace的作用,首先我们使用 unshare 命令来创建一个 IPC Namespace:
-
-
[root@centos7 centos]#
下面我们需要借助两个命令来实现对 IPC Namespace 的验证。
-
ipcs -q 命令:用来查看系统间通信队列列表。
-
ipcmk -Q 命令:用来创建系统间通信队列。
我们首先使用 ipcs -q 命令查看一下当前 IPC Namespace 下的系统通信队列列表:
-
[centos
-
-
------ Message Queues --------
-
key msqid owner perms used-bytes messages
由上可以看到当前无任何系统通信队列,然后我们使用 ipcmk -Q 命令创建一个系统通信队列:
-
[root
-
Message queue id: 0
再次使用 ipcs -q 命令查看当前 IPC Namespace 下的系统通信队列列表:
-
[root
-
-
------ Message Queues --------
-
key msqid owner perms used-bytes messages
-
0x73682a32 0 root 644 0 0
可以看到我们已经成功创建了一个系统通信队列。然后我们新打开一个命令行窗口,使用ipcs -q 命令查看一下主机的系统通信队列:
-
[centos
-
-
------ Message Queues --------
-
key msqid owner perms used-bytes messages
通过上面的实验,可以发现,在单独的 IPC Namespace 内创建的系统通信队列在主机上无法看到。即 IPC Namespace 实现了系统通信队列的隔离。
1.6、User Namespace
User Namespace 主要是用来隔离用户和用户组的。一个比较典型的应用场景就是在主机上以非 root 用户运行的进程可以在一个单独的 User Namespace 中映射成 root 用户。使用 User Namespace 可以实现进程在容器内拥有 root 权限,而在主机上却只是普通用户。
User Namesapce 的创建是可以不使用 root 权限的。下面我们以普通用户的身份创建一个 User Namespace,命令如下:
-
[centos
-
[root
CentOS7 默认允许创建的 User Namespace 为 0,如果执行上述命令失败( unshare 命令返回的错误为 unshare: unshare failed: Invalid argument ),需要使用以下命令修改系统允许创建的 User Namespace 数量,命令为:echo 65535 > /proc/sys/user/max_user_namespaces,然后再次尝试创建 User Namespace。
然后执行 id 命令查看一下当前的用户信息:
-
[root
-
uid=0(root) gid=0(root) groups=0(root),65534(nfsnobody) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
通过上面的输出可以看到我们在新的 User Namespace 内已经是 root 用户了。下面我们使用只有主机 root 用户才可以执行的 reboot 命令来验证一下,在当前命令行窗口执行 reboot 命令:
-
[root
-
Failed to open /dev/initctl: Permission denied
-
Failed to talk to init daemon.
可以看到,我们在新创建的 User Namespace 内虽然是 root 用户,但是并没有权限执行 reboot 命令。这说明在隔离的 User Namespace 中,并不能获取到主机的 root 权限,也就是说 User Namespace 实现了用户和用户组的隔离。
2、Cgroups
Cgroups技术用来限制容器内进程使用CPU、内存的资源的使用量。
2.1、CPU子系统
我首先以 cpu 子系统为例,演示一下cgroups如何限制进程的 cpu 使用时间。由于cgroups的操作很多需要用到 root 权限,我们在执行命令前要确保已经切换到了 root 用户,以下命令的执行默认都是使用 root 用户。
第一步:在 cpu 子系统下创建 cgroup
cgroups的创建很简单,只需要在相应的子系统下创建目录即可。下面我们到 cpu 子系统下创建测试文件夹:
执行完上述命令后,我们查看一下我们新创建的目录下发生了什么?
-
-
total 0
-
-rw-r--r--. 1 root root 0 Sep 5 09:19 cgroup.clone_children
-
--w--w--w-. 1 root root 0 Sep 5 09:19 cgroup.event_control
-
-rw-r--r--. 1 root root 0 Sep 5 09:19 cgroup.procs
-
-rw-r--r--. 1 root root 0 Sep 5 09:19 cpu.cfs_period_us
-
-rw-r--r--. 1 root root 0 Sep 5 09:19 cpu.cfs_quota_us
-
-rw-r--r--. 1 root root 0 Sep 5 09:19 cpu.rt_period_us
-
-rw-r--r--. 1 root root 0 Sep 5 09:19 cpu.rt_runtime_us
-
-rw-r--r--. 1 root root 0 Sep 5 09:19 cpu.shares
-
-r--r--r--. 1 root root 0 Sep 5 09:19 cpu.stat
-
-r--r--r--. 1 root root 0 Sep 5 09:19 cpuacct.stat
-
-rw-r--r--. 1 root root 0 Sep 5 09:19 cpuacct.usage
-
-r--r--r--. 1 root root 0 Sep 5 09:19 cpuacct.usage_percpu
-
-rw-r--r--. 1 root root 0 Sep 5 09:19 notify_on_release
-
-rw-r--r--. 1 root root 0 Sep 5 09:19 tasks
由上可以看到我们新建的目录下被自动创建了很多文件,其中 cpu.cfs_quota_us 文件代表在某一个阶段限制的 CPU 时间总量,单位为微秒。例如,我们想限制某个进程最多使用 1 核 CPU,就在这个文件里写入 100000(100000 代表限制 1 个核) ,tasks 文件中写入进程的 ID 即可(如果要限制多个进程 ID,在 tasks 文件中用换行符分隔即可)。
此时,我们所需要的 cgroup 就创建好了。对,就是这么简单。
第二步:创建进程,加入 cgroup
这里为了方便演示,我先把当前运行的 shell 进程加入 cgroup,然后在当前 shell 运行 cpu 耗时任务(这里利用到了继承,子进程会继承父进程的 cgroup)。
使用以下命令将 shell 进程加入 cgroup 中:
查看一下 tasks 文件内容:
-
-
3485
-
3543
其中第一个进程 ID 为当前 shell 的主进程,也就是说,当前 shell 主进程为 3485。
第三步:执行 CPU 耗时任务,验证 cgroup 是否可以限制 cpu 使用时间
下面,我们使用以下命令制造一个死循环,来提升 cpu 使用率:
执行完上述命令后,我们新打开一个 shell 窗口,使用 top -p 命令查看当前 cpu 使用率,-p 参数后面跟进程 ID,我这里是 3485。
-
-
top - 09:51:35 up 3 days, 22:00, 4 users, load average: 1.59, 0.58, 0.27
-
Tasks: 1 total, 0 running, 1 sleeping, 0 stopped, 0 zombie
-
-
KiB Mem : 32779616 total, 31009780 free, 495988 used, 1273848 buff/cache
-
KiB Swap: 0 total, 0 free, 0 used. 31852336 avail Mem
-
-
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
-
3485 root 20 0 116336 2852 1688 S 99.7 0.0 2:10.71 bash
通过上面输出可以看到 3485 这个进程被限制到了只能使用 100 % 的 cpu,也就是 1 个核。说明我们使用 cgroup 来限制 cpu 使用时间已经生效。此时,执行 while 循环的命令行窗口可以使用 Ctrl+c 退出循环。
为了进一步证实 cgroup 限制 cpu 的准确性,我们修改 cpu 限制时间为 0.5 核,命令如下:
同样使用上面的命令来制造死循环:
保持当前窗口,新打开一个 shell 窗口,使用 top -p 参数查看 cpu 使用率:
-
-
top - 10:05:25 up 3 days, 22:14, 3 users, load average: 1.02, 0.43, 0.40
-
Tasks: 1 total, 1 running, 0 sleeping, 0 stopped, 0 zombie
-
-
KiB Mem : 32779616 total, 31055676 free, 450224 used, 1273716 buff/cache
-
KiB Swap: 0 total, 0 free, 0 used. 31898216 avail Mem
-
-
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
-
3485 root 20 0 115544 2116 1664 R 50.0 0.0 0:23.39 bash
通过上面输出可以看到,此时 cpu 使用率已经被限制到了 50%,即 0.5 个核。
验证完 cgroup 限制 cpu,我们使用相似的方法来验证 cgroup 对内存的限制。
2.2、memory子系统
第一步:在 memory 子系统下创建 cgroup
同样,我们查看一下新创建的目录下发生了什么?
-
total 0
-
-rw-r--r--. 1 root root 0 Sep 5 10:18 cgroup.clone_children
-
--w--w--w-. 1 root root 0 Sep 5 10:18 cgroup.event_control
-
-rw-r--r--. 1 root root 0 Sep 5 10:18 cgroup.procs
-
-rw-r--r--. 1 root root 0 Sep 5 10:18 memory.failcnt
-
--w-------. 1 root root 0 Sep 5 10:18 memory.force_empty
-
-rw-r--r--. 1 root root 0 Sep 5 10:18 memory.kmem.failcnt
-
-rw-r--r--. 1 root root 0 Sep 5 10:18 memory.kmem.limit_in_bytes
-
-rw-r--r--. 1 root root 0 Sep 5 10:18 memory.kmem.max_usage_in_bytes
-
-r--r--r--. 1 root root 0 Sep 5 10:18 memory.kmem.slabinfo
-
-rw-r--r--. 1 root root 0 Sep 5 10:18 memory.kmem.tcp.failcnt
-
-rw-r--r--. 1 root root 0 Sep 5 10:18 memory.kmem.tcp.limit_in_bytes
-
-rw-r--r--. 1 root root 0 Sep 5 10:18 memory.kmem.tcp.max_usage_in_bytes
-
-r--r--r--. 1 root root 0 Sep 5 10:18 memory.kmem.tcp.usage_in_bytes
-
-r--r--r--. 1 root root 0 Sep 5 10:18 memory.kmem.usage_in_bytes
-
-rw-r--r--. 1 root root 0 Sep 5 10:18 memory.limit_in_bytes
-
-rw-r--r--. 1 root root 0 Sep 5 10:18 memory.max_usage_in_bytes
-
-rw-r--r--. 1 root root 0 Sep 5 10:18 memory.memsw.failcnt
-
-rw-r--r--. 1 root root 0 Sep 5 10:18 memory.memsw.limit_in_bytes
-
-rw-r--r--. 1 root root 0 Sep 5 10:18 memory.memsw.max_usage_in_bytes
-
-r--r--r--. 1 root root 0 Sep 5 10:18 memory.memsw.usage_in_bytes
-
-rw-r--r--. 1 root root 0 Sep 5 10:18 memory.move_charge_at_immigrate
-
-r--r--r--. 1 root root 0 Sep 5 10:18 memory.numa_stat
-
-rw-r--r--. 1 root root 0 Sep 5 10:18 memory.oom_control
-
----------. 1 root root 0 Sep 5 10:18 memory.pressure_level
-
-rw-r--r--. 1 root root 0 Sep 5 10:18 memory.soft_limit_in_bytes
-
-r--r--r--. 1 root root 0 Sep 5 10:18 memory.stat
-
-rw-r--r--. 1 root root 0 Sep 5 10:18 memory.swappiness
-
-r--r--r--. 1 root root 0 Sep 5 10:18 memory.usage_in_bytes
-
-rw-r--r--. 1 root root 0 Sep 5 10:18 memory.use_hierarchy
-
-rw-r--r--. 1 root root 0 Sep 5 10:18 notify_on_release
-
-rw-r--r--. 1 root root 0 Sep 5 10:18 tasks
其中 memory.limit_in_bytes 文件代表内存使用总量,单位为 byte。
例如,这里我希望对内存使用限制为 1G,则向 memory.limit_in_bytes 文件写入 1073741824,命令如下:
第二步:创建进程,加入 cgroup
同样把当前 shell 进程 ID 写入 tasks 文件内:
第三步,执行内存测试工具,申请内存
这里我们需要借助一下工具 memtester,memtester 的安装这里不再详细介绍了。具体安装方式可以参考这里。
安装好 memtester 后,我们执行以下命令:
-
-
memtester version 4.2.2 (64-bit)
-
Copyright (C) 2010 Charles Cazabon.
-
Licensed under the GNU General Public License version 2 (only).
-
-
pagesize is 4096
-
pagesizemask is 0xfffffffffffff000
-
want 1500MB (1572864000 bytes)
-
got 1500MB (1572864000 bytes), trying mlock ...Killed
该命令会申请 1500 M 内存,并且做内存测试。由于上面我们对当前 shell 进程内存限制为 1 G,当 memtester 使用的内存达到 1G 时,cgroup 便将 memtester 杀死。
上面最后一行的输出结果表示 memtester 想要 1500 M 内存,但是由于 cgroup 限制,达到了内存使用上限,被杀死了,与我们的预期一致。
我们可以使用以下命令,降低一下内存申请,将内存申请调整为 500M:
-
-
memtester version 4.2.2 (64-bit)
-
Copyright (C) 2010 Charles Cazabon.
-
Licensed under the GNU General Public License version 2 (only).
-
-
pagesize is 4096
-
pagesizemask is 0xfffffffffffff000
-
want 500MB (524288000 bytes)
-
got 500MB (524288000 bytes), trying mlock ...locked.
-
Loop 1/1:
-
Stuck Address : ok
-
Random Value : ok
-
Compare XOR : ok
-
Compare SUB : ok
-
Compare MUL : ok
-
Compare DIV : ok
-
Compare OR : ok
-
Compare AND : ok
-
Sequential Increment: ok
-
Solid Bits : ok
-
Block Sequential : ok
-
Checkerboard : ok
-
Bit Spread : ok
-
Bit Flip : ok
-
Walking Ones : ok
-
Walking Zeroes : ok
-
8-bit Writes : ok
-
16-bit Writes : ok
-
-
Done.
这里可以看到,此时 memtester 已经成功申请到 500M 内存并且正常完成了内存测试。