深入解析 Docker 架构原理

深入解析 Docker 架构原理
Docker
2019 年 08 月 10 日
原文https://i4t.com/4248.html

一、Docker 简介
什么是 Docker?
Docker 的英文翻译是”搬运工“的意思,他搬运的东西就是我们常说的集装箱 Container,Container 里面装的是任意类型的 App,我们的开发人员可以通过 Docker 将 App 变成一种标准化的、可移植的、自管理的组件,我们可以在任何主流的操作系统中开发、调试和运行。

从概念上来看 Docker 和我们传统的虚拟机比较类似,只是更加轻量级,更加方便使用

Docker 和虚拟机主要的区别有一下几点:

  1. 虚拟化技术依赖的是物理 CPU 和内存,是硬件级别的;而我们的 Docker 是构建在操作系统层面的,利用操作系统的容器化技术,所以 Docker 同样的可以运行在虚拟机上面
  2. 虚拟机中的系统就是我们常说的操作系统镜像,比较复杂;而 Docker 比较轻量级,我们可以使用 Docker 部署一个独立的 Redis,就像类似于在虚拟机当中安装一个 Redis 应用,但是我们用 Docker 部署的应用是完全隔离的。
  3. 在传统的虚拟化技术是通过快照来保存的;而 Docker 引用了类似于源码的管理机制,将容器的快照历史版本一一记录下来,切换成本之低
  4. 传统的虚拟化技术在构建系统的时候非常复杂;而 Docker 可以通过一个简单的 Dockerfile 文件来构建整个容器,更重要的是 Dockerfile 可以手动编写,这样应用开发人员可以通过发布 Dockerfile 来定义应用的环境和依赖,这样对于持续交付非常有利
    image_1dhr8o7qrg2p1bhji21ber1at113.png-55.5kB

Docker Engine
Docker Engine 是一个 C/S 架构的应用程序,主要包含下面几个组件;

常驻后台进程 Dockerd
一个用来和 Dockerd 交互的 REST API Server
命令行 CLI 接口,通过和 REST API 进行交互
image_1dhq84mqt1i0g143f185s1843f7gm.png-34kB

Docker 架构

Docker 使用了 C/S 体系架构,Docker 客户端与 Docker 守护进程通信,Docker 守护进程负责构建,运行和分发 Docker 容器。Docker 客户端和守护进程可以在同一个系统上运行,也可以将 Docker 客户端连接到远程 Docker 守护进程。Docker 客户端和守护进程使用 REST API 通过 UNIX 套接字或网络接口进行通信。
image_1dhr90pg31hd71ck876gsju1bgh1g.png-66.2kB

Docker Damon DockerD 用来监听 Docker API 的请求和管理 Docker 对象,比如镜像、容器、网络和 Volume
Docker Client docker client 是我们和 Docker 进行交互的最主要的方式方法,比如可以通过 docker run 来运行一个容器,然后我们的这个 client 会把命令发送给上面的 Docker
Docker Registry 用来存储 Docker 镜像的仓库,Docker Hub 是 Docker 官方提供的一个公共仓库,而且 Docker 默认也是从 Docker Hub 上查找镜像的,当然你也可以很方便的运行一个私有仓库,当我们使用 docker pull 或者 docker run 命令时,就会从我们配置的 Docker 镜像仓库中去拉取镜像,使用 docker push 命令时,会将我们构建的镜像推送到对应的镜像仓库中
Images 镜像,镜像是一个制度模板,带有 Docker 容器的说明,一般来说的,镜像会基于另外的一些基础镜像上面安装一个 Nginx 服务器,这样就可以构建一个属于我们自己的镜像了
Containers 容器,容器是一个镜像的可运行的实例,可以使用 Docker REST API 或者 CLI 来操作容器,容器的实质是进程,但与直接在宿主执行的实例进程不同,容器进程属于自己的独立的命名空间。因此容器可以拥有自己的 root 文件系统、自己的网络配置、自己的进程空间、甚至自己的用户 ID。容器内的经常是运行在一个隔离的环境里,使用起来,就好像在一个独立于宿主的系统下操作一样。这种特性使得容器封装的应用比直接在宿主运行更加安全

二、Docker 核心概念
①Docker namespace
Linux 内核 2.4.19 中开始陆续引用了 namespace 概念。目的是将某个特定的全局系统资源 (global system resource) 通过抽象方法使得 namespace 中的进程看起来拥有它们自己的隔离的全局系统资源实例
命名空间是 Linux 内核强大的特性。每个容器都有自己的命名空间,运行在其中的应用都是在独立操作系统中运行一样。命名空间保证了容器之间彼此互不影响
image_1dhrm4rc72l23bf8kvng411pu1t.png-175.5kB

②Docker CGroups
Docker 容器使用 Linux namespace 来隔离其运行环境,使得容器中的进程看起来就像在一个独立的环境中运行。但是光有运行环境隔离还不够,因为这些进程还是可以不受限制地使用系统资源,比如网络、磁盘、CPU 以及内存等。关于其目的,是为了防止它占用了太多的资源而影响到其它进程;另一方面,在系统资源耗尽的时候,Linux 内核会触发 OOM (out of memory killer,OOM 会在系统内存耗尽的情况下跳出来,选择性的干掉一些进程以求释放一些内存) 这会让一些被杀掉的进程成了无辜的替死鬼,因此为了让容器中的进程更加可控,Docker 使用 Linux cgroups 来限制容器中的进程允许使用的系统资源
Linux Cgroup 可以让系统中所运行任务 (进程) 的用户定义组分配资源—- 比如 CPU 时间、系统内存、网络带宽

③Docker UnionFS
UnionFS 顾名思义,可以把文件系统上多个目录 (分支) 内容联合挂载到同一个目录下,而目录的物理位置是分开的。

要理解 unionFS,我们首先需要先了解 bootfs 和 rootfs
1.boot file system (bootfs) 包含操作系统 boot loader 和 kernel。用户不会修改这个文件系统,一旦启动成功后,整个 Linux 内核加载进内存,之后 bootfs 会被卸载掉,从而释放内存。同样的内核版本不同 Linux 发行版,其 bootfs 都是一直的
2.root file system (rootfs) 包含典型的目录结构 (/dev/,/proc,/bin,/etc,/lib,/usr,/tmp)
Linux 系统在启动时,rootfs 首先会被挂载为只读模式,然后在启动完成后被修改为读写模式,随后它们就可以被修改了

假设 Dockerfile 内容如下

FROM ubuntu:14.04
ADD run.sh /
VOLUME /data
CMD [“./run.sh”]
联合文件系统对应的层次结构如下图所示
58C7C152-A413-4EA5-884E-C29472B38DBB.png-125.5kB

图中的顶上两层,是 Docker 为 Docker 容器新建的内容,而这两层属于容器范畴。这两层分别为 Docker 容器的初始层 (init Layer) 与可读写层(Read-write Layer)

初始层: 大多是初始化容器环境时,与容器相关的环境信息,如容器主机名,主机 host 信息以及域名服务文件等。
读写层: Docker 容器内的进程只对可读可写层拥有写权限,其它层进程而言都是只读的 (Read-Only)。关于 VOLUME 以及容器的 host、hostname、resolv.conf 文件都会挂载到这里

FROM ubuntu:14.04 设置基础镜像,此时会使用 Ubuntu:14.04 作为基础镜像
ADD run.sh / 将 Dockerfile 所在目录下的 run.sh 加至镜像的根目录,此时新一层的镜像只有一项内容,即根目录下的 run.sh
VOLUME /data 设置镜像的存储,此 VOLUME 在容器内部的路径为 /data。此时并未在新一层的镜像中添加任何文件,但是更新了镜像的 json 文件,以便通过此镜像启动容器时获取这方面的信息 (下面会有详细的介绍)
CMD [“./run.sh”] 设置镜像的默认执行入口,此命令同样不会在新建镜像中添加任何文件,仅仅在上一层镜像 json 文件的基础上更新新的镜像的 json 文件
三、Docker 存储驱动
Docker 最开始采用 AUFS 作为文件系统,也得益于 AUFS 分层的概念,实现了多个 Container 可以共享一个 image。但是由于 AUFS 未并入 Linux 内核,且只支持 Ubuntu,考虑到兼容性问题,在 Docker 0.7 版本中引入了存储驱动,目前,Docker 支持 AUFS、Btrfs、Devicemapper、OverlayFS、ZFS 五种存储驱动。

原理说明
写时复制 (CoW)
所有驱动都用到的技术————写时复制,Cow 全称 copy-on-write,表示只是在需要写时才去复制,这个是针对已有文件的修改场景。比如基于一个 image 启动多个 Container,如果每个 Container 都去分配一个 image 一样的文件系统,那么将会占用大量的磁盘空间。而 CoW 技术可以让所有的容器共享 image 的文件系统,所有数据都从 image 中读取,只有当要对文件进行写操作时,才从 image 里把要写的文件复制到自己的文件系统进行修改。所以无论有多少个容器共享一个 image,所做的写操作都是对从 image 中复制到自己的文件系统的副本上进行,并不会修改 image 的源文件,且多个容器操作同一个文件,会在每个容器的文件系统里生成一个副本,每个容器修改的都是自己的副本,互相隔离,互不影响。使用 CoW 可以有效的提高磁盘的利用率。

用时分配 (allocate-on-demand)
写是分配是用在原本没有这个文件的场景,只有在要新写入一个文件时才分配空间,这样可以提高存储资源的利用率。比如启动一个容器,并不会因为这个容器分配一些磁盘空间,而是当有新文件写入时,才按需分配新空间。

存储驱动介绍
AUFS
AUFS (AnotherUnionFS) 是一种 UnionFS,是文件级的存储驱动。AUFS 能透明覆盖一或多个现有文件系统的层状文件系统,把多层合并成文件系统的单层表示。简单来说就是支持将不同目录挂载到同一个虚拟文件下的文件系统。这种文件系统可以一层一层地叠加修改文件。无论底下有多少层都是只读的,只有最上层的文件系统是可写的。当需要修改一个文件时,AUFS 创建该文件的一个副本,使用 CoW 将文件从只读层复制到可写层进行修改,结果也保存在科协层。在 Docker 中,只读层就是 image,可写层就是 Container。
image_1dhrpna9ppp0psca7k1cu216od2v.png-238.7kB

OverlayFS
OverlayFS 是一种和 AUFS 很类似的文件系统,与 AUFS 相比,OverlayFS 有以下特性;
1) 更简单地设计;
2) 从 Linux 3.18 开始,就加入了 Linux 内核主线;
3) 速度更快
因此,OverlayFS 在 Docker 社区关注提高很快,被很多人认为是 AUFS 的继承者。Docker 的 overlay 存储驱动利用了很多 OverlayFS 特性来构建和管理镜像与容器的磁盘结构
从 Docker1.12 起,Docker 也支持 overlay2 存储驱动,相比于 overlay 来说,overlay2 在 inode 优化上更加高效,但 overlay2 驱动只兼容 Linux kernel4.0 以上的版本

注意: 自从 OverlayFS 加入 kernel 主线后,它的 kernel 模块中的名称就从 overlayfs 改为 overlay 了

OverlayFS 使用两个目录,把一个目录置放于另一个智商,并且对外提供单个统一的视角。这两个目录通常被称作层,这个分层的技术被称作 union mount。术语上,下层的目录叫做 lowerdir,上层的叫做 upperdir。对外展示的统一视图称作 merged

image_1dhrq9chmjlug3s1uavu7srqh3c.png-306.8kB

注意镜像层和容器层是如何处理相同文件的: 容器层 (upperdir) 的文件是显性的,会隐藏镜像层 (lowerdir) 相同文件的存在。并在容器映射 (merged) 显示出统一视图

overlay 驱动只能工作在两层之上,也就是说多层镜像不能用多层 OverlayFS 实现。替代的,每个镜像层在 /var/lib/docker/overlay 中用自己的目录来实现,使用硬链接这种有效利用空间的方法,来引用底层分享的数据。

注意: Docker1.10 之后,镜像层 ID 和 /var/lib/docker 中的目录名不再一一对应

创建一个容器,overlay 驱动联合镜像层和一个新目录给容器。镜像顶层中的 overlay 是只读 lowerdir,容器的新目录是可写的 upperdir

OverlayFS (overlay2)镜像分层与共享
overlay 驱动只工作在一个 lower OverlayFS 层之上,因此需要硬链接来实现多层镜像,但 overlay2 驱动原生地支持多层 lower OverlayFS 镜像 (最多 128 层)。因此 overlay2 驱动在合层相关的命令(如 build 何 commit) 中提供了更好的性能,与 overlay 驱动对比,减少了 inode 消耗

容器 overlay 读写
有三种场景,容器会通过 overlay 只读访问文件
容器层不存在的文件 如果容器只读打开一个文件,但该容器不在容器层 (upperdir),就要从镜像层(lowerdir) 中读取。这会引起很小的性能消耗
只存在于容器层的文件 如果容器只读权限打开一个文件,并且容器只存在于容器层 (upperdir) 而不是镜像层(lowerdir),那么直接从镜像层读取文件,无额外的性能损耗
文件同时存在于容器层和镜像层 那么会读取容器层的文件,因为容器层 (upperdir) 隐层了镜像层 (lowerdir) 的同名文件,因此,也没有额外的性能损耗

有以下场景容器修改文件
第一次写一个文件,容器第一次写一个已经存在的文件,容器层不存在这个文件。overlay/overlay2 驱动执行 copy-up 操作,将文件从镜像层拷贝到容器层。然后容器修改容器层新拷贝的文件

copy-up 操作只发生在第一次写文件时,后续的对同一个文件的鞋操作都是直接针对拷贝到容器层的文件
OverlayFS 只工作在两层中。这比 AUFS 要在多层镜像中查找时性能要好
删除文件和目录 删除文件时,容器会在镜像层创建一个 whiteout 文件,而镜像层的文件并没有删除,但是 whiteout 文件会隐藏它。容器中删除一个目录,容器层会创建一个不透明目录,这和 whiteout 文件隐藏镜像层的文件类似

重命名目录 只有在源文件和目的路径都在顶层容器层时,才允许执行 rename 操作,否则返回 EXDEV。因此,应用需要能够处理 EXDEV,并且回滚操作,执行替代的”拷贝和删除”策略

在 Docker 中配置 overlay2 存储驱动
为了给 Docker 配置 overlay 存储驱动,你的 Docker host 必须在 Linux kernel3.18 版本之上,并且加载了 overlay 内核驱动。对于 overlay2 驱动,kernel 版本必须在 4.0 或以上。OverlayFS 可以运行在大多数 Linux 文件系统之上。

注意: 在开始配置之前,如果你已经在使用 Docker daemon,并且有一些想保留的镜像,请将他们 push 到镜像仓库中

我这里使用 Centos7.6 内核 4.18 演示

1. 停止 Docker
[root@i4t ~]# systemctl stop docker
2. 检查 kernel 版本,确定 overlay 的内核模块是否加载
[root@i4t ~]# uname -r
4.18.9-1.el7.elrepo.x86_64
[root@i4t ~]# lsmod |grep overlay
overlay 90112 0
#如果没有过滤出 overlay 模块,说明驱动没有加载,使用下面方法进行加载
[root@i4t ~]# modprobe overlay
3. 使用 verlay2 存储来启动 docker
#配置方法有 2 种
(1) 在 Docker 的启动配置文件中添加–storage-driver=overlay2 的标志到 DOCKER_OPTS 中,这样可以持久化配置
(2) 或者将配置持久化到配置文件 /etc/docker/daemon.json 中
“storage-driver”: “overlay2”
image_1dhrt4093hak1ilk1amn1c4j11kn3p.png-63.2kB

接下来可以检查 Docker 是否使用 overlay2 作为存储引擎
image_1dhrt6fo3aun1e7gqct10in1q3f46.png-67.5kB

Devicemapper
Device mapper 是 Linux 内核 2.6.9 后支持的,提供的一种从逻辑设备到物理设备的映射框架机制,在该机制下,用户可以很方便的根据自己需要制定实现存储资源的管理策略。AUFS 和 OverlayFS 都是文件级存储,而 Devicemapper 是块级存储,所有的操作都是直接对块进行操作,而不是文件。Devicemapper 驱动会先在块设备上创建一个资源池,然后在资源池上创建一个带有文件系统的基本设备,所有镜像都是这个基本设备的快照,而容器则是镜像的快照。所以在容器看到文件系统是资源池上基本设备的文件系统的快照,并不是为容器分配空间。当要写入一个新文件时妹子容器的镜像内为其分配新的块并写入数据,这个叫做用时分配,上面也介绍了。当要修改已有文件时,再使用 CoW 为容器快照分配块空间,将要修改的数据复制到容器快照中的新快里再进行修改。

Devicemapper 驱动默认会创建一个 100G 的文件包含镜像和容器,每个容器被限制在 10G 大小的卷内,可以自己配置调整
image_1dhrtkjoq1uh6k5vc74s1f6t44j.png-279.9kB

Btrfs
Btrfs 被称为下一代写时复制的文件系统,并入 Linux 内核,也是文件级存储,但可以像 Devicemapper 一直被操作底层设备。Btrfs 把文件系统的一部分配置为一个完整的子文件系统,称之为 subvolume。采用 subvolume,一个大的文件系统可以被划分多个子文件系统,这些子文件系统共享底层的设备空间,在需要磁盘空间时便从底层设备中分配,比如 Btrfs 支持动态添加设备。用户在系统中新增加硬盘后,可以使用 Btrfs 的命令将该设备添加到文件系统中。Btrfs 把一个大的文件系统当成一个资源池,配置成多个完整的子文件系统,还可以往资源池里加新的子文件系统,而基础镜像则是子文件系统的快照,每个子镜像和容器都有自己的快照,这些快照都是 subvolume 的快照

image_1dhru0qkf1eogfgk17jsm6a13j550.png-102.1kB

当写入一个新文件时,在容器的快照里为其分配了一个新的数据块,文件写在这个空间里,叫做用时分配。而当修改已有文件时,使用 CoW 复制分配一个新的原始数据和快照,在这个新分配的空间变更数据,等结束后再进行相关的数据结构指引到新子文件系统和快照,原来的原始数据和快照没有指针指示,被覆盖。

ZFS
ZFS 文件系统是一个革命性的全新的文件系统,它从根本上改变了文件系统的管理方式,ZFS 完全抛弃了”卷管理”,不再创建虚拟的卷,而是把所有设备集中到一个存储池中来进行管理,用”存储池”的概念来管理物理存储空间。过去,文件系统都是构建在物理设备之上的,并为数据提供冗余,”卷管理”的概念提供了一个单设备的映像。而 ZFS 创建在虚拟的,被称为”zpools”的存储池上。每个存储池由若干虚拟设备 (virtual devices,vdevs) 组成。这些虚拟设备可以是原始磁盘,也可以是一个 RAID 的镜像设备,或者是非标准 RAID 等级的多磁盘组。这样 zpool 上的文件系统可以使用这些虚拟设备的总存储容量。
image_1dhrug9rp1b8bqpe5udpcj1ndl5d.png-89kB

在 Docker 中使用 ZFS,首先从 zpool 里分配一个 ZFS 文件系统给镜像的基础层,而其它镜像层则是这个 ZFS 文件系统快照的克隆,快照只是只读的,而克隆是可写的,当容器启动时则在镜像的最顶层生成一个可写成
image_1dhruj4ld15ao14781n65a7h1eia5q.png-101kB

当要写一个新文件时,使用按需分配,一个新的数据块从 zpool 里生成,新的数据写入这个块,而这个新的空间存储于容器 (ZFS 的克隆) 里。当要修改一个已存在的文件时,使用写时复制,分配一个新空间并把原始数据复制到新空间完成修改

存储驱动的对比及适应场景
image_1dhs0bli8m8bu2qqpa1rsl4ve67.png-339kB

一般来说,overlay2 驱动更快一些,几乎肯定比 AUFS 和 devicemapper 更快,在某些情况下,可能比 Btrfs 也快。在使用 overlay2 存储驱动时,需要注意以下几点

Page Caching 页缓存
OverlayFS 支持页缓存,也就是说如果多个容器访问同一个文件,可以共享一个或多个页缓存选项。这使得 overlay2 驱动高效地利用了内存,是 Pass 平台或者高密度场景很好的选择

copy_up 和 AUFS 一样,在容器第一次修改文件时,OverlayFS 都需要执行 copy_up 操作,这会给操作带来一些延迟————尤其这个拷贝很大的文件时,不过一旦文件已经执行了这个向上拷贝的操作后,所有后续对这个文件的操作都只针对这份容器层的拷贝而已

Inode limits 使用 overlay 存储驱动可能导致过多的 inode 消耗,尤其是 Docker host 上镜像和容器的数目增长时。大量的镜像或者很多容器启停,会迅速消耗该 Docker host 的 inode。但是 overlay2 存储驱动不存在这个问题
针对 overlay2 小结
overlay2 存储驱动已经成为了 Docker 首选存储驱动,并且性能优于 AUFS 和 devicemapper。不过,也带来了一些与其他文件系统不兼容性,如对 open 和 rename 操作的支持,另外,overlay 和 overlay2 相比,overlay2 支持了多层镜像,优化了 inode 的使用。

五、Docker 安装
本文使用 Centos 安装为例,更多系统版本安装请参考官方文档

本次的环境版本如下

[root@i4t ~]# uname -r
4.18.9-1.el7.elrepo.x86_64
[root@i4t ~]# cat /etc/redhat-release
CentOS Linux release 7.6.1810 (Core)
#关于内核升级可以参考 k8s 1.13 安装 https://i4t.com/4087.html
docker 官方提供了安装脚本,我们确认好版本就可以直接安装

#我这里使用 18.06 为 Docker 版本
export VERSION=18.06
curl -fsSL “https://get.docker.com/” | bash -s – –mirror Aliyun
#这里说明一下,如果想使用 yum list –showduplicates ’docker-ce’查询可用的 docker 版本。需要先使用 docker 官方脚本安装了一个 docker,才可以 list 到其他版本
设置 overlay2 为默认存储驱动, 并配置加速器

mkdir -p /etc/docker/
cat > /etc/docker/daemon.json <<EOF
{
“exec-opts”: [“native.cgroupdriver=systemd”],
“registry-mirrors”: [“https://hjvrgh7a.mirror.aliyuncs.com”],
“log-driver”: “json-file”,
“log-opts”: {
“max-size”: “100m”
},
“storage-driver”: “overlay2”
}
EOF
#这里配置当时镜像加速器, 可以不进行配置,但是建议配置
要添加我们 harbor 仓库需要在添加下面一行
“insecure-registries”: [“harbor.i4t.com”],
默认 docker hub 需要 https 协议,使用上面配置不需要配置 https
设置 docker 开机启动,CentOS 安装完成后 docker 需要手动设置 docker 命令补全

yum install -y epel-release bash-completion && cp /usr/share/bash-completion/completions/docker /etc/bash_completion.d/
systemctl enable –now docker
六、Docker 基础操作
关于 Docker 常用命令可以参考https://i4t.com/1730.html

获取镜像
Docker 官方提供了一个公共的镜像仓库: Docker Hub, 我们可以从上面获取镜像

#获取镜像的格式
$ docker pull [选项] [Docker Registry 地址 [: 端口]/ 仓库名 [: 标签 / 版本]]
Docker 镜像仓库地址: 这里的地址格式一般为 [域名 /IP]:[端口号],默认是 Docker Hub
仓库名: 这里的仓库名称是分两个字段,即 < 用户名 >/< 软件名 >。对于 Docker Hub,比如不给出用户名,默认为 library,也就是官方镜像
这里使用 Nginx 镜像举例

[root@i4t ~]# docker pull nginx
Using default tag: latest
latest: Pulling from library/nginx
f5d23c7fed46: Pull complete
918b255d86e5: Pull complete
8c0120a6f561: Pull complete
Digest: sha256:eb3320e2f9ca409b7c0aa71aea3cf7ce7d018f03a372564dbdb023646958770b
Status: Downloaded newer image for nginx:latest
上面的命令中没有给出 Docker 镜像仓库地址,因此将会从 Docker Hub 获取镜像,而镜像名称为 nginx,因此将会获取官方镜像 library/nginx 仓库中标签为 latest 也就是最新版,当然也可以选择镜像版本,但是我们要提前去 Docker Hub 仓库查看对应的 Tag 版本。从下来过程中也可以看到我们上面说的分层存储概念,镜像由多个存储层所构建。也是一层层的去下载,并非简单的单一下载。下载过程中给出了每一层的 ID 的前 12 位,并在下载结束后,给出该镜像完整的 sha256 的摘要,以确保下载一致性

更多的基础命令可以看https://i4t.com/1730.html 我这里介绍一下前面常用命令没有将的内容

Docker 数据持共享与持久化
目前在 Docker 容器中管理数据主要有两种方式:

数据卷 (Data Volumes)
挂载主机目录 (Bind Mounts)
数据卷
数据卷是一个可供容器或多个容器使用的特殊目录,它绕过 UFS,可以提供很多有用的特性 (相当于 NFS)

  1. 数据卷可以在容器之间共享和重用
  2. 对数据卷的修改会马上生效
  3. 对数据卷的更新,不会影响镜像
  4. 数据卷默认会一直存在,即使容器被删除
    #数据卷的使用,类似于 NFS 挂载,镜像中的被指定为挂载点的目录中会隐藏掉,能显示看 的是挂载的数据卷
    首先我们先创建一个数据卷

$ docker volume create mysql-volume
查看所有的数据卷

$ docker volume ls
DRIVER VOLUME NAME
local mysql-volume
#如果使用的 Docker 有容器在运行,这里可能会不止一个。但是如果不添加 -v 参数,当容器停止或者删除时,volume 同时也会被删除
我们可以使用 inspect 命令查看指定数据卷的信息

$ docker volume inspect mysql-volume
[
{
“CreatedAt”: “2019-08-10T05:18:55+08:00”,
“Driver”: “local”,
“Labels”: {},
“Mountpoint”: “/var/lib/docker/volumes/mysql-volume/_data”,
“Name”: “mysql-volume”,
“Options”: {},
“Scope”: “local”
}
]
启动一个挂载数据卷的容器,使用 docker run 命令时,使用–mount 标记来将数据卷挂载到容器里。在一次 docker run 中可以挂载多个数据卷

#这里演示使用 mysql 镜像,创建一个名为 abcdocker 的容器,并加载 mysql-volume 数据卷到容器的 /var/lib/mysql 目录 (因为这个目录就是 mysql 默认的存储目录)
$ docker run -d –name abcdocker -v mysql-volume:/var/lib/mysql -e MYSQL_ALLOW_EMPTY_PASSWORD=true mysql:5.7
#参数说明
run 启动并创建容器
-d 后台运行
–name 设置名词
-v 挂载数据卷
-e 设置命令(因为 mysql 默认需要设置密码,使用这个变量是可以不设置密码的)
mysql:5.7 为 mysql 的镜像版本
因为我本地并没有下载 mysql 的镜像,所以在运行容器的时候会自动帮我们拉取镜像
image_1dhs46och16jbp518j214noqda71.png-249.2kB

我们可以通过 inspect 查看 web 容器的详细信息

$ docker inspect abcdocker
…省略号…
“Mounts”: [
{
“Type”: “volume”,
“Name”: “mysql-volume”,
“Source”: “/var/lib/docker/volumes/mysql-volume/_data”,
“Destination”: “/var/lib/mysql”,
“Driver”: “local”,
“Mode”: “z”,
“RW”: true,
“Propagation”: ""
}
],
…省略号…
#从配置中我们可以看到我们容器挂载了一个名词为 mysql-volume 的存储卷,并且挂载到 /var/lib/mysql 目录下
接下来我们可以进入到容器查看

$ docker exec -it abcdocker /bin/bash
登陆 mysql 创建并创建一个数据库名称为 abcdocker 数据库
image_1dhs4bs5re4sbj240t1ppo1t7b7e.png-222.4kB

接下来我们可以将容器删除,并从新创建一个,查看是否有为 abcdocker

#删除 abcdocker 的数据库容器
$ docker rm -f abcdocker
#并创建一个新的
$ docker run -d –name abcdocker -v mysql-volume:/var/lib/mysql -e MYSQL_ALLOW_EMPTY_PASSWORD=true mysql:5.7
#查看是否有 abcdocker 数据库
mysql> show databases;
+——————–+
| Database |
+——————–+
| information_schema |
| abcdocker |
| mysql |
| performance_schema |
| sys |
+——————–+
5 rows in set (0.00 sec)
image_1dhs4ioefcs14a1c2v1qm717pu7r.png-280.7kB

数据卷是被设计用来持久化数据的,它的生命周期独立于容器,Docker 不会在容器被删除后自动删除数据卷,并且也不存在垃圾回收这样的机制来处理没有任何容器引用的数据卷。如果需要删除在删除容器同时移除数据卷。可以在删除容器时使用 docker rm -v 命令。无主的数据卷可能会占用很多空间

#删除数据卷名称为 mysql-volume 命令如下
docker volume rm mysql-volume
#删除所有数据卷 (请谨慎操作)
docker volume prune
挂载主机目录
Docker 持久化存储除了有逻辑卷还有一个是挂载目录

#挂载一个主机目录作为数据卷,可以使用–mount 或者使用 -v 指定目录 (-v 也可以指定数据卷) -p 参数为端口映射后面会说
[root@i4t ~]# docker run -d –name abcdocker-nginx -p 80:80 -v /data:/data nginx
630f0d194583b5e3b547572c018bae1ac78f9341364a2c9eebf4ba898c9bf23e
[root@i4t ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
630f0d194583 nginx “nginx -g ’daemon of…” 3 seconds ago Up 2 seconds 0.0.0.0:80->80/tcp abcdocker-nginx
[root@i4t ~]# docker exec -it abcdocker-nginx /bin/bash
root@630f0d194583:/# cd /data/
root@630f0d194583:/data# ls
root@630f0d194583:/data# echo “i4t.com” >abcdocker.txt
root@630f0d194583:/data# exit
exit
[root@i4t ~]# cat /data/abcdocker.txt
i4t.com
上面的测试我们是将宿主机的 /data 目录挂载到容器的 /data 目录,本地目录的路径必须是绝对路径,如果使用 -v 参数本地目录不存在 Docker 会自动创建一个文件夹。这样宿主机和 Docker 的 /data 目录数据就同步,相当于 NFS 挂载

查看数据卷的具体信息,可以在宿主机使用 inspect 查看容器信息

$ docker inspect abcdocker-nginx
…省略号…
“Mounts”: [
{
“Type”: “bind”,
“Source”: “/data”,
“Destination”: “/data”,
“Mode”: "",
“RW”: true,
“Propagation”: “rprivate”
}
],
…省略号…
七、Docker 网络模式
首先 Docker 网络模式有 Bridge 模式、Host 模式、Container 模式、None 模式,一共四种

Bridge
当 Docker 进程启动时,会在主机上创建一个名为 Docker0 的虚拟网桥,此主机上启动的 Docker 容器默认会连接到这个虚拟网桥上。虚拟网桥的工作方式和物理交换机相似,这样主机上的所有容器就通过交换机连在了一个二层网络中。从 docker0 子网中分配一个 IP 给容器使用,并设置 docker0 的 IP 地址为容器的默认网关。在主机上创建一对虚拟网卡 veth pair 设备,Docker 将 veth pair 设备的一端放在新创建的容器中,并命名为 eth0(容器内部网卡),另一端在放在主机中,以 vethxxx 这样类似的名称命名,并将这个网络设备加入到 docker0 网桥中。可以使用 brctl show 命令查看

image_1dhs62lvdrvesevj8bf0e1dpl88.png-39.1kB

如果有多个容器之间需要互相通信,推荐使用 Docker Compose 或者使用 k8s 编排工具

Host
如果启动容器的时候使用 host 模式,那么这个容器将不会获取一个独立的 Network Namespace,而是和宿主机共用一个 Network Namespace(这里和我们平常使用的虚拟机的仅主机模式相似)。容器将不会虚拟出自己的网卡,配置自己的 IP 等,而是使用宿主机的 IP 和端口。但是,容器的其他方便,如文件系统、系统进程等还是和宿主机隔离的

image_1dhs6d500roc1tqj3p1t0tbfd8l.png-24.9kB

Container
这个模式指定新创建的容器和已经存在的容器共享一个 Network Namespace,而不是和宿主机共享。新创建的容器也不会自己创建网卡,IP 等。而是和一个指定的容器共享 IP、端口范围等。同样,两个容器除了网络方面,其他的还都是属于隔离。两个容器的进程可以通过宿主机的 lo 网卡设备进行通信

image_1dhs6tndi1aq3qtmeqh1mu1dtl92.png-27.9kB

None
使用 none 模式,Docker 容器拥有自己的 Network Namespace,但是,并不为 Docker 容器进行任何网络配置。也就是说,这个 Docker 容器没有网卡、IP、路由等信息。需要我们自己为 Docker 容器添加网卡、配置 IP 等

image_1dhs72eab17igbqq81q1gfi1ka69f.png-18kB

以上几种网络模式配置只需要在 docker run 的时候使用–net 参数就可以指定,默认情况下是使用 Bridge 模式,也是常见的模式

#例如配置一个 host 仅主机模式
$ docker run -itd –net=host –name abbcdocker_host1 nginx
八、Docker 图形化管理和监控
Portainer
Portainer (基于 Go 开发) 是一个轻量级的管理界面,可以轻松的管理 Docker 或者 Docker 相关的集群

Portainer 的使用意图是简单部署,它包含可以在任何 Docker 引擎上运行的单个容器 (Dockerfor Linux && Docker for Windows)
Portainer 允许管理 Docker 容器、image、volume、network 等。它与独立的 Docker 引擎和 Docker Swarm 兼容

安装 Docker 图形化界面

#其他平台安装 Portainer 可以参考官方文档https://www.portainer.io/installation/
$ docker volume create portainer_data
$ docker run -d -p 8000:8000 -p 9000:9000 -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer
#使用 portainer 需要映射 8000 和 9000 端口,同时需要将 docker.sock 映射到容器中
接下来使用浏览器访问 ip:9000 端口, 打开页面我们就需要创建一个密码,用户名默认是 admin (密码需要是 8 位数)
image_1dhs7pbo015ng1orc1c2rbgp1la29s.png-103.5kB

这里的连接方式有几种,常见的就是本地直接连接,就是宿主机本身连接,使用宿主机连接自己就需要 -v 参数,将 /var/run/docker.sock 映射到容器。 大家的步骤和我一样,就按照我的步骤操作即可
image_1dhs7tp9e9a016mrtipmmneava9.png-121.8kB

这里就是主页面,我们在这可以镜像、network、以及容器的相关信息

image_1dhs7ujnscve16kq163k1ame1ojlam.png-320.9kB

这里显示的也比较全,在里面也可以创建容器,设置网络相关。 我就不一一演示了,喜欢的可以自己看一下
image_1dhs80esdq4d1n601fdg9691u4db3.png-140.6kB

除了 portainer 还有一个 Rancher
Rancher 是一个开源的企业级容器管理平台,通过 Rancher,企业不必自己使用一系列的开源软件去从头搭建容器服务平台。Rancher 提供了在生产环境中使用管理 Docker 和 Kubernetes 的全栈化容器部署与管理平台 (关注 abcdocker,后面我会讲)

image_1dhs8che7b1upfbnsj19d214f7bg.png-324.8kB

cAdvisor
cAvisor 是 Google 开发的容器监控工具,在前面 Prometheus 我也介绍 cAdvisor,有兴趣的可以点开下面链接看一下

监控 Docker Host cAdvisor 会显示当前 host 的资源使用情况,包括 CPU、内存、网络、文件系统等。
监控容器 点击 Docker Containers 链接,显示容器列表。可以详细的打印出每个容器的监控页面
由于 cAvisor 提供的操作界面略显简陋,而且需要在不同页面之间跳转,并且只能监控一个主机,不支持集群。但是 cAdvisor 的一个亮点是它可以将监控到的数据导出给第三方工具,由这些工具进行进一步处理

这里可以将 cAdvisor 定位为一个监控数据收集器,收集和导出数据是它的强项,而并非展示数据,cAdvisor 支持很多第三方工具,其中就包括 prometheus

这里我们演示一下如何通过 docker 运行 cAvisor

#由于 cAvisor 是国外的镜像,这里我们使用微软的代理拉取镜像启动容器
$ docker pull gcr.azk8s.cn/google_containers/cadvisor:latest
$ docker run
–volume=/:/rootfs:ro
–volume=/var/run:/var/run:rw
–volume=/sys:/sys:ro
–volume=/var/lib/docker/:/var/lib/docker:ro
–volume=/dev/disk/:/dev/disk:ro
–publish=8080:8080
–detach=true
–name=cadvisor
gcr.azk8s.cn/google_containers/cadvisor:latest
我这里已经将端口映射到宿主机的 8080 端口,我们检查 docker 容器启动成功后,就可以直接访问 ip:8080 端口
image_1dhs94pbhen1af1157012hjv6vbt.png-236.2kB

image_1dhs9bl318bt14h11mb31q9abi7cq.png-147.9kB

这里我们可以看到宿主机的一些信息
image_1dhs9cga81p4p14495hg1k331q8hd7.png-171.7kB

同时也可以访问http://ip:8080/docker/ 查看容器的运行状态
image_1dhs9hqpa1atl1gqj5al17i2m58dk.png-236.5kB

同时 aAdvisor 还提供了一个 Rest API
https://github.com/google/cadvisor/blob/master/docs/api.md

cAdvisor 通过该 REST API 暴露监控数据 (metrics),格式如下

http://:/api//
image_1dhs9nhc3s881hi31q2f1q7a15n2e1.png-1520.6kB