跳到主要内容

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
  • 挂载宿主机上的日志目录
权限与 SELinux 问题

容器内进程默认以 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 容器不支持。

三种挂载方式对比​

对比项VolumeBind Mounttmpfs
存储位置/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 的区别

容器不指定网络时会进默认的 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 流程:

  1. 外部请求到达宿主机 8080 端口
  2. 进入 PREROUTING 链,被跳转到 DOCKER 链
  3. DOCKER 链命中 DNAT 规则,目标地址被改写成容器 IP 172.17.0.2:80
  4. 内核按新目标地址路由,经 FORWARD 链放行,送达容器
  5. 回程流量做反向地址转换,客户端看到的来源仍是宿主机

容器访问外网是反方向的 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 → 宿主机端口映射 → 宿主机防火墙 → 云安全组。一层层往外走,每确认一层就排除一类问题,比到处乱试快得多。