复习一下 Docker 的一些基础知识,还有对一些概念的理解 (
关于 Docker#
Docker
是一款开源的应用容器引擎,它利用容器化技术,极大的加速了应用的构建、测试和部署。其核心思想是将应用程序及其所有依赖都打包进一个称为 "容器" 的标准化单位中,这个容器可以在任何支持 Docker 的系统上运行,实现了前所未有的可移植性和环境一致性。
和传统的虚拟化技术 (例如:KVM) 相比,Docker 容器更加高效和轻量化,虚拟机需要在每个应用程序中运行一个完整的操作系统,而 Docker 则和宿主机共享系统内核,这显著的减少了资源开销和启动速度。这种架构不仅是提高了资源利用率,同时也为应用提供了更强的隔离性,使得每个容器都独立于宿主机和其他容器运行。
Docker 不仅仅是一个工具,它引领了一场 “云原生” 时代的变革,深刻的改变了企业管理应用交付和部署周期的方式。
Docker Engine 核心架构#
Docker Engine 是 Docker 平台的核心,它是一个 客户端 - 服务端
架构的应用程序,负责容器创建、运行和管理,这种架构使得 Docker 命令的执行和其生产的实际效果通过 API 解耦。
客户端 - 服务端#
Docker Engine 本质上就是一个开源的容器化技术,用于构建和容器化应用程序。主要包含以下几部分:
- 服务器 (Server):一个长期运行的守护进程 (daemon process),名为
dockerd
,这个守护进程负责实际执行构建镜像、运行容器、管理网络和存储等核心任务 - REST API:
dockerd
通过一个REST API
功能,暴露其功能接口,这个 API 接口可以用来与dockerd
交互,API 可以通过UNIX 套接字
或网络接口
访问 - 客户端 (Client):命令行工具,名为
docker
。用户通过docker
发出命令,这些命令转换为对dockerd
的REST API
请求,并发送给dockerd
进行执行。
这种架构使得 Docker 非常灵活,例如:docker
可以在本地运行,但是 dockerd
可以在远程服务器上运行,使用网络进行通讯。此外,由于所有操作都使用 API 进行,这也为自动化工具和脚本提供了便利,无需笨拙的包装本地 CLI
命令。
主要组件#
Docker 守护进程 (dockerd)#
dockerd
是一个在宿主机上运行的后台服务。它监听来自 Docker 客户端 (通过 Docker API
) 的请求,并管理 Docker 对象,如镜像 (images)、容器 (containers)、网络 (networks) 和数据卷 (volumes)。它是 Docker 架构的核心元素,负责构建、运行和分发 Docker 容器。dockerd
可以使用符合 OCI
(Open Container Initiative) 标准的运行时,如 ContainerD
和 CRI-O
来运行容器。
OCI(Open Container Initiative)是一个在 Linux 基金会支持下的开放治理结构(项目),旨在围绕容器格式和运行时创建开放的行业标准 。它于 2015 年 6 月由 Docker、CoreOS 及其他容器行业领导者共同发起成立 。
ContainerD 是一个业界标准的核心容器运行时,它管理其主机系统上完整的容器生命周期。这包括了镜像的传输和存储、容器的执行和监管、存储以及网络连接等任务。
CRI-O 是一个实现了 Kubernetes 容器运行时接口 (CRI) 的工具,它允许 Kubernetes 使用符合 OCI (Open Container Initiative) 标准的运行时来管理 pod 和容器。
守护进程的配置可以通过 daemon.json
文件进行。它可以通过 Unix 套接字(默认为 /var/run/docker.sock
)、TCP 套接字或文件描述符(fd)来监听 API 请求。
Docker Engine API(REST API)#
- Docker Endgine API 是一个由
dockerd
提供的 Restful API。Docker 客户端通过这个 API 与dockerd
通信,该 API 规范了应用程序可以向守护进程发送指令的接口,从而管理容器的构建、运行与分发等任务。 - 这个 API 基于 HTTP,支持通过
Unix 套接字
或TLS 加密的 TCP 连接
进行访问。对于 HTTPS 加密的套接字,仅支持 TLS 1.0 及更高版本,出于安全原因不支持 SSLv3 及更低版本。
Docker 客户端 (Docker Client - Docker Cli)#
Docker CLI 是与 Docker 交互的主要方式,通常是一个 CLI 工具,命令以 docker 开头。用户通过该 CLI 工具发送命令给 dockerd
。一个 Docker CLI 可以和多个 dockerd
进行交互。
核心概念#
Container#
容器是一种标准化的软件单元,它将应用程序代码及其所有依赖项打包在一起,确保应用程序在从一个计算环境迁移到另一个计算环境时能够快速、可靠地运行 。这种自包含的特性确保了在开发者笔记本电脑上正常工作的应用程序,在私有数据中心或公有云中也能同样正常工作 。
从本质上讲,容器虚拟化了操作系统层面,允许应用程序在隔离的用户空间中运行,同时共享主机操作系统的内核 。容器不仅仅是简单的软件包,它们是按照一种通用方式进行打包的,这种标准化是其广泛采用的关键推动因素 。
容器技术的核心在于其 “操作系统虚拟化” 的理念 ,这与虚拟机 (VM) 所采用的硬件虚拟化形成了鲜明对比 。虚拟机通过 Hypervisor(虚拟机监控器)在物理硬件之上创建完整的虚拟硬件环境,每个虚拟机都拥有自己独立的操作系统。而容器则直接在宿主机的操作系统内核之上创建隔离的运行环境。
容器化#
容器化是一种软件部署过程,它将应用程序的代码及其运行所需的所有文件、库、配置和二进制文件打包成一个单一的可执行镜像 。这个镜像随后作为容器运行。该过程隔离了应用程序,使其仅与主机共享操作系统内核,从而允许单个软件包在多种设备或操作系统上运行 。
容器化的典型生命周期通常包括三个阶段 :
-
开发 (Develop): 开发者在提交源代码时,将应用程序的依赖项定义在一个容器镜像文件中。该文件通常与源代码一同存储。
-
构建 (Build): 镜像被发布到一个容器仓库 (Container Repository),在那里进行版本控制并标记为不可变。这个阶段实质上创建了容器。
-
部署 (Deploy): 将容器化的应用程序在本地、持续集成 / 持续交付 (CI/CD) 管道中,或在生产环境中运行。
容器化技术有效解决了传统软件开发中因操作系统、库版本或配置差异导致应用程序在不同环境中行为不一致的问题,即所谓的 “在我机器上能跑” 的难题 。
容器镜像的 “不可变性” 是实现部署一致性和可靠性的基石。这意味着一旦镜像构建完成,它就不会再被修改。所有从该镜像衍生的容器实例都将是完全相同的 。这种特性促使运维实践从修补实时系统转向用新的、更新的镜像替换旧系统 。这种不可变性确保了应用程序的每个版本实例都完全相同,消除了配置漂移,使得部署高度可预测和可重复。这反过来又简化了回滚和调试过程。
容器技术的核心工作原理#
操作系统级虚拟化原理#
容器技术依赖于操作系统级虚拟化,这是一种轻量级的虚拟化形式,其核心思想是操作系统内核允许多个隔离的用户空间实例存在 。这些隔离的实例即为容器。与虚拟机所采用的硬件虚拟化不同 —— 硬件虚拟化需要 Hypervisor 为每个客户操作系统模拟硬件 —— 操作系统级虚拟化直接在宿主操作系统的内核中构建抽象层 。
所有容器共享同一个宿主操作系统的内核。这意味着在容器内部运行的操作系统用户空间(例如,特定的 Linux 发行版)必须与宿主机的内核兼容(例如,Linux 容器运行在 Linux 宿主机上)。尽管如此,每个容器都拥有自己独立的根文件系统,并且可以在用户空间运行不同的发行版(例如,在同一个 Linux 内核上运行不同的 Linux 发行版)。由于容器内的程序通过正常的系统调用接口与内核通信,因此操作系统级虚拟化几乎没有性能开销,这是其相较于虚拟机的一个显著优势 。
共享内核架构是一把双刃剑:它既是容器实现高效率(低开销、高密度)的主要原因 ,也是其安全方面的一个核心关切点(内核漏洞可能会影响所有容器)。
此外,对宿主机操作系统内核的兼容性要求(例如,Linux 容器在 Linux 上运行,Windows 容器在 Windows 上运行)意味着容器并非像虚拟机那样,可以作为在任何宿主机上运行任何操作系统的通用解决方案 。
核心内核机制:命名空间 (Namespaces) 与控制组 (cgroups)#
- 命名空间 (Namespaces):提供进程隔离。它们将内核资源进行分区,使得一组进程看到的是一套资源,而另一组进程看到的是另一套不同的资源 。这给予了进程仿佛运行在各自独立系统中的错觉。
- Linux 内核实现了多种类型的命名空间:
- PID (Process ID) 命名空间:隔离进程 ID。每个 PID 命名空间可以拥有自己的 PID 为 1 的进程。
- **NET (Network) 命名空间:** 隔离网络接口、IP 地址、路由表、端口号等网络资源。
- **MNT (Mount) 命名空间:** 隔离文件系统挂载点,允许不同命名空间中的进程拥有不同的文件系统层级视图。
- **UTS (UNIX Timesharing System) 命名空间:** 隔离主机名和域名
- **IPC (Inter-Process Communication) 命名空间:** 隔离 System V IPC 对象(如消息队列、信号量、共享内存)
- **User 命名空间:** 隔离用户和组 ID,允许一个进程在命名空间内有 root 权限,而在宿主机则没有这些权限。
- Linux 内核实现了多种类型的命名空间:
- 控制组 (cgroups): 用于管理和限制一组进程的资源使用(如 CPU、内存、I/O、网络带宽)。进程可以被组织成层级树状结构。关键的 cgroup 控制器包括:CPU(调节 CPU 周期分配)、内存(限制内存使用量)、设备(控制设备访问)、I/O(调节 I/O 速率)以及 Freezer(暂停和恢复进程组)。
容器平台利用这些特性来创建和管理容器。
命名空间的细粒度特性允许进行精确调整的隔离。这意味着并非所有容器都需要相同级别的隔离;某些容器可以为特定目的共享某些命名空间(例如网络命名空间),这提供了灵活性,但如果管理不当,也会增加复杂性和潜在的安全风险 。
容器引擎与运行时#
容器引擎(或称容器运行时)是负责从容器镜像创建并运行容器的软件 。它充当容器与操作系统之间的中介,提供并管理容器所需的资源 。常见的容器引擎包括 Docker Engine 、containerd 和 CRI-O 。
现代容器引擎通常将高级管理功能(如镜像拉取、API 处理)与低级容器执行分离开来:
- containerd: 这是一个行业标准的的核心容器运行时,负责管理其宿主系统的完整容器生命周期,从镜像传输和存储到容器执行和监管,再到低级存储和网络附件的管理 。Docker 已将 containerd 捐赠给云原生计算基金会 (CNCF) 。
- runc: 这是一个符合 OCI 规范的低级命令行工具,用于创建和运行容器。它被 containerd 等高级运行时所使用 。
容器运行时负责为容器设置命名空间和 cgroups,然后在其中运行应用程序进程 。
Image#
理解容器镜像#
容器镜像是一个轻量级、独立、可执行的软件包,包含了运行应用程序所需的一切:代码、运行时、系统工具、系统库和设置 。它代表了封装应用程序及其所有软件依赖项的二进制数据 。镜像是只读的模板 ;当容器运行时,它便是该镜像的一个实例 。
基于不可变性原则,所有源自同一镜像的容器都是相同的 。
镜像通常由一个名为Dockerfile
的文本文件创建,该文件包含组装镜像的指令 。镜像名称可以包含仓库主机名、路径、名称以及标签 (tag) 或摘要 (digest)(例如,fictional.registry.example/imagename:tag
)。标签用于表示不同版本(例如,v1.42.0
),而摘要是镜像内容的唯一哈希值,确保了内容的不可篡改性 。
使用基于内容的摘要来标识镜像 为镜像的完整性和精确版本控制提供了强有力的保证。这对于安全性(确保拉取的是未被篡改的镜像)和可复现性(确保构建和部署的一致性)至关重要,尤其是在复杂的软件供应链中。在 Kubernetes 的 CI/CD 实践中,推荐使用不可变的镜像标签(通常解析为摘要)。
容器镜像的分层架构#
容器镜像由一系列层 (layers) 组成 。Dockerfile 中的每条指令(如RUN
、COPY
、ADD
)通常会创建一个新的层 。这些层堆叠在一起,每一层都代表了相对于前一层的文件系统变更(添加、修改或删除文件)。
层一旦创建就是不可变的 。当容器运行时,一个可写层(“容器层”)会添加到不可变的镜像层之上 。这种分层架构带来了诸多好处,例如:
- 效率: 层可以在镜像之间共享。如果多个镜像共享相同的基础层(例如,操作系统层、运行时层),这些层在主机上只需存储一次,下载一次即可 。这节省了磁盘空间和网络带宽。
- 更快的构建: Docker 可以缓存层。如果 Dockerfile 中的某条指令及其上下文没有改变,就可以重用缓存中已有的层,从而加快镜像构建速度 。
分层文件系统是使容器变得实用的关键优化。如果没有它,每个镜像都将是整体式的,快速下载和高效存储的优势将不复存在,这将严重阻碍容器的采用。分层文件系统直接解决了这个问题,使得拥有许多共享共同基础的不同镜像成为可能。
结构不良的 Dockerfile 可能导致镜像臃肿,中间层包含不必要的数据,或者缓存未命中从而减慢构建速度 。
Registry#
容器仓库 (Container Repository) 用于存储容器镜像 。而 容器注册中心 (Container Registry) 则是一个或多个仓库的集合,通常还包括身份验证、授权和存储管理等功能 。仓库可以是公开的(例如 Docker Hub),也可以是私有的(供内部团队或受控共享使用)。
Volume#
Docker Volume 提供了一种独立于容器生命周期的数据存储和管理方案,解决了容器临时性存储带来的数据丢失问题。
理解 Volume#
Docker Volume 是由 Docker 管理的、用于持久化容器数据的存储机制 。它本质上是宿主机文件系统中的一个特定目录,但其创建、生命周期和管理都由 Docker 引擎负责 。在 Linux 系统中,这些 Volume 通常存储在 /var/lib/docker/volumes/
目录下 。一个关键的原则是,非 Docker 进程不应直接修改这些由 Docker 管理的文件和目录,以避免潜在的数据损坏或管理冲突 。
Volume 可以被一个或多个容器挂载和使用 。当容器被删除时,其关联的 Volume 默认情况下会继续存在,确保了数据的持久性 。Volume 可以是命名的(Named Volume),也可以是匿名的(Anonymous Volume)。命名卷具有用户指定的明确名称,便于管理和引用;而匿名卷在创建时会由 Docker 分配一个随机的、唯一的名称 。
Volume 的重要性和优势#
Docker Volume 在容器化应用中扮演着至关重要的角色,其重要性和优势体现在多个方面:
- 数据持久化 (Data Persistence): 这是 Volume 最核心的功能。Docker 容器的存储层默认是临时的,当容器停止或被删除时,容器内部所做的任何文件系统更改都会丢失 。Volume 的生命周期独立于容器,即使容器被删除,存储在 Volume 中的数据默认也会保留下来,从而实现了关键数据的持久化存储 。
- 数据共享与重用 (Data Sharing and Reuse): Volume 可以在多个容器之间共享和重用 。这意味着不同的容器可以访问和操作同一份数据,非常适用于微服务架构中需要协作处理数据的场景。
- 性能提升 (Performance Improvement): 与直接在容器的可写层中进行读写操作相比,使用 Volume 通常能提供更好的 I/O 性能 。这是因为 Volume 的读写操作通常直接作用于宿主机的文件系统,绕过了容器存储驱动(如 OverlayFS、AUFS)所引入的联合文件系统(Union File System)的额外抽象层和写时复制(Copy-on-Write)机制的开销 。
- 易于备份与迁移 (Easier Backup and Migration): 由于 Volume 由 Docker 统一管理,并且其数据存储在宿主机的特定位置,这使得对 Volume 中的数据进行备份、恢复和迁移变得相对简单和直接 。
- 独立于容器镜像 (Independent of Container Image): 对 Volume 中数据的修改不会影响到容器所使用的原始镜像 。这符合 Docker 镜像应保持无状态和不可变性的最佳实践,使得镜像可以被更广泛地复用。
- 支持 Volume 驱动 (Support for Volume Drivers): Docker Volume 支持通过 Volume 驱动与各种外部存储系统集成,如云存储服务(AWS S3, Azure Files)、网络文件系统(NFS)等 。
- 跨平台兼容性 (Cross-Platform Compatibility): Volume 可以在 Linux 和 Windows 容器上工作,提供了跨操作系统的持久化数据解决方案 。
这些优势使得 Volume 成为 Docker 官方推荐的数据持久化方式 。它不仅解决了数据持久化的问题,还通过提供标准化的管理接口和对外部存储的良好支持,增强了 Docker 应用在生产环境中的可靠性和可管理性。
工作机制#
Volume 如何实现数据持久化#
Docker Volume 实现数据持久化的核心机制在于将容器内指定的路径(挂载点)与宿主机文件系统上由 Docker 管理的一个特定目录进行映射 。当容器向其内部的挂载点写入数据时,这些数据实际上被写入了宿主机上的对应目录,从而脱离了容器自身临时的可写层 。
具体来说,其工作机制可以概括为:
- 创建与映射: 当创建一个 Volume(无论是显式使用
docker volume create
命令,还是在运行容器时隐式创建)时,Docker 会在宿主机的特定存储位置(如 Linux 上的/var/lib/docker/volumes/
)为该 Volume 分配一个目录 。 - 绕过联合文件系统 (UFS): Volume 的读写操作绕过了容器存储驱动所管理的联合文件系统 (Union File System) 。容器的可写层通常基于 UFS 实现写时复制 (Copy-on-Write),这会带来一定的性能开销。而 Volume 直接将 I/O 操作导向宿主机的文件系统,减少了抽象层,从而提高了性能 。
- 独立生命周期: Volume 的数据存储在宿主机上,其生命周期独立于任何使用它的容器 。这意味着即使所有使用该 Volume 的容器都被删除,Volume 及其中的数据默认情况下仍然存在于宿主机上,直到被显式删除 。
这种机制类似于操作系统中的 “挂载” 概念 。
数据填充与覆盖机制#
当将 Volume 挂载到容器内的一个目录时,Docker 处理该目录下已存在数据的方式取决于 Volume 的状态(是否为空)以及挂载选项。
- 挂载空 Volume 到已有数据的容器目录:
- 如果将一个空的 Volume(无论是新创建的还是已存在但为空的 Volume)挂载到容器内一个已存在文件或目录的路径上,Docker 默认会将容器内该路径下的现有文件和目录复制到这个空的 Volume 中 。
- 如果不想发生这种复制行为,可以使用
volume-nocopy
选项(在使用--mount
语法时)或-v
语法的相应变体来阻止 。
- 挂载非空 Volume 到容器目录 (无论容器目录是否为空):
- 如果将一个非空的 Volume(即 Volume 中已经包含数据)挂载到容器内的一个路径上,那么容器内该路径原有的任何内容都将被这个 Volume 的内容所遮蔽 (obscured) 或覆盖 。
- 对于 Docker 容器,一旦 Volume 被挂载,没有直接的 “卸载” 操作来恢复被遮蔽的文件;通常需要重新创建容器并且不挂载该 Volume 才能访问原始文件 。
- 启动容器时指定不存在的 Volume:
- 如果在启动容器时指定了一个尚不存在的 Volume 名称,Docker 会自动为你创建一个空的 Volume,然后应用上述 “挂载空 Volume 到已有数据的容器目录” 的逻辑,即容器内挂载点的数据会被复制到新创建的 Volume 中 。
这些数据填充和覆盖机制为初始化 Volume 数据提供了便利,同时也需要用户注意其行为,以避免意外覆盖或丢失数据。
Volume 的类型#
Docker Volume 主要分为命名卷和匿名卷,它们在创建和引用方式上有所不同。
- 命名卷 (Named Volumes)
命名卷是指在创建时被赋予一个明确、易于识别的名称的 Volume 。这是 Docker 官方推荐使用的 Volume 类型,尤其是在开发和生产环境中 。
命名卷的引入,使得 Volume 不再仅仅是容器的附属品,而是可以独立管理和维护的数据存储单元。
- 匿名卷 (Anonymous Volumes)
匿名卷是在创建时没有被赋予明确名称的 Volume 。当它们被初始化时,Docker 会为其分配一个随机生成的、全局唯一的 ID(通常是一个长哈希字符串)作为其 “名称” 。
匿名卷提供了数据持久化的能力,但由于其管理上的不便,Docker 官方和社区通常推荐在需要持久化数据的场景中使用命名卷 。
高级应用与特性#
数据共享机制#
- 容器间共享 (Sharing Between Containers):
这是 Volume 的一个核心功能。多个容器可以同时挂载并访问同一个命名卷 。当一个容器向该共享卷中写入数据时,其他挂载了相同卷的容器可以立即看到这些更改。这种机制对于构建需要协作的微服务应用至关重要。
- 容器与宿主机共享 (Sharing Between Container and Host):
- 通过 Volume:Docker 管理的 Volume 存储在宿主机文件系统的特定位置(通常是
/var/lib/docker/volumes/
)。尽管 Docker 官方不推荐非 Docker 进程直接修改这些由 Docker 管理的文件 ,但宿主机上的进程(例如备份工具、监控代理)在技术上是可以访问这些路径的。然而,需要注意的是,直接从宿主机操作 Volume 内容可能会绕过 Docker 的管理机制,带来潜在风险。Docker 文档明确指出,如果需要从宿主机和容器双向访问文件,绑定挂载是更合适的选择 。 - 通过绑定挂载 (Bind Mounts): 这是实现容器与宿主机之间直接、双向文件共享的更常用且推荐的方式 。用户可以明确指定宿主机上的一个目录或文件,将其映射到容器内部。这种方式透明度高,宿主机和容器对共享数据的修改会立即相互可见。
- 通过 Volume:Docker 管理的 Volume 存储在宿主机文件系统的特定位置(通常是
在微服务和分布式应用架构中,Volume 的共享能力是关键的赋能因素。它允许状态、配置或中间处理数据在不同的服务组件(容器)之间解耦地传递和共享。
Volume 驱动 (Volume Drivers)#
Volume 驱动是 Docker 存储架构的核心组成部分,它赋予了 Docker Volume 极大的灵活性和可扩展性,使其能够与各种不同的存储后端集成 。
- 工作原理: Volume 驱动充当 Docker 引擎与具体存储系统之间的桥梁。当 Docker 需要对 Volume 执行操作(如创建、删除、挂载、卸载、获取路径等)时,它会调用相应的 Volume 驱动插件来实现这些功能。
- 默认驱动 (
local
): Docker 自带一个名为local
的默认 Volume 驱动 。这个驱动将 Volume 数据存储在 Docker 宿主机的本地文件系统上。对于单机部署和本地开发环境,local
驱动通常已足够使用。 - 内置与第三方驱动: 除了
local
驱动,Docker 生态系统还支持许多其他的内置或第三方 Volume 驱动。这些驱动可以将 Volume 数据存储在:- 网络文件系统 (NFS): 允许多个 Docker 主机共享存储在 NFS 服务器上的 Volume。
- 云存储服务: 例如 AWS Elastic Block Store (EBS), AWS S3 (通过特定驱动如 Cloudstor ), Azure Disk Storage, Azure Files, Google Persistent Disk 等。这使得容器化应用可以利用云平台提供的持久化、高可用和可扩展的存储服务。
- 分布式文件系统: 如 GlusterFS, Ceph 等。
- 块存储设备: 通过 iSCSI, Fibre Channel 等协议连接的存储区域网络 (SAN) 设备。
Volume 驱动机制体现了 Docker 平台在存储方面的开放性和可插拔架构。它使得 Docker 不仅仅局限于本地存储,而是能够融入企业复杂的数据中心环境或无缝对接云原生存储服务。这种能力对于在生产环境中大规模部署有状态的 Docker 应用至关重要,因为它允许企业根据自身需求(性能、成本、可用性、合规性等)选择最合适的存储解决方案,并将其与 Docker 工作负载集成。
Networks#
理解 Docker Networks#
Docker 网络是 Docker 平台的一个核心组成部分,专门设计用于满足容器化应用的通信需求 。根本目标是提供一种机制,使得容器能够相互通信,容器能够与它们的宿主机通信,以及容器能够与外部网络(如互联网或其他局域网内的服务)通信 。
简而言之,Docker 网络负责管理和编排容器的所有网络交互。
Docker 网络可以被视为一个由两个或多个能够通过物理或虚拟方式进行通信的设备(在这里主要是容器和主机)组成的集群。Docker 包含一个专门的网络单元,用于处理容器、Docker 主机以及外部用户之间执行的所有通信任务。
网络模型#
Docker 的网络功能是基于一个名为容器网络模型 (Container Network Model, CNM) 的规范来实现的。CNM 是一个开放的设计规范,它定义了 Docker 网络的基础构建块和交互方式,为 Docker 提供了灵活且可插拔的网络架构 。
CNM 的核心组件包括沙箱 (Sandbox)、端点 (Endpoint) 和网络 (Network)。
- 沙箱 (Sandbox): 沙箱代表了一个容器的网络栈配置。它包含了容器的网络接口(如
eth0
)、路由表、DNS 设置等网络相关的配置信息。每个容器都拥有一个独立的网络沙箱,这确保了容器之间在网络层面上的隔离。沙箱的实现通常依赖于操作系统的网络命名空间技术 。
网络命名空间 (Network Namespace) 是一种 Linux 内核提供的强大功能,它允许创建多个隔离的网络栈 (network stack) 。可以将其理解为在同一个操作系统内核下,创建出多个独立的、虚拟的网络环境。
Docker 利用了 Linux 网络命名空间技术来实现容器之间以及容器与宿主机之间的网络隔离 。
- 端点 (Endpoint): 端点是一个虚拟的网络接口,其主要作用是将沙箱(即容器的网络栈)连接到一个或多个网络上。一个端点只能属于一个网络,并且也只能属于一个沙箱。
- 网络 (Network): 网络是一组可以直接相互通信的端点的集合。从概念上讲,网络是 Docker 中实现容器间连接和隔离的基本单位。Docker 允许创建多种类型的网络(例如桥接网络、覆盖网络等),每种类型的网络都由特定的网络驱动程序实现,并为连接到该网络的容器提供特定的通信能力 。
这三个组件协同工作,构成了 Docker 网络的基础。当一个容器需要连接到一个网络时,Docker 引擎会为该容器创建一个沙箱(如果尚不存在),然后创建一个端点,并将这个端点的一端连接到容器的沙箱,另一端连接到指定的网络。通过这种方式,容器便获得了在该网络中进行通信的能力。
CNM 的设计不仅仅是 Docker 内部实现的一个细节,它更提供了一个标准化的模型。这种模型化的方法使得 Docker 网络本身具有高度的可插拔性和可扩展性。
网络驱动#
Docker 网络驱动是实现 CNM 规范的具体模块,它们负责创建和管理网络,并定义容器与主机系统之间、容器与容器之间的通信规则和行为 。
概述#
网络驱动在 Docker 架构中扮演着连接层和实现层。它们是 Docker 引擎与底层网络基础设施(无论是物理网络还是虚拟网络)之间的桥梁。当用户发出创建网络或连接容器到网络的指令时,Docker 引擎会调用相应的网络驱动来执行这些操作。
每个网络驱动都封装了一套特定的网络技术和逻辑。例如,bridge
驱动利用 Linux 内核的桥接功能和 iptables
规则,overlay
驱动则依赖于 VXLAN 隧道技术和键值存储来实现跨主机通信。
Bridge 网络驱动#
Bridge 网络驱动是 Docker 中最常用也是默认的网络模式 。当安装 Docker 时,系统会自动创建一个名为 bridge
(有时也被称为 docker0
)的默认虚拟网桥 。所有新启动的容器,如果在启动时没有明确指定连接到其他网络,都会默认连接到这个 bridge
网络上。
- 工作原理、默认网桥
Bridge 驱动的工作机制是在 Docker 主机上创建一个软件实现的虚拟网桥。这个虚拟网桥的行为类似于一个物理交换机,所有连接到该网桥的容器都会获得一个 IP 地址,并且可以在这个虚拟网络内部相互通信 。容器通过一对虚拟以太网设备(veth pair)连接到这个网桥,一端在容器的网络命名空间内(通常命名为 eth0
),另一端连接到宿主机上的虚拟网桥。
Host 网络驱动#
Host 网络驱动提供了一种特殊的网络模式,它完全移除了容器与 Docker 主机之间的网络隔离层 。当容器使用 Host 网络模式时,它不再拥有自己独立的网络命名空间,而是直接共享和使用宿主机的网络命名空间 。
- 机制、性能影响与隔离性降低
在这种模式下,容器不会被分配自己独立的 IP 地址。相反,它直接使用宿主机的 IP 地址和端口空间 。这意味着,如果一个在 Host 模式下运行的容器内应用监听了某个端口(例如 80 端口),那么该应用将直接在宿主机的相应 IP 地址的 80 端口上提供服务,对外部网络可见。
所有与端口映射相关的 Docker 命令参数(如
-p HOST_PORT:CONTAINER_PORT
、--publish
、-P
、--publish-all
)在 Host 模式下都会被忽略
-
性能优势:Host 模式最显著的优点是网络性能。由于容器直接使用主机的网络栈,数据包无需经过 Docker 引擎引入的网络地址转换 (NAT) 或用户态代理(userland-proxy)的额外处理层次。这消除了这些中间层可能带来的性能开销和延迟,使得 Host 模式下的网络通信速度几乎等同于直接在主机上运行应用。
-
隔离性降低与安全风险:由于容器共享主机的网络,它不再受到 Docker 默认提供的网络隔离保护。容器内的进程可以直接访问主机的所有网络接口,并且主机上的其他进程也可以直接访问容器内监听的端口。这种直接暴露带来了显著的安全风险,因为容器的漏洞可能会直接影响到主机。
用户态代理 (userland proxy),通常指的是 Docker 引擎中的
docker-proxy
进程,它在特定的网络场景下负责端口转发 。
None 网络驱动#
None 网络驱动为 Docker 容器提供了最高级别的网络隔离。当一个容器被配置为使用 none
网络驱动时,Docker 不会为其配置任何外部网络接口,除了一个必需的环回接口 (loopback interface, 通常是 lo
) 。
这代表着,连接到 none
网络的容器:
- 没有
eth0
或类似的外部网络接口。 - 没有被分配 IP 地址(除了环回地址
127.0.0.1
)。 - 无法与 Docker 主机通信(除了通过环回接口与自身通信)。
- 无法与其他容器通信。
- 无法访问外部网络(如互联网)。
Overlay 网络驱动#
Overlay 网络驱动是专为跨多个 Docker 主机环境设计的解决方案,其核心目标是使运行在不同物理或虚拟机上的容器能够像在同一个局域网中一样相互通信 。
这对于构建和管理分布式应用,尤其是在 Docker Swarm 集群模式下部署微服务架构,是至关重要的。
- 工作原理 (VXLAN):Overlay 网络通过在现有的物理网络(通常是 Layer 3 网络)之上创建一个虚拟的 Layer 2 网络来实现跨主机通信。它通常采用 VXLAN (Virtual Extensible LAN) 隧道技术 。VXLAN 将原始的以太网帧(容器发出的网络包)封装在一个 UDP 包中,然后通过底层的主机网络进行传输。当这个 UDP 包到达目标主机时,目标主机上的 Docker 引擎会解封装,提取出原始的以太网帧,并将其传递给目标容器。通过这种方式,即使容器分布在不同的物理网络段,它们也感觉自己连接在同一个虚拟的二层网络上,可以使用相同的子网 IP 地址进行通信。
- Docker Swarm 集成: Overlay 网络是 Docker Swarm 模式的核心网络功能和默认选择 。当初始化一个 Swarm 集群或将节点加入 Swarm 时,Docker 会自动创建一些预定义的 overlay 网络(如
ingress
网络,用于处理外部流量的路由网格)。用户也可以轻松创建自定义的 overlay 网络,并将 Swarm 服务部署到这些网络上。Swarm 管理器会负责协调和管理 overlay 网络的配置,确保跨集群中所有节点的容器路由正确无误。 - 服务发现: 在 Docker Swarm 的 overlay 网络中,内置了强大的服务发现机制 。当一个服务(由一个或多个容器副本组成)部署到 overlay 网络时,Swarm 会为该服务分配一个虚拟 IP (VIP),并且 Swarm 内嵌的 DNS 服务器会将服务名解析到这个 VIP。这意味着,网络内的其他容器或服务可以直接通过服务名来访问目标服务,而无需关心其副本运行在哪个节点上,也无需知道具体的容器 IP 地址。
- 负载均衡: Docker Swarm 利用 overlay 网络和其 ingress 路由网格,为发布到集群外部的服务提供自动的负载均衡 。当外部请求到达集群中任何一个节点的已发布端口时,Swarm 会将流量分发到该服务的某个健康运行的容器副本上,无论该副本位于哪个节点。对于集群内部的服务间通信,通过服务名访问时,Swarm 也会在服务的多个副本之间进行负载均衡。
Overlay 网络驱动通过其强大的跨主机通信能力、与 Swarm 的深度集成以及内置的服务发现和负载均衡特性,为构建和运维复杂的分布式容器化应用提供了坚实的基础。
Macvlan 网络驱动#
容器作为物理网络设备
Macvlan 网络驱动允许为 Docker 容器分配一个独立的 MAC (Media Access Control) 地址,并将其虚拟网络接口直接桥接到宿主机的物理网络接口上 。这使得容器在物理网络层面看起来就像一个独立的物理设备,拥有自己的 MAC 地址和 IP 地址,可以直接参与到宿主机所在的局域网中,与其他物理设备或虚拟机进行通信,而无需经过 Docker 主机的 NAT 或端口映射。
- 限制与注意事项
- 主机与容器通信:默认情况下,使用 Macvlan 网络的容器无法直接与它们的宿主机进行通信。这是因为流量从容器发出后,会直接通过父物理接口出去,宿主机的网络栈无法截获这些发往自身的流量。如果需要宿主机与 Macvlan 容器通信,通常需要额外的网络配置。
- 网络设备支持: 宿主机的物理网络接口(或其驱动程序)需要支持混杂模式 (promiscuous mode),以便能够接收发往多个 MAC 地址的帧 。
- IP 地址和 MAC 地址管理: 需要仔细规划 IP 地址和 MAC 地址的分配,以避免与现有网络中的其他设备冲突。过多的 MAC 地址也可能给网络交换机带来压力(MAC 地址表耗尽)或触发安全策略(如端口安全限制)。