Docker(一)概览

Docker概览(一)概览

第一节 容器的发展

1.1 过去

过去服务器只能运行单一应用,旧时代的Windows和Linux系统都没有相应的技术手段来保证能够在一台服务器上稳定安全的同时运行多个应用。所以每当业务部门需要新增一个应用就要采购一台新的服务器,而对于应用所需服务器性能只能凭经验来推测,所以不得不买性能要大幅优于所需的硬件,最终必然造成大量的浪费。

1.2 虚拟机

为了解决上述问题,VMware公司开发了虚拟机,终于可以有一种允许多应用能稳定安全运行在同一台服务器的技术。虚拟机是一项划时代的技术,让现有的资产可以发挥更多的作用。

但虚拟机需要依赖于其专用的操作系统,而OS会占用额外的CPU、RAM和存储,这些资源本可以用来运行更多的应用。每个OS都需要补丁和监控,还有一些情况下OS需要许可证才可以运行。还有一些挑战如虚拟机启动会较慢,可移植性很差等等。

1.3 容器

后来,像谷歌这样的公司开始采用容器(Container)技术来解决虚拟机的这些缺点。容器不会独占OS,从而节省了大量的系统资源,也省下了花费在许可证和为OS打补丁的运维成本。容器还有启动快和便于迁移等优势,从PC迁移到云上或再迁移到数据中心的虚拟机或物理机上都非常简单。

1.4 Linux容器

现代的容器技术起源于Linux,是很多力量持续贡献的产物,这些年对容器影响较大的技术包括:内核命名空间、控制组、联合文件系统、当然还有Docker。

1.5 Docker

容器技术的复杂度一直以来是导致其没有被普遍应用的原因,但Docker技术的诞生改变了这一情况。Docker使容器变得简单。

1.6 Windows容器

近些年微软致力于在Windows平台发展Docker和容器技术,实现容器所需的核心Windows内核技术被统称为Windows容器。用户空间通过Docker来完成与Windows容器的交互,所以同Linux上使用Docker几乎一直。

运行中的容器共享宿主机的内核,这意味着基于Windows的容器化应用在Linux上是无法运行的,但目前Windows版Docker已可以在Windows模式和Linux模式间切换。Docker发展速度日新月异,请及时了解最新技术进展。

1.7 Kubernetes

Kubernetes是谷歌的一个开源项目,是容器编排领域的领头羊。简单理解就是Docker之上的一个平台,采用Docker作为其底层容器操作。

Kubernetes采用Docker作为其默认容器运行时(container runtime),包括Kubernetes启动和停止容器,以及镜像拉取等。Kubernetes提供了一个可插拔的容器运行时接口CRI,帮助Kubernetes实现将运行时环境从Docker快速替换为其他容器运行时,未来Kubernetes会将默认由Docker转为containerd。

第二节 了解Docker

2.1 简介

Docker是一种运行于Linux和Windows上的软件,用于创建、管理和编排容器。Docker是在GitHub上开发的Moby开源项目的一部分。Docker公司是整个Moby开源项目的维护者,还提供包含支持服务的商业版本的Docker。

“Docker”一词来自英国口语,意为码头工人(Dock Worker),即从船上装卸货物的人

2.2 Docker运行时与编排引擎

多数技术人员在谈到Docker时,主要是指Docker引擎。Docker引擎是用于运行和编排容器的基础设施工具。有VMware管理经验的读者可以将其类比为ESXi(运行虚拟机的核心管理程序),而Docker引擎是运行容器的核心容器运行时。

其他Docker公司或第三方的产品都是围绕Docker引擎进行开发和集成的。如图2.2所示,Docker引擎位于中心,其他产品基于Docker引擎的核心功能进行集成。

围绕Docker引擎进行开发和集成的产品

Docker引擎可以从Docker网站下载,也可以基于GitHub上的源码进行构建。无论是开源版本还是商业版本,都有Linux和Windows版本。Docker引擎主要有两个版本:企业版(EE)和社区 版(CE)。每个季度,企业版和社区版都会发布一个稳定版本。社区版本会提供4个月的支持,而企业版本会提供12个月的支持。社区版还会通过Edge方式发布月度版。

2.3 Docker开源项目(Moby)

“Docker”一词也会用于指代开源Docker项目。其中包含一系列可以从Docker官网下载和安装的工具,比如Docker服务端和Docker客户端。不过,该项目在2017年于Austin举办的DockerCon上正式命名为Moby项目。由于这次改名,GitHub上的docker/docker库也被转移到了moby/moby。

Moby项目的目标是基于开源的方式,发展成为Docker上游,并将Docker拆分为更多的模块化组件。Moby项目托管于GitHub的Moby代码库,包括子项目和工具列表。核心的Docker引擎项目位于GitHub的moby/moby,但是引擎中的代码正持续被拆分和模块化。

多数项目及其工具都是基于Golang编写的,这是谷歌推出的一种新的系统级编程语言,又叫Go语言。使用Go语言的读者,将更容易为该项目贡献代码。

2.4 容器生态

Docker公司的一个核心哲学通常被称为“含电池,但可拆卸”(Batteries included but removable)。意思是许多Docker内置的组件都可以替换为第三方的组件,网络技术栈就是一个很好的例子。Docker核心产品内置有网络解决方案。但是网络技术栈是可插拔的,这意味着Docker内置的网络方案可以被替换为第三方的方案,许多人都会这样使用。

早期的时候,经常出现第三方插件比 Docker 提供的内置组件更好的情况。然而这会对Docker公司的商业模式造成冲击。毕竟,Docker公司需要依靠盈利来维持基业长青。因此,“内置的电池”变得越来越好用了。这也导致了生态内部的紧张关系和竞争的加剧。简单来说,Docker内置的“电池”仍然是可插拔的,然而越来越不需要将它们移除了。

2.5 开放容器计划

如果不谈及开放容器计划(The Open Container Initiative, OCI)的话,对Docker和容器生态的探讨总是不完整的。

OCI是一个旨在对容器基础架构中的基础组件(如镜像格式与容器运行时)进行标准化的管理委员会。简短的历史介绍是,一个名为CoreOS的公司不喜欢Docker的某些行事方式。因此它就创建了一个新的开源标准,称作“appc”,该标准涉及诸如镜像格式和容器运行时等方面。此外它还开发了一个名为rkt(发音“rocket”)的实现。

两个处于竞争状态的标准将容器生态置于一种尴尬的境地。这使容器生态陷入了分裂的危险中,同时也令用户和消费者陷入两难。虽然竞争是一件好事,但是标准的竞争通常不是。因为它会导致困扰,降低用户接受度,对谁都无益。

考虑到这一点,所有相关方都尽力用成熟的方式处理此事,共同成立了OCI——一个旨在管理容器标准的轻量级的、敏捷型的委员会。当前OCI已经发布了两份规范(标准):镜像规范和运行时规范。Docker 1.11版本中,Docker引擎架构已经遵循OCI运行时规范了。到目前为止,OCI已经取得了不错的成效,将容器生态团结起来。OCI在Linux基金会的支持下运作,Docker公司和CoreOS公司都是主要贡献者。

第三节 安装Docker

Docker可以安装在Windows、Mac、Linux之上。除此之外还可以在云上安装,也可以在个人笔记本电脑上安装等等。本节仅整理比较重要的几种安装方式。

1.桌面安装:

  • Windows版Docker(Docker for Windows)
  • Mac版Docker(Docker for Mac)

2.服务器安装:

  • Linux
  • Windows Server 2016

3.Docker引擎升级

4.Docker存储驱动的选择

3.1 Windows版Docker(DfW)

Windows版Docker需要运行在一个安装了64位Windows 10 操作系统的计算机上,通过启动一个独立的引擎来提供Docker环境。

Windows版Docker是一个社区版本(Community Edition,CE)的应用,并不是为生产环境设计的。Windows版Docker在某些版本特性上可能是延后支持的。这是因为Docker公司对该产品的定位是稳定性第一,新特性其次。

在安装之前,Windows版Docker的环境有以下要求:

  • Windows 10 Pro / Enterprise / Education(1607 Anniversary Update、 Build 14393或者更新的版本)。
  • Windows必须是64位的版本。
  • 需要启用Windows操作系统中的Hyper-V和容器特性。

开启Hyper-V和容器

接下来的步骤会假设读者的计算机已经开启了BIOS设置中的硬件虚拟化支持。如果没有开启,则需要在机器上执行下面的步骤。

首先,读者需要确认在Windows 10操作系统中,Hyper-V和容器特性已安装并且开启。

  1. 右键单击Windows开始按钮并选择“应用和功能”页面。
  2. 单击“程序和功能”链接。
  3. 单击“启用或关闭Windows功能”。
  4. 确认Hyper-V和容器复选框已经被勾选,并单击确定按钮。

按上述步骤操作完成后,会安装并开启Hyper-V和容器特性,需要重启操作系统。之后就可以安装Windows版Docker。

  1. 访问Docker的下载页面,并单击其中的Download for Windows按钮。
  2. 单击后会跳转到Docker商店,需要读者使用自己的Docker ID进行登录。
  3. 单击任意Get Docker下载链接。Docker for Windows分为稳定版(Stable)和抢鲜版(Edge)。抢鲜版当中包含一些新特性,但是可能不够稳定。单击下载链接后,会将名为Docker for Windows Installer.exe的安装包下载到默认下载目录。
  4. 找到上一步下载的安装包并运行即可。

家庭版手动伪装专业版

windows10家庭版无法安装docker,没有Hyper-V等组件,需要下载docker toolbox安装,当然也可以直接将以下代码保存到.cmd文件并用管理员权限执行来安装Hyper-V。

1
2
3
4
5
pushd "%~dp0"
dir /b %SystemRoot%\servicing\Packages\*Hyper-V*.mum >hyper-v.txt
for /f %%i in ('findstr /i . hyper-v.txt 2^>nul') do dism /online /norestart /add-package:"%SystemRoot%\servicing\Packages\%%i"
del hyper-v.txt
Dism /online /enable-feature /featurename:Microsoft-Hyper-V-All /LimitAccess /ALL

管理员身份执行以下cmd文件,伪装成win10专业版

1
2
REG ADD "HKEY_LOCAL_MACHINE\software\Microsoft\Windows NT\CurrentVersion" /v EditionId /T REG_EXPAND_SZ /d 
Professional /F

但执行失败,所以还是用toolbox来安装了。

http://mirrors.aliyun.com/docker-toolbox/windows/docker-toolbox/

正式安装

以管理员身份运行安装向导,并按照提示一步一步完成整个安装过程。安装完成后Docker会作为系统服务自动启动,并且在Windows的通知栏看到Docker的大鲸鱼图标。

打开命令行或者PowerShell界面,并尝试执行下面的命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Client: 
Version: 18.01.0-ce
API version: 1.35
Go version: go1.9.2
Git commit: 03596f5
Built: Wed Jan 10 20:05:55 2018
OS/Arch: windows/amd64
Experimental: false
Orchestrator: swarm
Server:
Engine:
Version: 18.01.0-ce
API version: 1.35 (minimum version 1.12)
Go version: go1.9.2
Git commit: 03596f5
Built: Wed Jan 10 20:13:12 2018
OS/Arch: linux/amd64
Experimental: false

注意观察命令输出内容,其中Server部分中的OS/Arch属性展示了当前的操作系统是linux/amd64。这是因为在默认安装方式中,Docker daemon是运行在Hyper-V虚拟机中的一个轻量级Linux上的。这种情况下只能在Windows版Docker上运行Linux容器。

如果想要运行原生Windows容器(Native Windows Container),可以右击Windows通知栏中的Docker鲸鱼图标,并选择“切换到Windows容器”。使用下面的命令也可以完成切换(进入\Program Files\Docker\Docker目录下执行)。

1
C:\Program Files\Docker\Docker> .\dockercli -SwitchDaemon

如果已经开启了Windows容器特性,则只需要花费数秒就能完成切换。一旦切换完成,在命令行中执行docker version指令的输出内容如下。

1
2
3
4
5
6
7
8
9
10
11
12
C:\> docker version 
Client:
<Snip>
Server:
Engine:
Version: 18.01.0-ce
API version: 1.35 (minimum version 1.24)
Go version: go1.9.2
Git commit: 03596f5
Built: Wed Jan 10 20:20:36 2018
OS/Arch: windows/amd64
Experimental: true

可以看到,现在Server版本信息变成了windows/amd64,这意味着Docker daemon运行在原生Windows内核上,并且只能运行Windows容器了。同时也可以发现,Experimental这个属性的值为true。这表示当前运行的Docker版本是实验版本。可以通过运行dockercli -Version命令来查看当前的Docker版本。dockercli命令在C:\Program Files\Docker\Docker目录下。

Windows版Docker包括Docker引擎(客户端和daemon)、Docker Compose、Docker Machine以及Docker Notary命令行。通过下列命令确认各个模块已经成功安装。

1
2
3
4
5
6
7
8
9
10
11
12
13
C:\> docker --version 
Docker version 18.01.0-ce, build 03596f5

C:\> docker-compose --version
docker-compose version 1.18.0, build 8dd22a96

C:\> docker-machine --version
docker-machine.exe version 0.13.0, build 9ba6da9

C:\> notary version
notary
Version: 0.4.3
Git commit: 9211198

3.2 Mac版Docker(DfM)

Mac版Docker并不是为生产环境而设计的,Mac版Docker就是一个流畅、简单并且稳定版的boot2docker。对于Mac版Docker来说,提供基于Mac原生操作系统中Darwin内核的Docker引擎没有什么意义。所以在Mac版Docker当中,Docker daemon是运行在一个轻量级的Linux VM之上的。Mac版Docker通过对外提供daemon和API的方式与Mac环境实现无缝集成,这意味着我们可以在Mac上打开终端并直接使用Docker命令。

尽管在Mac上实现了无缝集成,还是要谨记Mac版Docker底层是基于Linux VM运行的,所以说Mac版Docker只能运行基于Linux的Docker容器。不过这样已经很好了,因为大部分容器实际上都是基于Linux的。

安装过程暂略,个人还未有Mac产品。

3.3 在Linux上安装Docker

在Linux上安装Docker是常见的安装场景,并且安装过程非常简单。通常难点在于Linux不同发行版之间的轻微区别,比如Ubuntu和CentOS之间的差异。接下来的示例基于Ubuntu版本Linux,同样适用于更低或者更高的版本。理论上,下面的示例在CentOS的各种版本上也是可以执行的。至于Linux操作系统是安装在自己的数据中心,还是第三方公有云,或是笔记本的虚拟机上,都没有任何的区别。唯一需求就是这台机器是Linux操作系统,并且能够访问 https://get.docker.com。

首先需要选择安装的Docker版本。当前有两个版本可供选择: 社区版(Community Edition,CE)和企业版(Enterprise Edition,EE)。

下面使用wget命令来运行一个Shell脚本,完成Docker CE的安装。更多其他在Linux上安装Docker的方式,可以打开Docker主页面,单击页面中Get Started按钮来获取。

(1)在Linux机器上打开一个新的Shell

(2)使用wget从https://get.docker.com获取并运行Docker安装脚本,然后采用Shell中管道(pipe)的方式来执行这个脚本。

1
2
3
4
5
6
7
8
9
$ wget -qO- https://get.docker.com/ | sh

modprobe: FATAL: Module aufs not found /lib/modules/4.4.0-36-generic + sh -c 'sleep 3; yum -y -q install docker-engine'
<Snip>
If you would like to use Docker as a non-root user, you should now consider adding your user to the "docker" group with something like:

sudo usermod -aG docker your-user

Remember that you will have to log out and back in...

(3)最好通过非root用户来使用Docker。这时需要添加非root用户到本地Docker Unix组当中。下面的命令展示了如何把名为npoulton的用户添加到Docker组中,以及如何确认操作是否执行成功。请自行使用系统中的有效用户。

1
2
3
4
$ sudo usermod -aG docker npoulton

$ cat /etc/group | grep docker
docker:x:999:npoulton

如果当前登录用户就是要添加到Docker组中的用户的话,则需要重新登录,组权限设置才会生效。这样Docker已经在Linux机器上安装成功。运行下面命令来确认安装结果。

1
2
3
4
5
$ docker --version
$ docker system info

--有问题可以尝试重启docker
service docker restart

3.4 在Windows Server 2016上安装Docker

暂略

3.5 Docker引擎(Engine)升级

升级Docker引擎(Engine)是一项重要的任务,尤其是生产环境。

需要重视升级操作的每个前置条件,包括确保容器配置了正确的重启策略;在Swarm Mode模式下使用服务时,需要确保正确配置了draining node。当完成了上述前置条件的检查之后,可以通过如下步骤完成升级操作。

  1. 停止Docker守护程序。
  2. 移除旧版本Docker。
  3. 安装新版本Docker。
  4. 配置新版本的Docker为开机自启动。
  5. 确保容器重启成功。

不同版本的Linux在升级Docker的时候,命令可能略有区别。

在Ubuntu 16.04上升级Docker CE

假设已经完成了全部的升级前置步骤并且Docker处于可以 升级的状态,同时还可以用root用户身份运行升级命令。以root用户运行升级命令是不推荐的,但是可以简化示例。需要通过sudo来执行下列指令。

(1)更新APT包列表。

1
$ apt-get update

(2)卸载当前Docker。

1
$ apt-get remove docker docker-engine docker-ce docker.io -y

在之前的版本中,Docker引擎的包名可能有多个。这条命令能够确保已经安装的Docker包全部被删除。

(3)安装新版本Docker。

有不同版本的Docker可供选择,并且有多种方式可以安装Docker。 无论是Docker CE还是Docker EE,都有不止一种安装方式。例如, Docker CE可以通过apt或者deb包管理方式进行安装,也可以使用Docker 官网上的脚本。

接下来的命令会使用get.docker.com的脚本完成最新版本Docker CE 的安装和配置。

1
$ wget -qO- https://get.docker.com/ | sh

(4)将Docker配置为开机自启动。

1
2
3
4
5
$ systemctl enable docker
Synchronizing state of docker.service...
Executing /lib/systemd/systemd-sysv-install enable docker
$ systemctl is-enabled docker
enabled

此时可能想重启自己的节点。这样可以确保刚安装的Docker不会对系统开机有任何的影响。

(5)检查并确保每一个容器和服务都已经重启成功。

1
2
3
4
5
6
7
8
$ docker container ls 
CONTAINER ID IMAGE COMMAND CREATED STATUS

97e599aca9f5 alpine "sleep 1d" 14 minutes ago Up 1 minute

$ docker service ls
ID NAME MODE REPLICAS IMAGE
ibyotlt1ehjy prod-equus1 replicated 1/1 alpine:latest

请注意,更新Docker还有其他的方法,此处只是介绍了基于Ubuntu Linux 16.04 版本的方式。

在Windows Server 2016上升级Docker EE

暂略

3.6 Docker存储驱动的选择

每个Docker容器都有一个本地存储空间,用于保存层叠的镜像层(Image Layer)以及挂载的容器文件系统。默认情况下,容器的所有读写操作都发生在其镜像层上或挂载的文件系统中,所以存储是每个容器的性能和稳定性不可或缺的一个环节。

以往,本地存储是通过存储驱动(Storage Driver)进行管理的,有时候也被称为Graph Driver或者GraphDriver。虽然存储驱动在上层抽象设计中都采用了栈式镜像层存储写时复制(Copy-on-Write)的设计思想,但是Docker在Linux底层支持几种不同的存储驱动的具体实现,每一种实现方式都采用不同方法实现了镜像层和写时复制。虽然底层实现的差异不影响用户与Docker之间的交互,但是对Docker的性能和稳定性至关重要。

在Linux上,Docker可选择的一些存储驱动包括AUFS(最原始也是 最老的)、Overlay2(可能是未来的最佳选择)、Device Mapper、Btrfs 和ZFS。

Docker在Windows操作系统上只支持一种存储驱动,即Windows Filter。

存储驱动的选择是节点级别的。这意味着每个Docker主机只能选择一种存储驱动,而不能为每个容器选择不同的存储驱动。在Linux上,读者可以通过修改/etc/docker/daemon.json文件来修改存储引擎配置,修改完成之后需要重启Docker才能够生效。下面的代码片段展示了如何将存储驱动设置为overlay2。

1
{  "storage-driver": "overlay2" }

如果修改了正在运行Docker主机的存储引擎类型,则现有的镜像和容器在重启之后将不可用,这是因为每种存储驱动在主机上存储镜像层的位置是不同的(通常在/var/lib/docker / < storagedriver>/…目录下)。修改了存储驱动的类型,Docker就无法找到原有的镜像和容器了。切换到原来的存储驱动,之前的镜像和容器就可以继续使用了。

如果希望在切换存储引擎之后还能够继续使用之前的镜像和容器,需要将镜像保存为Docker格式,上传到某个镜像仓库,修改本地 Docker存储引擎并重启,之后从镜像仓库将镜像拉取到本地,最后重启容器。

通过下面的命令来检查Docker当前的存储驱动类型。

1
2
3
4
5
6
7
$ docker system info 
<Snip>
Storage Driver: overlay2
Backing Filesystem: xfs
Supports d_type: true
Native Overlay Diff: true
<Snip>

选择存储驱动并正确地配置在Docker环境中是一件重要的事情,特别是在生产环境中,建议参阅Docker官网上由Linux发行商提供的最新文档来做出选择。

3.6.1 Device Mapper配置

大部分Linux存储驱动不需要或需要很少的配置。但是,Device Mapper通常需要合理配置之后才能表现出良好的性能。

默认情况下,Device Mapper采用loopback mounted sparse file作为底层实现来为Docker提供存储支持。如果需要的是开箱即用并且对性能没什么要求,那么这种方式是可行的。但这并不适用于生产环境。实际上,默认方式的性能很差,并不支持生产环境。

为了达到Device Mapper在生产环境中的最佳性能,读者需要将底层实现修改为direct-lvm模式。这种模式下通过使用基于裸块设备(Raw Block Device)的LVM精简池(LVM thin pool)来获取更好的性
能。

在Docker 17.06以及更高的版本中可以配置direct-lvm作为存储驱动,但到当前该方式存在某种限制。其中最主要的一点是,这种方式只能配置一个块设备,并且只有在第一次安装后才能设置生效。未来可能会有改进,但就目前情况来看配置单一块设备这种方式在性能和可靠性上都有一定的风险。

3.6.2 让Docker自动设置direct-lvm

下面的步骤会将Docker配置存储驱动为Device Mapper,并使用direct-lvm模式。

(1)将下面的存储驱动配置添加到/etc/docker/daemon.json当中。

1
2
3
4
5
6
7
8
{ 
"storage-driver": "devicemapper",
"storage-opts": [
"dm.directlvm_device=/dev/xdf",
"dm.thinp_percent=95",
"dm.thinp_metapercent=1", "dm.thinp_autoextend_threshold=80", "dm.thinp_autoextend_percent=20", "dm.directlvm_device_force=false"
]
}

Device Mapper和LVM是很复杂的知识点,并不在讨论范围之内。下面简单介绍一下各配置项的含义。

  • dm.directlvm_device:设置了块设备的位置。为了存储的最佳
    性能以及可用性,块设备应当位于高性能存储设备(如本地SSD) 或者外部RAID存储阵列之上。
  • dm.thinp_percent=95:设置了镜像和容器允许使用的最大存储 空间占比,默认是95%。
  • dm.thinp_metapercent:设置了元数据存储(MetaData Storage)允许使用的存储空间大小。默认是1%。
  • dm.thinp_autoextend_threshold:设置了LVM自动扩展精简池 的阈值,默认是80%。
  • dm.thinp_autoextend_percent:表示当触发精简池(thin pool)自动扩容机制的时候,扩容的大小应当占现有空间的比例。
  • dm.directlvm_device_force:允许用户决定是否将块设备格式 化为新的文件系统。

(2)重启Docker。

(3)确认Docker已成功运行,并且块设备配置已被成功加载。

1
2
$ docker version 
$ docker system info

即使Docker在direct-lvm模式下只能设置单一块设备,其性能也会显著优于loopback模式。

3.6.3 手动配置Device Mapper的direct-lvm

完整介绍如何进行Device Mapper direct-lvm的手动配置有些超出范畴。但是,下面列出的内容是我们需要了解并在配置的时候仔细斟酌的。

  • 块设备(Block Device):在使用direct-lvm模式的时候,读者需要有可用的块设备。这些块设备应该位于高性能的存储设备之上,比如本地SSD或者外部高性能LUN存储。如果Docker环境部署在企业私有云(On-Premise)之上,那么外部LUN存储可以使用FC、iSCSI,或者其他支持块设备协议的存储阵列。如果Docker环境部署在公有云之上,那么可以采用公有云厂商提供的任何高性能的块设备(通常基于SSD)。
  • LVM配置:Docker的Device Mapper存储驱动底层利用LVM(Logical Volume Manager)来实现,因此需要配置LVM所需的物理设备、卷组、逻辑卷和精简池。读者应当使用专用的物理卷并将其配置在相同的卷组当中。这个卷组不应当被Docker之外的工作负载所使用。此外还需要配置额外两个逻辑卷,分别用于存储数据和源数据信息。另外,要创建LVM配置文件、指定LVM自动扩容的触发阈值,以及自动扩容的大小,并且为自动扩容配置相应的监控,保证自动扩容会被触发。
  • Docker配置:修改Docker配置文件之前要先保存原始文件(etc/docker/daemon.json),然后再进行修改。读者环境中的dm.thinpooldev配置项对应值可能跟下面的示例内容有所不同,需要修改为合适的配置。
1
2
3
4
5
6
{  
"storage-driver": "devicemapper",
"storage-opts": [
"dm.thinpooldev=/dev/mapper/docker-thinpool", "dm.use_deferred_removal=true", "dm.use_deferred_deletion=true"
]
}

修改并保存配置后,可以重启Docker daemon。如果想获取更多细节信息,可以参考Docker文档,或者咨询Docker 技术账户管理员。

第四节 纵观Docker

在继续深入研究Docker之前,需要先对Docker进行一个整体介绍。主要包含两部分内容:运维(Ops)视角。 开发(Dev)视角。

4.1 运维视角

在安装Docker的时候,会涉及两个主要组件:Docker客户端和 Docker daemon(有时也被称为“服务端”或者“引擎”)。daemon实现了Docker引擎的API。

使用Linux默认安装时,客户端与daemon之间的通信是通过本地IPC/UNIX Socket完成的(/var/run/docker.sock);在Windows上是通过名为npipe:////./pipe/docker_engine的管道(pipe)完成的。可以使用docker version命令来检测客户端和服务端是否都已经成功运行,并且可以互相通信。

如果能成功获取来自客户端和服务端的响应,那么可以继续后面的操作。如果正在使用Linux,并且服务端返回了异常响应,则可尝试在命令的前面加上sudo——sudo docker version。如果加上sudo之后命令正常运行,那么需要将当前用户加入到docker用户组,或者给后面的命令都加上sudo前缀。

4.1.1 镜像

Docker镜像理解为一个包含了OS文件系统和应用的对象会很有帮助,与虚拟机模板类似。虚拟机模板本质上是处于关机状态的虚拟机。在Docker世界中,镜像实际上等价于未运行的容器,可以将镜像比作类(Class)。

在Docker主机上运行docker image ls命令。

1
2
$ docker image ls 
REPOSITORY TAG IMAGE ID CREATED SIZE

如果运行命令环境是刚完成Docker安装的主机,或者是Play With Docker,那么Docker主机中应当没有任何镜像,命令输出内容会如上所示。

在Docker主机上获取镜像的操作被称为拉取(pulling)。如果使用Linux,那么会拉取ubuntu:latest镜像;如果使用Windows,则会拉取microsoft/powershell:nanoserver镜像(本人环境是centos,所以直接docker pull centos)。

1
2
3
4
5
6
7
8
latest: Pulling from library/ubuntu 
50aff78429b1: Pull complete
f6d82e297bce: Pull complete
275abb2c8a6f: Pull complete
9f15a39356d6: Pull complete
fc0342a94c89: Pull complete
Digest: sha256:fbaf303...c0ea5d1212
Status: Downloaded newer image for ubuntu:latest

再次运行docker image ls命令来查看刚刚拉取的镜像。

1
2
3
4
$ docker images 
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu latest 00fd29ccc6f1 3 weeks ago 111MB
centos latest 0f3e07c0138f 2 months ago 220MB

关于镜像的存储位置以及镜像内部构成,会在后续进行整理。现在只需知道镜像包含了基础操作系统,以及应用程序运行所需的代码和依赖包。刚才拉取的ubuntu镜像有一个精简版的Ubuntu Linux文件系统,其中包含部分Ubuntu常用工具。而Windows示例中拉取的microsoft/powershell镜像,则包含了带有PowerShell的 Windows Nano Server操作系统。如果拉取了如nginx或者microsoft/iis这样的应用容器,则会得到一个包含操作系统的镜像,并且在镜像中还包括了运行Nginx或IIS所需的代码。

重要的是,Docker的每个镜像都有自己的唯一ID。用户可以通过引用镜像的ID或名称来使用镜像。如果用户选择使用镜像ID,通常只需要输入ID开头的几个字符即可——因为ID是唯一的,Docker知道用户想引用的具体镜像是哪个。

4.1.2 容器

到目前为止,我们已经拥有一个拉取到本地的镜像,可以使用docker container run命令从镜像来启动容器。

在Linux中启动容器的命令如下。

1
2
3
$ docker container run -it ubuntu:latest /bin/bash 
$ docker container run -it centos:latest /bin/bash
root@6dc20d508db0:/#

在Windows中启动容器的命令如下。

1
2
3
4
5
> docker container run -it microsoft/powershell:nanoserver pwsh.exe

Windows PowerShell
Copyright (C) 2016 Microsoft Corporation. All rights reserved.
PS C:\>

每个实例中的提示符都发生了变化,是因为-it参数会将Shell切换到容器终端,现在已经位于容器内部了。

接下来分析一下docker container run命令:docker container run告诉Docker daemon启动新的容器。其中-it参数告诉Docker开启容器的交互模式并将当前的Shell连接到容器终端。接下来,命令告诉Docker,用户想基于ubuntu:latest镜像启动容器(如果用户使用Windows,则是基于 microsoft/powershell:nanoserver镜像)。最后,命令告诉Docker,用户想要在容器内部运行哪个进程。对于Linux示例来说是运行Bash Shell,对于Windows示例来说则是运行PowerShell。

在容器内部运行ps命令查看当前正在运行的全部进程。

Linux示例如下。

1
2
3
4
root@6dc20d508db0:/# ps -elf 
F S UID PID PPID NI ADDR SZ WCHAN STIME TTY TIME CMD
4 S root 1 0 0 - 4560 wait 13:38 ? 00:00:00 /bin/bash
0 R root 9 1 0 - 8606 - 13:38 ? 00:00:00 ps -elf

Windows示例如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
PS C:\> ps

Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName
------- ------ ----- ----- ------ -- -- ----------
0 5 964 1292 0.00 4716 4 CExecSvc
0 5 592 956 0.00 4524 4 csrss
0 0 0 4 0 0 Idle
0 18 3984 8624 0.13 700 4 lsass
0 52 26624 19400 1.64 2100 4 powershell
0 38 28324 49616 1.69 4464 4 powershell
0 8 1488 3032 0.06 2488 4 services
0 2 288 504 0.00 4508 0 smss
0 8 1600 3004 0.03 908 4 svchost
0 12 1492 3504 0.06 4572 4 svchost
0 15 20284 23428 5.64 4628 4 svchost
0 15 3704 7536 0.09 4688 4 svchost
0 28 5708 6588 0.45 4712 4 svchost
0 10 2028 4736 0.03 4840 4 svchost
0 11 5364 4824 0.08 4928 4 svchost
0 0 128 136 37.02 4 0 System
0 7 920 1832 0.02 3752 4 wininit
0 8 5472 11124 0.77 5568 4 WmiPrvSE

Linux容器中仅包含两个进程。

  • PID 1:代表/bin/bash进程,该进程是通过docker container run命令来通知容器运行的。
  • PID 9:代表ps -elf进程,查看当前运行中进程所使用的命令/程序。

命令输出中展示的ps -elf进程存在一定的误导,因为这个程序在ps命令退出后就结束了。这意味着容器内长期运行的进程其实只有/bin/bash。

Windows容器运行中的进程会更多,这是由 Windows 操作系统工作方式决定的。虽然Windows容器中的进程比Linux容器要多,但与常见的Windows服务器相比,其进程数量却是明显偏少的。

按Ctrl-PQ组合键,可以在退出容器的同时还保持容器运行。这样Shell就会返回到Docker主机终端。可以通过查看Shell提示符来确认。

现在已经返回到Docker主机的Shell提示符,再次运行ps命令。

Linux示例如下。

1
2
3
4
5
6
7
8
9
$ ps -elf 
F S UID PID PPID NI ADDR SZ WCHAN TIME CMD
4 S root 1 0 0 - 9407 - 00:00:03 /sbin/init
1 S root 2 0 0 - 0 - 00:00:00 [kthreadd]
1 S root 3 2 0 - 0 - 00:00:00 [ksoftirqd/0]
1 S root 5 2 -20 0 - 00:00:00 [kworker/0:0H]
1 S root 7 2 -0 - 0 - 00:00:00 [rcu_sched]
<Snip>
0 R ubuntu 22783 22475 0 - 9021 - 00:00:00 ps -elf

Windows示例如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
> ps 
Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName
------- ------ ----- ----- ------ -- -- ----------
220 11 7396 7872 0.33 1732 0 amazon-ssm-agen
84 5 908 2096 0.00 2428 3 CExecSvc
87 5 936 1336 0.00 4716 4 CExecSvc
203 13 3600 13132 2.53 3192 2 conhost
210 13 3768 22948 0.08 5260 2 conhost
257 11 1808 992 0.64 524 0 csrss
116 8 1348 580 0.08 592 1 csrss
85 5 532 1136 0.23 2440 3 csrss
242 11 1848 952 0.42 2708 2 csrss
95 5 592 980 0.00 4524 4 csrss
137 9 7784 6776 0.05 5080 2 docker
401 17 22744 14016 28.59 1748 0 dockerd
307 18 13344 1628 0.17 936 1 dwm
<SNIP>
1888 0 128 136 37.17 4 0 System
272 15 3372 2452 0.23 3340 2 TabTip
72 7 1184 8 0.00 3400 2 TabTip32
244 16 2676 3148 0.06 1880 2 taskhostw
142 7 6172 6680 0.78 4952 3 WmiPrvSE
148 8 5620 11028 0.77 5568 4 WmiPrvSE

可以看到与容器相比,Docker主机中运行的进程数要多很多。 Windows容器中运行的进程要远少于Windows主机,Linux容器中的进程数也远少于Linux主机。

在之前的步骤当中,是使用Ctrl-PQ组合键来退出容器的。在容器内部使用该操作可以退出当前容器,但不会杀死容器进程。可以通过docker container ls命令查看系统内全部处于运行状态的容器。

1
2
3
$ docker container ls 
CONTAINER ID IMAGE COMMAND CREATED STATUS NAMES
e2b69eeb55cb ubuntu:latest "/bin/bash" 7 mins Up 7 min vigilant_borg

上述的输出显示只有一个运行中的容器,就是前面示例中创建的那个容器,证明了容器在退出后依然是运行的。

4.1.3 连接到运行中的容器

执行docker container exec命令,可以将Shell连接到一个运行中的容器终端。因为之前示例中的容器仍在运行,所以下面的示例会创建到该容器的新连接。

Linux示例如下。

1
2
$ docker container exec -it vigilant_borg bash 
root@e2b69eeb55cb:/#

示例中的容器名为“vigilant_brog”。每个人环境中的容器名称会不同(本机是thirsty_hermann),所以请记得将“vigilant_brog”替换为自己Docker主机上运行中的容器名称或者ID。

Windows示例如下。

1
2
3
4
5
> docker container exec -it pensive_hamilton pwsh.exe

Windows PowerShell
Copyright (C) 2016 Microsoft Corporation. All rights reserved.
PS C:\>

注意,Shell提示符又发生了变化。此时已登录到了容器内部。docker container exec命令的格式是docker container exec <options> <container-name or container-id> <command/app>。在示例中,将本地Shell连接到容器是通过-it参数实现的。本例中使用名称引用容器,并且告诉Docker运行Bash Shell(在Windows示例中是 PowerShell)。使用十六进制ID的方式也可以很容易地引用具体容器。

再次使用Ctrl-PQ组合键退出容器。Shell提示符应当退回到Docker主机中。

再次运行docker container ls命令来确认容器仍处于运行状态。

1
$ docker container ls CONTAINER ID   IMAGE          COMMAND      CREATED  STATUS    NAMES e2b69eeb55cb   ubuntu:latest  "/bin/bash"  9 mins   Up 9 min  vigilant_borg

通过docker container stop和docker container rm命令来停止并杀死容器。切记需要将示例中的名称/ID替换为读者自己的容器对应的名称和ID。

1
2
$ docker container stop vigilant_borg vigilant_borg
$ docker container rm vigilant_borg vigilant_borg

通过运行docker container ls命令,并指定-a参数来确认容器 已经被成功删除。添加-a的作用是让Docker列出所有容器,甚至包括那些处于停止状态的。

1
2
$ docker container ls -a 
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

4.2 开发视角

容器即应用!接下来会分析一份应用代码中的Dockerfile并将其容器化,最终以容器的方式运行。

进入到仓库文件目录之下,查看其内容。

1
2
3
4
5
6
7
8
9
10
$ cd psweb 
$ ls -l
total 28
-rw-rw-r-- 1 ubuntu ubuntu 341 Sep 29 12:15 app.js
-rw-rw-r-- 1 ubuntu ubuntu 216 Sep 29 12:15 circle.yml
-rw-rw-r-- 1 ubuntu ubuntu 338 Sep 29 12:15 Dockerfile
-rw-rw-r-- 1 ubuntu ubuntu 421 Sep 29 12:15 package.json
-rw-rw-r-- 1 ubuntu ubuntu 370 Sep 29 12:15 README.md
drwxrwxr-x 2 ubuntu ubuntu 4096 Sep 29 12:15 test
drwxrwxr-x 2 ubuntu ubuntu 4096 Sep 29 12:15 views

Linux的示例是一个简单的Node.js Web应用。Windows示例是一个简单的ASP.NET Web应用。每个仓库中都包含一个名为Dockerfile的文件。Dockerfile是一个纯文本文件,其中描述了如何将应用构建到Docker镜像当中。

查看Dockerfile的全部内容。

1
2
3
4
5
6
7
8
9
10
$ cat Dockerfile

FROM alpine
LABEL maintainer="nigelpoulton@hotmail.com"
RUN apk add --update nodejs nodejs-npm
COPY . /src
WORKDIR /src
RUN npm install
EXPOSE 8080
ENTRYPOINT ["node", "./app.js"]

现在只需要知道Dockerfile的每一行都代表一个用于构建镜像的指令即可。使用docker image build命令,根据Dockerfile中的指令来创建新的镜像。示例中新建的Docker镜像名为test:latest。一定要在包含应用代码和Dockerfile的目录下执行这些命令。

1
2
3
4
5
6
7
8
9
10
11
12
$ docker image build -t test:latest .

Sending build context to Docker daemon 74.75kB
Step 1/8 : FROM alpine
latest: Pulling from library/alpine
88286f41530e: Pull complete
Digest: sha256:f006ecbb824...0c103f4820a417d
Status: Downloaded newer image for alpine:latest
---> 76da55c8019d
<Snip>
Successfully built f154cb3ddbd4
Successfully tagged test:latest

一旦构建完成,就可以确认主机上是否存在test:latest镜像。

1
2
3
4
$ docker image ls 
REPO TAG IMAGE ID CREATED SIZE
Test latest f154cb3ddbd4 1 minute ago 55.6MB
...

现在已经拥有一个新的Docker镜像,其中包含了应用程序。从镜像启动容器,并测试应用。Linux代码如下。

1
2
3
4
$ docker container run -d \  
--name web1 \
--publish 8080:8080 \
test:latest

打开Web浏览器,在地址栏中输入容器运行所在的Docker主机的DNS名称或者IP地址,并在后面加上端口号8080。然后就能看到图4.1的Web页面。

Linux系统测试应用Web界面

Windows代码如下。

1
2
3
4
> docker container run -d \  
--name web1 \
--publish 8080:80 \
test:latest

打开Web浏览器,在地址栏中输入容器运行所在的Docker主机的DNS名称或者IP地址,并在后面加上端口号8080,然后就能看到图4.2的Web页面。

Windows系统测试应用Web界面

已经成功将应用代码构建到了Docker镜像当中,然后以容器的方式启动该镜像,这个过程叫作应用容器化


参考:

🔗 《深入浅出Docker》