什么是Docker
在容器技术中,我们讲到了Docker就是一个应用容器引擎,可以将应用及依赖打包,然后发布到Linux上。相对于虚拟机,它开销更小,而且还有一定的隔离性。
Docker基本概念
镜像image
类似于虚拟机镜像,是一个只读的模板,可以通过它来创建Container。
一个镜像包含操作系统+软件运行环境+用户程序。
Docker中的镜像最大的特点在于它们是分层
的。如下图,
每一层就是一个iMage,多个镜像又可以打包为一个image(各个镜像通过链表
相连),只有最上层是可写的,其他的都只能读。
这样的好处可以复用大量的镜像,便于分发和存储。而且底层的镜像是只读的,所以容器中的进程无法对他们进行修改,安全性得到保证。
Container
容器就是通过镜像创建的一个实例,也就是运行着的镜像
。每个容器
都是独立的进程,多个容器之间共享内核,容器没有IP地址,是一个封闭的沙箱
容器是一个动态的环境,而镜像文件属于静态内容,所以还需要有一个 JSON文件
来记录镜像与容器之间的关系。
Docker架构
介绍了Docker镜像和容器的概念以后,我们来看一下怎么样将镜像转换为容器实例的。
服务器安装了Docker以后,就会在后台跑一个Docker daemon
后台程序,用户不能直接和守护进程打交道,但是可以通过Docker Client
与其进行交互。
Docker Daemon会监听Docker Client
发过来的指令,并进行docker镜像下载,运行容器等核心操作。
如果本地有镜像,就直接从本地拉取,如果本地没有,就会到远端的仓库里面(Docker Hub)里面去找。Docker Hub将全世界的Docker数据汇聚在一起,是Docker生态的命脉。这也是Docker发展这么快的重要原因,因为Docker的技术很容易复制,但是Docker Hub汇聚了全球的数据可复制性几乎不存在。
Docker 的核心技术
Docker 的出现因为开发和运维阶段需要一种虚拟化技术解决开发环境和生产环境环境一致的问题,以及资源隔离的问题。
接下来会介绍几种 Docker 使用的核心技术,如果我们了解它们的使用方法和原理,就能清楚 Docker 的实现原理
NameSpaces
我们知道,在一台Linux主机上运行多个进程实际上是会相互影响的,每一个进程其实可以看到另外的进程,也可以访问任意的文件。
风险就在于一个进程被攻入,则入侵者可以访问当前服务器的所有服务与文件。这也是我们之前要引入虚拟机来提高资源利用率的关键,因为我们要保证资源利用率提升的时候,还要保证安全性。
但是虚拟机毕竟太重了,而Docker其实就通过Linux的NameSpaces来实现不同容器的隔离。
使用了Linux 的命名空间
实现进程的隔离,它可以使Docker 容器内部的任意进程都对宿主机器的进程一无所知。我们每次运行 docker run 时,都会创建一个用于设置进程间隔离的实例
所以
命名空间
是Linux自带的,可以分离资源的一种方法,它可以在创建进程的时候,设置新进程于其他进程之间的隔离、网络隔离、文件系统隔离。
上面只实现了进程的隔离,同样使用命名空间可以创建隔离的网络环境,保证容器之间的网络隔离,但是还不妨碍每个容器访问外部的网络。
除了网络隔离以及进程的隔离之外,我们还希望容器中的进程不能访问主机上的其他目录,所以如果一个容器需要启动,那么它需要提供一个新根文件系统(rootfs)。
这样每个容器都有自己单独的命名空间,运行在其上的应用就像在独立的操作系统上一样,彼此不影响。
CGroups
通过命名空间,我们可以为新的进程隔离
- 文件系统
- 网络
- 进程的隔离
但是命名空间不能隔离物理资源,比如CPU和内存。
如果某个容器在执行CPU密集型任务,那么势必会影响其他容器的任务的执行。所以我们引入了CGroups(Control Groups)
控制组这种控制机制,主要用于隔离主机上的物理资源。同样这种机制也是由Linux 内核提供的。它将进程管理从 cpuset 中剥离出来
Cgroup
可以将任意进程进行分组化管理,它提供一个CGroup虚拟文件系统,作为用户接口,要使用CGoup必须挂载这种文件系统。
启动一个容器时,Docker 会为这个容器创建一个与容器标识符相同的 CGroup,在当前的主机上 CGroup 就会有以下的层级关系:
每个Cgroup下面都有一个tasks文件,存储着当前CGroup中进程的pid,如果系统管理员控制某个容器的资源消耗,只需要改变文件中的内容即可。
UnionFS(联合文件系统)
通过Linux的命名空间可以解决进程、网络、文件系统隔离的问题,通过控制组可以实现CPU、内存的隔离。那么如何对应用所需的依赖以及代码进行打包呢?通过镜像。镜像本质上就是一个压缩包,也就是一个文件
而已。
我们之前说过Docker中镜像是分层
的,每一个镜像是由一系列只读的层组成的,这也符合我们的认知,打包好的东西当然不允许随便修改。
但是我们要在镜像的基础上创建新的镜像,如果不能修改岂不是很尴尬,实际上可以在原有的镜像上创建新的逻辑层,每一层都是对当前容器一个小的修改而已。
如下图所示,新建的层是可读可写的。
容器和镜像的区别
就在于,所有的镜像都是只读的,而每一个容器其实等于镜像加上一个可读写的层,也就是同一个镜像可以对应多个容器。
这种镜像层共享的方式,可以减少磁盘的占用。
我们知道镜像其实就是一种文件
,对镜像的每一次修改都可以做为一层叠加在原有的镜像之上,每一层都是对当前容器一个小的修改而已。这些新增的层级与原有的镜像通过链表
的形式相连,然后挂载在同一个虚拟文件系统下,这就是所谓的联合文件系统(Union FS)
因为基础的镜像是没有修改的,是被所有的容器共享的,容器管理的只有自己改动以后的层,大大提高了存储效率。
AUFS
UnionFS 其实就是把多个文件系统(容器)联合
到同一个挂载点(镜像)的服务,而AUFS(AnotherUnionFS)
是它的升级版,它可以将不同的目录挂载在同一个虚拟文件系统下。
每一个镜像层都是建立在另一个镜像层之上的,同时所有的镜像层都是只读的,只有每个容器最顶层的容器层才可以被用户直接读写。
所有的容器都建立在一些底层服务(Kernel)上,包括命名空间、控制组、rootfs 等等,这种组装
方式提供了非常大的灵活性
LXC(Linux Container)
LXC可以提供操作系统层级的虚拟化,而且因为是共享内核的,执行时不需要重复加载内核,启动速度非常快,内存消耗更小。
它可以通过NameSpaces和Cgroup机制可以用来建立和管理Container。它最大的优势就是整合入了内核。
Docker就是在LXC的基础上进行了标准化,它提供了标准的打包方案。比如对Apache的相关环境进行打包,最底层是基础的根目录image,然后叠加apache以及相关依赖,这些镜像由AUFS文件系统加载并合并在统一的路径中,它们都是只读。
为了使容器中的进程能对镜像进行修改,需要再叠加一层可写的空白Layer。这样,通过层级化的image,不同的Docker共用底层文件系统、依赖工具。
Dockerfile是怎么把镜像变成容器的?
简单来讲,Dockerfile构建出Docker镜像,通过Docker镜像运行Docker容器。它们分别代表软件开发、交付标准、部署和运维的三个阶段。
如下图所示,通过Dockerfile可以构建出一个新镜像。但是镜像毕竟是静止的。那怎么运行起来,也就是怎么把镜像变成一个进程的呢?
镜像变容器依据的就是每个镜像的JSON
文件,通过解析它就知道了镜像之上应运行什么进程,配置什么样的环境变量。
那么谁来执行镜像变容器的操作呢?前面已经说过通过Daemon守护进程
即可实现。所以Docker容器实质就是一个进程,其父进程就是Docker的守护进程。守护进程 手握Docker镜像JSON
文件,为容器配置相应的环境,并真正运行Docker镜像所指定的进程,完成Docker容器的真正创建。然后JSON文件就失去作用了,此时镜像就只是为容器提供文件系统,供容器
内部进程访问。
那么容器内部的进程会对Docker镜像进行篡改吗?当然不会,因为镜像层是只读的,所以写操作无法作用到镜像中。Daemon进程会在Docker镜像最上层
之上,再加一个可读写层,那么如果容器要写底层镜像中的文件怎么办?可以使用Copy on Write
的方法,也就是说写之前先复制到可读写层
,然后再对这个副本进行写。
总之,对容器进程而言,它只能看到最上层的文件
Docker到底是什么
讲了这么多技术的东西,我们回过头来看Docker到底是什么。很多人喜欢用集装箱
来比喻Docker。集装箱来能解决什么问题?就是可以把货物规范化
的摆放,而且集装箱和集装箱之间不会互相影响。
在集装箱之前,货物运输是没有统一标准的,所以如果一个货物在铁路、公路、海运等多种不同运输方式之间切换的时候,会消耗大量的人力和物力。对应开发和部署来说,有可能程序是在Ubuntu上开发,但是要在CentOS上运行,可能会出现各种乱七八糟的问题。
出现了集装箱以后,所有的货物可以放到统一的箱子(容器)里面,然后中转工作由机械搞定(自动化),效率大大提升。
那么这种改变最重要的就在与标准化
因为规范了集装箱的大小,所以才可以在铁路、公路、海运上自由的切换,自动化工具才有作用。按照这个思路Docker其实就和集装箱一样,开发工程师可以把开发程序放到“集装箱”里面,然后运维人员使用标准化的操作工具去操作。
实际上Docker的发明人根据自己运维PaaS平台的经验,对DevOps
工作各个阶段的进行标准化,将系统底层的cgroup,namespace,aufs等技术集成在镜像的发布工具中。
那么Docker的实质是什么?就是针对PaaS平台的自动化运维工具,而自动化运维的前提当然就是标准化,只有标准化了以后才能用脚本进行自动化部署。 那Docker在标准化上是怎么做的?
- 标准化文件:定义一系列软件版本内容。dockerfile就是文档,而且可以用来产生镜像,如果要修改docker镜像中的环境,先修改dockerfile,这样就保证了文档和环境是一致的。
- 统一应用的操作:docker使用docker start作为统一标准,而不是自己定义启动脚本,对启动环境的进程做标准化
但是Docker真的是万能的吗?真的可以解决一切问题?真不是。即使是集装箱,也无法解决一切的运输问题。
所以对任何技术都要辩证的来看。Docker最重要的是标准化,但是现在的云计算环境里面群雄逐鹿,很难说能统一出一个标准。只有等此行业被某几个大的云计算厂商瓜分完毕才可能。因为对用户而言,他为了业务的稳定性,可能租用了若干云计算厂商的云,他希望一套代码可以在各处运行,自然希望云的环境标准一致。
Docker , OpenStack , K8s,Mesos对比
Openstack一直在构建开放、私有的上云的架构,但是因为各家都做各家的,实践很多,但是没有定下来业界标准,而且复杂、没解决业务问题。
13年,Docker异军突起,横扫DevOps运维圈,但是应用到日常的业务事务时却有各种问题。所以Google振臂一呼,推出了Kubernetes,基于容器的集群管理平台,把这个矛盾转成成动力,这个时候开始进入实践期。
Mesos是Apache的顶级开源项目,是最开发的二级资源调度开发Kernel,通过定制上面的编排工具,可以快速定制一套自家的PaaS解决方案,非常受开发者喜欢。
Mesos是制作分布式系统最佳的基础组件平台。
Docker与OpenStack的区别
OpenStack:云计算管理平台,主要管理的对象是虚拟机和物理机,当然也可以管理存储和网络,但是还是以机器为中心的。OpenStack 主要针对 Iaas 平台,
Docker Engine 主要是用来创建和管理容器的,它和容器的关系就好比KVM与虚拟机的关系,Docker engine本身的定位不仅仅在于单机上容器的管理,所以现在被加入了各种高级功能,如Docker集群,容器编排、服务发现等。Docker主要针对 Paas 平台,是以应用为中心。
Kubernetes与Docker
Kubernetes(K8S)主要用于管理容器
,类似于Openstack与虚拟机的关系,所以K8s一般与Docker配合使用,调用每个节点上的Docker去创建和管理容器。
Mesos
Mesos:是一个通用资源管理平台,管理的是各种计算资源(CPU,内存,GPU ),它会搜集各节点的计算资源提供然后提供给上层的应用框架(Spark,Kubernetes)等。
Mesos也对容器进行深层次的支持,完整的运行了一个容器的运行时(类似Docker),所以可以把计算任务以容器的方式在集群中运行。 缺点是门槛高,需要自行编写代码进行调用,与K8s相比,更为灵活,因为不限定是否完全以容器来运行。
Kubernetes 是面向应用的 PaaS 层,Mesos 也偏向资源管理,但 Mesos 框架设计不错,基于它很容易构建 PaaS。
Kubernetes 的强项在于容器编排,可以很好解决应用上云的问题。Kubernetes 可以运行在 OpenStack 上
Mesos 牛叉在于数据中心资源统一管理,可以为多个框架分配资源,但不负责调度,可视为分布式操作系统内核,也可以部署在 OpenStack 上,也支持物理资源。
如果只用容器,Kubernetes 是不二之选;如果是运行的不仅仅是容器化的应用,Mesos 配合 Marathon 调度框架甚至 Kubernetes 都不错。