讲解Docker演变、基本概念、框架和基本运作,都属于非常基础的部分。
前言
Docker和K8s是我一直想学习的两个系列,之前在工作中虽然接触过相关知识,但是都不怎么深入,也不够系统,所以打算花2个月时间,把这两块内容补齐。
Docker是时下热门的容器技术,很多人会把Docker理解为一个轻量级虚拟机,但其实Docker与虚拟机(VM)是两种不同的计算机虚拟化技术,也有很多人会觉得,有了虚拟机,那为什么还要使用Docker呢?带着心里的一点点疑问,让我们一起来学习Docker吧。
Docker演变
没有虚拟化技术的原始年代
我们仔细想想,在没有计算虚拟化技术的“远古”年代,如果我们要部署一个应用程序(Application),一般的步骤是怎么样的?
第一步肯定是先要准备一台物理服务器,然后在物理服务器上安装一个操作系统(Operating System),有了操作系统之后,便在操作系统上安装运行我们的应用程序,这个过程可以用下面的图来表示:
那么,这种方式有什么问题呢?其实,在物理机上部署应用有以下几个缺点:
- 部署非常慢:因为我们得先准备硬件服务器,接着还要安装操作系统,然后再部署应用程序,而且应用程序还有很多的依赖软件,所以这个过程是比较慢的。
- 成本非常高:主要是物理器成本太高,即使是部署一个简单的应用,也需要一台服务器。
- 资源浪费:如果应用太简单,也容易浪费硬件资源,比如CPU和内存
- 迁移和扩展太慢:如果需要迁移应用,或者扩展应用,都要再准备其他的物理服务器,过程很麻烦,也很慢。
虚拟机
虚拟机(virtual machine)就是带环境安装的一种解决方案。它可以在一种操作系统里面运行另一种操作系统,比如在 Windows 系统里面运行 Linux 系统。应用程序对此毫无感知,因为虚拟机看上去跟真实系统一模一样,而对于底层系统来说,虚拟机就是一个普通文件,不需要了就删掉,对其他部分毫无影响。
虽然用户可以通过虚拟机还原软件的原始环境。但是,这个方案有几个缺点。
- 资源占用多:虚拟机会独占一部分内存和硬盘空间。它运行的时候,其他程序就不能使用这些资源了。哪怕虚拟机里面的应用程序,真正使用的内存只有 1MB,虚拟机依然需要几百 MB 的内存才能运行。
- 冗余步骤多:虚拟机是完整的操作系统,一些系统级别的操作步骤,往往无法跳过,比如用户登录。
- 启动慢:启动操作系统需要多久,启动虚拟机就需要多久。可能要等几分钟,应用程序才能真正运行。
Linux容器
由于虚拟机存在这些缺点,Linux 发展出了另一种虚拟化技术:Linux 容器(Linux Containers,缩写为 LXC)。
Linux 容器不是模拟一个完整的操作系统,而是对进程进行隔离。或者说,在正常进程的外面套了一个保护层。对于容器里面的进程来说,它接触到的各种资源都是虚拟的,从而实现与底层系统的隔离。由于容器是进程级别的,相比虚拟机有很多优势。
- 启动快:容器里面的应用,直接就是底层系统的一个进程,而不是虚拟机内部的进程。所以,启动容器相当于启动本机的一个进程,而不是启动一个操作系统,速度就快很多。
- 资源占用少:容器只占用需要的资源,不占用那些没有用到的资源;虚拟机由于是完整的操作系统,不可避免要占用所有资源。另外,多个容器可以共享资源,虚拟机都是独享资源。
- 体积小:容器只要包含用到的组件即可,而虚拟机是整个操作系统的打包,所以容器文件比虚拟机文件要小很多。
总之,容器有点像轻量级的虚拟机,能够提供虚拟化的环境,但是成本开销小得多。
Docker是什么?
Docker 属于 Linux 容器的一种封装,提供简单易用的容器使用接口。它是目前最流行的 Linux 容器解决方案。Docker 将应用程序与该程序的依赖,打包在一个文件里面。运行这个文件,就会生成一个虚拟容器。程序在这个虚拟容器里运行,就好像在真实的物理机上运行一样。有了 Docker,就不用担心环境问题。
Docker 的接口相当简单,用户可以方便地创建和使用容器,把自己的应用放入容器。容器还可以进行版本管理、复制、分享、修改,就像管理普通的代码一样。
Docker是在操作系统进程层面的隔离,而虚拟机是在物理资源层面的隔离,两者完全不同,另外,我们也可以通过下面的一个比较,了解两者的根本性差异。
Docker用途
开发环境与生产环境折射的是开发人员与运维人员之间的矛盾,也许我们常常会听到开发人员对运维人员说的这样一句话:“在我的电脑运行没问题,怎么到了你那里就出问题了,肯定是你的问题”,而运维人员则认为是开发人员的问题。
开发人员需要在本机安装各种各样的测试环境,因此开发的项目需要软件越多,依赖越多,安装的环境也就越复杂。同样的,运维人员需要为开发人员开发的项目提供生产环境,而运维人员除了应对软件之间的依赖,还需要考虑安装软件与硬件之间的兼容性问题。
就是这样,所以我们经常看到开发与运维相互甩锅,怎么解决这个问题呢?
容器就是一个不错的解决方案,容器能成为开发与运维之间沟通的语言,因为容器就像一个集装箱一样,提供了软件运行的最小化环境,将应用与其需要的环境一起打包成为镜像,便可以在开发与运维之间沟通与传输。
Docker 的主要用途,目前有三大类。
- 提供一次性的环境。比如,本地测试他人的软件、持续集成的时候提供单元测试和构建的环境。
- 提供弹性的云服务。因为 Docker 容器可以随开随关,很适合动态扩容和缩容。
- 组建微服务架构。通过多个容器,一台机器可以跑多个服务,因此在本机就可以模拟出微服务架构。
Docker基本概念
镜像(Image)、容器(Container)与仓库(Repository),这三个是docker中最基本也是最核心的概念,对这三个概念的掌握与理解,是学习docker的关键。
镜像(Image)
Docker本质上是一个运行在Linux操作系统上的应用,而Linux操作系统分为内核和用户空间,无论是Centos还是Ubuntu,都是在启动内核之后,通过挂载Root文件系统来提供用户空间的,而Docker镜像就是一个Root文件系统。Docker镜像是一个特殊的文件系统,提供容器运行时所需的程序、库、资源、配置等文件,另外还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像是一个静态的概念,不包含任何动态数据,其内容在构建之后也不会被改变。
因为镜像包含操作系统完整的 root 文件系统,其体积往往是庞大的,因此在 Docker 设计时,充分利用 Union FS (联合文件系统)的技术,将其设计为分层存储的架构,所以一个镜像实际上是由多层文件系统联合组成。镜像构建时,会一层层构建,前一层是后一层的基础;每一层构建完就不会再发生改变,后一层上的任何改变只发生在自己这一层。比如,删除前一层文件的操作,实际不是真的删除前一层的文件,而是仅在当前层标记为该文件已删除。在最终容器运行的时候,虽然不会看到这个文件,但是实际上该文件会一直跟随镜像。因此,在构建镜像的时候,需要额外小心,每一层尽量只包含该层需要添加的东西,任何额外的东西应该在该层构建结束前清理掉。
分层存储的特征使得镜像的复用、定制变得更为容易。甚至可以用之前构建好的镜像作为基础层,然后进一步添加新的层,以定制自己所需的内容,构建新的镜像。
容器(Container)
镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的 类 和 实例 一样,镜像是静态的定义,容器是镜像运行时的实体,容器可以被创建、启动、停止、删除、暂停等。
容器的实质是进程,但与直接在宿主执行的进程不同,容器进程运行在属于自己的、独立的命名空间中。因此容器可以拥有自己的 root 文件系统、自己的网络配置、自己的进程空间,甚至自己的用户 ID 空间。容器内的进程运行在一个隔离的环境里,使用起来,就好像是在一个独立于宿主的系统下操作一样,这种特性使得容器封装的应用比直接在宿主运行更加安全。
前面讲过镜像使用的是分层存储,容器也是如此。每一个容器运行时,是以镜像为基础层,在其上创建一个当前容器的存储层,我们可以称这个为容器运行时读写而准备的存储层称为容器存储层。容器存储层的生存周期和容器一样,容器消亡时,容器存储层也随之消亡。因此,任何保存于容器存储层的信息都会随容器删除而丢失。
按照 Docker 最佳实践的要求,容器不应该向其存储层内写入任何数据,容器存储层要保持无状态化。所有的文件写入操作,都应该使用数据卷(Volume)、或者绑定宿主目录,在这些位置的读写会跳过容器存储层,直接对宿主(或网络存储)发生读写,其性能和稳定性更高。数据卷的生存周期独立于容器,容器消亡,数据卷不会消亡,因此,使用数据卷后,容器删除或者重新运行之后,数据都不会丢失。
仓库(Repository)
镜像构建完成后,可以很容易的在当前宿主机上运行,但如果需要在其它服务器上使用这个镜像,就需要一个集中的存储、分发镜像的服务,这就是镜像仓库(Registry)。
Docker Hub就是Docker提供用于存储和分布镜像的官方Docker Registry,也是默认的Registry,其网址为https://hub.docker.com,我们使用docker pull命令便从Docker Hub上拉取镜像。
Docker Hub有很多官方或其他开发提供的高质量镜像供我们使用,当然,如果要将我们自己构建的镜像上传到Docker Hub上,我们需要在Docker Hub上注册一个账号,然后把自己在本地构建的镜像发送到Docker Hub的仓库当中,Docker Registry包含很多个仓库,每个仓库对应多个标签,不同标签对应一个软件的不同版本。
Docker Client
Docker 客户端(docker)是用户与 Docker 交互的主要方式。当你使用 docker run 等命令时,客户端会将这些命令发送到 dockerd,dockerd 负责将其执行。一个 Docker客户端可以与多个 dockerd 进行通讯。
Docker daemon
Docker daemon(dockerd)负责监听 Docker API 请求并管理 Docker 对象,如镜像,容器,网络和卷,守护程序彼此之间也可以进行通讯。
Docker主机(Host)
一个物理或者虚拟的机器用于执行 Docker 守护进程和容器。
Docker框架与运作
Docker框架
Docker 使用客户端-服务器 (C/S) 架构模式,包括客户端和服务端。
使用远程API来管理和创建Docker容器:Docker daemon 作为服务端接受来自客户的请求,并处理这些请求( 创建、运行、分发容器)。客户端和服务端既可以运行在一个机器上,也可通过 socket 或者 RESTful API 来进行通信。
Docker运作
当我们想运行一个容器的时候,Docker会执行如下操作:
- 拉取镜像,若本地已经存在该镜像,则不用到Docker仓库去拉取;
- 使用镜像创建新的容器;
- 分配union文件系统并且挂载一个可读写的层,任何修改容器的操作都会被记录在这个读写层上,你可以保存这些修改成新的镜像,也可以选择不保存,那么下次运行改镜像的时候所有修改操作都会被消除;
- 分配网络/桥接接口,创建一个允许容器与本地主机通信的网络接口;
- 设置ip地址,从池中寻找一个可用的ip地址附加到容器上,换句话说,localhost并不能访问到容器;
- 运行你指定的程序;
- 捕获并且提供应用输出:连接并且记录标准输出、输入和错误让你可以看到你的程序是如何运行的。
结语
网上关于Docker的基础知识比较多,但是感觉比较散,也比较杂,且有些重点知识没有突出。
本文主要将Docker基础知识重新进行整理,不涉及任何Docker命令、示例和原理部分,因为想让这篇文章更加纯粹一些,通过这篇文章,你应该会对Docker有一个整体的了解,下一篇文章会结合具体的实例,讲解Docker的常用命令。