目录

docker容器网络如何工作

容器网络工作原理

一句话概括就是通过namespace隔离并通过veth连接到root namespace

namespace

  • 启动一个http server
1
python3 -m http.server 8080

查看效果

1
curl localhost:8080

返回如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Directory listing for /</title>
</head>
<body>
<h1>Directory listing for /</h1>
<hr>
<ul>
</ul>
<hr>
</body>
</html>

此时再次执行python3 -m http.server 8080会报错端口已经被占用, 但是可以使用linux的net namespace特性, 在一个隔离的网络命名空间中使用8080端口

  • 创建一个新的namespace ns1
1
sudo ip netns add ns1
  • 查看ns1下的所有网络设备, 使用ip netns exec ns1 命令指定namespace执行命令.
1
2
3
4
# sudo ip netns exec ns1 ip a

1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

可以看到新的namespace里面有一个默认的回环网卡lo, 状态是DOWN, 需要启动它, 不然新的namespace中的localhost访问不到的

1
sudo ip netns exec ns1 ip link set dev lo up
1
2
3
4
5
6
7
8
# sudo ip netns exec ns1 ip a

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default 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
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever

然后在新的 namespace 中启动一个http server

1
sudo ip netns exec ns1 python3 -m http.server 8080

这里启动正常, 此时执行 sudo ip netns exec ns1 curl localhost:8080 也可以访问到这个新的namespace中的http服务

vethpair

vethpair是linux的一种虚拟网卡, 成对出现, 一般用于连接两个不同的namespace, 可以理解成一根网线连接着两张网卡. 这里我们需要创建一个vethpair让host空间可以访问到ns1中的http服务.

  • 创建 vethpair
1
sudo ip link add dev veth1a type veth peer name veth1b
  • 查看 vethpair
1
2
3
4
5
6
# ip a

21: veth1b@veth1a: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether 0e:73:a9:02:85:6c brd ff:ff:ff:ff:ff:ff
22: veth1a@veth1b: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether 52:89:67:a9:41:d3 brd ff:ff:ff:ff:ff:ff

这里可以看到有连个网络设备 veth1a 和 veth1b.

  • 启动veth1a
1
sudo ip link set dev veth1a up
  • 将veth1b 设置到 namespace ns1 中, 并启动 veth1b
1
2
sudo ip link set veth1b netns ns1
sudo ip netns exec ns1 ip link set dev veth1b up
1
2
3
4
# ip a

22: veth1a@if21: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state LOWERLAYERDOWN group default qlen 1000
    link/ether 52:89:67:a9:41:d3 brd ff:ff:ff:ff:ff:ff link-netns ns1

此时 veth1b 在列表中消失了, 这是因为 veth1b 已经在ns1中, 对host不可见了.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
sudo ip netns exec ns1 ip a

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default 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
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
21: veth1b@if22: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 0e:73:a9:02:85:6c brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fe80::c73:a9ff:fe02:856c/64 scope link
       valid_lft forever preferred_lft forever
  • 给虚拟网卡设置ip
1
2
sudo ip address add 172.18.0.10/24 dev veth1a
sudo ip netns exec ns1 ip address add 172.18.0.11/24 dev veth1b
  • 查看ip
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
22: veth1a@if21: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 52:89:67:a9:41:d3 brd ff:ff:ff:ff:ff:ff link-netns ns1
    inet 172.18.0.10/24 scope global veth1a
       valid_lft forever preferred_lft forever
    inet6 fe80::5089:67ff:fea9:41d3/64 scope link
       valid_lft forever preferred_lft forever
...
21: veth1b@if22: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 0e:73:a9:02:85:6c brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.18.0.11/24 scope global veth1b
       valid_lft forever preferred_lft forever
    inet6 fe80::c73:a9ff:fe02:856c/64 scope link
       valid_lft forever preferred_lft forever

此时两张网卡都被设上了ip, 分别是

  • host veth1a: 172.18.0.10
  • ns1 veth1b: 172.18.0.11

他们已经可以相互ping通了,

1
2
3
4
5
6
7
ping 172.18.0.11
PING 172.18.0.11 (172.18.0.11) 56(84) bytes of data.
64 bytes from 172.18.0.11: icmp_seq=1 ttl=64 time=0.127 ms
------------
sudo ip netns exec ns1 ping 172.18.0.10
PING 172.18.0.10 (172.18.0.10) 56(84) bytes of data.
64 bytes from 172.18.0.10: icmp_seq=1 ttl=64 time=0.046 ms

http 服务也可以通过这个ip访问

1
2
3
4
5
6
7
curl 172.18.0.11:8080

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
...

至此, 一个隔离的网段被创建出来了, 宿主机可以通过 172.18.0.11 访问ns1中的服务, 但此时有一个问题, ns1中的网络访问不了外网, 也不能通过host的ip访问host namespace. 这个问题可以通过创建一个网桥来解决.

网桥

  • 创建网桥
1
2
3
sudo ip link add dev br1 type bridge
sudo ip address add 172.18.0.1/24 dev br1
sudo ip link set br1 up
  • 连接veth到网桥
1
sudo ip link set dev veth1a master br1
  • 设置路由
1
sudo ip netns exec ns1 ip route add default via 172.18.0.1
  • 设置转发规则
1
2
3
sudo iptables --append FORWARD --in-interface br1 --out-interface eth0 --jump ACCEPT
sudo iptables --append POSTROUTING --table nat --out-interface eth0 --jump MASQUERADE
sudo iptables --append FORWARD --in-interface eth0 --out-interface br1 --jump ACCEPT
  • 删掉 veth1a 的ip
1
sudo ip address delete 172.18.0.10/24 dev veth1a

此时可以ns1可以连接外网了

清理配置

1
2
3
4
5
6
sudo iptables --delete FORWARD --in-interface br1 --out-interface eth0 --jump ACCEPT
sudo iptables --delete FORWARD --in-interface eth0 --out-interface br1 --jump ACCEPT
sudo iptables --delete POSTROUTING --table nat --out-interface eth0 --jump MASQUERADE
sudo ip link delete dev br1
sudo ip link delete dev veth1a
sudo ip netns delete ns1

参考

本文主要参考 How Do Kubernetes and Docker Create IP Addresses