Docker的这三大模块——网络、存储、镜像管理,构成了容器技术核心的“铁三角”。
理解它们不能死记硬背指令,而要先吃透背后的设计逻辑:网络解决的是“容器怎么通”,存储解决的是“数据怎么留”,镜像解决的是“应用怎么装”。
一、网络:从隔离到互联的精密设计
Docker的网络设计基于Linux内核的Namespace(命名空间)与veth pair(虚拟以太网对)技术。它的核心逻辑是:为每个容器营造独立的网络协议栈,再通过虚拟设备像插网线一样将它们连起来。
1. 设计逻辑与核心模式
Docker守护进程启动时,默认创建一个叫docker0的虚拟网桥,这就像一个软件实现的交换机。当你创建容器时,Docker会做两件事:在容器内部创建一个eth0网卡,在宿主机上创建一个veth开头的虚拟接口,然后把它们一对一配对起来,并把宿主机这端的veth“插”到docker0网桥上。
这套机制支撑起了四种核心网络模式:
- bridge(桥接模式):默认模式。宿主机是一个大的局域网,每个容器都有独立IP,通过NAT访问外网。适合大多数单机容器场景。
- host(主机模式):容器直接“借用”宿主机的网络栈,没有自己的IP,网络性能最高,但端口冲突风险大。适合对网络延迟极度敏感的服务。
- none(无网络):容器只有
lo回环接口,完全隔离。适合不需要网络,或要自定义网络的特殊场景。 - container(容器共享模式):新容器直接加入一个已存在容器的网络命名空间,共享IP和端口。适合让边车(Sidecar)模式,比如让一个日志采集容器共享应用容器的网络来监听本地端口。
2. 关键指令与进阶操作
基础网络模式的指定很简单,比如docker run --network host。但更关键的,是自定义网络。
默认的bridge网络有一个致命缺陷:容器间无法通过服务名互相通信,只能靠IP。一旦容器重启,IP就会变化。已过时的--link参数就是为了解决这个问题的,但官方早已不推荐使用。
正确做法是创建自定义的bridge网络:
# 创建一个名为 my-net 的自定义桥接网络dockernetwork create my-net# 将容器启动到这个网络中dockerrun-d--nameweb--networkmy-net nginxdockerrun-d--nameapp--networkmy-net my-app这时,app容器可以直接ping web,因为Docker为自定义网络内置了DNS服务,将容器名解析为IP。这才是服务发现最原生的实现。
你还可以进行更精细的规划:
# 创建网络时指定子网段和IP范围dockernetwork create--driverbridge--subnet=192.168.100.0/24 --ip-range=192.168.100.128/25 my-gateway对于跨主机的集群,则需要使用overlay模式,这通常与Docker Swarm结合,允许不同宿主机上的容器在同一个扁平网络内通信。
二、存储:数据的永恒与瞬逝
容器存储的核心矛盾在于:容器本身是无状态的,但业务数据需要持久化。容器中写入的数据默认存活在可写层,一旦容器被删除,数据就没了。
Docker的解决方案是将数据绕过容器文件系统,直接存储在宿主机上,从而获得独立于容器生命周期的持久性。主要有两种方式,理解它们的区别是选型的关键。
1. Volume vs. Bind Mount:设计的权衡
数据卷(Volumes)
这是Docker推荐的方式。它由Docker完全管理(默认路径在/var/lib/docker/volumes/),你可以把它想象成一个Docker“托管”的U盘。它的好处是:与宿主机路径解耦,可以在不同系统上移植;可以安全地在多个容器间共享;可以通过卷驱动插件对接远程云存储(如NFS、AWS EBS)。这正是为生产环境的数据库设计的。
绑定挂载(Bind Mounts)
这是直接将宿主机上的一个绝对路径“映射”进容器,就像在墙上凿了个洞。它的强项是实时共享:你修改了宿主机上的代码文件,容器内立刻生效。因此,这是开发环境的神器,但在生产环境中使用需要格外注意权限和路径管理。
| 特性 | 数据卷 (Volumes) | 绑定挂载 (Bind Mounts) |
|---|---|---|
| 管理者 | Docker | 用户 |
| 适用场景 | 数据库、生产环境持久化数据 | 开发环境代码同步、配置文件注入 |
| 可移植性 | 高,与宿主机路径无关 | 低,强依赖宿主机文件系统结构 |
| 性能 | 较高,直接写主机文件系统 | 高,但大量小文件IO可能较差 |
2. 关键指令与备份恢复
使用-v或更推荐的--mount语法来挂载,后者对路径不存在等错误更明确。
# Volume方式:创建一个具名卷,并挂载到容器的 /var/lib/mysqldockervolume create mysql_datadockerrun-d--namedb-vmysql_data:/var/lib/mysql mysql:8.0# Bind Mount方式:将宿主机 ./app 目录挂载到容器的 /app,并以只读方式dockerrun-d--nameweb-v/home/user/app:/app:ro nginx# --mount语法更清晰,明确指定typedockerrun-d--namedb--mounttype=volume,source=mysql_data,target=/var/lib/mysql mysql:8.0数据卷的强大还体现在数据备份与恢复,可以直接用一个临时的“工具人”容器来完成:
# 备份:启动一个临时容器,挂载数据卷和一个备份目录,执行tar打包dockerrun--rm--volume=mysql_data:/volume--volume=/backup:/backup alpinetarcvf /backup/mysql_backup.tar /volume# 恢复:同样用临时容器,但执行解包dockerrun--rm--volume=new_mysql_data:/volume--volume=/backup:/backup alpinetarxvf /backup/mysql_backup.tar-C/三、镜像管理:分层构建的工程之美
镜像是容器的“模具”。它的设计精髓在于联合文件系统(UnionFS)带来的分层存储与写时复制机制。
1. 设计逻辑:分层与写时复制
一个Docker镜像不是单一的整体,而是由多个只读层叠加而成。你用Dockerfile里的每一行指令(FROM,RUN,COPY)都会创建一个新层。这样做的好处是惊人的效率提升:当你拉取一个新版本的nginx镜像时,只会下载你本地没有的那几层。
容器启动时,Docker会在所有只读层之上,添加一个薄薄的可写层(Container Layer)。当容器修改一个文件时,Docker会通过“写时复制”技术,先将这个文件从下层拷贝到可写层,然后再进行修改。这一机制保证了镜像层不可变,能被无数个容器实例安全地共享。
2. 从构建到分发的全生命周期
构建:不仅仅是打包
一个高质量的Dockerfile是一场关于“瘦身”和“加速”的工程实践。多阶段构建是其中的典范,它将编译环境和运行环境分离,最终镜像只包含运行时所需的二进制和依赖,体积可能从1.2GB骤降到15MB。
# 第一阶段:构建阶段,使用庞大的SDK镜像 FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build WORKDIR /src COPY *.csproj . RUN dotnet restore COPY . . RUN dotnet publish -c Release -o /app # 第二阶段:运行阶段,仅使用精简的运行时镜像 FROM mcr.microsoft.com/dotnet/aspnet:6.0 WORKDIR /app COPY --from=build /app . EXPOSE 80 ENTRYPOINT ["dotnet", "MyApp.dll"]分发:从本地到仓库
镜像的流动离不开仓库(Registry)。docker build -t my-app:v1 .只是把它留在了本地。把它推送到仓库(如Docker Hub、私有Harbor),才能真正实现跨环境交付。标签(Tag)是版本管理的核心,生产环境务必避免使用飘忽不定的latest标签,应使用语义化版本或Git Commit哈希。
安全:左移的防线
镜像安全不能等上线后再扫。现在Docker已内置docker scout命令,可以在构建后立即进行漏洞扫描,在生产上线前把高危CVE挡在门外。
# 快速扫描镜像的高危漏洞dockerscout cves --only-severity critical,high my-app:v1梳理这三大模块可以看到,Docker并非一堆命令的堆砌。网络通过虚拟化桥接和DNS实现了服务的互联与发现;存储通过卷与挂载弥合了容器的短暂性与数据的持久性间的鸿沟;镜像则通过分层与写时复制,用工程化的方式解决了环境一致性与交付效率的难题。这三者共同构成了容器即服务、一次构建到处运行的技术根基。