Docker 为什么这么牛?
点击关注公众号,Java干货及时送达
1. Docker出现的背景
在平常的研发和项目场景中,以下情况普遍存在:
个人开发环境 为了做大数据相关项目,需要安装一套CDH集群,常见的做法是在自己电脑里搭建3台与CDH版本对应的虚拟机,把CDH集群装起来后,考虑到以后很有可能还要使用一个干净的CDH集群,为了避免以后重复安装环境,通常会对整套CDH集群做一个备份,这样电脑里就有6个虚拟机镜像了。另外,后面在学习其他技术时,比如学习Ambari大数据集群,那么为了不破坏已有的虚拟机环境,又要重新搭建3台虚拟机,本机磁盘很快被一大堆的虚拟机镜像占满。 公司内部开发环境 公司里往往会以小团队的方式来做项目,一般由运维部门从他们管理的服务器资源中分配出虚拟机供团队内部开发测试使用。 比如做一个与机器学习相关的项目: 1)小明在运维部门分配的虚拟机上搭建了一套Ambari集群,拿来跑大数据相关业务 开发/测试/现场环境 研发人员在开发环境里写好了代码做好测试后,提交给测试部门,测试人员在测试环境跑起来发现有BUG,研发人员说在开发环境没这个BUG,和测试人员多次扯皮解决BUG后发布版本,发到现场在生产环境部署后,又发现有BUG,这下轮到工程人员和测试人员扯皮。有时候为了兼容特殊的现场环境,还需要对代码进行定制化修改,拉出分支,这样导致了每次到现场升级都是一场噩梦 升级或迁移项目 在每次发版本要升级到现场时,如果现场起了多个tomcat应用,那么需要对每个tomcat都先停掉,替换war包,然后再起起来,轮流着做,不仅繁琐而且很容易出错,如果遇到升级后出现严重BUG,还要手工做回退。另外,如果项目想上云,那么在云上部署后要重新进行一轮测试,如果后面考虑还云厂商,可能相同的测试还要再进行一次(比如更换了数据存储组件),费时费力。
总结以上列举的所有场景,他们存在的一个共同的问题是:没有一种既能够屏蔽操作系统差异,又能够以不降低性能的方式来运行应用的技术,来解决环境依赖的问题。Docker应运而生。
2. Docker是什么
Linux Container
是Linux系统提供的容器化技术,简称LXC
,它结合Namespace和CGroup技术为用户提供了更易用的接口来实现容器化。LXC仅为一种轻量级的容器化技术,它仅能对部分资源进行限制,无法做到诸如网络限制、磁盘空间占用限制等。dotCloud公司结合LXC和以下列出的技术
实现了Docker容器引擎,相比于LXC,Docker具备更加全面的资源控制能力,是一种应用级别的容器引擎。
Chroot:该技术能在container里构造完整的Linux文件系统; Veth:该技术能够在主机上虚拟出一张网卡与container里的eth0网卡进行桥接,实现容器与主机、容器之间的网络通信; UnionFS:联合文件系统,Docker利用该技术“Copy on Write”的特点实现容器的快速启动和极少的资源占用,后面会专门介绍该文件系统; Iptables/netfilter:通过这两个技术实现控制container网络访问策略; TC:该技术主要用来做流量隔离,限制带宽; Quota:该技术用来限制磁盘读写空间的大小; Setrlimit:该技术用来限制container中打开的进程数,限制打开的文件个数等
也正是因为Docker依赖Linux内核的这些技术,至少使用3.8或更高版本的内核才能运行Docker容器,官方建议使用3.10以上的内核版本。
3. 与传统虚拟化技术的区别
4. Docker基本概念
Docker主要有如下几个概念:
引擎:创建和管理容器的工具,通过读取镜像来生成容器,并负责从仓库拉取镜像或提交镜像到仓库中; 镜像:类似于虚拟机镜像,一般由一个基本操作系统环境和多个应用程序打包而成,是创建容器的模板; 容器:可看作一个简易版的Linxu系统环境(包括root用户权限、进程空间、用户空间和网络空间等)以及运行在其中的应用程序打包而成的盒子; 仓库:集中存放镜像文件的场所,分为公共仓库和私有仓库,目前最大的公共仓库是官方提供的Docker Hub,此外国内的阿里云、腾讯云等也提供了公共仓库; 宿主机:运行引擎的操作系统所在服务器。
5. Docker与虚拟机、Git、JVM的类比
为了让大家对Docker有更直观的认识,下面分别进行三组类比:
Docker的仓库思想与Git是相同的。
Java是基于JVM适配操作系统的特点来屏蔽系统的差异,Docker则是利用内核版本兼容性的特点来实现一次构建导出运行,只要Linux系统的内核是3.8或更高的版本,就都能把容器跑起来。
当然,正如Java中如果应用代码使用了JDK10的新特性,基于JDK8就无法运行一样,如果容器内的应用使用了4.18版本的内核特性,那么在CentOS7(内核版本为3.10)启动容器时,虽然容器能够启动,但里面应用的功能是无法正常运行的,除非把宿主机的操作系统内核升级到4.18版本。
6. Docker镜像文件系统
UnionFS可以把多个物理位置独立的目录(也叫分支)内容联合挂载到同一个目录下,UnionFS允许控制这些目录的读写权限,此外对于只读的文件和目录,它具有“Copy on Write(写实复制)”的特点,即如果对一个只读的文件进行修改,在修改前会先把文件复制一份到可写层(可能是磁盘里的一个目录),所有的修改操作其实都是对这个文件副本进行修改,原来的只读文件并不会变化。其中一个使用UnionFS的例子是:Knoppix,一个用于Linux演示、光盘教学和商业产品演示的Linux发行版,它就是把一个CD/DVD和一个存在在可读写设备(例如U盘)联合挂载,这样在演示过程中任何对CD/DVD上文件的改动都会在被应用在U盘上,不改变原来的CD/DVD上的内容。
UnionFS有很多种,其中Docker中常用的是AUFS,这是UnionFS的升级版,除此之外还有DeviceMapper、Overlay2、ZFS和 VFS等。Docker镜像的每一层默认存放在/var/lib/docker/aufs/diff
目录中,当用户启动一个容器时,Docker引擎首先在/var/lib/docker/aufs/diff
中新建一个可读写层目录,然后使用UnionFS把该可读写层目录和指定镜像的各层目录联合挂载到/var/lib/docker/aufs/mnt
里的一个目录中(其中指定镜像的各层目录都以只读方式挂载),通过LXC等技术进行环境隔离和资源控制,使容器里的应用仅依赖mnt目录中对应的挂载目录和文件运行起来。
利用UnionFS写实复制的特点,在启动一个容器时, Docker引擎实际上只是增加了一个可写层和构造了一个Linux容器,这两者都几乎不消耗系统资源,因此Docker容器能够做到秒级启动,一台服务器上能够启动上千个Docker容器,而传统虚拟机在一台服务器上启动几十个就已经非常吃力了,而且虚拟机启动很慢,这是Docker相比于传统虚拟机的两个巨大的优势。
当应用只是直接调用了内核功能来运作的情况下,应用本身就能直接作为最底层的层来构建镜像,但因为容器本身会隔绝环境,因此容器内部是无法访问宿主机里文件的(除非指定了某些目录或文件映射到容器内),这种情况下应用代码就只能使用内核的功能。但是Linux内核仅提供了进程管理、内存管理、文件系统管理等一些基础且底层的管理功能,在实际的场景中,几乎所有软件都是基于操作系统来开发的,因此往往都需要依赖操作系统的软件和运行库等,如果这些应用的下一层直接是内核,那么应用将无法运行。所以实际上应用镜像往往底层都是基于一个操作系统镜像来补足运行依赖的。
Docker中的操作系统镜像,与平常安装系统时用的ISO镜像不同。ISO镜像里包含了操作系统内核及该发行版系统包含的所有目录和软件,而Docker中的操作系统镜像,不包含系统内核,仅包含系统必备的一些目录(如/etc /proc等)和常用的软件和运行库等,可把操作系统镜像看作内核之上的一个应用,一个封装了内核功能,并为用户编写的应用提供运行环境的工具。应用基于这样的镜像构建,就能够利用上相应操作系统的各种软件的功能和运行库,此外,由于应用是基于操作系统镜像来构建的,就算换到另外的服务器,只要操作系统镜像中被应用使用到的功能能适配宿主机的内核,应用就能正常运行,这就是一次构建到处运行的原因。
下图形象的表现出了镜像和容器的关系:
7. Docker基础操作系统
以上系统镜像分别适用于不同的场景:
BusyBox:一个极简版的Linux系统,集成了100多种常用Linux命令,大小不到2MB,被称为“Linux系统的瑞士军刀”,适用于简单测试场景; Alpine:一个面向安全的轻型Linux发行版系统,比BusyBox功能更完善,大小不到5MB,是官网推荐的基础镜像,由于其包含了足够的基础功能和体积较小,在生产环境中最常用; Debian/Ubuntu:Debian系列操作系统,功能完善,大小约170MB,适合研发环境; CentOS/Fedora:都是基于Redhat的Linux发行版,企业级服务器常用操作系统,稳定性高,大小约200MB,适合生产环境使用。
8. Docker持久化存储
根据前面介绍的容器UnionFS写实复制的特点,可知在容器里增加、删除或修改文件,其实都是对可写层里的文件副本进行了操作。在容器关闭后,该可写层也会被删除,对容器的所有修改都会失效,因此需要解决容器内文件持久化的问题。Docker提供了两种方案来实现:
把宿主机文件系统里的目录映射到容器内的目录, 如下图所示
。如此一来,容器内在该目录里创建的所有文件,都存储到宿主机的对应目录中,在关闭容器后,宿主机的目录依然存在,再次启动容器时还能读取到之前创建的文件,因此实现了容器的文件持久化。当然同时要明白,如果是对镜像自带文件进行了修改,由于镜像是只读的,该修改操作无法在关闭容器时保存下来,除非在修改了文件后构建一个新的镜像。
把多台宿主机的磁盘目录通过网络联合为共享存储,然后把共享存储中的特定目录映射给特定的容器, 如下图所示
。这样容器在重启时,还是能读取到关闭前创建的文件。生产环境中常用NFS作为共享存储方案。
9. Docker镜像制作方法
镜像制作方法有两种:
通过正在运行的容器生成新镜像
这种方式比较简单,但无法直观的设置环境变量、监听端口等内容,适合在简单使用的场景运用。
通过Dockerfile文件来生成新镜像
FROM ubuntu/14.04 # 基础镜像
MAINTAINER guest # 制作者签名
RUN apt-get install openssh-server -y # 安装ssh服务
RUN mkdir /var/run/sshd # 创建目录
RUN useradd -s /bin/bash -m -d /home/guest guest # 创建用户
RUN echo ‘guest:123456’| chpasswd # 修改用户密码
ENV RUNNABLE_USER_DIR /home/guest # 设置环境变量
EXPOSE 22 # 容器内默认开启的端口
CMD ["/usr/sbin/sshd -D"] # 启动容器时自动启动ssh服务
Docker引擎可以根据以上Dockerfile定义的步骤,构造出一个带有ssh服务的Ubuntu镜像。
10. Docker的使用场景
Docker作为一种轻量级的虚拟化方案,应用场景十分丰富,下面收集了一些常见的场景:
作为轻量级虚拟机使用 可以使用Ubuntu等系统镜像创建容器,当作虚拟机来使用,相比于传统虚拟机,启动速度更快,资源占用更少,单机可以启动大量的操作系统容器,方便进行各种测试; 作为云主机使用 结合Kubernetes这样的容器管理系统,可以在大量服务器上动态分配和管理容器,在公司内部,甚至可以取代VMWare这样的虚拟机管理平台,使用Docker容器作为云主机使用; 应用服务打包 在Web应用服务开发场景,可以把Java运行环境、Tomcat服务器打包为一个基础镜像,在修改了代码包后加入到基础镜像来构建一个新的镜像,能很方便的升级服务和控制版本; 容器云平台CaaS Docker的出现,使得很多云平台供应商开始提供容器云的服务,简称容器即服务CaaS,以下对比一下IaaS、PaaS和SaaS: IaaS(基础设施即服务):提供虚拟机或者其他基础资源作为服务提供给用户。用户可以从供应商那里获得虚拟机或者存储等资源来装载相关的应用,同时这些基础设施的繁琐的管理工作将由IaaS供应商来处理。其主要的用户是企业的系统管理员和运维人员; 持续集成和持续部署 互联网行业提倡敏捷开发,持续集成部署CI/CD便是最典型的开发模式。使用Docker容器云平台,就能实现从代码编写完成推送到Git/SVN后,自动触发后端CaaS平台将代码下载、编译并构建成测试Docker镜像,再替换测试环境容器服务,自动在Jenkins或者Hudson中运行单元/集成测试,测试通过后,马上就能自动将新版本镜像更新到线上,完成服务升级。整个过程全自动化,一气呵成,最大程度地简化了运维,而且保证线上、线下环境完全一致,而且线上服务版本与Git/SVN发布分支也实现统一。 解决微服务架构的实施难题 基于Spring Cloud这样的微服务框架,能够实现微服务的管理,但微服务本身还是需要运行在操作系统上。一个采用微服务架构开发的应用中,微服务的个数往往很多,这就导致了一台服务器上往往需要启动多个微服务来提高资源的利用率,而微服务本身可能就只能兼容部分操作系统,这就导致了就算有大量的服务器资源(操作系统可能不一样),但由于微服务本身与操作系统可能相关,就不能做到让微服务在任意服务器上运行,这就带来了资源的浪费和运维的困难。利用Docker容器的环境隔离能力,让微服务运行在容器内,就能够解决以上所说的问题。 执行临时任务 有时候用户只是想执行一次性的任务,但如果用传统虚拟机的方式就要搭建环境,执行完任务后还要释放资源,比较麻烦。使用Docker容器就可以构建临时的运行环境,执行完任务后关闭容器即可,方便快捷。 多租户环境 利用Docker的环境隔离能力,可以为不同的租户提供独占的容器,实现简单而且成本较低。
11. 总结
Docker的技术并不神秘,只是整合了前人积累的各种成果实现的应用级的容器化技术,它利用各种Linux发行版中使用了版本兼容的内核容器化技术,来实现镜像一次构建到处运行的效果,并且利用了容器内的基础操作系统镜像层,屏蔽了实际运行环境的操作系统差异,使用户在开发应用程序时,只需确保在选定的操作系统和内核版本上能正确运行即可,几乎不需要关心实际的运行环境的系统差异,大大提高效率和兼容性。
但随着容器运行得越来越多,容器管理将会称为另一个运维的难题,这时候就需要引入Kubernetes、Mesos或Swarm这些容器管理系统,后面有机会再介绍这些技术。
最后,关注公众号Java技术栈,在后台回复:面试,可以获取我整理的 Java 系列面试题和答案,非常齐全。
关注Java技术栈看更多干货