Docker 存储与网络
Docker Volume
Volume(数据卷)是 Docker 官方推荐的持久化方式。数据由 Docker 统一管理,存放在宿主机的 /var/lib/docker/volumes/ 目录下,容器删了数据还在,适合数据库这类需要长期保存的数据。
常用子命令
# 创建数据卷
docker volume create mydata
# 查看数据卷列表
docker volume ls
# 查看数据卷详情(挂载点、驱动等)
docker volume inspect mydata
# 删除指定数据卷
docker volume rm mydata
# 清理所有未被容器使用的数据卷
docker volume prune
挂载到容器
docker run -d --name web -v mydata:/usr/share/nginx/html nginx
也可以用 --mount 写法,参数含义更清晰:
docker run -d --name web --mount source=mydata,target=/usr/share/nginx/html nginx
备份与恢复
数据卷没有内置的备份命令,常用做法是起一个临时容器,把数据卷和宿主机目录同时挂进去,用 tar 打包:
# 备份:把 mydata 打包到当前目录
docker run --rm \
-v mydata:/data \
-v $(pwd):/backup \
busybox tar czf /backup/mydata-backup.tar.gz -C /data .
# 恢复:解包回数据卷
docker run --rm \
-v mydata:/data \
-v $(pwd):/backup \
busybox tar xzf /backup/mydata-backup.tar.gz -C /data
数据库容器正在写入时直接打包,可能拿到不完整的数据。稳妥做法是先 docker stop 再备份,或者用数据库自带的导出工具(如 mysqldump)做逻辑备份。
Bind Mount
Bind Mount 是把宿主机的任意路径直接挂进容器,容器里看到的就是宿主机上的那份文件,改一边另一边立即生效。
# 把宿主机 /opt/site 挂到 nginx 的站点目录,只读
docker run -d --name web -v /opt/site:/usr/share/nginx/html:ro nginx
--mount 写法:
docker run -d --name web \
--mount type=bind,source=/opt/site,target=/usr/share/nginx/html,readonly \
nginx
适用场景:
- 开发环境挂载源码,改完代码容器里立即生效
- 挂载配置文件,比如 nginx.conf
- 挂载宿主机上的日志目录
容器内进程默认以 root 写文件,写进 Bind Mount 的文件在宿主机上属主也会变成 root,普通用户可能改不动。开了 SELinux 的系统(如 CentOS)挂载时要加 :z 或 :Z 后缀,否则容器内会报 Permission denied。
tmpfs
tmpfs 把数据放在宿主机内存里,不落盘,容器一停数据就没了。适合放临时文件、缓存,以及不想留痕的敏感数据(比如密钥)。
# --tmpfs 写法
docker run -d --name app --tmpfs /run busybox sleep 3600
# --mount 写法,可以指定大小
docker run -d --name app2 \
--mount type=tmpfs,destination=/cache,tmpfs-size=100m \
busybox sleep 3600
tmpfs 只在 Linux 上可用,Windows 容器不支持。
三种挂载方式对比
| 对比项 | Volume | Bind Mount | tmpfs |
|---|---|---|---|
| 存储位置 | /var/lib/docker/volumes/,由 Docker 管理 | 宿主机任意路径,用户自己维护 | 宿主机内存,不落盘 |
| 生命周期 | 独立于容器,容器删除后仍在,要 docker volume rm 才会删掉 | 跟随宿主机文件,容器删除不影响 | 容器停止即丢失 |
| 适用场景 | 数据库等需要持久化的业务数据 | 开发挂载源码、配置文件 | 临时文件、缓存、敏感数据 |
Volume Driver
创建数据卷时可以指定驱动(--driver),默认是 local,数据就存在本机磁盘。
local 驱动配合参数也能挂载 NFS 共享目录,让多台机器共用一份数据:
docker volume create --driver local \
--opt type=nfs \
--opt o=addr=192.168.1.100,rw \
--opt device=:/nfs/data \
nfsdata
想接更多存储后端就得用第三方驱动插件,常见的有:
- REX-Ray:对接云盘、Ceph 等
- NetApp Trident:对接 NetApp 存储
- 云厂商插件:阿里云、AWS 等都有各自的 Docker Volume 插件
第三方驱动要先在宿主机上安装对应插件才能用,docker plugin ls 可以查看已安装的插件。
Bridge Network
原理一句话:Docker 在宿主机上建一个虚拟网桥(默认叫 docker0),每个容器通过一对 veth 设备接在网桥上,容器访问外网时由宿主机做 NAT 转发。
# 查看网络列表
docker network ls
# 创建自定义 bridge 网络
docker network create mynet
# 启动容器时加入网络
docker run -d --name web --network mynet nginx
# 查看网络详情(子网、接入了哪些容器)
docker network inspect mynet
适用场景:单机部署多容器通信的默认选择。
容器不指定网络时会进默认的 bridge。默认 bridge 里的容器只能用 IP 互相访问,不支持按容器名解析;自定义 bridge 网络内置 DNS,可以直接用容器名通信。实际使用建议都建自定义网络。
Host Network
原理一句话:容器不做网络隔离,直接使用宿主机的网络栈,容器里监听的端口就是宿主机的端口。
docker run -d --name web --network host nginx
启动后宿主机的 80 端口直接被 nginx 占用:
ss -tlnp | grep :80
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=12345,fd=6))
适用场景:对网络性能敏感(少了 NAT 一层转发)、需要直接用宿主机网络抓包调试的场景。
Host 模式没有端口隔离,两个容器不能监听同一个端口;而且只在 Linux 上有效,Docker Desktop(Mac / Windows)上不可用。用 Host 模式时 -p 参数会被忽略。
None Network
原理一句话:容器只有 lo 回环网卡,完全断网,既出不去也进不来。
docker run -d --name offline --network none busybox sleep 3600
docker exec offline ip addr
输出里只有 lo:
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
适用场景:安全要求高的离线任务,比如批量处理敏感数据,确保数据不可能外泄。
Macvlan
原理一句话:Macvlan 给容器分配真实局域网的 IP 和独立 MAC 地址,容器在网络上看起来就像一台独立的物理机。
# 创建 macvlan 网络,parent 指定宿主机网卡
docker network create -d macvlan \
--subnet=192.168.1.0/24 \
--gateway=192.168.1.1 \
-o parent=eth0 \
macnet
# 启动容器并指定局域网 IP
docker run -d --name web --network macnet --ip 192.168.1.200 nginx
适用场景:需要容器拥有局域网独立 IP 的场景,比如老旧系统迁移、服务需要被局域网内其他设备直接访问。
Macvlan 要求宿主机网卡开启混杂模式,公有云的虚拟机一般不允许,所以云上基本用不了。另外宿主机和 Macvlan 容器之间默认网络不通,这是内核的限制,不是配置错了。
Overlay Network(了解)
了解即可,这是 Swarm 集群场景用的。原理一句话:Overlay 通过 VXLAN 隧道把多台宿主机上的容器连成同一个二层网络,容器跨机器通信就像在同一台机器上。
# 先初始化 Swarm
docker swarm init
# 创建 overlay 网络
docker network create -d overlay --attachable myoverlay
# 部署服务时使用
docker service create --name web --network myoverlay nginx
适用场景:Swarm 集群内跨节点通信。现在主流编排是 Kubernetes,单机学习阶段知道这种模式是干什么的就够了。
没初始化 Swarm 就创建 Overlay 网络会直接报错,这是正常现象。
Docker DNS
Docker 在每个容器里内置了一个 DNS 服务器(embedded DNS),监听容器内的 127.0.0.11:53。容器加入自定义网络后,可以直接用容器名或网络别名解析对方。
验证一下:
docker network create mynet
docker run -d --name web --network mynet nginx
# 在同一网络里用容器名解析
docker run --rm --network mynet busybox nslookup web
Server: 127.0.0.11
Address 1: 127.0.0.11
Name: web
Address 1: 172.18.0.2
容器里的 /etc/resolv.conf 指向的就是这个内置 DNS:
docker exec web cat /etc/resolv.conf
nameserver 127.0.0.11
options ndots:0
还可以给容器加网络别名,比如让 db 容器同时能用 mysql 这个名字被访问:
docker run -d --name db --network mynet --network-alias mysql mysql:8
默认 bridge 网络不支持按容器名解析,只有自定义网络(bridge、overlay 等)才有这个能力。
容器互联
容器之间互相访问有两种办法:老旧的 --link 和现在的自定义网络。
--link(已过时)
docker run -d --name db -e MYSQL_ROOT_PASSWORD=123456 mysql:8
docker run -d --name app --link db:db nginx
--link 的原理是往容器 /etc/hosts 里写一条静态记录,再注入一组环境变量。官方早已把它标记为过时功能,随时可能移除,新环境不要再用。
自定义网络(推荐)
docker network create appnet
docker run -d --name db --network appnet -e MYSQL_ROOT_PASSWORD=123456 mysql:8
docker run -d --name app --network appnet nginx
同一个网络里的容器互相用名字访问即可,app 连接数据库时主机名直接写 db。
两种方式对比
| 对比项 | --link | 自定义网络 |
|---|---|---|
| 官方状态 | 已标记过时,可能移除 | 推荐做法 |
| 解析方式 | 写 /etc/hosts 静态条目 | 内置 DNS 动态解析 |
| 解析方向 | 单向,只有 link 方知道对方 | 双向互通 |
| 容器重建 | 目标 IP 变了要重启依赖方 | 名字不变就不用动 |
已经在运行的容器可以用 docker network connect 补加入网络,不用重建:
docker network connect appnet db
Port Mapping
Bridge 模式下容器的端口默认只在容器网络内可达,要对外提供服务得做端口映射。
-p 8080:80:宿主机 8080 端口转发到容器 80 端口-p 127.0.0.1:8080:80:只绑定宿主机本地回环,外部访问不到-P:把镜像里 EXPOSE 声明的端口随机映射到宿主机空闲端口
docker run -d --name web -p 8080:80 nginx
# 查看映射关系
docker port web
80/tcp -> 0.0.0.0:8080
访问验证:
curl -I http://127.0.0.1:8080
HTTP/1.1 200 OK
Server: nginx/1.25.3
端口映射不是 Docker 自己在转发流量,底层是靠宿主机的 iptables NAT 规则实现的,具体见下一节。
-P 用的是宿主机随机端口,容器重建后端口可能变,只适合临时测试。生产环境用 -p 明确指定。
iptables NAT
端口映射的本质是 Docker 自动在宿主机 iptables 里写 NAT 规则。
查看 DOCKER 链的规则:
iptables -t nat -L DOCKER -n
Chain DOCKER (2 references)
target prot opt source destination
RETURN all -- 0.0.0.0/0 0.0.0.0/0
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:8080 to:172.17.0.2:80
以 -p 8080:80 为例,一次外部访问的完整 DNAT 流程:
- 外部请求到达宿主机 8080 端口
- 进入 PREROUTING 链,被跳转到 DOCKER 链
- DOCKER 链命中 DNAT 规则,目标地址被改写成容器 IP
172.17.0.2:80 - 内核按新目标地址路由,经 FORWARD 链放行,送达容器
- 回程流量做反向地址转换,客户端看到的来源仍是宿主机
容器访问外网是反方向的 NAT:数据包出宿主机时,POSTROUTING 链的 MASQUERADE 规则把源地址改成宿主机 IP。
iptables -t nat -L POSTROUTING -n
MASQUERADE all -- 172.17.0.0/16 0.0.0.0/0
DOCKER 链的规则由 Docker 自动维护,不要手工增删改。手动改完当时可能生效,容器或 Docker 服务一重启就被覆盖,还容易留下隐患。端口不通时先查规则在不在,而不是动手改。
网络故障排查
容器网络出问题,按「从里到外」的顺序排查:先看容器内,再看宿主机,最后看外部防火墙。
第一步:容器内能否出网
# 先 ping 一个公网 IP,排除 DNS 干扰
# nginx 镜像默认不带 ping,用 busybox 临时容器共享 web 的网络命名空间来测
docker run --rm --network container:web busybox ping -c 2 223.5.5.5
ping 不通:问题在容器网络配置或宿主机 NAT。ping 通 IP 但访问域名失败:跳到第二步,是 DNS 的事。
精简镜像(nginx、alpine 系)里经常没有 ping、curl、ss 这些工具。要么临时装(apt update && apt install -y iputils-ping dnsutils),要么像上面一样用 busybox 共享网络命名空间测,不污染业务容器。
第二步:能否解析 DNS
docker run --rm --network container:web busybox nslookup www.baidu.com
docker exec web cat /etc/resolv.conf
如果 resolv.conf 里的 DNS 服务器不可达,可以在 daemon.json 里给所有容器统一指定:
cat /etc/docker/daemon.json
{
"dns": ["223.5.5.5", "114.114.114.114"]
}
改完要 systemctl restart docker 生效。
第三步:端口映射是否生效
# 容器侧:确认进程确实在监听(镜像里没有 ss 就换 netstat 或先装 iproute2)
docker exec web ss -tln
# 宿主机侧:确认映射关系
docker port web
# 宿主机侧:确认端口在监听
ss -tlnp | grep 8080
# 宿主机本机先自测
curl -v http://127.0.0.1:8080
docker port 显示有映射,但宿主机 ss 查不到监听,说明 docker-proxy 或 iptables 规则出了问题,通常重启 Docker 服务能恢复。
第四步:宿主机 iptables / firewalld 是否拦截
# NAT 规则还在不在
iptables -t nat -L DOCKER -n
# FORWARD 链默认策略是不是 DROP
iptables -L FORWARD -n
# firewalld 状态和放行规则
systemctl status firewalld
firewall-cmd --list-all
FORWARD 默认策略是 DROP 又没有放行规则时,外部流量会卡在宿主机。另外云服务器还要检查安全组有没有放行对应端口,这一步经常被忽略。
容器内出网 → 容器内 DNS → 宿主机端口映射 → 宿主机防火墙 → 云安全组。一层层往外走,每确认一层就排除一类问题,比到处乱试快得多。