Docker 基础知识
一、认识 Docker
Docker 什么
Docker 是 Linux 容器的封装,并提供了简单易用的容器接口,是目前最流行的容器解决方案。在开发中,docker 是一个用于开发、发布和运行应用程序的开放平台,类似一个 KVM。但跟 KVM 有很大区别,KVM 是基于硬件系统的完全虚拟化,需要硬件 CPU 虚拟化支持,相当于一个 OS 系统,需要的存储资源太过庞大,操作时冗余步骤太多,并且启动慢(分钟级)。而 docker 可共享宿主机内核,更轻量,资源占用小,操作简单,启动快(秒级)
Docker 组成
一个 docker 容器能够完整地运行起来,需要 Linux 的 Namespace、Groups 和联合文件系统三大机制来完成的
1. Namespace
linux 内核的一个功能,该功能可以对内核资源进行隔离,使得容器可以在单独的命名空间里运行,并且可以访问当前命名空间的所有资源。Namespace 可以隔离 PID、主机名、用户 ID 、文件名、网络访问和进程间通信等相关资源。
Docker 主要用到以下六种命名空间
- Process ID(pid) namespace:用于隔离进程 ID。容器启动的进程在主机上是不同的,使用 pid namesoace,可以实现每个容器内的主进程 ID 为 1,而在主机上每个容器主进程 ID 是不一样的
- Network(net) namespace:隔离网络设备、IP 和端口信息。Net Namespace 可以让每个进程拥有自己独立的 IP 地址,端口和网卡信息
- mount(mnt) namespace:文件系统挂载点隔离。在容器内只能看到自己挂载点,容器内挂载信息的修改不会影响到主机的挂载目录
- Interprocess Communication(ipc) namespace:隔离进程间通信。可以是实现同一 IPC nameplace 内的进程可以通信
- UTS Namespace(uts) namespace:主机的隔离。dcoker 容器可以设置自己单独的主机名
- User Namespace (user) namespace:隔离用户和用户组。我们在主机上的是主机用户组里的其中一个用户,但在 Docker 容器里默认是 root 用户,Docker 容器中用户和主机上的用户是完全隔离的,容器中是拿不到主机上的 root 权限的
当 Docker 新建一个容器时,就会创建上面这六种 Namespace,然后将容器的进程加入到这些容器的 namespace 中,使得容器中的进程只能看到当前 Namespace 内的资源,实现 Docker 容器隔离,可以说没有 Namespace,就没有 Docker 容器。
2. Cgroups
也是内核的一项功能,可以限制进程资源的使用情况(CPU、内存、磁盘 I/O、网络等),避免多个容器之前或者和主机抢占资源。在容器的实现中,Cgroups 用来限制 CPU 和内存资源等的使用。
在 2006 年,Google 的工程师( Rohit Seth 和 Paul Menage 为主要发起人) 发起了这个项目,起初项目名称并不是 cgroups,而被称为进程容器(process containers)。在 2007 年 cgroups 代码计划合入 Linux 内核,但是当时在 Linux 内核中,容器(container)这个词被广泛使用,并且拥有不同的含义。为了避免命名混乱和歧义,进程容器被重名为 cgroups,并在 2008 年成功合入 Linux 2.6.24 版本中。cgroups目前已经成为 systemd、Docker、Linux Containers(LXC) 等技术的基础。
Cgroups 提供了以下功能
- 资源限制:限制资源(CPU、内存、磁盘 I/O、网络等)的使用量。防止一个容器使用资源过多影响主机或者其他容器的运行
- 优先级控制:控制不同的组对资源的使用优先级
- 审计:计算控制组的资源使用情况。容器的可视化监控就是读的这个模块实现的
- 控制:控制进程的关闭和恢复
如何实现的这些功能的,那就要了解三个核心概念:
- 子系统(subsystem):是内核的一个组件,一个子系统代表一个资源调度控制器。例如:内存子系统限制内存的使用量、CPU 子系统限制 CPU 的使用量
- 控制组(cgroup):表示一组进程与带有参数的子系统的惯量关系。一个进程使用了内存子系统限制内存使用量,那么这个进程和这个内存子系统的关联关系称为控制组。
- 层级树(hierarchy):是由一系列的控制组按照树状结构排列组成的。子控制组会继承于父控制组,避免重复定义资源的限制条件
Linux Cgroups 的使用
可以先查看下 linux 默认启动的子系统(不同的内核版本的 Cgroups 和使用方式会略有不同)
$ mount -t cgroup
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpu,cpuacct)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/rdma type cgroup (rw,nosuid,nodev,noexec,relatime,rdma)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_cls,net_prio)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
Linux 提供的主要 Cgroups 子系统
- cpu 子系统,主要限制进程的 cpu 使 用率。
- cpuacct 子系统,可以统计 cgroups 中的进程的 cpu 使用报告。
- cpuset 子系统,可以为 cgroups 中的进程分配单独的 cpu 节点或者内存节点。
- memory 子系统,可以限制进程的 memory 使用量。
- blkio 子系统,可以限制进程的块设备 IO。
- devices 子系统,可以控制进程能够访问某些设备。
- net_cls 子系统,可以标记 cgroups 中进程的网络数据包,然后可以使用 tc 模块(traffic control)对数据包进行控制。
- freezer 子系统,可以挂起或者恢复 cgroups 中的进程。
- ns 子系统,可以使不同 cgroups 下面的进程使用不同的 namespace。
下面以 cpu 子系统 为例使用下子系统
1. 在 cpu 子系统下创建 cgroup
创建一个目录即可:
# mkdir /sys/fs/cgroup/cpu/mydocker
查看下创建的目录里的内容
# ls -l /sys/fs/cgroup/cpu/mydocker
-rw-r--r-- 1 root root 0 7月 26 16:41 cgroup.clone_children
-rw-r--r-- 1 root root 0 7月 26 16:41 cgroup.procs
-r--r--r-- 1 root root 0 7月 26 16:41 cpuacct.stat
-rw-r--r-- 1 root root 0 7月 26 16:41 cpuacct.usage
-r--r--r-- 1 root root 0 7月 26 16:41 cpuacct.usage_all
-r--r--r-- 1 root root 0 7月 26 16:41 cpuacct.usage_percpu
-r--r--r-- 1 root root 0 7月 26 16:41 cpuacct.usage_percpu_sys
-r--r--r-- 1 root root 0 7月 26 16:41 cpuacct.usage_percpu_user
-r--r--r-- 1 root root 0 7月 26 16:41 cpuacct.usage_sys
-r--r--r-- 1 root root 0 7月 26 16:41 cpuacct.usage_user
-rw-r--r-- 1 root root 0 7月 26 16:41 cpu.cfs_period_us
-rw-r--r-- 1 root root 0 7月 26 16:41 cpu.cfs_quota_us
-rw-r--r-- 1 root root 0 7月 26 16:41 cpu.rt_period_us
-rw-r--r-- 1 root root 0 7月 26 16:41 cpu.rt_runtime_us
-rw-r--r-- 1 root root 0 7月 26 16:41 cpu.shares
-r--r--r-- 1 root root 0 7月 26 16:41 cpu.stat
-rw-r--r-- 1 root root 0 7月 26 16:41 notify_on_release
-rw-r--r-- 1 root root 0 7月 26 16:41 tasks
cpu.cfs_quota_us文件代表某一个阶段限制的 CPU 使用量。例如:限制某个进程最多会用 1 核 CPU,就在该文件里写入 100000(代表限制 1 个核),tasks 文件中写入进程的 ID 即可(如果要限制多个进程 ID,在 tasks 文件中用换行符分隔即可
2. 创建进程,加入 cgroup
# cd /sys/fs/cgroup/cpu/mydocker
# echo $$ > tasks
查看一下 tasks 文件内容:
# cat tasks
591354
627101
其中第一个进程 ID 为当前 shell 的主进程,也就是说,当前 shell 主进程为 591354。
3. 验证
在当前 shell 进程里执行耗时任务,查看当前进程的 CPU 使用率
# while true;do echo;done;
CPU 使用率情况
# top -p 591354
top - 16:58:36 up 198 days, 41 min, 1 user, load average: 0.02, 0.02, 0.00
Tasks: 1 total, 0 running, 1 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.7 us, 0.7 sy, 0.0 ni, 98.3 id, 0.0 wa, 0.0 hi, 0.3 si, 0.0 st
MiB Mem : 1827.0 total, 179.9 free, 664.8 used, 982.3 buff/cache
MiB Swap: 0.0 total, 0.0 free, 0.0 used. 1000.3 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
591354 root 20 0 29804 5548 3664 S 99.7 0.3 0:00.07 bash
一核的 CPU使用率为 100%,验证过程看到 591354 进程 CPU 的使用率一直都小于 100%。配置成功,删除 cgroup 时,只需要删除创建的文件夹即可
Docker 中使用 cgroup 子系统
docker 提供了命令参数来使用 cgroup 子系统,例如:限 制 nginx 的内存上限为 1 G
# docker run -it -m=1g nginx
3. 联合文件系统
又名 UnionFS(Union File System),是一种分层的轻量级的文件系统。它可以把多个文件目录联合挂载到同一目录下,从而形成一个单一的目录,使用上,就像是在操作一个目录一样来操作联合文件系统。
Docker 用来为容器提供构建层,使得容器可以实现写时复制以及镜像的分层构建和存储。可以说联合文件系统是 Docker 镜像和容器的基础,它使得 Docker 镜像可以为多层组成,并且每一层都可以共享。
联合文件系统只是一个概念,怎么实现才是关键。常用的联合文件系统有 AUFS、Overlay 和 Devicemapper 等。
a. AUFS
AUFS 是 Docker 最早使用的文件系统驱动,最早相比于不成熟的 Overlay 和 Devicemapper ,AUFS 是最稳定的文件系统驱动了。
AUFS 目前并未合到 Linux 内核中,因此只有 Ubuntu 和 Debian 等少数系统支持,若在 CentOS 等 OS 使用,需要安装 AUFS,AUFS 通常不建议用在生产环境。
Docker 支持:
-
在
/etc/docker新建daemon.json,并写下配置{
"storage-driver": "aufs"
} -
重启 Docker:
systemctl restart docker -
验证:
docker infoClient:
Debug Mode: false
Server:
Containers: 0
Running: 0
Paused: 0
Stopped: 0
Images: 1
Server Version: 19.03.12
Storage Driver: aufs
Root Dir: /var/lib/docker/aufs
Backing Filesystem: extfs
Dirs: 1
Dirperm1 Supported: true
AUFS 存储文件
AUFS 联合文件系统,意味着在宿主机是使用多层目录来存储的,每一层在 AUFS 中叫节点,在 Docker 称为层,最终呈现给用户的是一个单层的文件系统,我们把多层以单一层的方式呈现出来的过程叫做联合挂载。

由图看出,每一镜像层和容器层都是 /var/lib/docker下的子目录,镜像层和容器层都在/aufs/diff目录下,每一层目录名称都是镜像或容器的 ID 值,联合挂载点在 /aufs/mnt下,mnt 目录是是容器真正工作的目录,当镜像生成容器前后 mnt 的变化是怎样的呢?
未生成容器:
- Diff 文件夹:存储镜像内容,每一层都存放在以镜像 ID 命名的文件夹中
- layers 文件夹:存放镜像层关系元数据,在 diff 文件夹下的每一个镜像层在这里都会是一个文件,文件内容为该镜像层的父级镜像 ID
- mnt 文件夹:未生成容器时,该目录为空
生成容器后:
- Diff 文件夹:会在 diff 新增容器层,文件夹名称为 容器层 ID
- layers 文件夹:增加容器层相关的元数据
- mnt 文件夹:容器的联合挂载点,这和容器中看到的文件内容一致
AUFS 读取文件
会优先从容器层读取,容器层没有是在从镜像层读取
- 文件存在容器层时:从容器层读取
- 文件不存在容器层,存在镜像层时:从镜像层读取
- 文件既存在容器层,又存在镜像层是:从容器层读取
AUFS 修改、删除文件/文件夹
修改:第一次修改文件时,会将文件复制到容器层(文件过大是会影响性能,但也只影响文件第一次修改),然后对容器层的该文件进行修改操作(镜像层只读)。
删除:AUFS 并不会真正的删除,而是创建一个特殊的文件或文件夹,这种特殊的文件活文件夹会阻止容器的访问。
b. Devicemapper
Devicemapper 是 Linux 提供的框架,从 Linux 内核 2.6.9 版本开始引入,Devicemapper 与 AUFS 不同,AUFS 是文件系统,而 Devicemapper 是一种映射块设备的技术框架。
Devicemapper 提供了一种将物理块设备映射到虚拟块设备的机制,目前 Linux 下比较流行的 LVM (Logical Volume Manager 是 Linux 下对磁盘分区进行管理的一种机制)和软件磁盘阵列(将多个较小的磁盘整合成为一个较大的磁盘设备用于扩大磁盘存储和提供数据可用性)都是基于 Devicemapper 机制实现的。
工作部分
类似 C/S 架构模式,客户端负则具体的规则定义和配置,服务端根据客户端配置的规则来处理任务
- 用户空间:配置设备的映射策略与相关内核空间的控制逻辑
- 内核空间:负责用户空间的关联关系的实现,例如:接管 IO 请求,然后处理和过滤这些 IO 请求并转发的具体的物理设备上
工作机制
三个核心概念
- 映射设备(mapped device):对外界提供的逻辑虚拟设备
- 目标设备(target device) :是映射设备对应的物理设备或者物理设备上的摸个逻辑分段,是宿主机的物理设备
- 映射表(map table):记录映射设备到目标设备的映射关系,它记录了映射设备在目标设备的起始地址、范围和目标设备的类型等变量

映射设备通过映射表记录的关联关系关联到对应的物理设备上或者另一个映射设备上。
怎样存储数据的
快照(snapshot)
全球网络存储工业协会 SNIA(StorageNetworking Industry Association)对快照(Snapshot)的定义:关于指定数据集合的一个完全可用拷贝,该拷贝包括相应数据在某个时间点(拷贝开始的时间点)的映像。快照可以是其所表示的数据的一个副本,也可以是数据的一个复制品。
简单说,快照是数据某一时间点的存储状态,快照的主要的功能就是备份,方面出现故障时,使用备份的快照将数据回复到某一时间点的数据
当 Docker 使用 Devicemapper 作为文件存储驱动时,Docker 将镜像和容器的文件存储在瘦供给池(thinpool,用多少申请多少)中,并将这些内容挂载在 /var/lib/docker/devicemapper/ 目录下
-
devicemapper 目录(/var/lib/docker/devicemapper/devicemapper/):存储镜像和容器实际内容,该目录由一个或多个块设备构成。
-
metadata 目录(/var/lib/docker/devicemapper/metadata/): 包含 Devicemapper 本身配置的元数据信息, 以 json 的形式配置,这些元数据记录了镜像层和容器层之间的关联信息。
-
mnt 目录( /var/lib/docker/devicemapper/mnt/):是容器的联合挂载点目录,未生成容器时,该目录为空,而容器存在时,该目录下的内容跟容器中一致。
实现分层与共享
Devicemapper 使用专用的块设备实现镜像的存储,使用了写时复制来保障最大程度的节省存储空间,Devicemapper 的分层是依赖快照来实现的
Devicemapper 的每一镜像层都是其下一层的快照,最底层的镜像层是我们的瘦供给池,通过这种方式实现镜像分层有以下优点。
-
相同的镜像层,仅在磁盘上存储一次。例如,我有 10 个运行中的 busybox 容器,底层都使用了 busybox 镜像,那么 busybox 镜像只需要在磁盘上存储一次即可。
-
快照是写时复制策略的实现,也就是说,当我们需要对文件进行修改时,文件才会被复制到读写层。
-
相比对文件系统加锁的机制,Devicemapper 工作在块级别,因此可以实现同时修改和读写层中的多个块设备,比文件系统效率更高。
c. OverlayFS
overlay2 目前已经是 Docker 官方推荐的文件系统了,也是目前安装 Docker 时默认的文件系统,因为 overlay2 在生产环境中不仅有着较高的性能,它的稳定性也极其突出。但是 overlay2 的使用还是有一些限制条件的,例如要求 Docker 版本必须高于 17.06.02,内核版本必须高于 4.0 等。因此,在生产环境中,如果你的环境满足使用 overlay2 的条件,请尽量使用 overlay2 作为 Docker 的联合文件系统。
OverlayFS 的存储和文件读取、修改和删除操作和 AUFS 很类似