docker数据卷和网络

1. 数据卷

Docker 数据卷(Volume)是 Docker 提供的一种特殊机制,用于将容器内的数据与容器本身的生命周期解耦。它是一个由 Docker 管理的、存在于主机文件系统中的特定目录,可以被一个或多个容器挂载,从而实现数据的持久化和共享。

简单来说,容器可以被随时删除和重建,但数据卷里的数据会一直保留下来,直到你主动删除它。

Docker 数据卷就像一个专门为容器准备的“外接U盘”。

你可以把你的应用程序想象成一台电脑(容器)。这台电脑可能会因为升级、故障等原因被随时更换。但你最重要的文件(数据)都保存在一个外接U盘(数据卷)里。无论你换多少台新电脑,只要把这个U盘插上去,你的文件就都回来了。

1.1 工作原理

Docker 数据卷的工作原理可以简化为以下几个步骤:

  1. 创建 (Creation):

    你可以通过 docker volume create my-volume 命令创建一个“命名卷”(Named Volume)。Docker 会在主机的特定目录下(通常是 /var/lib/docker/volumes/)创建一个对应的文件夹来管理这个卷。

  2. 挂载 (Mounting):

    在启动容器时,使用 -v--mount标志,将这个卷“挂载”到容器内部的一个指定路径上。

    • 命令示例: docker run -d -v my-volume:/app/data my-image
  3. 读写 (I/O Operation):

    容器内的应用向 /app/data 目录进行读写操作。实际上,这些操作被 Docker 引擎透明地重定向到了主机上的 /var/lib/docker/volumes/my-volume/_data 目录。容器内的进程对此毫无感知,它只知道自己在跟 /app/data 交互。

  4. 解耦 (Decoupling):

    当容器停止或被删除时,这个挂载关系被解除,但主机上的数据卷目录及其内容保持不变。之后,你可以将它挂载到任何一个新的容器上。

1.2 命名卷 和 绑定挂载

最容易混淆的就是三种数据挂载方式:命名卷 (Named Volume)、绑定挂载 (Bind Mount) 和 tmpfs 挂载。

特性命名卷 (Named Volume)绑定挂载 (Bind Mount)tmpfs 挂载
一句话描述Docker 管理的“U盘”主机上的“共享文件夹”内存中的“临时文件夹”
主机路径由 Docker 在其管理区域自动创建和管理 (/var/lib/docker/volumes/...)用户指定主机上的任意路径和文件不写入主机磁盘,仅存在于主机内存中
管理方Docker用户/宿主机主机内存
可移植性高。不依赖主机文件结构,易于在不同主机间迁移和备份。低。强依赖于主机的特定文件路径,迁移时需确保目标主机有相同结构。不适用。数据非持久化。
自动填充是。如果将一个空卷挂载到容器内一个非空目录,卷会自动被填充为该目录的内容。否。主机目录会直接覆盖容器内的目录,可能导致容器原始文件丢失。不适用。
性能非常高。原生 Linux 性能。在 Docker Desktop (Mac/Win) 上性能优于绑定挂载。在原生 Linux 上性能高,但在 Docker Desktop 中因文件系统转换会有性能损耗。最高,因为是内存操作。
核心优势应用数据的持久化、备份与迁移开发时代码同步、挂载主机特定配置存储敏感信息(如密钥)、临时状态文件,避免磁盘 I/O
典型场景数据库文件、用户上传内容、应用配置开发环境的源码、Nginx 配置文件、Docker Socket缓存、临时会话数据、不想留痕的 secret
--mount 语法type=volume,source=my-vol,target=/apptype=bind,source=/path/on/host,target=/apptype=tmpfs,destination=/app

1.3 使用注意事项

  • 优先使用命名卷 (Named Volumes): 对于应用数据(数据库、配置文件、用户内容),始终优先使用命名卷。它们由 Docker 管理,路径和生命周期清晰,更便于备份、迁移和管理。

  • 谨慎使用绑定挂载 (Bind Mounts): 绑定挂载非常适合开发环境(将本地源码挂载到容器中实现热重载),或需要将主机的特定配置文件(如 /etc/resolv.conf)或套接字(如 docker.sock)提供给容器的场景。但在生产环境中滥用它会使容器与主机的特定文件结构紧密耦合,降低了可移植性。

  • 使用 --mount 语法: 新版本的 Docker 推荐使用 --mount 语法代替 -v。它更冗长,但也更明确、更易读,尤其是当需要配置额外选项(如只读 readonly)时。

  • 利用卷驱动 (Volume Drivers): 不要认为卷只能存在于本地磁盘。通过卷驱动,你可以将数据直接持久化到云存储(如 AWS S3, Azure Files, NFS),这对于构建跨主机的、高可用的服务至关重要。

  • 管理无主卷 (Dangling Volumes): 当你删除一个使用了“匿名卷”的容器而没有加 -v 选项时,这个卷不会被自动删除。久而久之会占用大量磁盘空间。定期使用 docker volume prune 清理无主卷是个好习惯。

1.4 常见问题

  • 绑定挂载的路径问题

    • 错误: docker run -v /my/app/code:/path/in/container my-image,如果 /my/app/code 在主机上不存在,Docker 会自动创建一个空目录。这可能会覆盖掉容器镜像里 path/in/container 的所有内容,导致应用启动失败。
    • 避免方法: 始终确保绑定挂载的源路径是存在的、正确的。如果是挂载单个文件,确保源是一个文件而非目录。
  • 绑定挂载的权限问题 (Permission Denied)

    • 错误: 在一个绑定挂载的目录中,容器内的应用(通常以非 root 用户运行)无法写入文件。这是因为容器内用户的 UID/GID 与主机上该目录的所有者/权限不匹配。
    • 避免方法:
      • 简单粗暴 (不推荐): 在主机上 chmod 777 /path/to/dir
      • 正确做法: 确保容器运行的用户 UID/GID 与主机目录权限匹配,或在启动容器时使用 --user $(id -u):$(id -g) 来匹配当前主机用户。
  • 匿名卷的滥用

    • 错误: 使用 docker run -v /app/data my-image。这里没有指定卷名,Docker 会创建一个丑陋的、哈希字符串作为名字的“匿名卷”。它很难被引用和管理,且容易被遗忘。
    • 避免方法: 养成好习惯,总是给你的数据卷起一个有意义的名字:docker run -v my-app-data:/app/data my-image

2. 网络

Docker 网络是让容器之间、容器与外部世界(比如你的电脑或互联网)能够互相通信,同时又能根据需要进行隔离的一套虚拟网络系统。它为每个容器提供了一个独立的网络环境,就像给每个程序分配了专属的网线和路由器。

2.1 一句话类比

Docker 网络就像一个大型酒店的内部电话系统。

  • 每个房间就是一个容器。
  • 默认情况下,每个房间的电话(bridge 网络)不仅可以打给前台(外部世界),还可以通过房间号(容器名/IP)直接呼叫同一楼层的其他房间。
  • 有些VIP 套房(host 网络)可以直接使用酒店的总机号码,完全暴露在外部,拥有最高的权限和性能。
  • 有些是跨地区的连锁酒店(overlay 网络),你可以用内线电话直接打到另一家分店的房间,完全感觉不到地理距离。
  • 还有些是完全隔音的密室(none 网络),里面没有电话,与世隔绝。
特性 / 驱动bridge (默认是 default bridge, 推荐自定义 bridge)hostoverlaymacvlannone
核心思想酒店内线电话系统:Docker 主机上的一个虚拟网桥,所有连接到它的容器都在一个独立的子网内。酒店总机:容器共享主机的网络栈,没有隔离。连锁酒店:在多个 Docker 主机之间创建一个虚拟的二层网络,实现跨主机容器通信。独立电话线:给容器分配一个物理网络上的 MAC 地址,使其看起来像一台独立的物理设备。隔音密室:容器拥有自己的网络栈,但没有任何网络接口。
适用场景单机上运行的多个容器需要通信(最常用)。需要极致网络性能,且不关心端口冲突的应用(如监控 Agent)。Docker Swarm 集群或任何需要跨主机容器通信的场景。需要让容器在局域网中拥有独立 IP 地址的遗留应用或特殊网络需求。需要完全隔离或进行自定义网络配置的场景(如批处理任务)。
优点设置简单,隔离性好,自定义 bridge 支持 DNS 解析。性能极高(无 NAT 开销),与主机网络完全一致。原生支持跨主机,加密通信,易于扩展。性能优秀,容器可被物理网络设备直接发现。最安全,完全隔离。
缺点跨主机通信复杂;有 NAT 性能损耗。无网络隔离,容器端口直接占用主机端口,容易冲突。有 VXLAN 封装的性能开销,配置相对复杂。配置复杂,需要物理网卡支持混杂模式,可能耗尽局域网 IP 地址。无网络功能,无法与外界或其它容器通信。

2.2 解决的问题

  1. 隔离性 (Isolation): 如果没有网络隔离,所有容器都会在同一个网络里“裸奔”,一个容器的网络问题可能会影响所有其他容器。这就像酒店里所有房间都挤在大堂,毫无隐私和安全可言。

  2. 通信 (Communication): 现代应用通常由多个微服务组成(例如一个 Web 前端、一个后端 API、一个数据库)。Docker 网络让这些作为独立容器运行的服务,可以像在同一台机器上一样方便、安全地互相“交谈”。

  3. 服务发现 (Service Discovery): 当你启动一个新容器时,它的 IP 地址可能是动态的。Docker 网络提供了一个内置的 DNS 服务,让你可以通过固定的容器名(例如 mysql-db)来访问它,而不用关心它那随时可能变化的 IP 地址。

  4. 可移植性 (Portability): 定义好的网络配置可以随着你的应用一起打包(例如使用 Docker Compose),确保应用在任何环境(开发、测试、生产)下都能以同样的方式连接和运行。

2.3 实现原理

Docker 的网络功能基于一个叫做 容器网络模型 (Container Network Model, CNM) 的规范。你可以把它理解为一套“建筑蓝图”,而 Docker 的 libnetwork 库就是实现了这套蓝图的工程队。

其核心组成部分如下:

  • 沙箱 (Sandbox): 一个独立的网络栈。包括网络接口、路由表、DNS 设置等。你可以把一个沙箱看作是一个房间的网络配置。每个容器都有自己的沙箱。
  • 端点 (Endpoint): 一个虚拟的网络接口,像一个“网线插口”。它负责将沙箱连接到一个网络上。一个端点只能属于一个网络,但一个沙箱可以有多个端点(即一个容器可以连接到多个网络)。
  • 网络 (Network): 一个可供端点连接的“交换机”。同一个网络内的所有端点可以互相通信。Docker 中 bridge, overlay 等都是不同类型的网络实现。

2.4 使用注意事项

  1. 绝不使用默认 Bridge 网络进行应用部署: 始终为你的应用创建一个或多个自定义 Bridge 网络。这是最重要的一条!最好使用 docker-compose,因为它会自动为你创建。

  2. 优先使用 DNS 进行容器间通信: 硬编码 IP 地址是万恶之源。利用自定义网络的 DNS 功能,使用容器名进行通信。

  3. 理解端口映射 p vs EXPOSEEXPOSE 仅仅是元数据,告诉使用者这个容器内的服务监听哪个端口,它本身不会开放任何端口。

    -p HostPort:ContainerPort (--publish) 或 -P (--publish-all) 才是真正将容器端口映射到主机上,让外部可以访问的命令。

  4. 按需选择网络驱动:单机应用用 bridge,跨主机集群(如 Docker Swarm)用 overlay,需要极致性能且不在乎IP冲突风险时考虑 hostmacvlan

  5. Docker 内嵌的 DNS 服务器

    当你创建一个自定义 bridge 网络时,Docker 会在这个网络上启动一个隐藏的 DNS 服务器。当你从一个容器 ping another-container 时,请求会被这个 DNS 服务器拦截,它会查找名为 another-container 的容器在该网络中的 IP 地址并返回。这就是服务发现的魔法所在。

2.5 常见问题

  • 我的容器为什么 PING 不通另一个容器?

    • 陷阱:两个容器都用 docker run 启动,但没有指定 --network,它们被连接到了 default bridge 网络。在这个网络里,Docker 不提供基于容器名的 DNS 解析。

    • 避坑:先 docker network create my-app-net,然后在 docker run 时都加上 --network my-app-net。这样就可以直接 ping container_name 了。

  • “host 模式真香,性能真好,所有容器都用它!”

    • 陷阱:滥用 host 网络会完全破坏容器的隔离性。如果两个容器都想监听 80 端口,第二个容器将无法启动,因为它会发现主机的 80 端口已被占用。
    • 避坑:仅在性能是首要瓶颈且你完全清楚其影响时才使用 host 网络。对于绝大多数 Web 应用,bridge 网络的性能损耗微乎其微。

3. 提问问题

选择 -v 还是 -–mount 参数

  • 使用 --mount 语法: 新版本的 Docker 推荐使用 --mount 语法代替 -v。它更冗长,但也更明确、更易读,尤其是当需要配置额外选项(如只读 readonly)时。

卷的自动填充是什么意思

卷的自动填充是指:当你将一个全新的、空的命名卷挂载到容器中一个已经存在内容的目录时,Docker 会自动将该目录中的内容复制到这个卷里。

  1. 必须是命名卷 (Named Volume) 或匿名卷 (Anonymous Volume)。 绑定挂载(Bind Mount)绝不会发生自动填充。绑定挂载是主机优先,它会直接覆盖容器内的目录。
  2. 卷必须是空的 (Empty)。 如果你挂载一个已经有内容的卷,Docker 会认为你希望使用卷里的数据,于是它会覆盖容器内的目录,而不是从容器里复制。
  3. 只发生在第一次挂载时。 一旦卷被填充过一次,它就不再是“空的”了,这个自动填充过程就再也不会对这个卷发生了。

macvlan 是个什么

macvlan 是一种特殊的 Docker 网络模式,它允许你为每个容器分配一个虚拟的 MAC 地址(就像电脑网卡的物理地址),让容器在局域网(LAN)中看起来就像一台独立的物理设备。

  • 想让容器像一台新电脑一样加入局域网,有自己的 IP -> 用 macvlan
  • 想让容器共享宿主机的 IP,追求极致性能 -> 用 host

“致命”的隔离缺陷:主机无法访问容器

这是 macvlan 最令人困惑和头疼的一个特性:默认情况下,运行容器的 Docker 主机无法通过其 macvlan IP 地址与容器通信。

解决方法 (更麻烦): 解决方法是再创建一个 macvlan 网络接口并分配给主机自己,然后通过这个新接口去和容器通信。这无疑让配置变得更加复杂。

主流的云服务提供商(AWS, Azure, GCP)的网络环境都经过了高度虚拟化和安全封装。它们通常不允许你在它们的虚拟网卡上随意创建 MAC 地址或开启混杂模式。因此,macvlan 在云平台上几乎毫无用武之地。云原生世界有其自己更高级的网络解决方案(如 AWS VPC CNI)。

mac 宿主机默认名称和IP 是个啥

host.docker.internal

是一个由 Docker 引擎内置的、特殊的 DNS 名称。当你从容器内部去访问这个地址时,Docker 会自动把它解析成宿主机在 Docker 网络里的内部 IP 地址。

  • 稳定可靠:无论你用的是默认的 bridge 网络还是自定义的网络,这个名字始终指向宿主机,你不需要去关心具体的 IP 地址是什么。
  • 跨平台:它在 Docker Desktop (Windows 和 Mac) 上是默认开启的。在 Linux 上,从 Docker 20.10 版本开始也已支持。

网关 IP 172.17.0.1

这个 IP 地址也确实是指向宿主机的,但它有一个重要的限制。

  • 它是什么?:172.17.0.1 是 Docker 默认 bridge 网络 (docker0) 的网关(Gateway)地址。在这个默认网络里,宿主机扮演了网关的角色,所以这个 IP 就代表了宿主机。
  • 缺点:
    • 不可靠:这个 IP 地址只在默认的 bridge 网络中有效。如果你按照最佳实践,创建并使用了一个自定义的 bridge 网络,那么那个网络的网关地址就会是别的,比如 172.18.0.1 或其他你定义的网段的第一个 IP。一旦网络改变,代码里写死的 172.17.0.1 就会立刻失效。
    • 不易记忆且不直观:一串数字远不如一个有意义的名字 host.docker.internal 好记。

一句话总结:永远优先使用 host.docker.internal。它更现代、更稳定、更具可移植性。只有在非常老旧的 Docker 环境下,或者有特殊原因时,才会考虑去查找并使用网关 IP 的方式。

-p 和 -P 的区别

  • 小写的 -p (--publish) 是 “精确制导”,你来精确指定哪个对哪个。
  • 大写的 -P (--publish-all) 是 “随缘映射”,让 Docker 帮你自动搞定。
1
2
3
4
5
6
7
# 这条命令的意思是,将 我宿主机的 8080 端口 映射到 Nginx 容器内的 80 端口。之后,我访问我电脑的 `http://localhost:8080`,流量就会被 Docker 转发到容器的 80 端口,从而看到 Nginx 的欢迎页面。
docker run -d -p 8080:80 nginx


# 你需要使用 docker ps 命令来查看 Docker 到底给你分配了哪个端口。
# 主要用在开发和测试环境。当你需要快速启动同一个服务的多个实例,而又不想手动去一个个找可用的宿主机端口时,-P 就非常方便,它能自动帮你避免端口冲突。
docker run -d -P nginx

4. 进阶

4.1 默认 Bridge vs 自定义 Bridge 的巨大差异

  • 默认 bridge 网络:

    1
    docker run my-image
    • 缺点: 不支持通过容器名进行 DNS 解析。你只能用难以管理的内部 IP (172.17.0.x) 通信。
  • 自定义 bridge 网络:

    1
    docker network create --driver bridge my-net
    • 巨大优势: 内置了 DNS 服务。连接到同一个自定义网络的容器,可以直接通过容器名作为主机名进行通信。比如 web 容器可以直接 ping api,Docker 会自动解析到 api 容器的内部 IP。这是构建多容器应用的基石!

4.2 Docker 与 iptables 的深度集成

docker 的网络魔法很大程度上是靠在宿主机上自动配置 iptables 规则实现的。

  • 端口映射 (-p) 就是在 iptablesPREROUTINGOUTPUT 链(DOCKER 自定义链)里添加了 DNAT (目标地址转换) 规则。
  • 理解这一点对于排查“端口映射了但就是不通”这类问题至关重要,因为可能是其他防火墙规则(如 firewalld, ufw)干扰了 Docker 创建的规则。