刚开始学习docker的时候,在ppt中看到,docker本质就是进程,云里雾里,就稀里糊涂,过了一段时间不用docker,连docker长啥样都记不起来了。这就是知其然而不知其所然。
而真正能理解docker的本质是进程,必须具备的是linux的基础知识,从基础知识角度来进行理解,否则就是雾里看花,糊里糊涂。
资料上大多介绍,docker的两大关键技术是Namespace和cgroup,以及docker的关键创新镜像。所以让我们从linux基础知识上剖析一下。
Namespace
做个试验,依次执行如下命令:
sudo docker pull busybox
sudo docker run -it busybox /bin/sh
/ # ps
PID USER TIME COMMAND
1 root 0:00 /bin/sh
7 root 0:00 ps
解释一下:
setp1: 命令docker pull download下来busybox,busybox是个基础镜像。
setp2: 使用docker run,加上-it参数,运行/bin/sh,建立一个交互式命令窗口(注意如果退出用Ctrl + P + Q,退出交互式窗口,这样不会关闭容器)
setp3: 执行ps命令
魔力就在这里了,为何我们看到这里有两个进程,一个是刚刚执行的PS,一个是/bin/sh,而且进程号是1,而退出到容器外,使用
ps aux | grep /bin/sh
root 18531 0.0 0.0 1296 4 ? Ss+ 17:19 0:00 /bin/sh
ubuntu 21065 0.0 0.0 13228 928 pts/4 S+ 17:48 0:00 grep --color=auto /bin/sh
只能看到18531这个进程有/bin/sh,这是怎么实现的呢?
int pid = clone(main_function, stack_size, CLONE_NEWPID | SIGCHLD, NULL);
注意增加了一个CLONE_NEWPID参数,实施了一个障眼法,在这个进程里,进程看不到所有它前面的进程,认为自己是1号进程。这就是PID namespace.
而Namespace机制还有 PID, UTS, network, user, mount, IPC,这些Namespace技术改变了容器的视图,让容器以为自己在一个房间里,起到了隔离作用。
Linux Control Group
这个是什么作用了,就是限制,容器的本质是进程,如上namespace实施了障眼法,对资源进行了隔离,但是如果这个进程把资源都消耗光了,容器就没有什么价值了。
在 Linux 中,Cgroups 给用户暴露出来的操作接口是文件系统,即它以文件和目录的方式组织在操作系统的 /sys/fs/cgroup 路径下。
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_cls,net_prio)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpu,cpuacct)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
memory on /run/lxcfs/controllers/memory type cgroup (rw,relatime,memory)
cpuset on /run/lxcfs/controllers/cpuset type cgroup (rw,relatime,cpuset)
freezer on /run/lxcfs/controllers/freezer type cgroup (rw,relatime,freezer)
blkio on /run/lxcfs/controllers/blkio type cgroup (rw,relatime,blkio)
hugetlb on /run/lxcfs/controllers/hugetlb type cgroup (rw,relatime,hugetlb)
pids on /run/lxcfs/controllers/pids type cgroup (rw,relatime,pids)
devices on /run/lxcfs/controllers/devices type cgroup (rw,relatime,devices)
cpu,cpuacct on /run/lxcfs/controllers/cpu,cpuacct type cgroup (rw,relatime,cpu,cpuacct)
net_cls,net_prio on /run/lxcfs/controllers/net_cls,net_prio type cgroup (rw,relatime,net_cls,net_prio)
perf_event on /run/lxcfs/controllers/perf_event type cgroup (rw,relatime,perf_event)
name=systemd on /run/lxcfs/controllers/name=systemd type cgroup (rw,relatime,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd)
可以看到可以限制cpu、内存等各类资源。
如果执行
docker run -it --cpu-period=100000 --cpu-quota=20000 busybox /bin/sh
如果查看 /sys/fs/cgroup/cpu/cpu.cfs_quota_us,会看到20000被写入到了文件中,这样20000/100000,说明写入到tasks中的进程智能占用20%的CPU。这就是限制作用的原理。
namespace和cgroup共同为创造了一个容器沙盒。
容器镜像
容器镜像不同于虚拟机镜像,虚拟机镜像一般是硬盘的拷贝,非常大,但是容器镜像就没有那么大,怎么做的呢?
容器是使用层结构的镜像,至少有三层,从上到下依次为:
- 读写层
- init层
- 只读层
(1) 只读层
是什么东西呢,只读层就是基础镜像,做个试验:
/ # ls
bin dev etc home proc root sys tmp usr var
在namespace试验中busybox镜像中执行ls,可以看到就是一个linux操作系统的系统文件,而且是精简版的。这些文件从哪里来,从busybox中,按照namespace技术,被mount到容器根目录下,检查这里面的文件,和容器外的文件中的内容还是有很大区别。
只读层的这个镜像我们可以在/var/lib/docker/overlay2下可以找到,可以证明上述mount namespace。
(2) init层
初始化的环境变量
(3) 读写层
读写层就是我们编写的程序,这些程序会覆盖下层的文件
这就是容器镜像的方式,由于容器镜像可以打包操作系统的文件,相当于大了原生的系统文件,所以具备了一致性能力,即打包后随处下载,都是一样的。
小结
通过深入linux基础知识的学习后,我们看到了容器是怎样的一个存在,是个进程,通过namespace作为障眼法进行屏蔽,通过cgroup进行资源限制,通过容器镜像的方式打包的一个沙盒。因此容器的本质就是镜像。
对比一下虚拟机,虚拟机是虚拟出硬件来,安装上独立的操作系统,因此本质上和docker有绝对的不同。