Docker 产生的背景

环境配置的难题

软件开发最大的麻烦事之一,就是环境配置。用户计算机的环境都不相同,你怎么知道自家的软件,能在那些机器跑起来?

用户必须保证两件事:操作系统的设置,各种库和组件的安装。只有它们都正确,软件才能运行。举例来说,安装一个 Python 应用,计算机必须有 Python 引擎,还必须有各种依赖,可能还要配置环境变量。

如果某些老旧的模块与当前环境不兼容,那就麻烦了。开发者常常会说:”它在我的机器可以跑了”(It works on my machine),言下之意就是,其他机器很可能跑不了。

环境配置如此麻烦,换一台机器,就要重来一次,旷日费时。很多人想到,能不能从根本上解决问题,软件可以带环境安装?也就是说,安装的时候,把原始环境一模一样地复制过来。

虚拟机

虚拟机(virtual machine)就是带环境安装的一种解决方案。它可以在一种操作系统里面运行另一种操作系统,比如在 Windows 系统里面运行 Linux 系统。应用程序对此毫无感知,因为虚拟机看上去跟真实系统一模一样,而对于底层系统来说,虚拟机就是一个普通文件,不需要了就删掉,对其他部分毫无影响。

虽然用户可以通过虚拟机还原软件的原始环境。但是,这个方案有几个缺点。

资源占用多

虚拟机会独占一部分内存和硬盘空间。它运行的时候,其他程序就不能使用这些资源了。哪怕虚拟机里面的应用程序,真正使用的内存只有 1MB,虚拟机依然需要几百 MB 的内存才能运行。

冗余步骤多

虚拟机是完整的操作系统,一些系统级别的操作步骤,往往无法跳过,比如用户登录。

启动慢

启动操作系统需要多久,启动虚拟机就需要多久。可能要等几分钟,应用程序才能真正运行。

Linux 容器

由于虚拟机存在这些缺点,Linux 发展出了另一种虚拟化技术:Linux 容器(Linux Containers,缩写为 LXC)。

Linux 容器不是模拟一个完整的操作系统,而是对进程进行隔离。 或者说,在正常进程的外面套了一个保护层。对于容器里面的进程来说,它接触到的各种资源都是虚拟的,从而实现与底层系统的隔离。

容器 原生运行于 Linux 中,并与其它的容器共享主机的内核。它以单独的进程运行,不会比其它可执行文件占用更多的内存,因此比较轻量。

相比之下,虚拟机 会运行一个完整的操作系统,该操作系统通过虚拟层对宿主的资源进行访问。通常比应用程序需要更多的资源来运行。

容器的特点

容器化之所以愈发流行是因为容器的以下特点:

  • 灵活:即使是最复杂的程序都可以被容器化
  • 轻量:容器会利用并共享主机的内核
  • 无缝:可以实时更新和升级
  • 便携:可以在本地构建,部署到云端,随处运行
  • 弹性:可以尽情扩展,自动发布容器的复本
  • 可堆叠:可以把服务在线垂直堆叠

容器比虚拟机的优势

由于容器是进程级别的,相比虚拟机有很多优势。

启动快

容器里面的应用,直接就是底层系统的一个进程,而不是虚拟机内部的进程。所以,启动容器相当于启动本机的一个进程,而不是启动一个操作系统,速度就快很多。

资源占用少

容器只占用需要的资源,不占用那些没有用到的资源;虚拟机由于是完整的操作系统,不可避免要占用所有资源。另外,多个容器可以共享资源,虚拟机都是独享资源。

体积小

容器只要包含用到的组件即可,而虚拟机是整个操作系统的打包,所以容器文件比虚拟机文件要小很多。

总之,容器有点像轻量级的虚拟机,能够提供虚拟化的环境,但是成本开销小得多。

Docker 简介

Docker 属于 Linux 容器的一种封装,提供简单易用的容器使用接口。它是目前最流行的 Linux 容器解决方案。

Docker 将应用程序与该程序的依赖,打包在一个文件里面。运行这个文件,就会生成一个虚拟容器。程序在这个虚拟容器里运行,就好像在真实的物理机上运行一样。有了 Docker,就不用担心环境问题。

总体来说,Docker 的接口相当简单,用户可以方便地创建和使用容器,把自己的应用放入容器。容器还可以进行版本管理、复制、分享、修改,就像管理普通的代码一样。

Docker 平台

Docker 是一个平台,开发人员与管理员可以在容器中进行 开发部署运行 应用程序。用 Linux 容器进行部署应用称为容器化。容器不是一个新概念,但将其用于简化应用的部署却是一个新的概念。

Linux 容器提供了一个宽松、隔离的环境,使用 Docker 可以在容器中运行应用程序,也可以将其完整打包。这种隔离性和安全性让我们可以在宿主机上同时运行多个容器。容器是轻量化的,因为它们没有虚拟层(hypervisor),从而不会带来附加的负载,可以在宿主机内核中直接运行。与虚拟机比起来,在同样的硬件条件下,可以运行更多的容器。甚至可以在虚拟机中运行 docker 容器。

Docker 提供一系列工具和一个平台,用来管理容器的整个生存周期:

  • 用容器来开发程序及其支持组件
  • 容器成为发布和测试应用的单元
  • 一切就绪时,将应用以容器或服务的形式,部署到生产环境,无论生产环境是在本地的数据中心、云端还是二者的混合。

Docker 引擎

Docker 引擎是一个 C/S 架构的应用,主要组件为:

  • 服务端:dockerd 守护进程
  • API:REST API,应用程序通过该接口与守护进程通信,通知其进行特定的操作
  • 客户端:CLI 客户端,即 docker 命令

image-center

docker 的命令行客户端借助脚本或直接的命令,使用 REST API 来控制服务端。有许多其它的 docker 应用使用的是底层的 API 和 CLI。

Docker 守护进程会创建并管理 docker 对象,如镜像、容器、网络、映射卷等。

REST API:Representational State Transfer is designed to take advantage of existing protocols. While REST can be used over nearly any protocol, it usually takes advantage of HTTP when used for Web APIs. This means that developers do not need to install libraries or additional software in order to take advantage of a REST API design.

Docker 的用途

对应用程序进行高速、一致的分发

Docker 简化了开发工作的流程,通过使用本地容器,开发人员可以工作于标准的环境中。容器的使用,特别有利于持续集成、持续交付。

持续集成/持续交付: CI/CD,Continous Integration,Continous Delivery。
在 CI 环境中,开发人员将会频繁地向主干提交代码。这些新提交的代码在最终合并到主干前,需要经过编译和自动化测试流进行验证。持续集成过程中很重视自动化测试验证结果,以保障所有的提交在合并主线之后的质量问题,对可能出现的一些问题进行预警。
CD 可以让软件产品的产出过程在一个短周期内完成,以保证软件可以稳定、持续的保持在随时可以释出的状况。它的目标在于让软件的建置、测试与释出变得更快以及更频繁。这种方式可以减少软件开发的成本与时间,减少风险。
有时候,持续交付也与持续部署(Continous Deployment)混淆。持续部署意味着所有的变更都会被自动部署到生产环境中。持续交付意味着所有的变更都可以被部署到生产环境中,但是出于业务考虑,可以选择不部署。如果要实施持续部署,必须先实施持续交付。

比如如下场景:

  • 开发人员在本地编写代码,然后使用 docker 容器将其工作分享给同事
  • 他们使用 docker 将应用置于测试环境中,进行自动、手动测试
  • 开发人员发现漏洞时,可以在开发环境中修复,并再次将其部署于测试环境,重新进行测试及检验
  • 测试结束后,只需把更新过的镜像推送到生产环境中,即可将修复交付给客户

可靠、弹性的部署

通过基于容器的 docker 平台,可以实现高度便携。docker 容器可以运行于开发人员的笔记本、数据中心的物理机或虚拟机中、云服务商或混合环境中。

Docker 的便携和轻量化的天性,使得在我们在业务需要时,可以更加轻松地、接近实时地进行动态管理工作负载,扩容、缩容应用与服务。

在同样的硬件上完成更多的工作

Docker 是轻量化的、高速的,它是传统虚拟机更有活力的、更合算的替代品,我们可以更加充分的利用现有的计算能力来完成我们的业务目标。Docker 对于高密度环境和中小型部署非常适合,只需使用较少的资源就可完成更多的工作。

使用的底层技术

Docker 是用 Go 语言编写的,利用了一些 Linux 内核的特点来实现其功能。

命名空间

Namespaces

Docker 使用命名空间来实现工作空间的隔离。运行一个容器时,docker 会为该容器创建一系列命名空间。

这些命名空间形成了一个 隔离层,容器的每个方面都在一个单独的命名空间中运行,其访问被限制在该命名空间中。

Docker 引擎使用到的命名空间主要有:

  • pid 命名空间:用于进程的隔离
  • net 命名空间:用于管理网络接口
  • ipc 命名空间:用于管理对 IPC 资源的访问(IPC:InterProcess Communication)
  • mnt 命名空间:用于管理文件系统挂载点
  • uts 命名空间:用于隔离内核和版本标识(UTS:Unix Timesharing System)

控制组

Control Groups,cgroups

Linux 中的 Docker 引擎还依赖于 cgroups 技术。一个 cgroup 会将应用限制于一组特定的资源上。通过 cgroup,Docker 引擎可以为多个容器共享可用的硬件资源,需要时还可以对使用的资源进行限制。例如,可以限制特定容器允许使用的内存数量。

联合文件系统

Uninon File Systems,UFS

UFS 文件系统是通过创建 来运行的,非常轻量、快速。Docker 引擎使用 UFS 为容器提供构建的组件,Docker 引擎可以使用多个 UFS 的变体,包括 AUFS,btrfs,vfs,DeviceMapper。

容器格式

Container format

Docker 引擎将 namesapces、cgroups、UFS 组合到一个封装(wrapper)中,称为容器格式。默认的容器格式为 libcontainer。未来,docker 可能会支持其它的容器格式。

Docker 的安装及配置

Docker 有两个版本: Docker Community Edition (CE) 和 Enterprise Edition (EE) 。

针对各平台的安装方法,官方文档中有详细的说明。

CentOS 中安装 Docker

方法一

用 docker 官方提供的脚本自动安装:

$ curl -sSL https://get.docker.com/ | sh

方法二

手动安装:

$ sudo yum install -y yum-utils device-mapper-persistent-data lvm2
$ sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
$ sudo yum install docker-ce

测试安装是否成功

查看当前安装的 Docker 版本:

docker --version

查看更详细的安装信息:

docker info

测试安装是否成功:

docker container run hello-world

查看当前镜像列表是否有刚刚下载的 hello-world:

docker image ls

查看当前容器,包括已经停止运行的:

docker container ls -a

用非特权用户管理 Docker

为了避免发生权限错误,可以把用户加入 docker 组。

Docker 守护进程会绑定到一个 Unix 套接字上,而不是 TCP 端口上。该套接字的所有者默认是 root,其它用户必须使用 sudo 来访问。这个守护进程始终会以 root 身份运行。

如果不愿意每次都使用 sudo docker,可以创建一个组,命名为 docker,然后把用户加入该组。Docker 守护进程启动时,它创建的 Unix 套接字是允许 docker 组的成员访问的。

docer 组会赋予用户和 root 同等的权限。

创建组并添加用户

创建组:

$ sudo groupadd docker

把用户添加到组中:

$ sudo usermod -aG docker $USER

退出并重新登陆,以激活新组。

测试是否可以不用 sudo 来运行 docer:

$ docker run hello-world