关于容器的历史、发展以及技术本质,在互联网上已经有非常多的文章了。这里旨在结合自身的工作经验和理解,通过一系列的文章,讲清楚这项技术。
容器的历史和发展 前世讲到容器,就不得不提lxc(linux container),他是docker的前生,或者说docker是lxc的使用者。完整的lxc能力在2008年合入linux主线,所以容器的概念在2008年就基本定型了,并不是后面docker造出来的。关于lxc的介绍很多,大体都会说“lxc是linux内核提供的容器技术,能提供轻量级的虚拟化能力,能隔离进程和资源”,但总结起来,无外乎就两大知识点cgroups(linux control group)和linux namespace。搞清楚他俩,容器技术就基本掌握了。
cgroups:重点在“限制”。限制资源的使用,包括cpu、内存、磁盘的使用,体现出对资源的管理能力。 namespace:重点在“隔离”。隔离进程看到的linux视图。说大白话就是,容器和容器之间不要相互影响,容器和宿主机之间不要相互影响。 少年期起步艰难2009年,cloud foundry基于lxc实现了对容器的操作,该项目取名为warden。2010年,dotcloud公司同样基于lxc技术,使用go语言实现了一款容器引擎,也就是现在的docker。那时,dotcloud公司还是个小公司,出生卑微的docker没什么热度,活得相当艰难。
成长为巨无霸2013年,dotcloud公司决定将docker开源。开源后,项目突然就火了。从大的说,火的原因就是docker的这句口号“build once,run anywhere”。呵呵,是不是似曾相识?对的,和java的write once,run anywhere一个道理。对于一个程序员来说,程序写完后打包成镜像就可以随处部署和运行,开发、测试和生产环境完全一致,这是多么大一个诱惑。程序员再也不用去定位因环境差异导致的各种坑爹问题。
docker开源项目的异常火爆,直接驱动dotcloud公司在2013年更名为docker公司。docker也快速成长,干掉了coreos公司的rkt容器和google的lmctfy容器,直接变成了容器的事实标准。也就有了后来人一提到容器就认为是docker。
总结起来,docker为什么火,靠的就是docker镜像。他打包了应用程序的所有依赖,彻底解决了环境的一致性问题,重新定义了软件的交付方式,提高了生产效率。
被列强蚕食docker在容器领域快速成长,野心自然也变大了。2014年推出了容器云产品swarm(kubenetes的同类产品),想扩张事业版图。同时docker在开源社区拥有绝对话语权,相当强势。这种走自己的路,让别人无路可走的行为,让容器领域的其他大厂玩家很是不爽,为了不让docker一家独大,决定要干他。
2015年6月,在google、redhat等大厂的“运作”下,linux基金会成立了oci(open container initiative)组织,旨在围绕容器格式和运行时制定一个开放的工业化标准,也就是我们常说的oci标准。同时,docker公司将libcontainer模块捐给cncf社区,作为oci标准的实现,这就是现在的runc项目。说白了,就是现在这块儿有个标准了,大家一起玩儿,不被某个特定项目的绑定。
讲到docker,就得说说google家的kubernetes,他作为容器云平台的事实标准,如今已被广泛使用,俨然已成为大厂标配。kubernetes原生支持docker,让docker的市场占有率一直居高不下。如图是2019年容器运行时的市场占有率。
但在2020年,kubernetes突然宣布在1.20版本以后,也就是2021年以后,不再支持docker作为默认的容器运行时,将在代码主干中去除dockershim。
如图所示,kubenetes自身定义了标准的容器运行时接口cri(container runtime interface),目的是能对接任何实现了cri接口的容器运行时。在初期,docker是容器运行时不容置疑的王者,kubenetes便内置了对docker的支持,通过dockershim来实现标准cri接口到docker接口的适配,以此获得更多的用户。随着开源的容器运行时containerd(实现了cri接口,同样由docker捐给cncf)的成熟,kubenetes不再维护dockershim,仅负责维护标准的cri,解除与某特定容器运行时的绑定。当然,也不是kubenetes不支持docker了,只是dockershim谁维护的问题。 随着kubenetes态度的变化,预计将会有越来越多的开发者选择直接与开源的containerd对接,docker公司和docker开源项目(现已改名为moby)未来将会发生什么样的变化,谁也说不好。
讲到这里,不知道大家有没有注意到,docker公司其实是捐献了containerd和runc。这俩到底是啥东西。简单的说,runc是oci标准的实现,也叫oci运行时,是真正负责操作容器的。containerd对外提供接口,管理、控制着runc。所以上面的图,真正应该长这样。
docker公司是一个典型的小公司因一个爆款项目火起来的案例,不管是技术层面、公司经营层面以及如何跟大厂缠斗,不管是好的方面还是坏的方面,都值得我们去学习和了解其背后的故事。
什么是容器按国际惯例,在介绍一个新概念的时候,都得从大家熟悉的东西说起。幸好容器这个概念还算好理解,喝水的杯子,洗脚的桶,养鱼的缸都是容器。容器技术里面的“容器”也是类似概念,只是装的东西不同罢了,他装的是应用软件本身以及软件运行起来需要的依赖。用鱼缸来类比,鱼缸这个容器里面装的应用软件就是鱼,装的依赖就是鱼食和水。这样大家就能理解docker的logo了。大海就是宿主机,docker就是那条鲸鱼,鲸鱼背上的集装箱就是容器,我们的应用程序就装在集装箱里面。
在讲容器的时候一定绕不开容器镜像,这里先简单的把容器镜像理解为是一个压缩包,后续再详细讲解。压缩包里包含应用的可执行程序以及程序依赖的文件(例如:配置文件和需要调用的动态库等),接下来通过实际操作来看看容器到底是个啥。
宿主机视角看容器 1、首先,我们启动容器。 dockerrun-d--name=@quot;aimar-1-container@quot;euleros_arm:2.0sp8spc306/bin/sh-c@quot;whiletrue;doechoaimar-1-container;sleep1;done@quot;这是docker的标准命令。意思是使用euleros_arm:2.0sp8spc306镜像(镜像名:版本号)创建一个新的名字为“aimar-1-container”的容器,并在容器中执行shell命令:每秒打印一次“aimar-1-container”。
参数说明:
-d:使用后台运行模式启动容器,并返回容器id。 --name:为容器指定一个名字。 dockerrun-d--name=@quot;aimar-1-container@quot;euleros_arm:2.0sp8spc306/bin/sh-c@quot;whiletrue;doechoaimar-1-container;sleep1;done@quot; 207b7c0cbd811791f7006cd56e17033eb430ec656f05b6cd172c77cf45ad093c从输出中,我们看到一串长字符207b7c0cbd811791f7006cd56e17033eb430ec656f05b6cd172c77cf45ad093c。他就是容器id,能唯一标识一个容器。当然在使用的时候,不需要使用全id,直接使用缩写id即可(全id的前几位)。例如下图中,通过docker ps查询到的容器id为207b7c0cbd81。
aimar-1-container容器启动成功后,我们在宿主机上使用ps进行查看。这时可以发现刚才启动的容器就是个进程,pid为12280。
我们尝试着再启动2个容器,并再次在宿主机进行查看,你会发现又新增了2个进程,pid分别为20049和21097。
所以,我们可以得到一个结论。从宿主机的视角看,容器就是进程。
2、接下来,我们进入这个容器。 dockerexec-it207b7c0cbd81/bin/bashdocker exec也是docker的标准命令,用于进入某个容器。意思是进入容器id为207b7c0cbd81的容器,进入后执行/bin/bash命令,开启命令交互。
参数说明:
-it其实是-i和-t两个参数,意思是容器启动后,要分配一个输入/输出终端,方便我们跟容器进行交互,实现跟容器的“对话”能力。
从hostname从kwephispra09909变化为207b7c0cbd81,说明我们已经进入到容器里面了。在容器中,我们尝试着启动一个新的进程。
[root@207b7c0cbd81/]#/bin/sh-c@quot;whiletrue;doechoaimar-1-container-embed;sleep1;done@quot;@amp;
再次回到宿主机进行ps查看,你会发现不管是直接启动容器,还是在容器中启动新的进程,从宿主机的角度看,他们都是进程。
容器视角看容器前面我们已经进入容器里面,并启动了新的进程。但是我们并没有在容器里查看进程的情况。在容器中执行ps,会发现得到的结果和宿主机上执行ps的结果完全不一样。下图是容器中的执行结果。
在container1容器中只能看见刚起启动的shell进程(container1和container1-embed),看不到宿主机上的其他进程,也看不到container2和container3里面的进程。这些进程像被关进了一个盒子里面,完全感知不到外界,甚至认为我们执行的container1是1号进程(1号进程也叫init进程,是系统中所有其他用户进程的祖先进程)。所以,从容器的视角,容器觉得“我就是天,我就是地,欢迎来到我的世界”。
但尴尬的是,在宿主机上,他们却是普通得不能再普通的进程。注意,相同的进程,在容器里看到的进程id和在宿主机上看到的进程id是不一样的。容器中的进程id分别是1和1859,宿主机上对应的进程id分别是12280和9775(见上图)。
总结通过上面的实验,对容器的定义就需要再加上一个定语。容器就是进程=@gt;容器是与系统其他部分隔离开的进程。这个时候我们再看下图就更容易理解,容器是跑在宿主机os(虚机容器的宿主机os就是guest os)上的进程,容器间以及容器和宿主机间存在隔离性,例如:进程号的隔离。
在容器内和宿主机上,同一个进程的进程id不同。例如:container1在容器内pid是1,在宿主机上是12280。那么该进程真正的pid是什么呢?当然是12280!那为什么会造成在容器内看到的pid是1呢,造成这种幻象的,正是linux namespace。
linux namespace是linux内核用来隔离资源的方式。每个namespace下的资源对于其他namespace都是不透明,不可见的。
namespace按隔离的资源进行分类:
前面提到的容器内外,看到的进程id不同,正是使用了pid namespace。那么这个namespace在哪呢?在linux上一切皆文件。是的,这个namespace就在文件里。在宿主机上的proc文件中(/proc/进程号/ns)变记录了某个进程对应的namespace信息。如下图,其中的数字(例如:pid:[ 4026534312])则表示一个namespace。
对于container1、container2、container3这3个容器,我们可以看到,他们的pid namespace是不一样的。说明他们3个容器中的pid相互隔离,也就是说,这3个容器里面可以同时拥有pid号相同的进程,例如:都有pid=1的进程。
在一个命名空间中,那这俩进程就相互可见,只是pid与宿主机上看到的不同而已。
至此,我们可以对容器的定义再细化一层。容器是与系统其他部分隔离开的进程=》容器是使用linux namespace实现与系统其他部分隔离开的进程。
扬州到南县物流专线昆明到安顺物流专线南宁到沁阳物流专线英文网站设计要关注的差异什么是RSS推广新站没有排名的问题,又该怎样获取排名【网站怎么优化】网站编辑是否需要考虑SEO问题北京到鄂尔多斯物流专线