瑞克
Published on 2026-07-16 / 0 Visits
0
0

Docker网络与数据

Docker网络与数据 —— 让容器互联互通、数据永不丢失


一、容器网络:让"集装箱"们互相通信

问题场景

小明用Docker分别运行了一个Nginx容器和一个PHP-FPM容器,但Nginx怎么也连不上PHP——它们明明是跑在同一台服务器上的,为什么不能通信?

根因:Docker默认给每个容器分配了独立的网络空间,容器之间就像住在不同房间、没有门牌号的人——互相找不到。

类比:想象一栋宿舍楼(宿主机),每间宿舍(容器)有自己的门牌号和WiFi。如果同学们想要互相串门,需要知道对方的房间号;如果想上网,还需要通过宿舍楼的大门(端口映射)。


二、Docker四大网络模式

2.1 模式总览

# 查看Docker支持的网络模式
docker network ls
网络模式参数类比特点
bridge(默认)--network bridge每间宿舍有独立的WiFi容器有独立IP,通过端口映射访问外部
host--network host住在客厅,共享一切容器直接使用宿主机的网络
none--network none被关在密室里没有网络,完全隔离
container--network container:NAME和室友合住与指定容器共享网络命名空间

2.2 Bridge模式(默认,最常用)

# 默认启动就是bridge模式
docker run -d --name web nginx

# 显式指定
docker run -d --name web --network bridge nginx

工作原理

┌─────────────────────────────────────────────┐
│               宿主机 (Host)                   │
│                                               │
│  ┌─────────────────────────────────┐          │
│  │        docker0 网桥              │          │
│  │     IP: 172.17.0.1/16           │          │
│  │                                   │          │
│  │  ┌──────────┐  ┌──────────┐     │          │
│  │  │ 容器A     │  │ 容器B     │     │          │
│  │  │172.17.0.2│  │172.17.0.3│     │          │
│  │  └──────────┘  └──────────┘     │          │
│  └─────────────────────────────────┘          │
│              ↕ NAT(端口映射)                  │
│         物理网卡 (eth0: 192.168.1.100)         │
└─────────────────────────────────────────────┘

docker0网桥详解

Docker安装后会自动创建一个虚拟网桥 docker0,相当于宿舍楼里的"内部交换机":

# 查看docker0网桥信息
ip addr show docker0
# 输出示例:
# 3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500
#     inet 172.17.0.1/16 scope global docker0

# 查看网桥上的容器连接
brctl show docker0    # 需要安装bridge-utils
# 或使用
bridge link show

# 每个容器启动时,Docker会:
# 1. 创建一对虚拟网卡(veth pair),像一根网线的两端
# 2. 一端插入docker0网桥
# 3. 另一端放入容器内,命名为eth0
# 4. 从172.17.0.0/16中分配一个IP给容器

互动提问:为什么docker0的IP是 172.17.0.1?它是一个B类私有地址段(172.16.0.0/12),不会和大多数家庭/学校网络(192.168.x.x或10.x.x.x)冲突。

2.3 Host模式

docker run -d --name web --network host nginx
# 容器直接使用宿主机的IP和端口
# 访问 http://宿主机IP:80 即可

特点

  • 性能好(没有NAT转换开销)
  • 但端口不能冲突(容器占了80端口,宿主机上其他程序就不能用80)
  • Linux有效,Windows/macOS的Docker Desktop不完全支持

类比:Host模式就像"不住宿舍了,直接住在学校大门口的门面房"——地址就是学校的地址,但没有隔离。

2.4 None模式

docker run -d --name isolated --network none alpine sleep 3600
# 容器内没有任何网络接口(只有lo回环)
docker exec isolated ip addr
# 只有 127.0.0.1(lo),无法访问任何网络

用途:安全敏感的计算任务、纯粹的数据处理容器。

2.5 Container模式

# 先启动一个"主容器"
docker run -d --name main-web nginx

# 新容器共享main-web的网络空间
docker run -d --name sidecar --network container:main-web alpine sleep 3600

# sidecar容器可以访问main-web的所有端口(包括未映射的)
# 但两个容器共享同一个IP

用途:Sidecar模式——主容器跑应用,辅助容器跑日志采集或监控代理。类比:你和室友合住,共用一个门牌号,但各干各的事。


三、自定义网络:给容器建一个"专属小区"

3.1 为什么需要自定义网络?

问题:默认的bridge网络不支持通过容器名直接访问其他容器。如果你有10个微服务容器,每次通信都要记IP地址,太不方便。

解决:创建自定义网络,自动启用内置DNS解析——容器名就是主机名!

3.2 创建自定义网络

# 创建桥接网络(最常用)
docker network create my-app-net

# 指定子网和网关
docker network create \
  --driver bridge \
  --subnet 10.10.0.0/24 \
  --gateway 10.10.0.1 \
  my-custom-net

# 指定IP地址范围
docker network create \
  --driver bridge \
  --subnet 10.20.0.0/16 \
  --ip-range 10.20.1.0/24 \
  my-range-net

# 查看网络详情
docker network inspect my-app-net

# 查看所有网络
docker network ls

3.3 在自定义网络中使用容器

# 启动MySQL容器
docker run -d \
  --name mysql-server \
  --network my-app-net \
  -e MYSQL_ROOT_PASSWORD=123456 \
  mysql:8.0

# 启动Web容器,通过容器名直接连接MySQL
docker run -d \
  --name my-web \
  --network my-app-net \
  my-webapp:latest

# 在my-web容器内,可以直接用 "mysql-server" 作为主机名连接数据库
docker exec my-web ping mysql-server
# PING mysql-server (10.10.0.2): 56 data bytes
# 64 bytes from 10.10.0.2: seq=0 ttl=64 time=0.085 ms

核心优势:自定义网络中的容器可以通过容器名互相发现,无需记IP!

3.4 网络管理命令

# 将运行中的容器加入网络
docker network connect my-app-net existing-container

# 将容器从网络断开
docker network disconnect my-app-net existing-container

# 删除网络
docker network rm my-app-net

# 清理所有未使用的网络
docker network prune

互动提问:如果两个容器在不同的自定义网络中,它们能互相通信吗?(答案:默认不能,除非同时连接到同一个网络,或者通过宿主机端口映射间接通信。)


四、端口映射:让外面的世界找到容器

4.1 端口映射详解

# -p 宿主机端口:容器端口(指定协议)
docker run -d -p 8080:80 nginx               # TCP(默认)
docker run -d -p 8080:80/udp nginx           # UDP
docker run -d -p 8080:80/tcp -p 8080:80/udp nginx  # 同时TCP和UDP

# 绑定到特定IP(只允许内网访问)
docker run -d -p 192.168.1.100:8080:80 nginx

# -P 大写P:随机映射所有EXPOSE的端口
docker run -d -P nginx
docker ps  # 查看随机分配的端口号
# 输出类似:0.0.0.0:49153->80/tcp

# 映射多个端口
docker run -d -p 8080:80 -p 8443:443 nginx

# 映射端口范围
docker run -d -p 8000-8010:8000-8010 myapp

4.2 端口映射原理

外部请求 → 宿主机:8080 → iptables DNAT规则 → 容器:80

     ┌──────────┐                    ┌──────────┐
     │  浏览器   │ ── 192.168.1.100  │ 宿主机    │
     │          │    :8080          │          │
     └──────────┘                   │ iptables │──→ ┌──────────┐
                                     │  DNAT   │    │ 容器      │
                                     └──────────┘    │ 172.17.  │
                                                      │ 0.2:80  │
                                                      └──────────┘
# 查看端口映射的iptables规则
sudo iptables -t nat -L -n | grep docker

5.1 --link(旧方式,不推荐)

# 问题:早期Docker没有自定义网络,容器间通信靠--link
docker run -d --name mysql-server mysql:8.0
docker run -d --name web --link mysql-server:db nginx
# web容器内的/etc/hosts会添加一行:172.17.0.2 db

# 缺点:
# 1. 必须在容器启动时指定,不能动态添加
# 2. 容器重启后IP可能变化,hosts文件不一定更新
# 3. 只支持默认的bridge网络

5.2 自定义网络DNS(推荐方式)

# 创建自定义网络
docker network create app-net

# 启动容器
docker run -d --name mysql-server --network app-net mysql:8.0
docker run -d --name web --network app-net nginx

# web容器内可以直接用 mysql-server 作为主机名
docker exec web ping mysql-server
# Docker内置DNS服务器自动解析容器名到IP

对比

特性--link自定义网络DNS
动态发现否(静态hosts文件)是(内置DNS)
支持默认bridge否(仅自定义网络)
容器重启后自动更新
网络隔离支持
推荐程度已废弃官方推荐

六、跨主机网络:让不同服务器的容器通信

问题场景

学校有两台服务器(Server-A和Server-B),分别运行着Docker容器。如何让Server-A上的容器访问Server-B上的数据库?

6.1 方案对比

方案原理适用场景复杂度
OverlayDocker Swarm原生支持小型集群
FlannelCoreOS开发,基于etcdKubernetes默认方案
CalicoBGP路由,纯三层方案大型Kubernetes集群

6.2 Overlay网络简介

# Docker Swarm模式下创建overlay网络
docker swarm init
docker network create -d overlay my-overlay-net

# 不同主机上的容器加入同一overlay网络后,可以直接通过容器名通信
# 底层使用VXLAN封装,跨主机的容器像在同一个二层网络中

原理类比:Overlay就像在两个宿舍楼之间建了一条"空中走廊",虽然物理上隔着一条街,但走起来就像在同一栋楼。

6.3 Flannel简介

Server-A                          Server-B
┌────────────┐                    ┌────────────┐
│ 容器 10.1. │    Flannel隧道     │ 容器 10.1. │
│  1.2       │ ════════════════ │  2.3       │
└────────────┘                    └────────────┘
     ↕                                  ↕
  flanneld ←─── etcd ───→ flanneld
(每个节点分配一个子网,通过封装实现跨节点通信)

6.4 Calico简介

Calico不使用隧道封装,而是利用BGP协议直接在三层路由:

Server-A (BGP Peer)  ←──BGP路由──→  Server-B (BGP Peer)
  容器网段 10.1.1.0/24                 容器网段 10.1.2.0/24

优点:无封装开销,性能好;支持细粒度网络策略
缺点:需要BGP支持,配置较复杂

互动提问:如果你的学校只有3台服务器,跑几十个容器,你会选择哪种跨主机方案?(提示:考虑复杂度和实际需求,Overlay可能最合适。)


七、数据卷:让容器数据"活"下来

问题场景

小明用Docker运行了一个MySQL数据库容器,辛辛苦苦录入了几千条学生成绩数据。一天容器意外被删除了——数据全没了!

根因:容器的文件系统是临时的("易失性"),容器删除后,里面写入的所有数据都会消失。

类比:容器就像一个"移动板房"——板房拆了,里面的家具(数据)也跟着没了。我们需要一个"独立的仓库"来存放重要物品。

7.1 三种数据挂载方式对比

方式命令参数类比特点
Volume(数据卷)-v mydata:/path专业仓库Docker管理,最推荐
Bind Mount(绑定挂载)-v /host/path:/path自家车库直接映射宿主机目录
tmpfs--tmpfs /path临时背包存在内存中,容器停止即消失

7.2 Volume(Docker数据卷)——最推荐

# 创建数据卷
docker volume create mysql-data

# 查看所有数据卷
docker volume ls

# 查看数据卷详情(在宿主机的实际存储位置)
docker volume inspect mysql-data
# 输出中 Mountpoint 显示实际路径,通常是:
# /var/lib/docker/volumes/mysql-data/_data

# 使用数据卷运行容器
docker run -d \
  --name my-mysql \
  -v mysql-data:/var/lib/mysql \
  -e MYSQL_ROOT_PASSWORD=123456 \
  mysql:8.0

# 即使容器被删除,数据卷中的数据依然保留
docker rm -f my-mysql
docker run -d \
  --name my-mysql-new \
  -v mysql-data:/var/lib/mysql \
  -e MYSQL_ROOT_PASSWORD=123456 \
  mysql:8.0
# 新容器挂载同一个数据卷,数据完好无损!

# 删除数据卷(谨慎!数据会真正丢失)
docker volume rm mysql-data

# 清理所有未使用的数据卷
docker volume prune

7.3 Bind Mount(绑定挂载)

# 将宿主机的目录直接映射到容器
docker run -d \
  --name my-web \
  -p 8080:80 \
  -v /home/user/my-website:/usr/share/nginx/html \
  nginx

# 修改宿主机文件,容器内立刻生效(开发调试利器)
echo "<h1>Hello</h1>" > /home/user/my-website/index.html
# 浏览器访问 http://localhost:8080 立刻看到变化

# 只读挂载(容器不能修改挂载的文件)
docker run -d \
  -v /home/user/config:/etc/nginx/conf.d:ro \
  nginx

# 注意:Bind Mount的宿主机路径必须是绝对路径

7.4 tmpfs(内存临时存储)

# 创建临时存储(存在内存中,不写入磁盘)
docker run -d \
  --name temp-app \
  --tmpfs /tmp:rw,size=100m \
  myapp:latest

# 适用场景:临时文件、密钥、缓存等不需要持久化的数据
# 容器停止后,tmpfs中的数据立即消失

7.5 三种方式详细对比

┌──────────────────────────────────────────────────────────┐
│                    宿主机 (Host)                          │
│                                                          │
│  ┌─ Volume ─────────┐  ┌─ Bind Mount ──┐                │
│  │ /var/lib/docker/  │  │ /home/user/   │                │
│  │   volumes/mydata/ │  │   my-website/ │                │
│  │   (Docker管理)    │  │   (用户管理)   │                │
│  └────────┬──────────┘  └───────┬───────┘                │
│           │                     │                         │
│  ┌────────┴─────────────────────┴────────────────┐       │
│  │                 容器 (Container)                │       │
│  │  ┌─────────┐  ┌─────────┐  ┌─────────┐       │       │
│  │  │ /data   │  │ /html   │  │ /tmp    │       │       │
│  │  │(Volume) │  │(Bind)   │  │(tmpfs)  │       │       │
│  │  └─────────┘  └─────────┘  └─────────┘       │       │
│  └────────────────────────────────────────────────┘       │
└──────────────────────────────────────────────────────────┘
对比维度VolumeBind Mounttmpfs
存储位置Docker管理的目录宿主机任意目录内存
持久性容器删除后保留容器删除后保留容器停止即消失
性能最好(内存速度)
可移植性高(Docker API管理)低(依赖宿主机路径)
多容器共享支持支持不支持
推荐场景数据库、应用数据开发调试、配置文件临时文件、密钥

八、数据持久化实战:MySQL容器

8.1 完整的MySQL容器部署方案

# ===== 第一步:创建数据卷和配置目录 =====
docker volume create mysql-data
mkdir -p /opt/mysql/conf /opt/mysql/init

# ===== 第二步:准备自定义配置文件 =====
cat > /opt/mysql/conf/my.cnf << 'EOF'
[mysqld]
# 字符集设置为UTF-8(支持中文)
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
# 最大连接数
max_connections=200
# InnoDB缓冲池大小
innodb_buffer_pool_size=256M
EOF

# ===== 第三步:准备初始化SQL =====
cat > /opt/mysql/init/init.sql << 'EOF'
CREATE DATABASE IF NOT EXISTS school DEFAULT CHARSET utf8mb4;
USE school;
CREATE TABLE IF NOT EXISTS students (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(50) NOT NULL,
    grade VARCHAR(10),
    score DECIMAL(5,2),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO students (name, grade, score) VALUES
    ('张三', '高三(1)班', 95.5),
    ('李四', '高三(2)班', 88.0),
    ('王五', '高三(1)班', 92.3);
EOF

# ===== 第四步:启动MySQL容器 =====
docker run -d \
  --name mysql-server \
  -p 3306:3306 \
  -v mysql-data:/var/lib/mysql \
  -v /opt/mysql/conf/my.cnf:/etc/mysql/conf.d/my.cnf:ro \
  -v /opt/mysql/init:/docker-entrypoint-initdb.d:ro \
  -e MYSQL_ROOT_PASSWORD=School2024! \
  -e MYSQL_DATABASE=school \
  mysql:8.0

# ===== 第五步:等待初始化完成并验证 =====
# 查看日志,等待出现 "ready for connections"
docker logs -f mysql-server

# 连接数据库验证
docker exec -it mysql-server mysql -uroot -p'School2024!' -e "
USE school;
SELECT * FROM students;
"

# ===== 第六步:模拟灾难恢复 =====
# 记录当前数据
docker exec mysql-server mysql -uroot -p'School2024!' -e "
USE school;
INSERT INTO students (name, grade, score) VALUES ('赵六', '高三(2)班', 91.0);
SELECT COUNT(*) AS total FROM students;
"

# "意外"删除容器
docker rm -f mysql-server

# 用同一数据卷重建容器
docker run -d \
  --name mysql-server \
  -p 3306:3306 \
  -v mysql-data:/var/lib/mysql \
  -v /opt/mysql/conf/my.cnf:/etc/mysql/conf.d/my.cnf:ro \
  -e MYSQL_ROOT_PASSWORD=School2024! \
  mysql:8.0

# 验证数据完好
docker exec mysql-server mysql -uroot -p'School2024!' -e "
USE school;
SELECT * FROM students;
"
# 数据完整保留,包括后来插入的"赵六"!

8.2 数据备份方案

# 方式1:使用mysqldump逻辑备份
docker exec mysql-server mysqldump -uroot -p'School2024!' --all-databases > backup_$(date +%Y%m%d).sql

# 方式2:直接备份数据卷
docker run --rm \
  -v mysql-data:/source:ro \
  -v /backup:/backup \
  alpine tar czf /backup/mysql-data_$(date +%Y%m%d).tar.gz -C /source .

# 恢复数据卷
docker run --rm \
  -v mysql-data:/target \
  -v /backup:/backup \
  alpine tar xzf /backup/mysql-data_20240101.tar.gz -C /target

互动提问docker-entrypoint-initdb.d 目录中的SQL脚本什么时候执行?答案是只在数据目录为空时(即首次初始化时),所以重建容器不会重复执行初始化脚本。


九、数据卷容器:共享数据的"中转站"

9.1 概念与使用

# 创建一个专门管理数据卷的"数据容器"
docker create \
  --name db-data-container \
  -v /dbdata \
  busybox

# 其他容器通过 --volumes-from 共享这个数据卷
docker run -d \
  --name app1 \
  --volumes-from db-data-container \
  myapp:latest

docker run -d \
  --name app2 \
  --volumes-from db-data-container \
  myapp:latest

# app1和app2共享同一个/dbdata目录
# 注意:--volumes-from是早期方案,现在推荐使用命名卷或自定义网络

十、资源限制:防止容器"霸占"系统资源

10.1 问题场景

一个"写bug"的应用程序内存泄漏,容器吃光了宿主机的所有内存,导致其他容器甚至宿主机本身都崩溃了。

10.2 内存限制

# 限制容器最多使用512MB内存
docker run -d --name limited-app --memory=512m myapp:latest

# 限制内存+swap总共1GB
docker run -d --name limited-app \
  --memory=512m \
  --memory-swap=1g \
  myapp:latest

# 查看容器内存使用情况
docker stats --no-stream limited-app

10.3 CPU限制

# 限制容器使用1个CPU核心
docker run -d --name limited-app --cpus=1 myapp:latest

# 限制容器使用0.5个CPU核心
docker run -d --name limited-app --cpus=0.5 myapp:latest

# 指定容器绑定到特定CPU核心(核心0和核心1)
docker run -d --name limited-app --cpuset-cpus="0,1" myapp:latest

# CPU权重(默认1024,值越大优先级越高)
docker run -d --name high-priority --cpu-shares=2048 myapp:latest
docker run -d --name low-priority --cpu-shares=512 myapp:latest

# 综合示例:限制2核CPU + 1GB内存
docker run -d \
  --name production-app \
  --cpus=2 \
  --memory=1g \
  --memory-swap=2g \
  myapp:latest

10.4 其他资源限制

# 限制进程数(防止fork炸弹)
docker run -d --pids-limit=100 myapp:latest

# 限制磁盘IO(读取速度限制为10MB/s)
docker run -d --device-read-bps /dev/sda:10mb myapp:latest

# 重启策略
docker run -d --restart=always myapp:latest      # 总是重启
docker run -d --restart=on-failure:3 myapp:latest # 失败时最多重启3次
docker run -d --restart=unless-stopped myapp:latest # 除非手动停止,否则总是重启

互动提问:如果给容器设置了 --memory=256m,但应用实际需要512MB会怎样?容器会被OOM Killer终止(Out of Memory),就像你的水杯只有256ml容量,却想倒512ml水——必然溢出。


十一、底层原理:cgroup和namespace

11.1 Namespace —— 隔离("给每个人一个独立房间")

Namespace是Linux内核的一种资源隔离机制,让容器内的进程"以为"自己独占整个系统。

Namespace类型隔离什么类比
PID进程编号每个房间有自己的门牌号系统
Network网络栈(IP/端口/路由表)每个房间有自己的网线和WiFi
Mount文件系统挂载点每个房间有自己的文件柜
UTS主机名和域名每个房间可以挂不同的门牌
IPC进程间通信每个房间内部的对讲机系统
User用户和组ID每个房间内的"管理员"在外部只是普通住户
# 查看当前系统的namespace
ls -la /proc/self/ns/

# 查看容器的namespace
docker inspect --format '{{.State.Pid}}' my-container
ls -la /proc/<PID>/ns/

# 对比宿主机和容器的PID namespace
# 宿主机上可以看到所有进程
ps aux | wc -l
# 容器内只能看到容器自己的进程
docker exec my-container ps aux | wc -l

通俗讲解:想象一座大酒店(宿主机),有100个房间(容器)。每个房间:

  • 有自己的门牌号(PID namespace)—— 房间内的1号不是酒店的1号
  • 有自己的电话线路(Network namespace)—— 房间内的电话号码与酒店总机不同
  • 有自己的储物柜(Mount namespace)—— 看不到其他房间的柜子
  • 可以取自己的房间名(UTS namespace)—— "豪华套房"而非"3楼第5间"

11.2 Cgroup —— 限制("给每个人规定能用的资源量")

Cgroup(Control Groups)是Linux内核的资源管控机制,限制一组进程能使用的系统资源。

子系统控制什么Docker参数
memory内存使用量--memory
cpuCPU使用比例--cpu-shares
cpusetCPU核心绑定--cpuset-cpus
blkio磁盘IO--device-read-bps
pids进程数量--pids-limit
# 查看容器的cgroup配置
# Docker通过cgroup文件控制容器资源
cat /sys/fs/cgroup/memory/docker/<容器ID>/memory.limit_in_bytes
cat /sys/fs/cgroup/cpu/docker/<容器ID>/cpu.cfs_quota_us

# cgroup v2 路径有所不同
cat /sys/fs/cgroup/system.slice/docker-<容器ID>.scope/memory.max

通俗讲解:还是酒店类比——

  • Cgroup就是酒店的"管理规定":
    • 每个房间最多用2度电(--memory=2g
    • 每个房间空调最多开2台(--cpus=2
    • 每个房间最多住3人(--pids-limit=100
  • 即使酒店有100度电、50台空调,每个房间也只能用规定额度
  • 超额的房间会被"限电"(OOM Kill或CPU Throttle)

11.3 Namespace + Cgroup = 容器的"两翼"

容器 = Namespace(隔离视图) + Cgroup(限制资源)

Namespace说:"你看到的是一整个世界"(但其实只是隔离的一小块)
Cgroup说:"你最多只能用这么多资源"(防止你影响别人)

两者配合,才实现了"安全隔离 + 资源可控"的容器化。

十二、Overlay文件系统:容器的"透明覆层"

12.1 Copy-on-Write(写时复制)原理

Docker使用OverlayFS作为默认的存储驱动,实现了镜像层和容器层的分离。

┌──────────────────────────────────┐
│  容器可写层 (Upper Dir)           │ ← 容器运行时的修改都写在这里
├──────────────────────────────────┤
│  镜像只读层 (Lower Dir) Layer 3   │ ← 镜像的最上层
├──────────────────────────────────┤
│  镜像只读层 (Lower Dir) Layer 2   │ ← 镜像中间层
├──────────────────────────────────┤
│  镜像只读层 (Lower Dir) Layer 1   │ ← 镜像基础层
└──────────────────────────────────┘

写时复制(CoW)原理

  1. 读取文件:从上往下逐层查找,找到即返回
  2. 修改文件:第一次修改时,先把文件从只读层"拷贝"到可写层,再在可写层修改
  3. 删除文件:在可写层创建一个"whiteout"标记,遮盖下层的文件
  4. 新建文件:直接写在可写层

类比:想象你在图书馆借了一本教科书(只读层),你需要做笔记但不想在原书上写——于是你用了一张透明覆膜(可写层)盖在书页上,笔记写在覆膜上,原书完好无损。

12.2 实践验证

# 查看Docker使用的存储驱动
docker info | grep "Storage Driver"

# 查看容器的overlay挂载信息
docker inspect <容器ID> --format '{{.GraphDriver.Data}}'

# 查看容器可写层的大小
docker ps -s
# SIZE列显示可写层大小,virtual SIZE是总大小

# 验证CoW:修改容器内文件
docker run -d --name test-cow ubuntu:22.04 sleep 3600
docker exec test-cow bash -c "echo '修改了' >> /etc/hostname"
# 此时/etc/hostname被拷贝到可写层并修改
# 原始镜像层中的/etc/hostname未受影响

互动提问:为什么10个基于同一个Ubuntu镜像的容器不会占用10倍空间?因为只读层是共享的,每个容器只有自己的可写层是独立的——这就是"层"的复用机制。


十三、网络排查工具箱

当容器网络出问题时,按照以下流程排查:

# ===== 1. 基础连通性 =====
# 容器能否ping通外网?
docker exec my-container ping -c 3 8.8.8.8

# 容器能否解析域名?
docker exec my-container ping -c 3 baidu.com

# ===== 2. 网络配置检查 =====
# 查看容器的IP和网关
docker exec my-container ip addr
docker exec my-container ip route

# 查看容器的DNS配置
docker exec my-container cat /etc/resolv.conf

# ===== 3. 端口与连通性 =====
# 从宿主机测试容器端口
curl -v http://localhost:8080
telnet localhost 8080
nc -zv localhost 8080

# 容器间连通性测试
docker exec container-a ping container-b
docker exec container-a curl http://container-b:80

# ===== 4. 深入排查 =====
# 查看Docker网桥状态
ip addr show docker0
bridge link show

# 查看iptables规则
sudo iptables -t nat -L -n
sudo iptables -L FORWARD -n

# 查看容器的veth网卡对
docker inspect <容器ID> --format '{{.NetworkSettings.Networks}}'
ip link show | grep veth

# 抓包分析
docker exec my-container sh -c "apt-get update && apt-get install -y tcpdump && tcpdump -i eth0 -n port 80"

# ===== 5. DNS排查 =====
# 测试Docker内置DNS
docker exec my-container nslookup other-container 127.0.0.11
# 127.0.0.11 是Docker自定义网络的内置DNS服务器地址

运维常用命令精选

# === 网络管理 ===
docker network ls                                  # 列出所有网络
docker network create --subnet 10.10.0.0/24 mynet  # 创建自定义网络
docker network inspect mynet                       # 查看网络详情
docker network connect mynet my-container          # 容器加入网络
docker network disconnect mynet my-container       # 容器离开网络
docker network prune -f                            # 清理未使用网络

# === 数据卷管理 ===
docker volume ls                                   # 列出所有数据卷
docker volume create mydata                        # 创建数据卷
docker volume inspect mydata                       # 查看数据卷详情
docker volume prune -f                             # 清理未使用数据卷
docker system df -v                                # 查看详细的磁盘使用

# === 资源监控 ===
docker stats                                       # 实时资源使用(Ctrl+C退出)
docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}"  # 格式化输出
docker top my-container                            # 容器内进程列表

# === 排查利器 ===
docker exec -it my-container sh -c "cat /etc/resolv.conf"  # DNS配置
docker exec -it my-container ip addr               # 网络接口
docker inspect --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' my-container  # 获取容器IP
docker logs --since 5m my-container                # 最近5分钟日志

趣味命令

# 1. 看看Docker给你创建了多少虚拟网卡
ip link show | grep -c veth
# 每个运行中的容器都有一对veth网卡

# 2. 在容器内看看"你是谁"
docker run --rm alpine sh -c "whoami && hostname && cat /etc/hostname"

# 3. 测试容器的网络隔离
docker run -d --name iso-test --network none alpine sleep 300
docker exec iso-test ping -c 2 baidu.com
# 100% packet loss —— 完全隔离!

# 4. 看看容器的cgroup限制
docker run --rm --memory=128m alpine cat /sys/fs/cgroup/memory.max 2>/dev/null || \
docker run --rm --memory=128m alpine cat /sys/fs/cgroup/memory/memory.limit_in_bytes 2>/dev/null

# 5. 查看Docker网桥上连着多少容器
bridge link show 2>/dev/null || ip link show type veth

# 6. 容器里的"我来自哪里"
docker run --rm alpine sh -c "cat /etc/os-release | head -3"

# 7. 一键查看所有容器的IP地址
docker inspect -f '{{.Name}} - {{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $(docker ps -aq)

# 8. 画一个ASCII网络拓扑图
docker run --rm alpine sh -c "
echo '=== Docker Network Topology ==='
echo '    [Internet]'
echo '        |'
echo '    [Host eth0]'
echo '        |'
echo '    [iptables NAT]'
echo '        |'
echo '    [docker0 bridge]'
echo '        |'
echo '   ┌────┼────┐'
echo '[Container1] [Container2] [Container3]'
"

小结:Docker网络让容器能够互联互通,自定义网络配合DNS解析是最推荐的方案。数据卷解决了容器数据持久化的问题,MySQL实战展示了完整的部署与恢复流程。资源限制(cgroup)和进程隔离(namespace)是容器的两大底层支柱,而Overlay文件系统实现了高效的层式存储。


Comment