Docker 基础概念

虚拟化技术

虚拟化技术是一种计算机资源管理技术,它控制计算机硬件资源并将其抽象化,以统一的形式呈现给应用程序。对于用户而言,虚拟化只是定义了隐藏真实环境的方式,而要实现虚拟化,还需要具体执行虚拟化过程的程序。

虚拟化技术可分为基于硬件的虚拟化和基于软件的虚拟化。

  • 硬件虚拟化需要使用专门的硬件平台,与普通硬件设备存在兼容性问题,但其优点是可以更高效地利用硬件资源。

  • 软件虚拟化通常在应用程序和硬件资源之间建立一个 Hypervisor 层,应用程序通过该虚拟化环境中提供的调用接口来操作资源。例如,Java 虚拟机(JVM,Java Virtual Machine)通过这种方式实现了跨平台运行。

从对象所在的层次来看,软件虚拟化又可分为应用虚拟化(如手机模拟器、Wine 等)和平台虚拟化。常见的平台虚拟化包括以下几类:

  • 完全虚拟化

    虚拟机模拟了完整的底层硬件环境和特权指令的执行过程,例如 VMware Workstation、VirtualBox、QEMU 等。

  • 硬件辅助虚拟化

    利用 CPU 的辅助支持(如 Intel-VT、AMD-V)来处理敏感指令,实现完全虚拟化的功能,例如 VMware、Xen、KVM 等。

  • 部分虚拟化

    只对部分硬件资源进行虚拟化,需要修改客户操作系统。

  • 超虚拟化(Paravirtualization)

    以软件形式提供部分硬件接口给客户机操作系统,例如早期的 Xen。

  • 操作系统级虚拟化

    通过创建多个虚拟的操作系统实例来隔离不同的进程,例如容器相关技术。

传统基于硬件层实现的虚拟化需要额外的虚拟机管理应用和虚拟机操作系统层。而 Docker 容器是在操作系统层面上实现虚拟化,直接复用本地主机的操作系统,因此被称为轻量级虚拟化。

云服务类型

提供云服务的公司总体可以归为以下几大类的一种或多种:

  • IaaS

    基础设施即服务(IaaS,Infrastructure as a Service)指在云端为用户提供基础设施,如虚拟机、服务器、存储、负载均衡和网络等,代表有 AWS 和阿里云。

  • PaaS

    平台即服务(PaaS,Platform as a Service)指在云端为用户提供可执行环境、数据库、网站服务器和开发工具等,代表有 OpenShift、Google App Engine、dotCloud 等。

  • SaaS

    软件即服务(SaaS,Software as a Service)指在云端为用户提供软件,如 CRM 系统、邮件系统、在线办公等。代表有微软 Office 365、有道、Tower 等。

  • CaaS

    容器即服务(CaaS,Container as a Service)是虚拟云主机的升级版,使用容器替代虚拟机的服务模式。

IaaS、PaaS 和 SaaS 是云计算最基本的三种服务模式,客户通过云客户端来访问上述服务。

Docker 运行基础

Docker 主要使用的技术有 Libcontainer、Namespaces、CGroups 和 AUFS。

Namespaces

内核命名空间(Namespaces)是 Linux 下的资源隔离机制。它从内核层面将进程、进程组、IPC、网络、内存等资源分属于某个特定的命名空间,不同命名空间之间的资源相互隔离且不可见。

Docker 使用以下 6 种类型的命名空间:

  • PID Namespace:用于进程隔离,使容器具有独立的一套 PID。在宿主机上可以看到容器中的进程。
  • NET Namespace:用于虚拟网络环境,使容器拥有自己独立的网络协议栈视图,包括网卡、IP、路由、防火墙规则和套接字等。
  • IPC Namespace:用于隔离进程间的通信资源,使容器拥有自己的共享内存和 semaphore。
  • MNT Namespace:用于管理挂载点,使容器看起来拥有整个文件系统,类似于 chroot。
  • UTS Namespace:用于隔离主机名和域名,使容器拥有独立的主机名。
  • USER Namespace:用于隔离用户和组,使容器拥有独立于宿主机的用户和组。

被隔离在命名空间中的程序虽然无法感知命名空间外的程序的存在,但仍可以直接访问宿主机系统内核的功能和部分内核文件,只是在程序看来自己是独占系统的。

CGroups

控制组(CGroups, Control Groups)可以限制、记录和调整进程组所使用的物理资源,例如给某个进程分配更多的 CPU 资源或限制内存使用上限。它能确保各个容器可以公平地共享主机的物理资源,并在容器内部资源使用紧张时不会影响到宿主机。

控制组提供以下功能:

  • 资源限制(Resource Limiting):可以为组设置一定的内存使用上限,超过限制时可能会出现 OOM(Out of Memory)。
  • 优先级(Prioritization):通过调整组的优先级来分配 CPU 资源。
  • 资源审计(Accounting):统计系统资源的使用情况。
  • 隔离(Isolation):为组隔离命名空间。
  • 控制(Control):执行挂起、恢复和重启等操作。

Docker 中的控制组相关配置保存在 /sys/fs/cgroup/ 目录下,该目录下有许多子目录,也称为子系统,如 cpumemoryblkio 等。这些子系统是可以通过控制组进行资源限制的种类。

[root@server6 ~]$ ls /sys/fs/cgroup/
blkio/            cpu,cpuacct/      freezer/          net_cls/          perf_event/       
cpu/              cpuset/           hugetlb/          net_cls,net_prio/ pids/             
cpuacct/          devices/          memory/           net_prio/         systemd/ 

每个子系统对应的资源种类下,可以查看这些资源的具体限制方法:

[root@server6 ~]$ ls /sys/fs/cgroup/cpu/
cgroup.clone_children  cpuacct.usage          cpu.rt_runtime_us      system.slice/
cgroup.event_control   cpuacct.usage_percpu   cpu.shares             tasks
cgroup.procs           cpu.cfs_period_us      cpu.stat               user.slice/
cgroup.sane_behavior   cpu.cfs_quota_us       notify_on_release      
cpuacct.stat           cpu.rt_period_us       release_agent  

cpu 子系统下创建一个目录进行测试:

[root@server6 ~]$ mkdir /sys/fs/cgroup/cpu/mytest
[root@server6 ~]$ ls /sys/fs/cgroup/cpu/mytest
cgroup.clone_children  cpuacct.stat          cpu.cfs_period_us  cpu.rt_runtime_us  notify_on_release
cgroup.event_control   cpuacct.usage         cpu.cfs_quota_us   cpu.shares         tasks
cgroup.procs           cpuacct.usage_percpu  cpu.rt_period_us   cpu.stat

可以看到目录中自动生成了一些资源限制文件,该目录也被称为一个控制组。

接下来,在后台执行一个会占满 CPU 的脚本:

[root@server6 ~]$ while : ; do : ; done &
[1] 4195
[root@server6 ~]$ top
   PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND             
  4195 root      20   0  116208   1260    152 R  92.3  0.0   0:31.78 bash  

此时系统已经负载满,因为默认情况下 CPU 的 quota(配额)没有任何限制,而 CPU 的 period(周期)默认为 100ms。它们组合起来用来限制进程可以分配到的 CPU 时间:

[root@server6 ~]$ cat /sys/fs/cgroup/cpu/mytest/cpu.cfs_quota_us 
-1
[root@server6 ~]$ cat /sys/fs/cgroup/cpu/mytest/cpu.cfs_period_us 
100000

手动将 cpu.cfs_quota_us 参数改为 20000(20ms)。这意味着在每个 100ms 的时间内,被该控制组限制的进程只能使用 20ms 的 CPU 时间,相当于 20% 的 CPU 使用率:

[root@server6 ~]$ echo 20000 > /sys/fs/cgroup/cpu/mytest/cpu.cfs_quota_us 

接下来,将被限制的进程的 PID 写入 tasks 文件来生效:

[root@server6 ~]$ echo 4195 > /sys/fs/cgroup/cpu/mytest/tasks
[root@server6 ~]$ top
   PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND             
  4195 root      20   0  116208   1260    152 R  20.0  0.0   9:06.83 bash  

此时再次使用 top 命令查看,可以看到进程的 CPU 使用率被限制在预期的范围内。

简而言之,控制组就是一个子系统目录加上一组资源限制文件的组合。Docker 在每个子系统下为每个容器创建一个控制组(即创建新目录),然后在启动容器进程后,将进程的 PID 写入相应控制组的 tasks 文件中,以实现资源的限制:

[root@server6 ~]$ docker run -it --cpu-period=100000 --cpu-quota=20000 ubuntu /bin/bash
[root@server6 ~]$ ps aux | grep ubuntu
root       4297  0.0  0.5 1359472 46268 pts/1   Sl+  14:41   0:00 docker run -it --cpu-period=100000 --cpu-quota=20000 ubuntu /bin/bash
[root@server6 ~]$ cat /sys/fs/cgroup/cpu/docker/9c8d7b7f5f71198e07/cpu.cfs_period_us 
100000
[root@server6 ~]$ cat /sys/fs/cgroup/cpu/docker/9c8d7b7f5f71198e07/cpu.cfs_quota_us 
20000
[root@server6 ~]$ cat /sys/fs/cgroup/cpu/docker/9c8d7b7f5f71198e07/tasks 
4379

LXC

Linux 容器 (LXC, Linux Containers) 就是基于 Linux 内核通过调用 CGroups 和 Namespaces 来实现容器轻量级虚拟化的技术。同时,LXC 也是一组面向 Linux 内核容器的用户态 API 接口。用户通过 LXC 提供的功能可以创建一套完整且互相隔离的虚拟应用运行环境。

在 LXC 的实现中,运行在容器中的程序访问的是真实物理机系统,与通过虚拟机实现的虚拟化比,消除了 Hypervisor 层,大大提高了运行效率。

Libcontainer

Docker 采用 libcontainer 作为默认容器,取代了之前的 LXC。libcontainer 的隔离性主要是内核的命名空间来实现的,具体有 pid、net、ipc、mnt 和 uts 等命名空间,它们将容器的进程、网络、消息、文件系统和主机名进行隔离。

UFS

UnionFS(Union File System)是一个堆栈式的联合文件系统,能够把多个目录挂载成同一个目录的文件系统。

Docker 应用范围

Docker 是基于 Go 语言实现的容器项目,由 dotCloud 于 2013 年正式开源,2015 年开始大规模应用。

云平台

目前,Docker 主要应用于云平台。在 Docker 出现之前,各个云平台之间存在不兼容性,而 Docker 则屏蔽了硬件层的差异,提供了统一的用户应用层。因此,企业产品可以自由迁移至各种云平台。

Devops

Docker 的另一个应用领域是 DevOps。只需使用相同的数据镜像,就不需要考虑环境问题,减少了代码运行的差异,使开发团队能够专注于产品开发,而无需关注流程。

Docker 的标准化规范弥补了开发、业务和运维之间的需求差异,可以无缝地将产品在开发、测试和生产环境之间自由移动。

Docker 优缺点

总的来看,Docker 的缺点暂不构成问题,可以放心使用。

优点

使用 Docker 的优点有:

  • 高资源利用率

    传统虚拟机的硬件资源独立不共享,而 Docker 中所有容器共享同一个系统内核和硬件资源,对资源的利用率比虚拟机高得多。在 Docker 容器中,CPU 的损耗为 0%,磁盘 IO 的损耗小于 5%,网络在使用桥接模式时损耗为 15%。

  • 支持跨节点部署

    借助 Docker 定义的标准镜像数据格式,可以在不同操作系统和物理硬件环境中使用。

  • 版本可控,组件可复用

    Docker 利用 AUFS 文件系统的特性,使得镜像与镜像之间可以互相借用组合,产生不同的镜像。并借助标签功能对镜像进行标注,实现版本控制。

  • 共享镜像

    Docker 作为开源软件,构建出的镜像可以自由分发和共享(主要通过 Docker Hub)。

  • 轻量级,易维护

    Docker 在 Linux 系统中表现为一个普通的进程,因此管理非常容易。

缺点

Docker 的缺点有:

  • 宿主资源没有完全做到隔离

    虽然容器使用的系统资源相互隔离,但仍有一些内核资源未被隔离开。例如 /proc/sys 这些目录以及 SELinux、syslog 这些内核功能。因此,容器与宿主机共用内核版本,容器内无法升级内核,对内核版本有要求的应用可能与 Docker 不兼容。

  • Go 语言尚不成熟

    由于 Docker 全部采用 Go 语言编写,而 Go 语言还比较年轻,难保不会发生大的变动。这可能对 Docker 产生连带影响。

  • 控制权问题

    虽然 Docker 现在是开源的,但实际上被一家公司所控制,并不像其他大型开源项目那样由公共基金会管理。因此,后续可能变为收费闭源项目。

Docker 构架

Docker 是一个客户端/服务器 (C/S) 架构的程序。客户端只需要通过 Socket 向服务器或守护进程(Docker 引擎)发送请求,服务器或守护进程将完成所有工作并返回结果。

Docker 提供了一个命令行工具 docker 以及一套 RESTful API 来与守护进程交互。用户可以在一台主机上运行 Docker 守护进程和客户端,也可以从本地 Docker 客户端连接到运行在另一台主机上的远程 Docker 守护进程。

服务端

Docker 服务端一般在宿主机后台运行,dockerd 作为服务端接收来自客户端的请求,并通过 containerd 具体处理与容器相关的请求。服务端主要包括四个组件:

  • dockerd

    为客户端提供 RESTful API,响应来自客户端的请求。采用模块化的架构,通过专门的 Engine 模块来分发管理各个来自客户端的任务。

  • docker-proxy

    dockerd 的子进程,配置容器端口映射功能。

  • containerd

    dockerd 的子进程,提供 gRPC 接口响应来自 dockerd 的请求,对底层管理 runC 镜像和容器环境。

  • containerd-shim

    containerd 的子进程,为 runC 容器提供支持,同时作为容器内进程的根进程。

客户端

用户使用 Docker 可执行命令即为客户端程序,docker 程序能将命令转为 API 请求再发送到服务端。客户端在命令执行结束后会立即退出。

可以通过 -H 参数显式指定服务端地址,例如:

[root@server4 ~]$ docker -H tcp:192.168.2.241:5999 info

Docker 储存

Docker 支持的存储文件系统如下:

  • AUFS:最早支持的文件系统,对 Debian/Ubuntu 支持度好。
  • Btrfs:参考 ZFS 等特性设计的文件系统,试图用来取代 Device Mapper。
  • Device Mapper:RHEL 用来支持 Docker 开发的文件系统。
  • Overlay:类似 AUFS 的分层文件系统,性能更好。
  • Overlay 2:原生支持 128 层,效率比 Overlay 高。
  • VFS:基于普通文件系统(ext、nfs 等)的中间层抽象。
  • ZFS:为 Solaris 上专用的写时文件系统,在 Linux 上可以使用 ZFS on Linux。

它们都能实现分层的架构,同时又有各自的特性。Docker 安装时会根据当前系统配置选择默认最适合的驱动。

AUFS

AUFS(Another UnionFS 的简称)是对 UnionFS 的补充。随着 AUFS 的发展,它更名为 Advanced Multi Layered Unification Filesystem(高级多层次统一文件系统)。简单来说,AUFS 可以将分布在不同位置的目录挂载到同一个虚拟文件系统中。

Docker 参考 Linux 启动过程,首先将一个只读的 bootfs(虚拟文件系统)挂载到容器文件系统中,然后将只读的 rootfs(根目录文件系统)添加到 bootfs 之上。之后每次挂载一个只读的文件层(FS 文件层),将用户所需的文件内容挂载到 rootfs 之上。

这些文件层就是堆栈式文件系统中所保存的数据,在 AUFS 中,每个文件层被称为一个分支,整个文件系统称为联合文件系统。而 AUFS 则是用来管理和使用这些文件层的文件系统。

Docker 利用 AUFS 加载完最高层后,在顶部再添加一个具有读写权限的文件系统层。容器内部的应用对当前文件系统的所有写操作都会保存在这个文件系统层中。当使用 commit 命令后,Docker 将会将该文件系统层中的数据作为单独的一个文件层保存在 AUFS 中。

镜像(Image)可以理解为特定文件系统层的集合。从镜像启动容器(Container)时,Docker 会依次加载基础镜像(Base Image)及其上层镜像。通过将镜像划分为 AUFS 的文件层,使得所有容器可以共享文件层而不会发生写冲突。

当使用 AUFS 作为存储驱动时,镜像和容器都保存在宿主机的 /var/lib/docker/aufs 目录下:

  • /var/lib/docker/aufs/diff:镜像层数据保存目录。
  • /var/lib/docker/aufs/layers:镜像层的元数据文件,其中的内容记录了该层之下所有镜像层的名称。
  • /var/lib/docker/aufs/mnt:容器运行时容器中的文件系统挂载目录。

Device Mapper

Device Mapper 是 Docker 中支持的第二种存储驱动,在 Red Hat 发行版中存在。其特点是对块设备进行操作,而不是整个文件,在宿主机上很难区分镜像层和容器层。

Device Mapper 使用的数据块大小为 64KB,在大量小数据写操作时性能不如 AUFS。

创建镜像的流程与 AUFS 有一些不同:

  1. 创建一个 thin pool,该 pool 可以创建在块设备上,也可以创建在 sparse 文件上。
  2. 创建一个按需分配的基础设备,并在该设备上创建文件系统。
  3. 在创建镜像时,会在基础设备上进行快照,每创建一个镜像层,就会进行一次快照。在快照创建时不会占用空间,只有在内容发生变化时才会在 thin pool 中分配存储空间来保存数据。

在容器中读取数据的流程如下:

  1. 容器中的程序发起读请求,要访问指定地址的数据块。容器层只是镜像层的一个快照,它保存了数据块映射表,而不保存实际的数据。通过指针,它指向镜像层中真正的数据块。
  2. 程序通过映射表读取对应镜像层中的数据块,并将其复制到容器的内存区域。

在容器中修改数据的流程与 AUFS 类似,不同之处在于 AUFS 会将整个文件复制到容器层,而 Device Mapper 通过定位需要修改的数据块并分配新的存储区域,将需要修改的数据块复制到新分配的存储区域,然后由程序进行更新。

Btrfs

Docker 使用 Btrfs 中的子卷和快照技术来管理镜像和容器。Btrfs 中的子卷类似于 UNIX 文件系统,每个子卷都有自己的目录结构。

Btrfs 中的快照会复制整个子卷,并且快照是可读写的。快照保存在子卷中,也可以对快照进行递归的快照操作。

Btrfs 使用写时复制技术为子卷和快照分配底层存储空间,块大小通常为 1 GB。

Btrfs 将镜像的基础层作为一个子卷进行保存,其他镜像层和容器层则以快照的形式保存。

Btrfs 中的读写操作与 Device Mapper 类似,但是工作在文件层而不是数据块层。在修改文件时,Btrfs 不会直接修改原文件,而是在容器层中分配新的存储空间来保存修改后的数据,然后在元数据中更新数据的指向地址。

ZFS

在 Docker 中使用了三种 ZFS DataSet,包括文件系统、快照和克隆。其中,快照是只读的,克隆是从快照上创建而来,可读写。

镜像的基础层是一个 ZFS 文件系统,其他镜像层是一个 ZFS 克隆。在启动容器后,会在顶部增加一个读写层。

Overlay/Overlay2

OverlayFS 是一种联合文件系统,类似于 AUFS,使用两个目录进行分层,分别保存镜像层(lowerdir)和容器层(upperdir)。

在运行容器时,Overlay 会将所有镜像层对应的目录组合起来(merged/diff),并在顶层添加一个容器层。而 Overlay2 原生支持多个 lowerdir,因此在保存多层镜像时更具优势。

在 Overlay 中读取文件时,优先从容器层读取,如果容器层没有,则从镜像层读取。

在 Overlay 中修改文件时,会将文件从镜像层复制到容器层,并将所有的修改保存在容器层中。

在 Overlay 中删除文件时,会在容器层中新建一个 without(白障)文件,用于隐藏镜像层中的目标文件。删除目录时,会在容器层中新建一个 opaque 目录,用于隐藏镜像层中的目标目录。镜像层中的文件和目录不会被直接删除。

Docker 网络

Docker 的本地网络实现利用了 Linux 上的网络命名空间和虚拟网络设备。

Docker 中的网络接口默认都是虚拟接口。它通过在内核中进行数据复制来实现虚拟接口之间的数据转发,发送接口的发送缓存中的数据包直接被复制到接收接口缓存中,而无需通过外部物理网络设备进行交换。

网络模型

libnetwork 中的容器网络模型(CNM,Container Networking Model)非常简洁和抽象,可以让其上层使用网络功能的容器最大程度地忽略底层具体实现。

容器网络模型包括三种元素:

  • 沙盒(Sandbox):代表容器的网络栈,包括网卡、路由和 DNS 设置。Linux 网络命名空间是沙盒的标准实现。沙盒可以包含来自不同网络的接入点。
  • 接入点(Endpoint):作用是将沙盒接入网络。代表网络上可以挂载容器的接口,例如 veth pair,会分配 IP 地址。
  • 网络(Network):可以连通多个接入点的一个子网,例如 Bridge 或 VLAN 等。

对于 CNM 的容器管理系统来说,具体底层网络如何实现,不同子网如何隔离,都不关心。只要插件能提供网络和接入点,剩下的都是插件驱动的工作,这样就解耦了容器和网络功能。

Docker 镜像

镜像类似于虚拟机的镜像,可以将其理解为一个只读模板,保存着容器需要的环境和应用的执行代码。

镜像是创建 Docker 容器的基础,不同的镜像对应着不同的应用程序或服务。

镜像名称

镜像名称的格式如下:[命名空间|仓库地址]/镜像名:标签

  • 命名空间(Namespace)

    用于区别构建镜像的组织或个人,一般为 Docker Hub 上的用户名。没有命名空间的镜像代表由官方提供,由可靠且权威的第三方组织或机构维护的官方镜像。

  • 仓库地址(URL)

    如果镜像放置在第三方或私有仓库中,命名空间部分使用仓库 URL 路径来命名。

  • 镜像名(Repository)

    通常采用镜像中所包含的应用程序或服务的名称作为镜像名。

  • 标签(Tag)

    习惯采用镜像包含的应用程序或服务的版本作为镜像标签。

镜像分层

Docker 的镜像是一个多层结构,每一层都在原有层基础上进行改动,这称为镜像分层框架(image-layering framework):

  • 镜像最底层为启动文件系统(bootfs)镜像。
  • 往上一层是根镜像(rootfs),通常包含操作系统。它不包含内核,因此可以非常小。
  • 用户镜像构建于根镜像之上,可以由多个层组成。
  • 再往上一层是初始化(init)层,存放一些定制化文件,如 /etc/hosts 和 /etc/resolv.conf。
  • 最后是用户可读写的操作层。

类似于 Git 的原理,每一层镜像都可以视为一个提交,并拥有独立的 ID,最顶层的 ID 被视为镜像 ID(Image ID)。镜像 ID 使用 64 个十六进制字符串(256 比特)来表示。当不同的镜像包含相同的层时,本地仅保留一份相同内容的副本。

当上层和下层有相同的文件和配置时,上层会覆盖下层,以上层的数据为准。用户看到的是一个叠加之后的文件系统。

写时复制

写时复制指的是在复制某个数组或对象时,并不立即进行复制,而是先进行一些标记动作,只有在需要修改复制的数组或对象时,才真正复制出该变量的副本。

在 Docker 中,当通过镜像运行容器时,并不立即将镜像内容复制到沙盒环境,而是直接在镜像的基础上建立沙盒环境。容器运行的沙盒环境是位于镜像之上的一层临时可读写的镜像层,也被称为 thin 类型容器层。镜像层以只读方式堆叠在容器层的下方。

只有在发生文件修改时,才会将原始镜像中需要修改的文件复制到容器层中,并直接保存在容器层中。在 AUFS 文件系统中,删除文件是通过在容器层中生成一个空白文件来替代镜像层对应文件的方式,实现逻辑上的删除操作。

Docker 容器

容器是从镜像创建的应用运行实例,包含独立运行的一个或一组应用,以及运行环境。可以在容器中装入任何应用,所有应用的运行方式都一样,可以启动、开始、停止、删除等。同时可以在任何环境中运行容器,容器之间互相隔离。

从操作系统角度看,容器是运行在操作系统上的一个特殊进程,只不过加入了对资源的隔离和限制。在 Windows 和 Mac 上的 Docker 实现基于虚拟化技术实现,和 Linux 容器不同。

容器设计

虽然容器中能运行多个进程或多个不同的程序,但容器只会绑定一个进程。所以容器设计上应该以一个应用程序为主体,其他程序作为主体程序的支援。

这样容器启动时,主体程序也会被启动,Docker 会监视这个程序的主进程。进程退出时,容器也会停止运行。同样通过命令停止容器运行时,Docker 会发送停止信号给主体程序,让程序结束。

启动流程

使用 docker run 命令创建启动容器的流程如下:

  1. 执行 docker run 命令后,Docker 会在本地搜索所需镜像,如果没找到则从远程仓库中搜索并下载到本地,如果找到了,直接使用本地镜像。
  2. Docker 使用指定镜像创建一个新容器并启动。启动后在只读镜像文件层上挂载一层可读写的容器层,用来记录改变的数据。
  3. Docker 为容器分配一个虚拟网络接口,并通过网桥的方式将该网络接口桥接到宿主机上去。然后从网桥地址池取出一个 IP 地址分配给容器虚拟网络接口。
  4. Docker 在新容器中运行指定的命令或程序,执行完毕后被自动终止。

Docker 仓库

Docker 仓库是集中存放镜像文件的场所。

仓库注册服务器(Registry)是存放仓库的地方,往往有多个仓库地址,用来存放不同类型的资源。

仓库分类

根据所在位置,仓库可以分为本地仓库和远程仓库。

根据储存镜像公开分享与否,仓库可以分为公开仓库(Public)和私有仓库(Private)。

仓库组成

镜像仓库主要由两部分组成:

  • 镜像管理系统为镜像仓库提供类似代码库式的镜像存取和管理方式。
  • 用户系统为镜像仓库中的镜像管理操作的授权提供支持。

仓库角色

一般网络上的镜像仓库包含三个角色:

  • Index(索引):负责维护用户账号、镜像校验及公共命名空间等信息。
  • Registry(库):镜像和图标的仓库。
  • Client(客户端):用户通过客户端与仓库进行通信、鉴权、推送和拉取。

下载流程

以 Docker Hub 为例,下载镜像的流程如下:

  1. 客户端向 Index 发送下载某个镜像的请求。
  2. Index 返回三个信息:镜像位置、镜像所有层的校验信息、授权令牌。
  3. 用户使用授权 token 向目标 Registry 发出请求。
  4. Registry 向 Index 核实 token 是否授权。
  5. Index 返回授权验证结果,如果合法,则通过下载请求。

上传流程

以 Docker Hub 为例,上传镜像的流程如下:

  1. 用户发送带有证书的请求到 Index,要求分配库名。
  2. 在成功认证并确定命名空间和库名都能分配后,Index 返回一个临时 token。
  3. 镜像带着临时 token 向 Registry 发起推送请求。
  4. Registry 向 Index 检验该 token 的有效性,认证成功后开始读取客户端的数据流。
  5. Index 更新此次镜像的校验信息。

相关链接

和 Docker 相关的资源网站: