技术实现
Docker 是使用 Linux 的 Namespace 技术实现各种资源隔离的。
Namespace 是 Linux 内核的一项功能,该功能对内核资源进行分区,以使一组进程看到一组资源,而另一组进程看到另一组资源。Namespace 的工作方式通过为一组资源和进程设置相同的 Namespace 而起作用,但是这些 Namespace 引用了不同的资源。资源可能存在于多个 Namespace 中。这些资源可以是进程 ID、主机名、用户 ID、文件名、与网络访问相关的名称和进程间通信。
Namespace 类型
在最新的 Linux 5.6 内核中,提供了 8 种类型的 Namespace,但最新版本的 Docker 只使用了其中的前 6 种类型,如下表所示:
Namespace 名称 | 简称 | 作用 | 内核版本 |
---|---|---|---|
Mount | mnt | 隔离挂载点 | 2.4.19 |
Process ID | pid | 隔离进程ID | 2.6.24 |
Network | net | 隔离网络设备、端口号等 | 2.6.29 |
Interprocess Communication | ipc | 隔离信息量、消息队列和共享内存 | 2.6.19 |
UTS | uts | 隔离主机名和域名 | 2.6.19 |
User | user | 隔离用户和用户组 | 3.8 |
Control group | cgroup | 隔离 Cgroups 根目录 | 4.6 |
Time | time | 隔离系统时间 | 5.6 |
Namespace 各类型作用
通过使用
unshare
命令可以实现创建并访问不同类型的 Namespace,模拟 Dokcer 资源隔离的效果。以下只针对 Docker 使用的前 6 种类型来分析它们各自的作用:
##### Mount Namespace:实现在不同的进程中看到不同的挂载目录
### 通过unshare命令创建一个bash进程,作为窗口1
$ unshare --mount --fork /bin/bash
[root@centos7 ~]# mkdir /tmp/tmpfs
[root@centos7 ~]# mount -t tmpfs -o size=20m tmpfs /tmp/tmpfs
[root@centos7 ~]# df -h /tmp/tmpfs
Filesystem Size Used Avail Use% Mounted on
tmpfs 20M 0 20M 0% /tmp/tmpfs
### 打开一个新的命令行窗口,作为窗口2验证
[root@centos7 ~]# df -h /tmp/tmpfs
df: /tmp/tmpfs: No such file or directory
--------------------------------------------------------------------------------------------------
##### PID Namespace:实现每个容器的主进程为1号进程,而容器内的进程在主机上却拥有不同的PID
### 在当前命令行窗口加入了新创建的 PID Namespace,从而看不到主机上其他进程信息
$ unshare --pid --fork --mount-proc /bin/bash
[root@centos7 ~]# 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
--------------------------------------------------------------------------------------------------
##### UTS Namespace:实现在容器内的主机名称为任意自定义的主机名
### 在当前命令行窗口加入了新创建的 UTS Namespace,作为窗口1
$ unshare --uts --fork /bin/bash
[root@centos7 ~]# hostname -b utsdocker && hostname
utsdocker
### 打开一个新的命令行窗口,作为窗口2验证
[root@centos7 ~]# hostname
centos7
--------------------------------------------------------------------------------------------------
##### IPC Namespace:实现在容器内的不同空间下的进程间不能通信
### 在当前命令行窗口加入了新创建的 IPC Namespace,作为窗口1
$ unshare --ipc --fork /bin/bash
## 创建系统间通信队列
[root@centos7 centos]# ipcmk -Q
Message queue id: 0
## 查看系统间通信队列列表
[root@centos7 ~]# ipcs -q
------ Message Queues --------
key msqid owner perms used-bytes messages
0x73682a32 0 root 644 0 0
### 打开一个新的命令行窗口,作为窗口2验证
[root@centos7 ~]# ipcs -q
------ Message Queues --------
key msqid owner perms used-bytes messages
--------------------------------------------------------------------------------------------------
##### User Namespace:实现进程在容器内拥有root权限,而在主机上却只是普通用户
### unshare命令报错无效参数,需要修改系统允许创建 User Namespace 的数量
$ echo 65535 > /proc/sys/user/max_user_namespace
### 在当前命令行窗口加入了新创建的 User Namespace
$ unshare --user -r /bin/bash
[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
### 虽然已是root用户,但执行重启失败,说明并不能获取到主机的root权限
[root@centos7 ~]# reboot
Failed to open /dev/initctl: Permission denied
Failed to talk to init daemon.
--------------------------------------------------------------------------------------------------
##### Net Namespace:实现每个进程拥有自己独立的IP地址、端口和网卡信息
### 在当前命令行窗口加入了新创建的 Net Namespace,作为窗口1
$ unshare --net --fork /bin/bash
[root@centos7 ~]# 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
### 打开一个新的命令行窗口,作为窗口2验证
[root@centos7 ~]# 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.25.168.11/24 brd 172.25.168.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