大家好,这里是七七,今天起开起我们的Docker技术篇,本文是介绍Docker的,不介绍如何使用和安装Docker,只是单纯的介绍Docker。
目录
一、历史
二、Docker究竟是什么
三、Docker的结构与特性
1、Docker仓库
2、Docker自身程序
3、工作流程
4、Docker化应用的存在形式
5、Docker对变更的管理
四、为什么使用Docker
1、从代码管理说起
2、当前的优化策略
3、Github版的应用部署解决方案
4、Docker应用场景
场景一
场景二
5、Docker可以解决哪些痛点
1、开发人员
2、测试人员
3、运维人员
6、Docker的学习成本
关于Docker的历史,这里我们简要介绍它是dotCloud公司内部使用的Container容器技术,拿出来开源后广受好评。其老板随着Docker的知名度越来越高,直接将公司名字改为Docker股份有限公司,重心转向Docker。
之后,Docker发布了V1.0版,许多大公司均表示加入Docker阵营。在那之后,Docker又完成了几轮融资。如今,在业界影响力很大。
按照官方的说法,Docker是一个开源的应用容器引擎。但这个说法太抽象,不容易理解。
那就从最熟悉的事物说起,但凡从事过计算机相关行业的人,对Java、Android和Github都很熟悉
先说Java,在Java之前的编程语言,像C/C++,是严重依赖平台的,在不同平台下,需要重新编译才能运行。Java的一个非常重要的特性就是与平台无关性,而使用Java虚拟机是实现这一特性的关键。Java虚拟机屏蔽了与具体平台相关的信息,使得Java编译程序只需生成可以在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。
软件部署也依赖平台,Ubuntu的软件包在Centos下可能就运行不起来。和Java虚拟机类似,Docker使用容器引擎解决平台依赖问题,它在每台宿主机上都启动一个Docker的守护进程,守护进程屏蔽了与具体平台相关的信息,对上层应用提供统一的借口。这样,Docker化的应用,就可以在多个平台下运行,Docker会针对不同的平台,解析给不同平台下的执行驱动、存储驱动和网络驱动去执行。
Java曾提出"写一次,在哪儿都运行"的口号,而Docker则提出了"搭建、设置一次,在哪儿都能运行"。虽然,Java和Docker是为了解决不同领域的问题,但在平台移植方面却面临相同的问题,使用的解决方式也相似。
提起Android,我们会想到它是一个开源的手机操作系统,也是一个生态圈,其App以apk形式打包、发布,可以运行在任何厂商的Android手机上。它还有一个官方的安卓市场,提供各种各样的App,我们需要某个App时,就从安卓市场上搜索下载,手机开发者也可以编写一些App,发布到安卓市场,给别人使用,Android也允许在第三方的安卓市场上下载或上传应用。
如果把软件部署的应用看作Android的App,Docker简直和Android一模一样。Docker是一个开源的容器引擎,也有自己的生态圈,它的应用以镜像(image)的形式发布,可以运行在任何装有Docker引擎的操作系统上。它有一个官方的镜像仓库,提供各种各样的应用,当需要某个应用时,就从官方的仓库搜索并下载,个人开发者也可以提交镜像到官方仓库,分享给别人使用。Docker也允许使用第三方的镜像仓库。
最后,再谈Github。它主要用来做版本控制,不仅可以比较两个版本的差异,还可以基于某些历史版本创建新的分支。
使用Docker后,软件部署的应用也可以具备类似Github的版本控制功能,对应用做一些修改,提交新版本,运行环境可以在多个版本间快速切换,自由选择使用哪个版本对外提供服务。
通过和Java、Android、Github的对比,大家对Docker应该有了比较直观的认识,Docker用来管理软件部署的应用,Docker把应用打包成一个镜像,镜像带有版本控制功能,应用的每次修改迭代就对应镜像的一个版本,制作好的镜像可以发布到镜像仓库;也可以直接从镜像仓库下载别人制作好的应用,不做任何修改,即可运行起来。
了解完Docker,我们再来看看它的结构。
如果把Docker当做一个独立的软件来看,它就是用Golang写的开源程序,采用C/S架构,包含Docker Sever和Docker Client,源代码在Github上。
如果把Docker看做一个生态的话,它主要由两部分组成:Docker仓库和Docker自身程序。拿iPhone做类比的话,Docker仓库相当于Appstore,Docker相当于iOS手机操作系统。
Docker仓库snag有非常多的应用,既有各大公司打包的应用,也有大量个人开发者提供的应用。如redis,ubuntu,mysql等等。
Docker本身是一个单机版的程序,运行在Linux上,属于用户态程序,通过一些接口和内核交互。
由于Docker需要用到Linux的cgroups、namespace等特性,所以目前只能运行在Linux下。
Docker是一个C/S架构,它的Docker Daemon作为Server端,在宿主机上以后台守护进程的形式运行。Docker Client使用比较灵活、既可以在本机上以bin命令的形式(如Docker info、Docker start)发送指令,也可以在远端通过RESTful API的形式发送指令;Docker的Server端接受指令并把指令分解为一系列任务去执行。
在了解了Docker的构成后,再来看看如何使用Docker。
首先,在Linux上安装Docker软件包,并启动Docker Daemon守护进程。然后,就可以通过Docker Client端发送各种指令,Docker Daemon守护进程执行完命令,向Client端返回结果。
假如要启动一个新的Docker应用app1,其工作流程大致如下:
- DockerClient向Daemon发送启动app1指令
- 因为我们的Linux只装有Docker软件包,没有app1相关软件或服务,Docker Daemon就发请求给Docker的官方仓库,在仓库中搜索app1
- 如果找到app1,就把它下载到我们的服务器上
- Docker Daemon启动app1
- 把启动app1应用是否成功的结果返回给Docker Client
Docker的其他操作,比如停止或删除Docker应用和启动的流程差不多,这里就不一一介绍了。
我们知道,经过这么多年,Linux下的软件不计其数,安装方式也千奇百怪。有些依赖特定操作系统,有些依赖特定内核版本,有些依赖一些第三方软件和共享库等。
既然软件安装部署方式没有一个统一的标准,那么Docker的官方仓库该如何做呢?总不能一个软件写一个安装说明书吧。
换个角度想一下,用户的需求只是把软件运行起来,至于怎么安装软件、软件运行在什么操作系统下用户不提关心。那么,就把软件和它依赖的环境(包括操作系统和共享库等)、依赖的配置文件打包在一起,以虚拟机的形式放到官方仓库,供大家使用。只要有虚拟机的运行环境,就可以不做任何修改把软件运行起来。只要有一个人把软件安装和配置好,交到官方仓库,其他人直接下载就可以用。以这种方式解决了软件安装部署方式没有一个统一标准的问题。
但这种软件部署方式却存在很多问题,一般一个软件包大小也就几兆到几十兆不等,但一个操作系统却有好几个G。如果每个软件都带上它依赖的操作系统,那么每个软件都有好几个G,很难受。
Docker为了解决这个问题,引入了分层的概念。把一个应用分为任意多个层,比如操作系统是第一层,依赖的库和第三方软件是第二层,应用的软件包和配置文件是第三层。如果这两个应用有相同的层,就可以共享这些层。
例如A应用和B应用的操作系统是一样的,它们就可以共享这一层,安装应用A时需要下载操作系统层,anzhuangyingyB就不用下载操作系统层,只需要下载它的依赖包和自身的软件包。因为主流的操作系统也就那么几个,最差情况下,也就把常用的操作系统都安装一遍,然后,包含操作系统的软件包就和传统的软件包一样大小了。
但这种共享层存在冲突问题,比如,应用A需要修改操作系统的某个配置,应用B不需要修改。如何解决这个冲突?我们规定层次是有优先级的,上层和下层有相同的文件和配置时,上层覆盖下层,数据以上层的数据为准。我们给每个应用一个优先级最高的空白层,如果需要修改下层的文件,就把这个文件拷贝到这个优先级最高的空白层进行修改,保证下层的文件不做任何改变。这样,从应用A的角度来看,文件已经修改成功了,而从应用B的角度来看文件没有发生任何改变。
Docker的分层和写时拷贝策略,解决了包含操作系统的应用程序比较大的问题。但我们只大,主流的虚拟机(KVM、Xen、VMWare等)一般比较笨重,除了虚拟机本身运行要消耗大量的系统资源外,启动一个虚拟机也要花费数分钟,如何把虚拟机做到轻量化呢?
以OpenVZ、VSever、LXC为代表的容器类虚拟机,是一种内核虚拟化技术,与宿主机运行在相同的Linux内核,不需要指令级模拟,性能消耗非常小,是非常轻量级的虚拟化容器,虚拟容器的系统资源消耗和一个普通的进程差不多。Docker就是使用LXC(后来又推出libcontainer)让虚拟机变得轻量化。
在Docker的官方仓库里,只需要它有完整的文件系统和程序包,没有动态生成新文件的需求;当把它下载到宿主机上运行对外提供服务时,有可能修改文件(比如输出新日志到日志文件中),需要有空白层用于写时拷贝。Docker把这两种不同状态做了区分,分别叫做镜像(image)和容器(container)
在仓库中的应用都是以镜像的形式存在的,把镜像从Docker仓库中下拉到本机,以这个镜像为模版启动应用,就叫容器。
在Docker的世界里,镜像和容器是其两大核心概念,几乎所有的指令和文档都是围绕这两个概念展开的。
对于软件开发来说,版本迭代、版本回退是常态,Docker对变更管理又有什么特别之处呢?
假若有一个应用的Docker镜像,它的V1.0版本有3层,每层大小如图所示:
接下来,我们要对它进行如下修改:
- 修改位于第一层的文件A
- 删除位于第二层的文件B
- 添加一个新文件C
Docker会新增一个第四层,针对上面的需求,处理方式如下:
- 把第一层的文件A拷贝到第四层,修改文件A的内容
- 在第四层,把名称为B的文件设置为不存在
- 在第四层,创建一个新文件C
通过增加一个第四层,我们的版本变为1.1,如图:
我们想把应用的V1.1版本发布到Docker仓库。Docker仓库已经存在这个应用镜像的V1.0版本,我们上传时,只需要把第四层(只有3M)上传到Docker仓库即可。
假设有一台远程服务器,正在运行这个应用的V1.0版,只需要从Docker仓库把第四层下载下来,就可以运行V1.1版了。
综上,Docker不仅有版本控制功能,还能利用分层特性做到增量更新。
你可能会说,因为别人用了,所以我也用。噢如果那样的话,可就太糟糕了☹️。
当深入了解Docker后,你想在部门推广Docker,就需要给大家讲明白为什么使用Docker。
当讲Docker是什么时,受众是一批Docker爱好者,Docker的原理和细节讲得越深入,越具体,就越受欢迎
当讲为什么要使用Docker时,受众可能从来没听说过Docker,他们更关注Docker能带来什么,引入Docker需要对现有的系统或程序做多大改造,Docker是不是足够的稳定,学习和使用Docker的成本有多高等问题。
现在的软件项目,失败的原因主要是设计结构不合理、编程人员水平不高、代码bug太多等因素。不管项目有多大,参与的人员有多么多,代码修改多么频繁,很少是由管理上混乱导致项目失败的。究其原因,主要是我们有非常优秀的代码管理工具——Git和SVN。它们有版本控制和中心仓库这两大核心功能,能保证大家不用担心下列问题:
- 快速把代码分享给别人
- 多人同时修改一个文件,导致代码不一致
- 分不清每个人在什么时间提交过什么代码
- 代码被误删误改,不知道哪些文件被删被改,也恢复不到以前的状态
- 变更和新分支太多,混乱到无法维护的地步
版本控制功能不仅能清晰记录每个开发者在什么时间交过什么代码,还能让大家在各个版本间自动地切换、融合。大家如果要对齐开发环境,不需要注意列出文件的名字、文件的内容,只需要简单地说下版号,就能保证大家的文件完全一致。
中心仓库可以保证多人协作有一个统一的平台,既可以与别人分享代码和开发进度,也可以对每个人的代码做异机备份,防止因为机器故障而导致数据丢失。
软件部署和代码部署面临很多相似的问题:
- 如何快速把一台机器的应用环境分享给其他机器使用,用于扩容和故障时服务转移
- 同一个功能模块的多台机器,软件版本和配置文件镜像出现不一致,很难被发现
- 多人维护一套系统,很难清晰地记录下每个人都做过什么操作、每次变更都有哪些内容,以便在故障时快速回退
- 有些配置文件或数据被误删了,恢复不回来,甚至很长一段时间都察觉不到
- 随着操作系统版本、软件、硬件的更新迭代,系统维护的复杂度直线上升,混乱不堪
既然代码管理做的这么好,那软件部署为什么不借鉴代码管理的方法呢? 噢 ,是因为大小
代码管理的对象是纯文件(代码),提亮小,有统一规范的文件编码方式,对平台无依赖。而软件部署管理的对象是一个环境,除了文件,还有二进制的软件和它依赖的运行环境(包括操作系统和依赖库)。由于操作系统和依赖库的体量很大,难以完全照搬代码管理的方式,用版本控制和中心仓库来解决软件部署的问题。
下面来看看,对于软件部署,目前主流的解决方案是什么样子
把环境分为两个部分:基础环境和应用环境。
- 基础环境:包含机器硬件、操作系统、提供基础服务的应用(如ssh、syslog等)
- 应用环境:包含应用需要的各种软件包和配置文件(虽然软件包中也可以包含配置文件,但对于一些变更频繁和需要个性化配置的文件,最好还是独立出来)。
- 基础环境统一化,尽量保持完全一致。比如,使用相同的硬件服务器,运行相同版本的操作系统和基础软件。
- 应用环境分解出一个个独立服务,每个服务再分解为包和配置文件,使用版本控制和中心仓库来对包与配置进行管理
整体方案如图所示:
在这个解决方案中,我们把软件包和配置都版本化,每次变更都提交一个版本到中心仓库,然后再下发到各台机器上。由于每台机器的基础环境都保持一致,所以软件部署时可以不关注底层适配问题,在一台机器打包编译的应用可以快速迁移到其他机器。
这个解决方案,在基础环境一致的前提下,实现了分解版的版本控制和中心仓库,针对分解出来的软件安装包和配置文件,可以通过版本来管理,并且可以把包和配置提交到中心仓库,让所有其他机器分享。
这个方案,存在以下两个问题:
- 基础环境难以改动:因为上层应用环境的软件和配置文件都是基于基础环境编译与配置的,一旦基础环境发生改变,可能导致上层的软件包和配置不能正常工作。
- 应用环境的维护成本取决于包和配置的数量:由于应用环境不是一个统一整体,而是分解出一个个包和配置,随着包和配置的增多,维护的复杂性也随之增加。
Github是最流行、最优秀的代码管理平台,Docker借鉴了Github的管理思路,打造了一个Github版的应用部署的管理方案。
如果使用Docker来管理部署应用,解决方案如图所示:
和上面的方案做下对比:
- 基础环境灵活,对硬件和操作系统都没有限制,只需要在每台计算机上安装Docker Engine,用于运行Docker应用。
- 应用环境也不再分解为一个个包和配置文件,而是作为一个有机的整体,这个有机整体包含应用需要的所有软件包、配置文件和依赖的运行环境(操作系统和依赖库),带有版本控制功能,也可以提交到中心仓库供大家共享。由于Docker镜像中包含应用运行所需要的所有包、配置和系统环境,下发镜像后不需要做任何安装配置,应用就可以直接执行,不会随着应用中包和配置数量的增加,导致安装部署变得复杂
Docker方案完美地把代码管理中的版本控制和中心仓库概念移植到应用部署领域,让大家顿时从应用部署烦琐、重复的工作中解脱出来,可以像使用Github管理代码那样优雅地管理应用的部署工作。
Docker这个方案能够顺利实施,一个关键点是通过分层共享和增量变更技术把应用的运行环境(包括操作系统在内)这么一个庞大的体量顺利瘦身,让应用运行环境的安装和修改在大多数情况下与只装软件包一样轻量、方便。
下面描述两个典型的Docker应用场景
在这两个应用场景中,有开发、测试(QA)和运维人员,分别维护开发、测试和生产环境的服务,他们有一个私有的Docker仓库,存储着各种各样的Docker化的应用镜像。
现在有一个需求,需要对应用App1做修改、测试和发布新变更到生产环境,步骤如下:
- 开发者先从私有仓库找到App1则个应用最新稳定版本,假设为V1.0版,把这个App:v1.0下载到开发机,修改,并提交到私有仓库,并告诉测试人员测试。
- 测试人员下载开发者刚提交的新版本App1:v1.1,测试,并把测试结果反馈给开发者
- 如果测试失败,开发者继续修改,提交新版本给测试人员做新一轮的测试;如果测试成功,开发把要发布的应用的名称和版本号提供给运维同事。
- 运维人员根据开发提供的应用名和版本号,把相关镜像从私有仓库下拉到各个生产环境的机器上,停掉就版本的Docker容器,启动新版本的Docker容器,完成发布。生产环境的机器上可以同时缓存应用的多个版本镜像,如果新发布的版本有问题,可以快速切换回原来的版本。
因为Docker的应用镜像包含应用运行需要的所有软件包、配置和操作系统,所以开发者打包好DockerDocker镜像,测试和运维获取,不需要任何修改就可以运行。
有一个应用App2,目前在生产环境正常运行,但由于系统比较老、缺乏维护、开发和运维经历过更替且交接文档不全,现在谁都不知道该如何部署App2这个应用到新服务器上。
如果App2是Docker化的应用,它可以直接把运行的环境转换为一个带版号的Docker镜像(如App2:v1.0),提交到私有仓库,供开发者修改或运维人员发布新机器
不管是在大公司还是小公司,开发人员经常被如下问题困扰:
- 为了节约成本,一台开发机多人使用,管理混乱,相互干扰
- 一个开发往往只用一套开发环境,同时有多个开发任务时,不得不反复修改开发环境,以适应不同的开发任务
- 多个开发人员希望保持相同的开发环境开发同一个项目,但开发环境难以复刻,即便大家起初的开发环境一样,随着项目的滚动、开发环境的不停更新,很难保证每个人的开发环境都同步更新
- 开发机硬件故障,需要更换新机器,重新搭建开发环境是件头疼的事。如果硬件故障,重要数据没备份,那就更让人崩溃
- 打算调研下新软件,安装配置文档复杂,仅仅把软件安装、运行起来就要花费大半天时间
上面问题带来的工作量不能体现开发人员的核心价值,但是是不得不面对的。
如果使用Docker,可以轻松解决上述问题:
- Docker化的应用使用容器虚拟化技术,每个应用都运行在独立的虚拟化环境中,天然具有隔离行,不用担心一机多用造成的管理混乱。
- 开发人员在多任务开发时,可以并行启动这些应用的Do可容器,每一个Docker应用有一个独立的运行环境,互不干扰。
- 开发机硬件故障,在新开发机上,重新从Docker仓库下拉开发环境的镜像,一两分钟内就可以重新搭建一套开发环境,并且及时新旧开发机的硬件和操作系统不一致,重新搭建的开发环境仍能保持和原来的环境一摸一样。另外,还可以通过Docker仓库,把重要变更及时备份到远端。
- Docker的每个复杂软件都可以制作成Docker镜像,分享给大家使用。随着Docker的流行,几乎所有的主流的软件都提供Docker化的部署方式。软件部署将成为再简单不过的事情。
测试人员经常费了九牛二虎之力测出一些bug,和开发逐一核对,发现大多数bug都是开发和测试环境不一致造成的。
测试人员经常为配置不同的测试环境浪费大量时间,还是不能保证和开发环境完全包次一致,开发人员虽然很认真负责地告诉测试人员如何配置测试环境,但还是经常性地遗漏一些配置。
使用Docker,不需要做任何配置,就能保证开发和测试环境完全一致,测试人员只需要关注测试本身就可以了。
运维人员大部分时间都浪费在装软件、修改配置上,重复单调,经常半夜还要起来做紧急扩容、故障机服务迁移。如果使用了Docker,好处显而易见:
- 服务具备快速部署能力,扩缩容、版本回退在几秒内就可以完成。
- 基于同一个Docker镜像部署服务,可以保证每台机器应用完全一致。
- 由于Docker化的应用是虚拟化,多个应用可以混合部署在一台机器上,互不干扰,可以提高机器使用率。
- Docker化的应用可以运行在不同的硬件和操作系统平台下,在不同的环境自由迁移。
- 通过Dockerfiel管理Docker镜像,即使系统多次易手、交接文档不全,运维人员也可以快速了解系统是如何搭建的。
- Docker倡导"Build once, Run anywhere",再烦琐的活儿,只需要做一次,制作成Docker镜像,在任何环境下都可以运行;还可以基于这个Docker镜像做修改,制作新的镜像。
上面只罗列几条好处,运维在使用Docker的过程中,还会发现很多意想不到的好处。一句话,Docker可以让运维工作变得简单和易于维护。
坦白说,Docker是有学习和使用成本的。
Docker虽然已经做的足够简单易用,但由于其定位是虚拟化容器,是一个单机版的应用,如果要给予Docker构建集群u或PaaS管理系统,如Web管理界面、任务调度策略、监控报警等,则还需要自己开发或从开发社区寻求帮助。
另外,传统的运维是以机器为中心,而Docker是以应用为中心,它会颠覆我们一些固有的运维习惯和运维方式。