cgroups java_容器底层原理之namespace和cgroups

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         16G0   16G   0% /dev

tmpfs            16G0   16G   0% /dev/shm

tmpfs            16G0   16G   0% /sys/fs/cgroup

tmpfs            16G   57M   16G1% /run

tmpfs3.2G     0  3.2G   0% /run/user/1000tmpfs            20M0   20M   0% /tmp/tmpfs

新开一个窗口,查看主机挂载信息

可看到挂载信息不同

[centos@centos7~]$ df -h

Filesystem      Size  Used Avail Use%Mounted on

devtmpfs         16G0   16G   0% /dev

tmpfs            16G0   16G   0% /dev/shm

tmpfs            16G   57M   16G1% /run

tmpfs            16G0   16G   0% /sys/fs/cgroup/dev/vda1       500G  1.4G  499G   1% /tmpfs3.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]#psaux

USER       PID%CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND

root1  0.0  0.0 115544  2004 pts/0    S    10:57   0:00bash

root10  0.0  0.0 155444  1764 pts/0    R+   10:59   0:00 ps aux

1.3、隔离网络设备、端口号

net namespace实现网络设备的隔离。

查看主机网络信息

$ ip a1: lo: mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00inet127.0.0.1/8scope host lo

valid_lft forever preferred_lft forever

inet6 ::1/128scope host

valid_lft forever preferred_lft forever2: eth0: mtu 1500 qdisc pfifo_fast state UP group default qlen 1000link/ether 02:11:b0:14:01:0c brd ff:ff:ff:ff:ff:ff

inet172.20.1.11/24 brd 172.20.1.255scope global dynamic eth0

valid_lft 86063337sec preferred_lft 86063337sec

inet6 fe80::11:b0ff:fe14:10c/64scope link

valid_lft forever preferred_lft forever3: docker0: mtu 1500qdisc noqueue state DOWN group default

link/ether 02:42:82:8d:a0:dfbrd ff:ff:ff:ff:ff:ff

inet172.17.0.1/16scope global docker0

valid_lft forever preferred_lft forever

inet6 fe80::42:82ff:fe8d:a0df/64scope link

valid_lft forever preferred_lft forever

创建一个net namespace

$sudo unshare --net --fork /bin/bash

[root@centos7 centos]#

查看此namespace的网络信息

[root@centos7 centos]# ip a1: lo: mtu 65536 qdisc noop state DOWN group default qlen 1000link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

1.4、隔离主机名

UTS Namespace 主要是用来隔离主机名的,它允许每个 UTS Namespace 拥有一个独立的主机名。

$sudo unshare --uts --fork /bin/bash

[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 ~]$ hostname

centos7

可以看到主机的名称仍然为 centos7,并没有被修改。由此,可以验证 UTS Namespace 可以用来隔离主机名。

1.5、IPC Namespace

IPC Namespace 主要是用来隔离进程间通信的。例如 PID Namespace 和 IPC Namespace 一起使用可以实现同一 IPC Namespace 内的进程彼此可以通信,不同 IPC Namespace 的进程却不能通信。

同样我们通过一个实例来验证下IPC Namespace的作用,首先我们使用 unshare 命令来创建一个 IPC Namespace:

复制代码

$sudo unshare --ipc --fork /bin/bash

[root@centos7 centos]#

下面我们需要借助两个命令来实现对 IPC Namespace 的验证。

ipcs -q 命令:用来查看系统间通信队列列表。

ipcmk -Q 命令:用来创建系统间通信队列。

我们首先使用 ipcs -q 命令查看一下当前 IPC Namespace 下的系统通信队列列表:

复制代码

[centos@centos7 ~]$ ipcs -q

------ Message Queues --------

key        msqid      owner      perms      used-bytes   messages

由上可以看到当前无任何系统通信队列,然后我们使用 ipcmk -Q 命令创建一个系统通信队列:

复制代码

[root@centos7 centos]# ipcmk -Q

Message queue id: 0

再次使用 ipcs -q 命令查看当前 IPC Namespace 下的系统通信队列列表:

复制代码

[root@centos7 centos]# ipcs -q

------ Message Queues --------

key        msqid      owner      perms      used-bytes   messages

0x73682a32 0          root       644        0            0

可以看到我们已经成功创建了一个系统通信队列。然后我们新打开一个命令行窗口,使用ipcs -q 命令查看一下主机的系统通信队列:

复制代码

[centos@centos7 ~]$ ipcs -q

------ 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@centos7 ~]$ unshare --user -r /bin/bash

[root@centos7 ~]#

CentOS7 默认允许创建的 User Namespace 为 0,如果执行上述命令失败( unshare 命令返回的错误为 unshare: unshare failed: Invalid argument ),需要使用以下命令修改系统允许创建的 User Namespace 数量,命令为:echo 65535 > /proc/sys/user/max_user_namespaces,然后再次尝试创建 User Namespace。

然后执行 id 命令查看一下当前的用户信息:

复制代码

[root@centos7 ~]# id

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@centos7 ~]# reboot

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 子系统下创建测试文件夹:

复制代码

#mkdir /sys/fs/cgroup/cpu/mydocker

执行完上述命令后,我们查看一下我们新创建的目录下发生了什么?

复制代码

#ls -l /sys/fs/cgroup/cpu/mydocker

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 中:

复制代码

#cd /sys/fs/cgroup/cpu/mydocker

#echo $$ > tasks

查看一下 tasks 文件内容:

复制代码

#cat tasks

3485

3543

其中第一个进程 ID 为当前 shell 的主进程,也就是说,当前 shell 主进程为 3485。

第三步:执行 CPU 耗时任务,验证 cgroup 是否可以限制 cpu 使用时间

下面,我们使用以下命令制造一个死循环,来提升 cpu 使用率:

复制代码

#while true;do echo;done;

执行完上述命令后,我们新打开一个 shell 窗口,使用 top -p 命令查看当前 cpu 使用率,-p 参数后面跟进程 ID,我这里是 3485。

复制代码

$top -p 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

%Cpu(s):  9.7 us,  2.8 sy,  0.0 ni, 87.4 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st

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 核,命令如下:

复制代码

#cd /sys/fs/cgroup/cpu/mydocker

#echo 50000 > cpu.cfs_quota_us

同样使用上面的命令来制造死循环:

复制代码

#while true;do echo;done;

保持当前窗口,新打开一个 shell 窗口,使用 top -p 参数查看 cpu 使用率:

复制代码

$top -p 3485

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

%Cpu(s):  5.0 us,  1.3 sy,  0.0 ni, 93.7 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st

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

复制代码

#mkdir /sys/fs/cgroup/memory/mydocker

同样,我们查看一下新创建的目录下发生了什么?

复制代码

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,命令如下:

复制代码

#cd /sys/fs/cgroup/memory/mydocker

#echo 1073741824 > memory.limit_in_bytes

第二步:创建进程,加入 cgroup

同样把当前 shell 进程 ID 写入 tasks 文件内:

复制代码

#cd /sys/fs/cgroup/memory/mydocker

#echo $$ > tasks

第三步,执行内存测试工具,申请内存

这里我们需要借助一下工具 memtester,memtester 的安装这里不再详细介绍了。具体安装方式可以参考这里。

安装好 memtester 后,我们执行以下命令:

复制代码

#memtester 1500M 1

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 500M 1

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 内存并且正常完成了内存测试。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
cgroup(Control Group)是Linux内核中的一个功能,用于对不同进程或进程组进行资源限制和隔离。其中,CFS(Completely Fair Scheduler)是Linux内核中的一种调度算法,用于对多个进程进行公平的CPU时间片分配。 cgroup对CPU隔离的原理是通过CFS调度算法和cgroup分组管理来实现的。首先,用户可以使用cgroup机制将一组相关进程划分到一个或多个cgroup中。然后,通过对cgroup设置不同的CPU资源限制,实现对这些进程的CPU使用量进行控制。 具体实现的原理是,内核通过cgroup结构体来维护cgroup信息,并通过层次结构组织多个cgroup。在每个cgroup中,可以通过设置相应的参数来控制CPU的使用。例如,可以将一组进程划分到一个单独的cgroup中,并设置该cgroup的CPU份额(cpu.shares)来决定其获取CPU时间片的优先级。相对而言,具有更高份额的cgroup会获得更多的CPU时间片,优先进行CPU调度。 另外,cgroup还提供了其他参数,如cpu.cfs_quota_us和cpu.cfs_period_us。使用这两个参数可以限制一个cgroup在一定时间段内获取的CPU时间片数量。并结合cfs.bandwidth来控制CPU的使用。这样可以进一步限制每个cgroup的CPU使用量,保证在物理机的CPU资源有限的情况下,每个cgroup获得公平的CPU时间片分配。 总而言之,cgroup通过使用CFS调度算法和设置cgroup参数,可以实现对CPU的隔离和限制。这样可以确保不同的进程组在使用CPU资源时能够公平竞争,避免某个进程或进程组占用过多的CPU,从而提高整个系统的性能和稳定性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值