摘要:Docker为什么火,靠的就是Docker镜像。他打包了应用程序的所有依赖,彻底解决了环境的一致性问题,从新定义了软件的交付形式,进步了生产效率。

本文分享自华为云社区《意识容器,咱们从它的历史开始聊起》,作者:技术火炬手。

对于容器的历史、倒退以及技术实质,在互联网上曾经有十分多的文章了。这里旨在联合本身的工作教训和了解,通过一系列的文章,讲清楚这项技术。

容器的历史和倒退

1、前世

讲到容器,就不得不提LXC(Linux Container),他是Docker的前生,或者说Docker是LXC的使用者。残缺的LXC能力在2008年合入Linux主线,所以容器的概念在2008年就根本定型了,并不是前面Docker造出来的。对于LXC的介绍很多,大体都会说“LXC是Linux内核提供的容器技术,能提供轻量级的虚拟化能力,能隔离过程和资源”,但总结起来,无外乎就两大知识点Cgroups(Linux Control Group)和Linux Namespace。搞清楚他俩,容器技术就根本把握了。

  • Cgroups:重点在“限度”。限度资源的应用,包含CPU、内存、磁盘的应用,体现出对资源的治理能力。
  • Namespace:重点在“隔离”。隔离过程看到的Linux视图。说大白话就是,容器和容器之间不要相互影响,容器和宿主机之间不要相互影响。

2、少年期起步艰巨

2009年,Cloud Foundry基于LXC实现了对容器的操作,该我的项目取名为Warden。2010年,dotCloud公司同样基于LXC技术,应用Go语言实现了一款容器引擎,也就是当初的Docker。那时,dotCloud公司还是个小公司,出世低微的Docker没什么热度,活得相当艰巨。

3、 成长为巨无霸

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镜像。他打包了应用程序的所有依赖,彻底解决了环境的一致性问题,从新定义了软件的交付形式,进步了生产效率。

4、 被列强鲸吞

Docker在容器畛域疾速成长,野心天然也变大了。2014年推出了容器云产品Swarm(K8s的同类产品),想扩张事业幅员。同时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。

如图所示,K8s本身定义了规范的容器运行时接口CRI(Container Runtime Interface),目标是能对接任何实现了CRI接口的容器运行时。在初期,Docker是容器运行时不容置疑的王者,K8s便内置了对Docker的反对,通过dockershim来实现规范CRI接口到Docker接口的适配,以此取得更多的用户。随着开源的容器运行时Containerd(实现了CRI接口,同样由Docker捐给CNCF)的成熟,K8s不再保护dockershim,仅负责保护规范的CRI,解除与某特定容器运行时的绑定。当然,也不是K8s不反对Docker了,只是dockershim谁保护的问题。 随着K8s态度的变动,预计将会有越来越多的开发者抉择间接与开源的Containerd对接,Docker公司和Docker开源我的项目(现已改名为moby)将来将会产生什么样的变动,谁也说不好。

讲到这里,不晓得大家有没有留神到,Docker公司其实是募捐了Containerd和runC。这俩到底是啥货色。简略的说,runC是OCI规范的实现,也叫OCI运行时,是真正负责操作容器的。Containerd对外提供接口,治理、管制着runC。所以下面的图,真正应该长这样。

Docker公司是一个典型的小公司因一个爆款我的项目火起来的案例,不论是技术层面、公司经营层面以及如何跟大厂缠斗,不论是好的方面还是坏的方面,都值得咱们去学习和理解其背地的故事。

什么是容器

按国际惯例,在介绍一个新概念的时候,都得从大家相熟的货色说起。幸好容器这个概念还算好了解,喝水的杯子,洗脚的桶,养鱼的缸都是容器。容器技术外面的“容器”也是相似概念,只是装的货色不同罢了,他装的是应用软件自身以及软件运行起来须要的依赖。用鱼缸来类比,鱼缸这个容器外面装的应用软件就是鱼,装的依赖就是鱼食和水。这样大家就能了解docker的logo了。大海就是宿主机,docker就是那条鲸鱼,鲸鱼背上的集装箱就是容器,咱们的应用程序就装在集装箱外面。

在讲容器的时候肯定绕不开容器镜像,这里先简略的把容器镜像了解为是一个压缩包。压缩包里蕴含利用的可执行程序以及程序依赖的文件(例如:配置文件和须要调用的动静库等),接下来通过实际操作来看看容器到底是个啥。

一、宿主机视角看容器:

1、首先,咱们启动容器。

docker run -d --name="aimar-1-container" euleros_arm:2.0SP8SPC306 /bin/sh -c "while true; do echo aimar-1-container; sleep 1; done"

这是Docker的规范命令。意思是应用euleros_arm:2.0SP8SPC306镜像(镜像名:版本号)创立一个新的名字为"aimar-1-container"的容器,并在容器中执行shell命令:每秒打印一次“aimar-1-container”。

参数阐明:
-d:应用后盾运行模式启动容器,并返回容器ID。
--name:为容器指定一个名字。

docker run -d --name="aimar-1-container" euleros_arm:2.0SP8SPC306 /bin/sh -c "while true; do echo aimar-1-container; sleep 1; done"207b7c0cbd811791f7006cd56e17033eb430ec656f05b6cd172c77cf45ad093c

从输入中,咱们看到一串长字符207b7c0cbd811791f7006cd56e17033eb430ec656f05b6cd172c77cf45ad093c。他就是容器ID,能惟一标识一个容器。当然在应用的时候,不须要应用全id,间接应用缩写id即可(全id的前几位)。例如下图中,通过docker ps查问到的容器id为207b7c0cbd81

aimar-1-container容器启动胜利后,咱们在宿主机上应用ps进行查看。这时能够发现方才启动的容器就是个过程,PID为12280。

咱们尝试着再启动2个容器,并再次在宿主机进行查看,你会发现又新增了2个过程,PID别离为20049和21097。

所以,咱们能够失去一个论断。从宿主机的视角看,容器就是过程。

2、接下来,咱们进入这个容器。

docker exec -it 207b7c0cbd81 /bin/bash
docker exec也是Docker的规范命令,用于进入某个容器。意思是进入容器id为207b7c0cbd81的容器,进入后执行/bin/bash命令,开启命令交互。

参数阐明:
-it其实是-i和-t两个参数,意思是容器启动后,要调配一个输出/输入终端,不便咱们跟容器进行交互,实现跟容器的“对话”能力。

从hostname从kwephispra09909变动为207b7c0cbd81,阐明咱们曾经进入到容器外面了。在容器中,咱们尝试着启动一个新的过程。

[root@207b7c0cbd81 /]# /bin/sh -c "while true; do echo aimar-1-container-embed; sleep 1; done" &

再次回到宿主机进行ps查看,你会发现不论是间接启动容器,还是在容器中启动新的过程,从宿主机的角度看,他们都是过程。

二、容器视角看容器:

后面咱们曾经进入容器外面,并启动了新的过程。然而咱们并没有在容器里查看过程的状况。在容器中执行ps,会发现失去的后果和宿主机上执行ps的后果齐全不一样。下图是容器中的执行后果。

在Container1容器中只能看见刚起启动的shell过程(container1和container1-embed),看不到宿主机上的其余过程,也看不到Container2和Container3外面的过程。这些过程像被关进了一个盒子外面,齐全感知不到外界,甚至认为咱们执行的container1是1号过程(1号过程也叫init过程,是零碎中所有其余用户过程的先人过程)。所以,从容器的视角,容器感觉“我就是天,我就是地,欢送来到我的世界”。

但难堪的是,在宿主机上,他们却是一般得不能再一般的过程。留神,雷同的过程,在容器里看到的过程ID和在宿主机上看到的过程ID是不一样的。容器中的过程ID别离是1和1859,宿主机上对应的过程ID别离是12280和9775(见上图)。

三、总结

通过下面的试验,对容器的定义就须要再加上一个定语。容器就是过程=>容器是与零碎其余局部隔离开的过程。这个时候咱们再看下图就更容易了解,容器是跑在宿主机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实现与零碎其余局部隔离开的过程。

点击关注,第一工夫理解华为云陈腐技术~