开发中遇到的问题
场景1
当你拥有一台新的电脑,你第一件事会做什么?
一般都是去装一些常用软件,比如QQ、微信、网易云音乐、Chrome浏览器等等。
当然作为开发者,还需要装一些环境,JDK,或者python等等。
假如现在让你负责给学校机房100台电脑,都安装以上软件和环境。那你怎么办?一台一台的手工去装?NONO
这时候你肯定想在一台电脑上把环境安装好,然后把这个系统打包,直接复制到其他所有电脑上。
场景2
开发:软件开发好了,你看一下
运维:有bug,我刚运行有bug
开发:在我电脑上都正常的,为什么到你电脑上就不行?
场景3
一台电脑往往需要运行多个服务,但是多个服务之间经常会互相干扰,比如端口被占用等等。
以上的场景,在我们开发过程中出现过无数次。
问题的原因可能系统环境的问题,可能是软件版本的问题,也有可能时缺少依赖的问题。归根结底还是运行时的环境不一样。
对于大型项目,可能会需要多台服务器,而且要求这些服务器的环境最好一模一样。如果其中某一台的环境不一样,都有可能导致莫名其妙的问题。
虚拟机
虚拟机(virtual machine)就是带环境安装的一种解决方案。它可以在一种操作系统里面运行另一种操作系统,比如在 Windows 系统里面运行 Linux 系统。应用程序对此毫无感知,因为虚拟机看上去跟真实系统一模一样,而对于底层系统来说,虚拟机就是一个普通文件,不需要了就删掉,对其他部分毫无影响。
虽然用户可以通过虚拟机还原软件的原始环境。但是,这个方案有几个缺点。
(1)资源占用多
虚拟机会独占一部分内存和硬盘空间。它运行的时候,其他程序就不能使用这些资源了。哪怕虚拟机里面的应用程序,真正使用的内存只有 1MB,虚拟机依然需要几百 MB 的内存才能运行。
(2)冗余步骤多
虚拟机是完整的操作系统,一些系统级别的操作步骤,往往无法跳过,比如用户登录。
(3)启动慢
启动操作系统需要多久,启动虚拟机就需要多久。可能要等几分钟,应用程序才能真正运行。
Linux 容器
由于虚拟机存在这些缺点,Linux 发展出了另一种虚拟化技术:Linux 容器(Linux Containers,缩写为 LXC)。
Linux 容器不是模拟一个完整的操作系统,而是对进程进行隔离。或者说,在正常进程的外面套了一个保护层。对于容器里面的进程来说,它接触到的各种资源都是虚拟的,从而实现与底层系统的隔离。
由于容器是进程级别的,相比虚拟机有很多优势。
(1)启动快
容器里面的应用,直接就是底层系统的一个进程,而不是虚拟机内部的进程。所以,启动容器相当于启动本机的一个进程,而不是启动一个操作系统,速度就快很多。
(2)资源占用少
容器只占用需要的资源,不占用那些没有用到的资源;虚拟机由于是完整的操作系统,不可避免要占用所有资源。另外,多个容器可以共享资源,虚拟机都是独享资源。
(3)体积小
容器只要包含用到的组件即可,而虚拟机是整个操作系统的打包,所以容器文件比虚拟机文件要小很多。
总之,容器有点像轻量级的虚拟机,能够提供虚拟化的环境,但是成本开销小得多。
Docker 是什么
Docker 是基于Linux容器技术开发一款容器引擎。
Docker 可以让你将所有应用软件以及它的以来打包成软件开发的标准化单元。
Docker 容器将软件以及它运行安装所需的一切文件(代码、运行时、系统工具、系统库)打包到一起,这就保证了不管是在什么样的运行环境,总是能以相同的方式运行。就好像 Java 虚拟机一样,“一次编写,到处运行(Write once, run anywhere)”,而 Docker 是“一次构建,到处运行(Build once,run anywhere)”。
Docker特性
更高效的虚拟化
Docker 容器的运行不需要额外的 hypervisor 支持,不同于虚拟机硬件层面的虚拟,它是内核级的虚拟化,因此可以实现更高的性能和效率。
环境标准化和版本控制
Docker通过虚拟化模拟系统环境与软件的版本控制,可以很方便的控制系统环境与软件版本管理
隔离性
各个软件虽然运行在相同系统内,但彼此隔离,互不干扰。
使用Docker优势
更快速的交付和部署
对开发和运维人员来说,最希望的就是一次创建或配置,可以在任意地方正常运行。
开发者可以使用一个标准的镜像来构建一套开发容器,开发完成之后,运维人员可以直接使用这个容器来部署代码。 Docker 可以快速创建容器,快速迭代应用程序,并让整个过程全程可见,使团队中的其他成员更容易理解应用程序是如何创建和工作的。 Docker 容器很轻很快!容器的启动时间是秒级的,大量地节约开发、测试、部署的时间。
更轻松的迁移和扩展
Docker 容器几乎可以在任意的平台上运行,包括物理机、虚拟机、公有云、私有云、个人电脑、服务器等。 这种兼容性可以让用户把一个应用程序从一个平台直接迁移到另外一个。
更简单的管理
使用 Docker,只需要小小的修改,就可以替代以往大量的更新工作。所有的修改都以增量的方式被分发和更新,从而实现自动化并且高效的管理。
Docker与虚拟机区别
每个虚拟机都包括应用程序、必要的二进制文件和库以及一个完整的客户操作系统(Guest OS),尽管它们被分离,它们共享并利用主机的硬件资源,将近需要十几个 GB 的大小。
Docker本质上是一种虚拟化的技术,不是虚拟机。Docker是在操作系统层面上实现虚拟化,直接复用本地主机的操作系统,而传统方式则是在硬件层面实现。
容器包括应用程序及其所有的依赖,但与其他容器共享内核。它们以独立的用户空间进程形式运行在主机操作系统上。他们也不依赖于任何特定的基础设施,Docker 容器可以运行在任何计算机上,任何基础设施和任何云上。
Docker 的容器利用了 LXC,管理利用了 namespaces 来做权限的控制和隔离,cgroups 来进行资源的配置,并且还通过 aufs 来进一步提高文件系统的资源利用率,而这些技术都不是 Docker 独创。
Docker与虚拟机
对比传统虚拟机总结
特性 | 容器 | 虚拟机 |
启动 | 秒级 | 分钟级 |
硬盘使用 | 一般为 MB | 一般为 GB |
性能 | 接近原生 | 弱于 |
系统支持量 | 单机支持上千个容器 | 一般几十个 |
Docker 与 Microservices 的关系
Microservices(微服务) 依赖于“基础设施自动化”,而 Docker 正是“基础设施自动化”的利器。可以说 Docker 的火爆,一定程度上也带动了微服务架构的兴起,而微服务的广泛应用也促进了 Docker 繁荣。可以说两者相辅相成。
Docker基础概念
了解了Docker的组成,再来了解一下Docker的三个主要概念:
- Docker image
- Docker container
- Docker hub/registry
镜像就相当于打包好的版本,镜像启动之后运行在容器中,仓库就是装存储镜像的地方。
Docker 镜像
镜像是只读的
镜像中包含有需要运行的文件。
镜像用来创建container,一个镜像可以运行多个container;
镜像可以通过Dockerfile创建,也可以从Docker hub/registry上下载。
Docker 容器
容器是Docker的运行组件,启动一个镜像就是一个容器,容器是一个隔离环境,多个容器之间不会相互影响,保证容器中的程序运行在一个相对安全的环境中。
容器实质上是镜像的运行实例进程,但与直接的宿主执行的进程不同,容器进程运行于属于自己的独立的命名空间。因此容器可以拥有自己的 root 文件系统、自己的网络配置、自己的进程空间、甚至自己的用户 ID 空间。容器内的进程是运行在一个隔离的环境里。虽然这种隔离特性和虚拟机很类似,但结构上是不同的。
镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的 类 和 实例 一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。
Docker 仓库
镜像构建完成后,可以很容易的在当前宿主机上运行,但是,如果需要在其它服务器上使用这个镜像,我们就需要一个集中的存储、分发镜像的服务,Docker Registry 就是这样的服务。
仓库分为公开仓库(Public)和私有仓库(Private)两种形式。
最大的公开仓库是 官方的Docker Hub存放了数量庞大的镜像供用户下载。 但是由于众所周知的原因,大陆访问官方仓库的速度比较慢,所以国内也提供了镜像,有兴趣的可以了解下
当然,用户也可以在本地网络内创建一个私有仓库。
Docker架构
解释一下上图中的元素:
- Docker守护进程(docker daemon)是运行在你的操作系统上的一个服务。目前,它只能运行在Linux上,因为它依赖于一些Linux内核特性(比如Cgroup与Namespace)。 但是,也有一些特殊的办法让Docker运行在MacOS与Windows上(运行在Linux虚拟机中)。
- Docker守护进程提供了REST API。许多工具(Docker命令行,Docker Compose等)都可以通过REST API与Docker守护进程进行交互,例如创建容器,构建镜像等。
- Docker命令行(docker CLI)是与Docker守护进程进行交互的主要工具。
Docker是C/S架构
Docker是Client/Server架构。其中Docker守护进程是服务端,Docker命令行是众多客户端之一。事实上,还有很多第三方的Docker客户端。
对于各种流行的编程语言,它们都有对应的Docker客户端。感兴趣的话,你也可以开发一个,使用REST API与Docker守护进程进行交互就好了。
通过客户端,你可以管理Docker的各种元素,包括镜像、容器、网络以及数据卷。
Docker命令行与守护进程如何交互?
从左至右理解上图:
- 最左侧是Docker客户端,即Docker命令行。我们可以运行各种Docker命令,比如构建镜像(docker build),下载镜像(docker pull),运行容器(docker run)。Docker命令行可以安装在各种操作系统上,例如Windows,MacOS或者Linux服务器。
- 中间是Docker主机,Docker守护进程运行在上面。Docker命令行可以轻松地连接远程的Docker主机(给定IP和端口即可)。而在MacOS与Windows上”运行”Docker时,Docker守护进程事实上运行在Linux虚拟机中。这里关键点在于,Docker守护进程和命令行可以运行在不同的主机上。
- 最右侧是Docker仓库,它也是Docker生态系统中的一份子。它是我们下载、上传、存储以及分享Docker镜像的地方。Docker仓库的细节与本文无关,因此不再赘述。
底层技术
Docker使用Go语言编写,并且使用了一系列Linux内核提供的性能来实现我们已经看到的这些功能。
Docker相比于虚拟机,在资源利用上的效率比虚拟机高出很多,另外Docker的启动速度也是虚拟机不能比的,那么Docker是如何实现这些的呢。首先Docker基于Linux,通过Linux的namespace实现了资源隔离,通过cgroups实现了资源限制,通过写时复制机制(copy-on-write)技术实现了高效的文件操作。
Namespaces命名空间
做资源隔离主要涉及6项隔离。linux内核中6种namespace隔离的系统调用如下:
Namespace | 隔离内容 |
UTS: Unix Timesharing System | 主机名与域名 |
IPC: InterProcess Communication | 信号量、消息队列和共享内存 |
PID: Process ID | 进程编号 |
NET: Networking | 网络设备、网络栈、端口等等 |
MNT: Mount | 挂载点(文件系统) |
User | 用户和用户组 |
Docker充分利用了一项称为namespaces的技术来提供隔离的工作空间,我们称之为 container(容器)。当你运行一个容器的时候,Docker为该容器创建了一个命名空间集合。
这样提供了一个隔离层,每一个应用在它们自己的命名空间中运行而且不会访问到命名空间之外。
Linux中namespaceAPI操作有
- clone接口(创建新进程的同时创建namespace)
- setns()接口(加入一个已经存在的namespace, Docker的exec命令在已经运行的容器中执行一个新的命令就需要用到该方法)
- unshare方法(使当前进程退出指定类型的namespace,并加入到新创建的namespace(相当于创建并加入新的namespace),与clone的不同之处在于,unshare运行在原先的进程上,不需要启动一个新的进程)
可以通过查看/proc/[pid]/nc文件来查看进程所属的namespace。另外说一下Linux中的fork,调用fork时候,系统创建新的进程,为其分配资源,把原来进程中的值都复制到新进程中,只有少量值与原来不同,fork的神奇之处在于,他只被调用了一次,却能够返回两次数值(父进程返回子进程id,子进程返回0,出错返回负值)
cgroups群组控制
cgroups(control group)也是LINUX内核提供的机制。可以限制、记录任务组所使用的物理资源(包括CPU,memory,IO等),为容器实现虚拟化提供了基本保证,是构建Docker等一系列虚拟化管理工具的基石。
其功能主要为:
- 资源限制:可以对任务使用的资源总额进行限制。比如内存上限。
- 优先级分配: 分配CPU时间片数量和磁盘IO带宽大小
- 资源统计: 云服务计费用这个就很好
- 任务控制:对任务执行挂起、恢复等操作。
cgroup包含:
- tasks任务:表示一个系统的进程或线程
- cgroup控制组:表示某种资源按照控制标准划分而成的任务组,包含一个或多个子系统,任务可加入某个cgroup,也可以从某个cgroup迁移到另外一个cgroup
- subsystem(子系统):子系统是一个资源调度控制器。(例如CPU子系统)
- hierarchy(层级):由一系列cgroup以一个树状结构排列而成,每个层级通过绑定对应的子系统进行资源控制。每个节点可以包含0个或多个子节点,子节点继承父节点挂载的子系统。整个操作系统可以有多个层级。
cgroups实现本质
cgroups的实现本质上是给系统进程挂上钩子(hooks),当task运行的过程中涉及到某个资源时就会触发钩子上所附带的subsystem进行检测,最终根据资源类别的不同使用对应的技术进行资源限制和优先级分配。
UnionFS联合文件系统
联合文件系统(UnionFS)是用来操作创建层的,使它们轻巧快速。Docker使用UnionFS提供容器的构造块。Docker可以使用很多种类的UnionFS包括AUFS, btrfs, vfs, and DeviceMapper。
container format容器格式
Docker连接这些组建到一个包装中,称为一个 container format(容器格式)。默认的容器格式是libcontainer。Docker同样支持传统的Linux容器使用LXC。在未来,Docker也许会支持其它的容器格式,例如与BSD Jails 或 Solaris Zone集成。
参考:
http://www.ruanyifeng.com/blog/2018/02/Docker-tutorial.html
https://www.w3cschool.cn/reqsgr/su3dwozt.html
https://yeasy.gitbooks.io/Docker_practice/content/
https://waylau.com/ahout-Docker/
https://ruby-china.org/topics/22004
http://www.pyjason.com/Docker_core_principles.html
https://draveness.me/Docker
https://blog.fundebug.com/2017/05/22/docker-cli-daemon/