docker是怎么实现隔离的
docker如何实现的进程隔离
docker 容器
docker实际上,就是一个系统联合几个组件一直在欺骗一个进程,主要依靠了三个帮凶 namespace,chroot,cgroup
namespace进程的隔离
Linux提供了namespace实现的进程隔离,如果想给一个进程"指定"一个PID只需要调用一个clone()函数即可,这样创建出来的进程在宿主机进程号还是随机的,但是在这个namespace中他的进程号就是1.
namespace只是在进程层面将她隔离开了,运行在namespace中的进程看不到其他的进程,但是宿主机的其他资源(CPU,内存,文件系统)还都是系统的,也就是这些资源依然是共享的.
简单举个例子,现在有一个公司叫做Linux.公司初创的时候就会自己有一些"元老",也就是系统进程.公司运转起来了就会不断地招人进来干活,也就是创建/删除 进程.
每个员工(进程)进入公司的时候都会给发一个工牌,工牌上面有一个他自己在这个公司里唯一的工号(PID),如100,公司里所有人都有这么一个,而1号工号,就是属于老板的.往下的2,3,4,5…也就是属于公司的"元老"们的.
容器就是在这个人进入公司后将它带到一个格子间,告诉他你的工号是"1",这个格子间就是你的办公室,你这个格子间怎么规划不管,只需要开始你的工作就行了.
但实际上,这个员工还拥有他在这个公司的"工号",如:100,只不过他不知道,认为自己就是老板罢了.
使用cgroup限制进程所能使用的资源
cgroup就是用来限制进程的CPU和内存等资源的.
cgroup被应用了Linux的"一切皆文件",他的目录在/sys/fs/cgroup
下面,
首先在cgroup目录下创建一个组,也就是一个目录
mkdir /sys/fs/cgroup/cpu/container
接下来查看下这个组,cgroup已经创建好了所需的文件
[root@es1 ~]# ls /sys/fs/cgroup/cpu/container
cgroup.clone_children cgroup.procs cpuacct.usage cpu.cfs_period_us cpu.rt_period_us cpu.shares notify_on_release
cgroup.event_control cpuacct.stat cpuacct.usage_percpu cpu.cfs_quota_us cpu.rt_runtime_us cpu.stat tasks
接下来创建一个进程
while : ; do : ; done &
他会什么都不做一直循环,但是会消耗大量的CPU资源.
最好在单核主机上尝试,如果主机资源过大效果不会很明显.但是能看到一颗核使用率百分百.
[root@es1 ~]# top
top - 20:44:54 up 13 days, 6:39, 1 user, load average: 1.25, 0.80, 0.66
Tasks: 204 total, 2 running, 202 sleeping, 0 stopped, 0 zombie
%Cpu0 : 1.3 us, 0.0 sy, 0.0 ni, 98.3 id, 0.0 wa, 0.0 hi, 0.3 si, 0.0 st
%Cpu1 :100.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu2 : 4.3 us, 0.3 sy, 0.0 ni, 95.0 id, 0.3 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu3 : 17.3 us, 0.3 sy, 0.0 ni, 82.4 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu4 : 89.7 us, 9.6 sy, 0.0 ni, 0.3 id, 0.0 wa, 0.0 hi, 0.3 si, 0.0 st
%Cpu5 : 30.3 us, 0.3 sy, 0.0 ni, 68.7 id, 0.7 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu6 : 2.7 us, 0.0 sy, 0.0 ni, 97.3 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu7 : 28.7 us, 0.0 sy, 0.0 ni, 71.3 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 32780784 total, 4227068 free, 9827028 used, 18726688 buff/cache
KiB Swap: 0 total, 0 free, 0 used. 22371664 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
2609 elastic+ 20 0 64.6g 13.5g 4.4g S 184.7 43.1 18427:33 java
12890 root 20 0 115436 584 176 R 100.0 0.0 1:22.94 bash
截取部分内容,CPU1 用户态使用率百分百.同时有一个PID
12890
的bash进程使用了百分一百的CPU(就是刚才创建的循环)
接下来使用cgroup限制进程使用资源,默认情况下刚才创建的那个"组"不会被关联到任何进程上,所以想让他对刚才的进程生效,则需要将进程的PID写入到指定文件.
echo '12890' >/sys/fs/cgroup/cpu/container/tasks
接下来限制CPU
[root@es1 ~]# cat /sys/fs/cgroup/cpu/container/cpu.cfs_quota_us
-1
对应目录下的cpu.cfs_quota_us 文件内容为"-1",也就是不作任何限制.
echo 20000 > /sys/fs/cgroup/cpu/container/cpu.cfs_quota_us
20000
意味着在CPU100ms的时间里,可以使用20ms的时间,也就是限制为使用率20%.
接下来重新查看使用率
[root@es1 ~]# top
top - 20:52:11 up 13 days, 6:47, 1 user, load average: 1.47, 1.44, 1.04
Tasks: 204 total, 4 running, 200 sleeping, 0 stopped, 0 zombie
%Cpu(s): 9.5 us, 0.0 sy, 0.0 ni, 90.5 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 32780784 total, 5483900 free, 9826072 used, 17470812 buff/cache
KiB Swap: 0 total, 0 free, 0 used. 22372168 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
2609 elastic+ 20 0 64.0g 12.7g 3.6g S 55.1 40.5 18433:55 java
12890 root 20 0 115436 584 176 R 20.3 0.0 8:31.47 bash
进程12890
使用率只有20%左右了.
cgroup没办法对进程精准限制CPU的使用率,所以会在20%这个值上下产生浮动.docker无非也就是在创建容器的时候同时创建一个cgroup组,将对应的PID写入进去,所以docker只能是个"单进程"模式,如果同时包含两个进程,那么只有主进程的资源限制可以生效了.
chroot
在将进程和宿主机资源都隔离开以后,只剩下一个 文件系统 了.
docker中每一个容器都有一个"自己"的根目录,但是如果在宿主机执行df -h
这样的命令,还是可以看到所有的容器的,所以容器内的"根目录"实际上是依赖于宿主机的文件系统而存在的.无非就是把宿主机的某个目录 "挂载"到了容器中,同时这个只对容器中的进程可见.
首先创建了一个容器,然后查看下主机的目录挂载
[root@worker3 ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
177db9617483 528909316/check:debian_11 "tail -f /etc/hosts" 2 weeks ago Up 2 weeks net2
[root@worker3 ~]# df -h
Filesystem Size Used Avail Use% Mounted on
devtmpfs 3.9G 0 3.9G 0% /dev
tmpfs 3.9G 0 3.9G 0% /dev/shm
tmpfs 3.9G 401M 3.5G 11% /run
tmpfs 3.9G 0 3.9G 0% /sys/fs/cgroup
/dev/mapper/centos-root 48G 20G 28G 42% /
/dev/sda1 1014M 178M 837M 18% /boot
overlay 48G 20G 28G 42% /data/docker/overlay2/072a996f43e54fc20475a5c2df7c61856bfd30525c7ec955c157390b6ad78144/merged
tmpfs 796M 0 796M 0% /run/user/0
只有一个容器所以只产生一个挂载,接下来看下类型为overlay的这条挂载目录下的内容:
[root@worker3 ~]# ls /data/docker/overlay2/072a996f43e54fc20475a5c2df7c61856bfd30525c7ec955c157390b6ad78144/merged
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
接下来进入容器查看下根目录
[root@worker3 ~]# docker exec -it 177db9617483 /bin/bash
root@net2:/# ls
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
一模一样,为了证实两个是一个目录,在容器中写入文件一些东西,然后退出容器查看主机的对应目录下,是否出现了相同的文件切内容相同
echo "hello world!" >mount_test.txt
root@net2:/# echo "hello world!" >mount_test.txt
root@net2:/# cat mount_test.txt
hello world!
root@net2:/#
exit
[root@worker3 ~]# cat /data/docker/overlay2/072a996f43e54fc20475a5c2df7c61856bfd30525c7ec955c157390b6ad78144/merged/mount_test.txt
hello world!
所以容器就是把主机上的一个目录"挂载"进去,用这个目录作为容器的"根目录".
实现这个过程,就是chroot完成的.
创建一个"根目录"
首先创建一个目录,选在了/test下面.创建了一个container1的目录作为假设的容器的根目录.
mkdir -p /test/container1
然后给这个"容器"创建一些基础目录存放一些基础命令
mkdir -p /test/container1/{bin,lib64,lib}
接下来复制个命令给他,同时把所需的库文件一并复制进去
cp /bin/{bash,ls} /test/container1/bin
list="$(ldd /bin/ls | egrep -o '/lib.*\.[0-9]')"
for i in $list; do cp "$i" "/test/container1${i}"; done
list="$(ldd /bin/bash | egrep -o '/lib.*\.[0-9]')"
# \cp :如果存在相同文件覆盖不提示
for i in $list; do \cp "$i" "/test/container1${i}"; done
ldd命令可以查看一个二进制可执行文件(比如命令)所依赖的库文件,然后全部复制到"容器"的对应目录
接下来,就是用chroot进入这个"容器"的"根目录"
chroot /test/container1/ /bin/bash
进入以后,直接敲任何命令都是找不到的,因为默认的环境变量都是空的.
[root@worker3 ~]# chroot /test/container1/ /bin/bash
bash-4.2# ls
bash: ls: command not found
声明一下变量,当执行命令时去/bin下面找.
PATH=/bin
bash-4.2# PATH=/bin
bash-4.2# ls
bin lib lib64
bash-4.2#
重新执行ls,可以查看到根目录的内容了.不过只有三个目录都是刚才创建的,而命令也只有bash和ls.因为别的都没有复制到容器里.
接下来ctrl +D就能退出这个"容器"了.
所以docker的"镜像"所包含的内容,实际上就是一个个的目录,下面存放了各种库文件.当部署一个容器的时候将这个压缩包"解压"到一个指定的位置,先用namespace将进程隔离开,然后cgroup给他施加一定的限制,随后给他挂载一些目录放些基础命令,容器就运转起来了.但是这个容器本身并不包含系统内核文件,所以所有的容器都是依托于Linux内核而存在的一个特殊进程.
把整个系统看做一个房子,一个容器就相当于房子里的一个箱子.
当创建一个进程的时候,namespace把他放进一个箱子里,让他看不见箱子外面的世界,cgroup来给他固定一个大小:箱子最大可以占用整个房间的多大空间.最后,chroot来在箱子里"装修"一下,比如装个电灯泡刷个大白,让他从内部看起来和外面的房间几乎没什么区别.
最后进程在这个箱子里开始工作,他可以随意改变箱子里面的一切这都不会影响到外面的房间,但是他这个箱子的空间是共享的外面房子的空间.箱子也是依托于这个房间而存在的.
参考文献
更多推荐
所有评论(0)