计算机网络 Lab3 容器网络
链路层准备网卡,网络层配置 IP 地址,运行时查表转发
Docker
基本概念
容器化:把应用和它的依赖(代码、运行环境、配置文件、系统库等)一起打包成一个标准化单元的过程。容器化的结果就是容器镜像。容器是镜像的运行实例。
Docker 就是实现容器化的一种工具。
优势:
- 解决环境不一致:一切都打包在一起
- 解决部署慢、依赖冲突:进程级隔离
- 解决资源浪费:容器共享内核资源
工作流程
- 打包:使用
Dockerfile定义应用依赖,执行docker build生成镜像。 - 运输:推送到镜像仓库(如 Docker Hub)
- 运行:使用
docker pull拉取镜像,然后使用docker run启动容器。启动后可以使用 docker 命令进行管理。
路由表
列标题解释
| 列名 | 含义 |
|---|---|
| Destination | 目标网络或目标主机地址。 |
| Gateway | 下一跳网关地址。0.0.0.0 表示“此网段内不需要网关”,直接通过接口发送ARP找到目标。 |
| Genmask | 网络掩码。与Destination一起决定这条路由匹配哪些IP地址。 |
| Flags | 路由标志: - U (Up):路由可用- G (Gateway):需要经过网关- H (Host):表示Destination是一个具体的主机地址(掩码为255.255.255.255) |
| Metric | 优先级(跃点数)。数值越小越优先。相同目标有多条路由时选Metric小的。 |
| Iface | 从哪个网络接口(网卡)发出。 |
逐条解读
1. 默认路由(Default Gateway)
0.0.0.0 10.0.2.2 0.0.0.0 UG 100 0 0 enp0s3
- 含义:凡是目标IP 不匹配 下面任何一条更具体的路由,都走这条。
- 操作:把数据包发给网关
10.0.2.2,从网卡enp0s3发出。 - 作用:这是你上网的出口。
10.0.2.2通常是虚拟机的NAT网关(如VirtualBox默认NAT网络)或家用路由器。
2. 直连局域网路由
10.0.2.0 0.0.0.0 255.255.255.0 U 100 0 0 enp0s3
- 含义:目标为
10.0.2.0/24(即10.0.2.1到10.0.2.254)的IP。 - 操作:不经过网关(Gateway为
0.0.0.0),直接通过enp0s3网卡发送ARP查询找到目标机器。 - 作用:让你能和同网段的其他设备通信(比如同Wi-Fi下的其他电脑)。
3. 主机路由(网关本身)
10.0.2.2 0.0.0.0 255.255.255.255 UH 100 0 0 enp0s3
- 含义:目标精确为
10.0.2.2这一台主机。 - 操作:直接通过
enp0s3发送(不经过额外网关)。 - 作用:明确告诉系统,网关
10.0.2.2本身就是同网段的一台设备,不需要再为它去找另一个网关。
4. 两条特定的主机路由(到外部IP的静态路由)
162.105.129.88 10.0.2.2 255.255.255.255 UGH 100 0 0 enp0s3
162.105.129.122 10.0.2.2 255.255.255.255 UGH 100 0 0 enp0s3
- 含义:访问精确IP
162.105.129.88或162.105.129.122时。 - 操作:把数据包发给网关
10.0.2.2(由enp0s3发出)。 - 作用:这通常是通过VPN、隧道或手动添加的路由。
162.105.129.x看起来像某个校园网(如北京大学)的网段。它强制这两个特定IP的流量走10.0.2.2,而不是默认路由(虽然这里默认路由也是10.0.2.2,但单独列出可能是为了设置特定的Metric或策略)。
5. Docker 网桥路由
172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0
- 含义:目标为
172.17.0.0/16的IP。 - 操作:直接通过
docker0虚拟网卡发送。 - 作用:这是Docker默认的容器虚拟网络。宿主机通过这条路由与容器通信。
6. 另一条直连局域网(可能是Host-Only网络)
192.168.56.0 0.0.0.0 255.255.255.0 U 0 0 0 enp0s8
- 含义:目标为
192.168.56.0/24。 - 操作:直接通过
enp0s8网卡发送。 - 作用:这通常是虚拟机软件(如VirtualBox的Host-Only网络)或另一块物理网卡的网段。
整体数据流向决策图(按优先级)
当你的系统要发送一个数据包到目标IP X 时,它会从上到下(从最精确到最模糊)匹配路由:
- 先匹配主机路由(
Genmask为255.255.255.255)
→ 如10.0.2.2、162.105.129.88等。匹配则走对应条目。 - 再匹配网段路由(
Genmask为255.255.255.0或255.255.0.0)
→ 如10.0.2.0/24、192.168.56.0/24、172.17.0.0/16。匹配则直接通过对应网卡发出。 - 最后匹配默认路由(
0.0.0.0)
→ 如果以上都不匹配,则走0.0.0.0/0发给网关10.0.2.2。
举个例子
- 访问
8.8.8.8:不匹配任何主机/网段路由 → 走默认路由 → 发给网关10.0.2.2→ 由enp0s3发出。 - 访问
10.0.2.5:匹配10.0.2.0/24→ 直接走enp0s3(不经过网关)。 - 访问
172.17.0.2(一个Docker容器):匹配172.17.0.0/16→ 直接走docker0网卡。 - 访问
162.105.129.88:匹配主机路由 → 发给网关10.0.2.2→ 由enp0s3发出(这里网关与默认路由相同,但Metric可能不同,所以单独列出来)。
你的网络环境推断
从这张路由表可以推测:
- 你正在运行 Linux 虚拟机或实体机(有
docker0接口,且网卡名为enp0sx)。 - 主网络是 NAT 模式:
10.0.2.0/24是 VirtualBox 默认的 NAT 网络,10.0.2.2是虚拟网关。 - 有一个 Host-Only 网络:
192.168.56.0/24是 VirtualBox 默认的 Host-Only 网络,用于宿主机与虚拟机通信。 - Docker 已安装:
172.17.0.0/16是 Docker 默认桥接网络。 - 可能连接到某个校园网/VPN:两条
162.105.129.x的主机路由暗示你可能需要访问特定校内资源,流量强制经过10.0.2.2。
Docker 命令
根据你的实验需求,我帮你整理了Docker核心命令体系。你的实验本质上是手动构建容器网络——绕开Docker自动网络管理,用veth pair和Linux网络命名空间自己搭建虚拟链路,这能帮你深入理解容器网络的底层原理。
一、镜像管理 (Images)
| 命令 | 作用 | 你的实验场景 |
|---|---|---|
docker images | 查看本地镜像列表 | 验证node镜像是否已创建 |
docker pull <镜像名> | 从仓库拉取镜像 | 拉取ubuntu:22.04(实验准备) |
docker build -t <镜像名> . | 根据Dockerfile构建镜像 | docker image build -t node . |
docker rmi <镜像名> | 删除镜像 | docker image rm node |
示例:
docker images # 查看所有镜像
docker build -t myapp:v1 . # 从Dockerfile构建
docker rmi node # 删除node镜像
二、容器生命周期管理
| 命令 | 作用 | 你的实验场景 |
|---|---|---|
docker create [选项] <镜像> | 创建容器(不启动) | docker container create --cap-add NET_ADMIN --name n1 node |
docker start <容器名> | 启动已创建的容器 | docker container start n1 |
docker stop <容器名> | 停止运行中的容器 | docker container stop n1 |
docker rm <容器名> | 删除容器 | docker container rm n1 |
docker ps | 查看运行中的容器 | 验证容器状态 |
docker ps -a | 查看所有容器(含已停止) | docker container list -a |
关键参数说明:
--cap-add NET_ADMIN:赋予容器配置网络的权限(配置路由、启用网卡等),实验必须--name:给容器指定名称,方便后续操作-it:交互式终端(与exec配合使用)
示例:
# 创建并启动两个带网络管理权限的容器
docker container create --cap-add NET_ADMIN --name n1 node
docker container create --cap-add NET_ADMIN --name n2 node
docker container start n1 n2
# 查看容器状态
docker ps -a
# 停止并删除
docker container stop n1 n2
docker container rm n1 n2
三、容器交互与调试
| 命令 | 作用 | 你的实验场景 |
|---|---|---|
docker exec -it <容器> /bin/bash | 进入容器的交互式shell | 在容器内配置路由、运行tcpdump |
docker exec <容器> <命令> | 在容器内执行单条命令 | docker exec -it n1 route -n |
docker logs <容器> | 查看容器日志 | 调试容器启动问题 |
docker cp <源路径> <目标路径> | 宿主机与容器间拷贝文件 | 从容器导出日志文件 |
示例:
# 进入容器交互式操作
docker exec -it n1 /bin/bash
# 在容器内执行单条命令
docker exec -it n1 ping 10.0.0.2
docker exec -it n1 route -n
# 查看容器日志
docker logs n1
四、网络操作(实验核心)
4.1 Docker网络基础命令
| 命令 | 作用 |
|---|---|
docker network ls | 列出所有Docker网络 |
docker network inspect <网络> | 查看网络详情(子网、网关、连接的容器) |
docker network create <网络> | 创建自定义网络 |
docker network connect <网络> <容器> | 将容器连接到网络 |
4.2 实验中的关键网络操作
步骤一:获取容器的网络命名空间:
ns1=`docker inspect -f '{{.State.Pid}}' n1` # 获取容器n1的PID
ln -s /proc/$ns1/ns/net /var/run/netns/$ns1 # 暴露命名空间
步骤二:创建veth pair并连接到容器:
# 创建一对相连的虚拟网卡
ip link add n1-eth0 type veth peer name n2-eth0
# 将一端移动到容器命名空间
ip link set n1-eth0 netns $ns1
# 在容器命名空间中配置网卡
ip netns exec $ns1 ip link set n1-eth0 up
ip netns exec $ns1 ip addr add 10.0.0.1/24 dev n1-eth0
步骤三:路由配置:
# 查看路由表
docker exec -it n1 route -n
# 添加路由(二层:指定dev)
docker exec -it n1 route add -net 10.0.1.0 netmask 255.255.255.0 dev n1-eth0
# 添加路由(三层:指定gateway)
docker exec -it n1 route add -net 10.0.1.0 netmask 255.255.255.0 gw 10.0.0.2
# 删除路由
docker exec -it n1 route del -net 10.0.1.0 netmask 255.255.255.0
# 开启IP转发(让容器成为路由器)
docker exec -it n2 bash -c "echo 1 > /proc/sys/net/ipv4/ip_forward"
步骤四:网络调试:
# 测试连通性
docker exec -it n1 ping 10.0.0.2
# 抓包分析(在另一个终端)
docker exec -it n2 bash
tcpdump -i n2-eth0
实验
注意需要使用
# -n 表示不做 DNS 反向解析直接显示 IP 地址,否则内网 IP 无法解析会显示为 bogon
tcpdump -i n2-eth0 -n
否则默认监听第一个非回环网卡,也就是 Docker 管理网络 eth0。
注意路由表条目:若 Gateway 为 0.0.0.0 意味着这是一个直连网络,所以会认为可以直接 ARP 获得 MAC 地址并发送。
成功后显示
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on n2-eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
07:36:34.442945 ARP, Request who-has 10.0.0.2 tell 10.0.0.1, length 28
07:36:34.442955 ARP, Reply 10.0.0.2 is-at 0e:73:7f:b7:aa:9c, length 28
07:36:34.442956 IP 10.0.0.1 > 10.0.1.2: ICMP echo request, id 10, seq 1, length 64
07:36:34.442971 IP 10.0.1.2 > 10.0.0.1: ICMP echo reply, id 10, seq 1, length 64
07:36:35.443362 IP 10.0.0.1 > 10.0.1.2: ICMP echo request, id 10, seq 2, length 64
07:36:35.443384 IP 10.0.1.2 > 10.0.0.1: ICMP echo reply, id 10, seq 2, length 64
可以看到第一次发送时需要 ARP,后续缓存后不再需要 ARP。
[*] 是合并成一个字符串
[@] 保持为独立元素
运行 make setup,输出
./lab.sh -ci
create_image()
create image node
# 略
Successfully built e3cbf7a0d8b2
Successfully tagged node:latest
./lab.sh -cn
create_nodes()
# 一堆 ID
./lab.sh -rn
run_node()
h1
h2
r1
r2
r3
r4
r5
./lab.sh -cl
create_links()
expose each node's namespace
there are 7 nodes
expose h1's namespace
expose h2's namespace
expose r1's namespace
expose r2's namespace
expose r3's namespace
expose r4's namespace
expose r5's namespace
there are 42 items of <node1 nic1 ip1 node2 nic2 ip2>
create the link for h1 h1-eth0 111.0.0.1/24 r1 r1-eth0 111.0.0.2/24
create the link for r1 r1-eth1 111.0.1.1/24 r2 r2-eth0 111.0.1.2/24
create the link for r2 r2-eth1 111.0.2.1/24 r3 r3-eth0 111.0.2.2/24
create the link for r3 r3-eth1 111.0.3.1/24 r5 r5-eth0 111.0.3.2/24
create the link for r1 r1-eth2 111.0.4.1/24 r4 r4-eth0 111.0.4.2/24
create the link for r4 r4-eth1 111.0.5.1/24 r5 r5-eth1 111.0.5.2/24
create the link for r5 r5-eth2 111.0.6.1/24 h2 h2-eth0 111.0.6.2/24
./lab.sh -cr
configure_route()
configure h1 with ip_forward
bash: line 1: /proc/sys/net/ipv4/ip_forward: Read-only file system
configure h2 with ip_forward
bash: line 1: /proc/sys/net/ipv4/ip_forward: Read-only file system
configure r1 with ip_forward
bash: line 1: /proc/sys/net/ipv4/ip_forward: Read-only file system
configure r2 with ip_forward
bash: line 1: /proc/sys/net/ipv4/ip_forward: Read-only file system
configure r3 with ip_forward
bash: line 1: /proc/sys/net/ipv4/ip_forward: Read-only file system
configure r4 with ip_forward
bash: line 1: /proc/sys/net/ipv4/ip_forward: Read-only file system
configure r5 with ip_forward
bash: line 1: /proc/sys/net/ipv4/ip_forward: Read-only file system
测试 make test,运行结果:
docker exec -it h1 ping 111.0.6.2 -c 5
PING 111.0.6.2 (111.0.6.2) 56(84) bytes of data.
64 bytes from 111.0.6.2: icmp_seq=1 ttl=61 time=0.437 ms
64 bytes from 111.0.6.2: icmp_seq=2 ttl=61 time=0.064 ms
64 bytes from 111.0.6.2: icmp_seq=3 ttl=61 time=0.098 ms
64 bytes from 111.0.6.2: icmp_seq=4 ttl=61 time=0.057 ms
64 bytes from 111.0.6.2: icmp_seq=5 ttl=61 time=0.057 ms
--- 111.0.6.2 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4105ms
rtt min/avg/max/mdev = 0.057/0.142/0.437/0.147 ms
docker exec -it h1 traceroute 111.0.6.2
traceroute to 111.0.6.2 (111.0.6.2), 30 hops max, 60 byte packets
1 111.0.0.2 (111.0.0.2) 0.050 ms 0.008 ms 0.007 ms
2 * * *
3 * * *
4 111.0.5.2 (111.0.5.2) 0.297 ms 0.021 ms 0.012 ms
5 111.0.6.2 (111.0.6.2) 0.088 ms 0.130 ms 0.025 ms
docker exec -it h2 traceroute 111.0.0.1
traceroute to 111.0.0.1 (111.0.0.1), 30 hops max, 60 byte packets
1 111.0.6.1 (111.0.6.1) 0.039 ms 0.006 ms 0.006 ms
2 * * *
3 111.0.1.1 (111.0.1.1) 0.025 ms 0.006 ms 0.006 ms
4 111.0.0.1 (111.0.0.1) 0.014 ms 0.008 ms 0.148 ms
显示 * * * 表示没有收到响应 ICMP 包,这是因为我们配置 route 时只配置了单向通路,所以对于路由器 r2、r3、r4 都只能单向发包,不能反向回复 ICMP 响应。
echo 'configure_route()' # 少了个 s
建议测试时先单独执行 -ci,然后可以把 -ci 和 -di 注释掉。
提供的代码
docker exec -it n2 bash -c "echo 0 > /proc/sys/net/ipv4/ip_forward"
有误,运行会报错
bash: line 1: /proc/sys/net/ipv4/ip_forward: Read-only file system
也即没有写入权限。但是由于创建容器时该文件默认为 1,故对实际运行不会产生影响。可以尝试写入 0 并检验发现确实无法修改。
可行的解决方案一是使用 --privileged 参数赋予容器更高高的权限,这样它就可以修改只读文件了:
docker container create --cap-add NET_ADMIN --privileged --name n1 node
另一种更推荐的办法是在创建容器时就执行系统调用确定好是否开启转发,而不是后续进行更改。
docker container create --cap-add NET_ADMIN --sysctl net.ipv4.ip_forward=1 --name n1 node