一、chroot
使用 chroot 创建一个隔离环境
# 拉取 busybox 镜像
docker pull busybox:1.36.0
# 镜像打成 tar 包
docker save -o busybox_1.36.0.tar busybox:1.36.0
zxm@zxm-pc:~/chroot-dir$ mkdir -p busybox_tmp
zxm@zxm-pc:~/chroot-dir$ mkdir -p busybox
zxm@zxm-pc:~/chroot-dir$ ls
busybox busybox_1.36.0.tar busybox_tmp
zxm@zxm-pc:~/chroot-dir$ tar -xf busybox_1.36.0.tar -C busybox_tmp/
zxm@zxm-pc:~/chroot-dir$ ls busybox_tmp/
af2c3e96bcf1a80da1d9b57ec0adc29f73f773a4a115344b7e06aec982157a33.json b2ba015c4eb00edaebd77acab07c710635615297303670b605a441babe0bb04a manifest.json repositories
zxm@zxm-pc:~/chroot-dir$ ls busybox_tmp/b2ba015c4eb00edaebd77acab07c710635615297303670b605a441babe0bb04a/
json layer.tar VERSION
zxm@zxm-pc:~/chroot-dir$ tar -xf busybox_tmp/b2ba015c4eb00edaebd77acab07c710635615297303670b605a441babe0bb04a/layer.tar -C busybox/
zxm@zxm-pc:~/chroot-dir$ ls busybox/
bin dev etc home lib lib64 root tmp usr var
进入容器内:
zxm@zxm-pc:~/chroot-dir$ sudo chroot busybox /bin/sh -i
/ # ls
bin dev etc home lib lib64 root tmp usr var
/ # whoami
root
挂载虚拟系统 sys、proc,它可以访问所有的网络设备,可以访问到主机上的进程及网络等信息,这表示没有任何的进程或者网络隔离,这带来的危险是我们甚至可以在容器内杀掉容器外的进程,或者通过容器来攻击主机。
/ # mkdir -p /sys
/ # mount -t sysfs sys /sys
/ # mkdir -p /proc
/ # mount -t proc proc /proc
/ # ip r
default via 192.168.1.1 dev enp4s0 metric 100
10.0.3.0/24 dev br-bfcfe6038b6e scope link src 10.0.3.1
169.254.0.0/16 dev enp4s0 scope link metric 1000
172.16.49.0/24 dev vmnet1 scope link src 172.16.49.1
172.16.107.0/24 dev vmnet8 scope link src 172.16.107.1
172.17.0.0/16 dev docker0 scope link src 172.17.0.1
172.19.0.0/16 dev br-220f5959c723 scope link src 172.19.0.1
192.168.1.0/24 dev enp4s0 scope link src 192.168.1.40 metric 100
/ # ls /sys/class/net/
br-220f5959c723 br-bfcfe6038b6e docker0 enp4s0 lo veth256e23f vethdc92448 vmnet1 vmnet8
/ # df -ah
Filesystem Size Used Available Use% Mounted on
sys 0 0 0 0% /sys
proc 0 0 0 0% /proc
/ # umount /sys/
/ # umount /proc/
chroot 一个只进行了文件系统隔离的容器
二、namespace
namespace 中的进程/进程组可以看起来拥有自己的独立资源,具体的“资源”表现形式取决于给它赋予了哪些 namespace
我们可以通过三个系统调用直接操作 namespace ,这三个系统调用分别是:
- clone: 可以通过传递不同 namespace 的标志来为新的(子)进程指定其所属的 namespace
- unshare: 允许一个进程(或线程)取消当前与其他进程(或线程)共享的执行上下文
- setns: 进入文件描述符指定的 namespace
1.unshare
参数:
-m, --mount[=<文件>]
取消共享 mounts 名字空间-u, --uts[=<文件>]
取消共享 UTS 名字空间(主机名等)-i, --ipc[=<文件>]
取消共享 System V IPC 名字空间-n, --net[=<file>]
取消共享网络名字空间-p, --pid[=<文件>]
取消共享 pid 名字空间-U, --user[=<文件>]
取消共享用户名字空间-C, --cgroup[=<文件>]
取消共享 cgroup 名字空间-f, --fork
在启动<程序>前 fork-r, --map-root-user
将当前用户映射为 root (连带打开 --user)--mount-proc[=<dir>]
mount proc filesystem first (implies --mount)-R, --root=<dir>
run the command with root directory set to<dir>
-w, --wd=<dir>
change working directory to<dir>
-S, --setuid <uid>
set uid in entered namespace-G, --setgid <gid>
set gid in entered namespace
(1) Mount Namespace
可以用来隔离不同的进程或进程组看到的挂载点,使用 Mount Namespace 可以实现容器内只能看到自己的挂载信息,在容器内的挂载操作不会影响主机的挂载目录。
容器内:
zxm@zxm-pc:~$ sudo unshare --mount --fork /bin/bash
root@zxm-pc:/home/zxm# df -h
文件系统 容量 已用 可用 已用% 挂载点
/dev/nvme0n1p2 457G 141G 294G 33% /
udev 16G 0 16G 0% /dev
tmpfs 16G 147M 16G 1% /dev/shm
tmpfs 3.2G 2.0M 3.2G 1% /run
tmpfs 5.0M 4.0K 5.0M 1% /run/lock
tmpfs 3.2G 48K 3.2G 1% /run/user/1000
tmpfs 16G 0 16G 0% /sys/fs/cgroup
/dev/nvme0n1p1 511M 6.1M 505M 2% /boot/efi
root@zxm-pc:/home/zxm# mount -t tmpfs -o size=20m tmpfs /tmp/tmpfs
root@zxm-pc:/home/zxm# df -h
文件系统 容量 已用 可用 已用% 挂载点
/dev/nvme0n1p2 457G 141G 294G 33% /
udev 16G 0 16G 0% /dev
tmpfs 16G 147M 16G 1% /dev/shm
tmpfs 3.2G 2.0M 3.2G 1% /run
tmpfs 5.0M 4.0K 5.0M 1% /run/lock
tmpfs 3.2G 48K 3.2G 1% /run/user/1000
tmpfs 16G 0 16G 0% /sys/fs/cgroup
/dev/nvme0n1p1 511M 6.1M 505M 2% /boot/efi
tmpfs 20M 0 20M 0% /tmp/tmpfs
主机:
zxm@zxm-pc:~$ df -h
文件系统 容量 已用 可用 已用% 挂载点
udev 16G 0 16G 0% /dev
tmpfs 3.2G 2.0M 3.2G 1% /run
/dev/nvme0n1p2 457G 141G 294G 33% /
tmpfs 16G 155M 16G 1% /dev/shm
tmpfs 5.0M 4.0K 5.0M 1% /run/lock
tmpfs 16G 0 16G 0% /sys/fs/cgroup
/dev/nvme0n1p1 511M 6.1M 505M 2% /boot/efi
tmpfs 3.2G 48K 3.2G 1% /run/user/1000
(2) PID Namespace
PID Namespace 的作用是用来隔离进程。在不同的 PID Namespace 中,进程可以拥有相同的 PID 号,利用 PID Namespace 可以实现每个容器的主进程为 1 号进程,而容器内的进程在主机上却拥有不同的PID。
容器内:
zxm@zxm-pc:~$ sudo unshare --fork --pid --mount-proc /bin/bash
root@zxm-pc:/home/zxm#
root@zxm-pc:/home/zxm# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 18:28 pts/1 00:00:00 /bin/bash
root 8 1 0 18:28 pts/1 00:00:00 ps -ef
(3) UTS Namespace
UTS Namespace 主要是用来隔离主机名的,它允许每个 UTS Namespace 拥有一个独立的主机名。
zxm@zxm-pc:~$ sudo unshare --fork --uts /bin/bash
root@zxm-pc:/home/zxm# hostname
zxm-pc
root@zxm-pc:/home/zxm# hostname -b docker-test
root@zxm-pc:/home/zxm# hostname
docker-test
(4) IPC Namespace
IPC Namespace 主要是用来隔离进程间通信的。例如 PID Namespace 和 IPC Namespace 一起使用可以实现同一 IPC Namespace 内的进程彼此可以通信,不同 IPC Namespace 的进程却不能通信。
zxm@zxm-pc:~$ sudo unshare --fork --ipc /bin/bash
root@zxm-pc:/home/zxm# ipcs -q
--------- 消息队列 -----------
键 msqid 拥有者 权限 已用字节数 消息
root@zxm-pc:/home/zxm# ipcmk -Q
消息队列 id:0
root@zxm-pc:/home/zxm# ipcmk -Q
消息队列 id:1
root@zxm-pc:/home/zxm# ipcs -q
--------- 消息队列 -----------
键 msqid 拥有者 权限 已用字节数 消息
0x7743ee6e 0 root 644 0 0
0xa2ffa527 1 root 644 0 0
(5) User Namespace
User Namespace 主要是用来隔离用户和用户组的。在主机上以非 root 用户运行的进程可以在一个单独的 User Namespace 中映射成 root 用户。
使用 User Namespace 可以实现进程在容器内拥有 root 权限,而在主机上却只是普通用户。
User Namesapce 的创建是可以不使用 root 权限的。
zxm@zxm-pc:~$ unshare --user -r /bin/bash
root@zxm-pc:~# id
用户id=0(root) 组id=0(root) 组=0(root),65534(nogroup)
root@zxm-pc:~# reboot
Failed to connect to bus: 不允许的操作
Failed to open initctl fifo: 权限不够
Failed to talk to init daemon.
我们在新创建的 User Namespace 内虽然是 root 用户,但是并没有权限执行 reboot 命令。这说明在隔离的 User Namespace 中,并不能获取到主机的 root 权限,也就是说 User Namespace 实现了用户和用户组的隔离。
(6) Net Namespace
Net Namespace 是用来隔离网络设备、IP 地址和端口等信息的。Net Namespace 可以让每个进程拥有自己独立的 IP 地址,端口和网卡信息。
root@zxm-pc:~# 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: enp4s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether a8:a1:59:52:6d:10 brd ff:ff:ff:ff:ff:ff
inet 192.168.1.40/24 brd 192.168.1.255 scope global noprefixroute enp4s0
valid_lft forever preferred_lft forever
inet6 fe80::4104:f838:4634:a65a/64 scope link noprefixroute
valid_lft forever preferred_lft forever
3: vmnet1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UNKNOWN group default qlen 1000
link/ether 00:50:56:c0:00:01 brd ff:ff:ff:ff:ff:ff
inet 172.16.49.1/24 brd 172.16.49.255 scope global vmnet1
valid_lft forever preferred_lft forever
inet6 fe80::250:56ff:fec0:1/64 scope link
valid_lft forever preferred_lft forever
4: vmnet8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UNKNOWN group default qlen 1000
link/ether 00:50:56:c0:00:08 brd ff:ff:ff:ff:ff:ff
inet 172.16.107.1/24 brd 172.16.107.255 scope global vmnet8
valid_lft forever preferred_lft forever
inet6 fe80::250:56ff:fec0:8/64 scope link
valid_lft forever preferred_lft forever
5: br-220f5959c723: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
link/ether 02:42:a1:c3:29:04 brd ff:ff:ff:ff:ff:ff
inet 172.19.0.1/16 brd 172.19.255.255 scope global br-220f5959c723
valid_lft forever preferred_lft forever
6: br-bfcfe6038b6e: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
link/ether 02:42:79:81:1b:1e brd ff:ff:ff:ff:ff:ff
inet 10.0.3.1/24 brd 10.0.3.255 scope global br-bfcfe6038b6e
valid_lft forever preferred_lft forever
7: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
link/ether 02:42:db:e1:9b:25 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft forever
root@zxm-pc:~# unshare --net --fork /bin/bash
root@zxm-pc:~# 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
namespace 或者 chroot 之类的都可以造出来一个 “容器”