Docker起源与容器技术原理

Docker起源
在说Docker之前先说一下“容器”,“容器”这个概念从来就不是什么新鲜的东西,也不是Docker公司发明的。即使在当时最热门的PaaS项目Cloud Foundry中,容器也只是其中最底层、最每人关注的一部分。
那正好以Cloud Foundry为例,来解说一下PasS技术。PasS之所以被大家接受是因为它提供了一种“应用托管”的能力。在当时,虚拟机和云计算已经是比较普遍的技术和服务了,主流用户普遍的用法就是租一批AWS或者OpenStack的虚拟机,然后像之前托管物理机那样,使用脚本或者手工部署应用。部署过程难免会碰到云主机与本地环境不一致的问题,所以当时云厂商们比的就是谁能带来更好的“上云”体验。而PaaS开源项目的出现就是当时解决这个问题的最佳方案。
只需一条命令就能把本地应用部署到云上,像Cloud Foundry 这样的PasS项目最核心的组件就是一套应用的打包和分发机制。由于在虚拟机上启动很多个应用,会调用操作系统的Cgroups和Namespace机制为每个应用单独创建一个称作“沙盒”的隔离环境,然后在"沙盒"中启动这些应用进程。这样就实现了多个应用互补干涉的再虚拟机运行起来的目的。
cf push "我的应用"
这正是PasS的核心能力,运行应用的“沙盒”就是所谓的“容器”。
事实上,Docker项目确实与Cloud Foundry的“沙盒”大部分功能和实现原理都是一致的,可以偏偏就是剩下的小部分不一致,成了Docker项目迅速发展的不二法宝。
这个功能,就是Docker镜像。Docker给PasS前辈们来了一次“降维打击”,其实是提供了一种非常便利的打包机制。这种机制直接打包了应用运行所需的整个操作系统,从而保证了本地和云端高度一致。对开发者们来说,带来了前所未有的快感。人们自然用脚投票,直接宣告了PasS时代的结束。
容器编排"战争"
虽然Docker项目在开发者中一日千里,但是Docker公司还是有些担忧的。他们明白用户最终要部署的是代码、是项目甚至的数据库。这就以为着,只有那些能为用户提供平台层能力的工具,才会真正成为开发者关心和愿意付费的产品。而Docker只是底层容器工具。
在2014年12月,Docker公司随即发布了Swarm,Swarm最大的亮点就是完全可以使用Docker原本的容器管理API来完成集群管理。
docker run -H "Swarm集群API地址" "我的容器"
Kubernetes诞生
在2014年6月,基础设施领域的领先者Google公司突然发力,正式宣告了一个名叫Kubernates项目诞生。这个项目如同当年Docker项目的横空出世一样,再一次改变了容器领域的格局。
而Kubernetes开始整个社区”民主化“架构,从API到容器运行时的每一层,Kubernetes项目都为开发者暴露出可以扩展的插件机制,鼓励用户通过代码的方式介入到Kubernates项目的每一个阶段。很快整个容器社区催生出大量、基于Kubernetes API和扩展接口二次创作比如:
- 热度很高的微服务治理项目 Istio;
- 被广泛采用的有状态应用部署框架 Operator;
- 还有想Rook 这样的开源创业项目;
不同于PasS化的路线,这是一次容器社区的繁荣,以Kubernates为核心的。
Docker话语权的分割
由于Docker公司对Docker项目话语权比较大,容器领域的其他几位玩家开始商议如何”切割“Docker公司的话语权,而”切割“的手段就是成立一个中立的基金会,那就是名为CNCF的基金会。这个基金会目的很好理解,它希望以Kubernetes项目为基础,安装独立基金会方式运营的平台级社区,来对抗Docker公司为核心的容器商业生态。
在囊括了容器监控事实标准的Prometheus项目之后,CNCF社区迅速在成员项目中添加Fluentd、OpenTracing、CNI等一系列容器生态知名工具和项目。
Linux容器技术
容器的本质是一种特殊的进程,容器的核心就是通过约束和修改进程的动态表现,从而创造出一个“边界”世界。对于Docker等大多数容器来说,Linux的Namespace技术用来修改进程视图的主要方法,Linux的Cgroups技术则是用来制造约束的主要手段。
让我们进入docker容器世界看看
docker run -it 容器名称 /bin/sh
or
docker exec -it 容器id /bin/sh
ps可以看到一个编号为1的进程(PID=1),而在这个容器中看到只有一个进程在运行,以及我们刚刚执行的ps,都已经被Docker隔离在一个与宿主机完全不同的世界当中。这到底是如何做到的呢?
原本我们系统执行运行一个业务进程,系统都分配一个进程编号,比如PID=100,这个进程编号是系统唯一的,就像是工号一样,然后Docker把PID=100的进程运行在容器当中,这时候Docker给PID=100的进程施了一个“障眼法”,让它看不到前面的其他进程,让它误任务自己是PID=1的进程。这种机制其实就是被隔离的空间做了手脚,实际上他们在宿主机上系统进程还是原来的PID=100。这种技术就是Linux的Namespace机制。
Namespace
Namespace的使用方式:其实只是Linux创建新进程的可选参数,我们知道Linux系统中创建进程是调用clone(),我们再参数中指定CLONE_NEWPID参数,这是新创建的这个进程会看到一个全新的进程空间,在这个进程空间里它的PID是1,之所以看到是因为这是一个“障眼法”,在宿主机进程空间这个进程还是PID=100。
除了PID Namespace,Linux操作系统还提供Mount、UTS、IPC、Network和User这些 Namespace,用来对各种不同进程上下文进行“障眼法”。这就是容器让你“看见”的基本原理。
所以DOcker容器这个听起来玄而又玄的概念,实际上是在创建容器进程时,指定了这个进程所需要启动的一组Namespace参数(资源、文件、设备、网络)。对于宿主和其他容器,它完全“看”不到。
所以说,容器其实就是一种特殊进程而已。
Cgroups
Linux Cgroups 的全称是Linux Control Group,它最主要的作用就是限制一个进程组能够使用的资源上线,包括CPU、内存、磁盘、网络带宽等待。此外,Cgroups还能够对进程进行优先设置、审计以及进程挂起和恢复等操作。
可以看到/sys/fs/cgroup下有很多诸如cpu、mem、cpuset子目录,这些都被Cgroups进行限制的资源种类。
ls /sys/fs/cgroup/cpu
根目录
为了能够让容器根目录看起来“真实”,我们一般会在这个容器的根目录挂载一个完整操作系统的文件系统,比如:Ubuntu16.04的ISO,这样容器启动后,我们在容器里通过执行"ls /“查看根目录下的内容,就是Ubuntu16.04的所有目录和文件。而你进入容器之后执行/bin/bash,就是/bin目录下的可执行文件,与宿主机的/bin/bash完全不同。
现在,可以理解对于Docker来说,最核心的原理实际上是未用户进程创建:
- Linux Namespace,让你可“看见”;
- 设置Cgroups参数,然你被“限制”;
- 挂载进程根目录(Change Root);
参考:
- https://docs.docker.com/get-started/overview/
- https://www.redhat.com/zh/topics/containers/whats-a-linux-container
- https://draveness.me/understanding-kubernetes/
- https://www.redhat.com/zh/topics/containers/kubernetes-architecture
- https://minikube.sigs.k8s.io/docs/start/
- http://jartto.wang/2020/07/15/start-k8s/
