03. Docker 数据管理与网络
3.1 Docker 数据管理
3.1.1 为什么需要数据持久化?
容器的特性:
- 容器是临时的 、无状态的
- 容器删除后,内部数据会丢失
- 容器重启后,数据会重置到镜像初始状态
需要持久化的场景:
- 数据库数据(MySQL、PostgreSQL、MongoDB)
- 应用日志
- 上传的文件
- 配置文件
- 开发时的代码热重载
3.1.2 三种数据挂载方式
Docker 提供三种数据挂载方式:
┌─────────────────────────────────────────────┐
│ Docker Host │
│ │
│ ┌────────────┐ ┌──────────────┐ ┌──────┐│
│ │ Volume │ │ Bind Mount │ │tmpfs││
│ │ (推荐方式) │ │ (主机目录) │ │(内存)││
│ └──────┬─────┘ └──────┬───────┘ └───┬──┘│
│ │ │ │ │
│ ┌──────▼───────────────▼───────────────▼──┐│
│ │ Container ││
│ │ /var/lib/mysql /app/code /tmp ││
│ └─────────────────────────────────────────┘│
└─────────────────────────────────────────────┘
对比表:
| 特性 | Volume | Bind Mount | tmpfs |
|---|---|---|---|
| 存储位置 | Docker管理目录 | 主机任意路径 | 内存 |
| 管理方式 | Docker命令管理 | 直接文件系统 | 自动清理 |
| 性能 | 优秀 | 依赖主机FS | 最快 |
| 适用场景 | 数据库、日志 | 开发、配置文件 | 临时数据、缓存 |
| 可移植性 | 高 | 低 | 高 |
3.2 Volume(数据卷)
3.2.1 Volume 的优势
- Docker 统一管理,易于备份和迁移
- 可以在多个容器间共享
- 性能优秀(不经过 UnionFS)
- 可以使用 Volume 驱动程序(支持远程主机、云存储)
- Windows 和 Linux 容器都支持
- 新建 Volume 可以预填充数据
3.2.2 创建和管理 Volume
创建数据卷:
bash
# 创建一个命名 Volume
docker volume create my-volume
# 创建并指定驱动
docker volume create --driver local my-volume
# 创建时设置选项
docker volume create \
--driver local \
--opt type=nfs \
--opt o=addr=192.168.1.1,rw \
--opt device=:/path/to/dir \
my-nfs-volume
查看 Volume:
bash
# 列出所有数据卷
docker volume ls
# 查看数据卷详细信息
docker volume inspect my-volume
# 输出示例:
[
{
"CreatedAt": "2024-01-01T10:00:00Z",
"Driver": "local",
"Mountpoint": "/var/lib/docker/volumes/my-volume/_data",
"Name": "my-volume",
"Options": {},
"Scope": "local"
}
]
删除 Volume:
bash
# 删除指定数据卷
docker volume rm my-volume
# 删除多个数据卷
docker volume rm volume1 volume2
# 清理未使用的数据卷(危险操作!)
docker volume prune
# 查看将被删除的数据卷
docker volume prune --dry-run
3.2.3 使用 Volume
方式 1:运行时创建匿名 Volume
bash
# -v 指定容器内路径,Docker自动创建匿名卷
docker run -d --name mysql-test \
-v /var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=secret \
mysql:8.0
# 查看自动创建的卷
docker volume ls
# DRIVER VOLUME NAME
# local a1b2c3d4e5f6... (匿名卷)
方式 2:运行时指定命名 Volume
bash
# -v volume-name:container-path
docker run -d --name mysql-prod \
-v mysql-data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=secret \
mysql:8.0
# 推荐:使用 --mount(更明确)
docker run -d --name mysql-prod \
--mount source=mysql-data,target=/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=secret \
mysql:8.0
方式 3:在 Dockerfile 中声明
dockerfile
FROM mysql:8.0
# 声明 Volume 挂载点
VOLUME /var/lib/mysql
# 运行时 Docker 会自动创建匿名卷
3.2.4 Volume 实战案例
案例 1:MySQL 数据持久化
bash
# 1. 创建数据卷
docker volume create mysql-data
# 2. 运行 MySQL 容器
docker run -d \
--name mysql \
-v mysql-data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=password123 \
-e MYSQL_DATABASE=myapp \
-p 3306:3306 \
mysql:8.0
# 3. 连接并创建数据
docker exec -it mysql mysql -uroot -ppassword123 -e "
USE myapp;
CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR(100));
INSERT INTO users VALUES (1, 'Alice'), (2, 'Bob');
"
# 4. 删除容器(数据不会丢失)
docker rm -f mysql
# 5. 用同样的数据卷重新创建容器
docker run -d \
--name mysql-new \
-v mysql-data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=password123 \
-p 3306:3306 \
mysql:8.0
# 6. 验证数据依然存在
docker exec -it mysql-new mysql -uroot -ppassword123 -e "
SELECT * FROM myapp.users;
"
# +----+-------+
# | id | name |
# +----+-------+
# | 1 | Alice |
# | 2 | Bob |
# +----+-------+
案例 2:容器间共享数据
bash
# 1. 创建共享数据卷
docker volume create shared-data
# 2. 容器 A 写入数据
docker run -d --name writer \
-v shared-data:/data \
ubuntu \
bash -c "echo 'Hello from container A' > /data/message.txt && sleep 3600"
# 3. 容器 B 读取数据
docker run --rm \
-v shared-data:/data \
ubuntu \
cat /data/message.txt
# 输出:Hello from container A
案例 3:备份和恢复数据
bash
# 备份数据卷
docker run --rm \
-v mysql-data:/source:ro \
-v $(pwd):/backup \
ubuntu \
tar czf /backup/mysql-backup.tar.gz -C /source .
# 恢复数据卷
# 1. 创建新的数据卷
docker volume create mysql-data-restored
# 2. 解压备份到新卷
docker run --rm \
-v mysql-data-restored:/target \
-v $(pwd):/backup \
ubuntu \
tar xzf /backup/mysql-backup.tar.gz -C /target
3.3 Bind Mount(绑定挂载)
3.3.1 什么是 Bind Mount?
Bind Mount 将主机的任意目录或文件直接挂载到容器中。
特点:
- 直接使用主机文件系统
- 主机和容器实时同步
- 适合开发环境(代码热重载)
- 依赖主机目录结构(可移植性差)
- 容器可以修改主机文件(安全风险)
3.3.2 使用 Bind Mount
基本语法:
bash
# -v 主机路径:容器路径[:选项]
docker run -v /host/path:/container/path myimage
# --mount(推荐,更明确)
docker run --mount type=bind,source=/host/path,target=/container/path myimage
示例:
bash
# 挂载当前目录到容器
docker run -d --name nginx-dev \
-v $(pwd)/html:/usr/share/nginx/html:ro \
-p 8080:80 \
nginx:alpine
# Windows PowerShell 使用
docker run -d --name nginx-dev `
-v ${PWD}/html:/usr/share/nginx/html:ro `
-p 8080:80 `
nginx:alpine
# 使用 --mount(更安全,目录不存在会报错)
docker run -d --name nginx-dev \
--mount type=bind,source=$(pwd)/html,target=/usr/share/nginx/html,readonly \
-p 8080:80 \
nginx:alpine
选项说明:
ro(readonly):只读模式rw(read-write):读写模式(默认)z/Z:SELinux 标签(Linux)
3.3.3 Bind Mount 实战
场景 1:Web 开发实时预览
bash
# 项目结构
my-website/
├── index.html
├── style.css
└── app.js
# 运行 NGINX 并挂载代码
docker run -d \
--name dev-server \
-v $(pwd):/usr/share/nginx/html:ro \
-p 8080:80 \
nginx:alpine
# 修改本地文件,浏览器刷新即可看到变化
场景 2:Node.js 开发热重载
bash
# 运行 Node.js 应用,挂载代码目录
docker run -d \
--name node-dev \
-v $(pwd):/app \
-v /app/node_modules \
-w /app \
-p 3000:3000 \
node:18 \
npm run dev
# 注意:-v /app/node_modules 防止覆盖容器内的依赖
场景 3:挂载配置文件
bash
# 自定义 NGINX 配置
docker run -d \
--name nginx-custom \
-v $(pwd)/nginx.conf:/etc/nginx/nginx.conf:ro \
-p 8080:80 \
nginx:alpine
场景 4:挂载 Docker Socket(高级用法)
bash
# 允许容器控制 Docker(如 Portainer、Docker-in-Docker)
docker run -d \
--name portainer \
-v /var/run/docker.sock:/var/run/docker.sock \
-p 9000:9000 \
portainer/portainer-ce
3.4 tmpfs Mount(临时文件系统)
3.4.1 tmpfs 特点
- 存储在内存中,不写入磁盘
- 性能极快
- 容器停止后数据消失
- 安全:敏感数据不持久化
适用场景:
- 临时缓存
- 敏感数据(密码、Token)
- 高频读写的临时文件
3.4.2 使用 tmpfs
bash
# 基本用法
docker run -d \
--tmpfs /app/cache \
myimage
# 指定大小和权限
docker run -d \
--tmpfs /app/cache:rw,size=100m,mode=1777 \
myimage
# 使用 --mount(推荐)
docker run -d \
--mount type=tmpfs,target=/app/cache,tmpfs-size=100m \
myimage
实战示例:
bash
# Redis 使用 tmpfs 存储临时数据
docker run -d \
--name redis-cache \
--tmpfs /data:rw,size=500m \
-p 6379:6379 \
redis:alpine \
redis-server --save ""
3.5 Docker 网络
3.5.1 Docker 网络模型
Docker 提供五种网络模式:
1. bridge(桥接网络)- 默认,容器间可通信
2. host(主机网络) - 与主机共享网络栈
3. none(无网络) - 容器无网络连接
4. container - 共享其他容器的网络
5. overlay(覆盖网络)- 跨主机容器通信(Swarm)
3.5.2 Bridge 网络(默认)
架构图:
┌──────────────────────────────────────────┐
│ Docker Host (192.168.1.100) │
│ │
│ ┌────────────────────────────────────┐ │
│ │ docker0 Bridge (172.17.0.1) │ │
│ │ │ │
│ │ ┌──────────┐ ┌──────────┐ │ │
│ │ │Container1│ │Container2│ │ │
│ │ │172.17.0.2│ │172.17.0.3│ │ │
│ │ └──────────┘ └──────────┘ │ │
│ └────────────────────────────────────┘ │
│ │ │
│ ┌─────▼──────┐ │
│ │ eth0 │ │
│ │ (物理网卡) │ │
│ └────────────┘ │
└──────────────────────────────────────────┘
查看默认网络:
bash
# 列出所有网络
docker network ls
# 输出:
# NETWORK ID NAME DRIVER SCOPE
# abc123 bridge bridge local
# def456 host host local
# ghi789 none null local
# 查看 bridge 网络详情
docker network inspect bridge
容器如何使用 bridge:
bash
# 默认连接到 bridge 网络
docker run -d --name web1 nginx
# 查看容器 IP
docker inspect web1 | grep IPAddress
# "IPAddress": "172.17.0.2"
# 容器间可以通过 IP 通信(但不能通过容器名)
docker run --rm alpine ping 172.17.0.2
3.5.3 自定义 Bridge 网络(推荐)
为什么要自定义网络?
- 容器间可通过容器名通信(自动 DNS 解析)
- 更好的隔离性
- 容器可动态连接/断开网络
- 可配置网络参数
创建自定义网络:
bash
# 创建网络
docker network create my-network
# 创建并指定子网
docker network create \
--driver bridge \
--subnet 192.168.100.0/24 \
--gateway 192.168.100.1 \
my-custom-network
# 查看网络详情
docker network inspect my-network
使用自定义网络:
bash
# 创建时指定网络
docker run -d --name web1 --network my-network nginx
docker run -d --name web2 --network my-network nginx
# 容器间通过名称通信
docker exec web1 ping web2
# PING web2 (192.168.100.3): 56 data bytes
# 64 bytes from 192.168.100.3: seq=0 ttl=64 time=0.123 ms
动态连接网络:
bash
# 将运行中的容器连接到网络
docker network connect my-network container-name
# 断开网络
docker network disconnect my-network container-name
# 连接时指定 IP
docker network connect --ip 192.168.100.10 my-network container-name
3.5.4 Host 网络
容器直接使用主机的网络栈,没有网络隔离。
bash
# 使用 host 网络
docker run -d --network host nginx
# 容器内的 80 端口直接映射到主机 80 端口
# 不需要 -p 参数
特点:
- 性能最佳(无 NAT 损耗)
- 端口冲突风险高
- 无网络隔离
使用场景:
- 性能要求极高的应用
- 监控工具(需要访问主机网络接口)
3.5.5 None 网络
容器没有网络接口(除了 loopback)。
bash
docker run -d --network none alpine sleep 3600
# 容器内无法访问外部网络
使用场景:
- 高度安全的隔离环境
- 只需本地计算的任务
3.5.6 Container 网络
共享另一个容器的网络命名空间。
bash
# 创建容器 A
docker run -d --name container-a nginx
# 容器 B 共享容器 A 的网络
docker run -d --name container-b --network container:container-a alpine sleep 3600
# container-b 可以通过 localhost 访问 container-a 的服务
docker exec container-b wget -O- http://localhost:80
使用场景:
- 边车模式(Sidecar Pattern)
- 服务网格(Service Mesh)
3.5.7 端口映射
基本映射:
bash
# 映射单个端口(主机端口:容器端口)
docker run -d -p 8080:80 nginx
# 映射到随机主机端口
docker run -d -p 80 nginx
# 查看映射的端口
docker port container-name
# 80/tcp -> 0.0.0.0:32768
# 映射多个端口
docker run -d -p 8080:80 -p 8443:443 nginx
# 指定协议
docker run -d -p 8080:80/tcp -p 53:53/udp nginx
# 绑定到特定 IP
docker run -d -p 127.0.0.1:8080:80 nginx
3.6 网络实战案例
案例 1:Web 应用 + 数据库
bash
# 1. 创建自定义网络
docker network create webapp-network
# 2. 运行 MySQL 数据库
docker run -d \
--name mysql \
--network webapp-network \
-e MYSQL_ROOT_PASSWORD=secret \
-e MYSQL_DATABASE=webapp \
mysql:8.0
# 3. 运行 Web 应用
docker run -d \
--name web \
--network webapp-network \
-p 8080:8080 \
-e DB_HOST=mysql \
-e DB_USER=root \
-e DB_PASSWORD=secret \
-e DB_NAME=webapp \
my-web-app:latest
# Web 应用通过容器名 "mysql" 连接数据库
案例 2:多层应用架构
bash
# 创建前端网络和后端网络
docker network create frontend
docker network create backend
# 数据库(仅在后端网络)
docker run -d \
--name db \
--network backend \
mysql:8.0
# API 服务(连接两个网络)
docker run -d \
--name api \
--network backend \
my-api:latest
docker network connect frontend api
# Web 前端(仅在前端网络)
docker run -d \
--name web \
--network frontend \
-p 80:80 \
nginx
# 结果:
# - web 可以访问 api
# - web 不能直接访问 db
# - api 可以访问 db
案例 3:服务发现
bash
# 创建网络
docker network create app-network
# 启动多个后端服务
docker run -d --name api-1 --network app-network my-api
docker run -d --name api-2 --network app-network my-api
docker run -d --name api-3 --network app-network my-api
# 启动负载均衡器(使用容器名进行负载均衡)
docker run -d \
--name lb \
--network app-network \
-p 80:80 \
my-load-balancer
3.7 网络故障排查
常用排查命令
bash
# 1. 查看容器网络配置
docker inspect container-name
# 2. 查看容器 IP
docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' container-name
# 3. 进入容器测试网络
docker exec -it container-name sh
# 在容器内执行
ping google.com
curl http://other-container
nslookup other-container
# 4. 查看端口映射
docker port container-name
# 5. 查看网络详情
docker network inspect network-name
常见问题
问题 1:容器无法通过名称通信
bash
# 原因:使用默认 bridge 网络
# 解决:使用自定义网络
docker network create my-network
docker run --network my-network ...
问题 2:端口映射不生效
bash
# 检查容器是否正在监听端口
docker exec container-name netstat -tuln
# 检查防火墙规则
sudo iptables -L -n | grep 8080
问题 3:容器无法访问外网
bash
# 检查 DNS 配置
docker exec container-name cat /etc/resolv.conf
# 手动指定 DNS
docker run --dns 8.8.8.8 --dns 8.8.4.4 myimage