前言
此篇博文是笔者所总结的 Docker 系列之一;
本文为作者的原创作品,转载需注明出处;
概述
本章节主要是描述 docker 的 network namespace; docker 的隔离网络环境是通过 linux network namespace 实现的。
docker 的网络实现
docker 的网络实现一共有四种模式,bridge、host、container 以及 none 模式
bridge 模式
原理
基本访问原理在 linux virtual veth pair 已经做了比较详细的阐述;这里需要解释的是为什么 docker 还需要借助 bridge 而不是直接使用 veth 来实现呢?
如图,原因是,可以通过一个共用的 bridge 桥接所有的 veth 接口并且与宿主机的物理网卡 en0 通讯,最重要的是,减少 iptables 的相关配置,这样,我就只需要对 bridge 进行 iptables 的规则配置,配置解耦。
这里需要注意的有
- bridge
linux 虚拟的 bridge 本身是一个三层设备,可以设置 IP 地址,可以当做一个网卡接口看待;并且,它会屏蔽掉任何被它桥接的网卡接口设备。具体描述参考linux virtual bridge - ip forward
这样,数据包可以通过 bridge 接口根据路由规则转发至 en0; - NAT
当数据包从 namespace 经过交换机通过 en0 发出之前,需要将源地址更改为 10.11.108.188,具体 NAT 流转过程,namespace 如何将数据包发出并接收反馈,详细过程参考 linux virtual veth pair ](/2017/01/07/linux-virtual-network/#VETH-设备)
docker bridge namespace 概述
网络拓扑图,
可见,docker 使用的是 172.17.0.0 网段的地址;
iptables 规则
1 | *nat |
主要有两条规则,
NAT
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
将源地址是 172.17.0.0 网段的数据包更改为 en0 的地址FILTER
-A FORWARD -o docker0 -m conntrack –ctstate RELATED,ESTABLISHED -j ACCEPT
iptables session 映射规则,使得虚拟机可以接收到外网主机的反馈。详细描述参考 linux virtual veth pair 中的用例描述。
显示 docker network namespace
默认情况下,docker network namespace 是不会展示的1
2mac@ubuntu:~$ ip netns show
ns1 (id: 0)
只显示了我们之前测试时创建的 ns1,并没有显示出 docker 所使用的 network namespace。
找到容器的主进程 ID
1
2mac@ubuntu:~$ docker inspect --format '{{.State.Pid}}' docker7_c
1963创建 /var/run/netns 目录以及符号连接
1
2mac@ubuntu:~$ sudo mkdir /var/run/netns
mac@ubuntu:~$ sudo ln -s /proc/1963/ns/net /var/run/netns/docker7_c容器的 network namespace 可见了,docker7_c,并且可以直接操作了
1
2
3mac@ubuntu:~$ ip netns show
docker7_c
ns1 (id: 0)操作 docker 的 namespace
1
2
3
4
5
6
7
8
9
10
11
12
13mac@ubuntu:~$ sudo ip netns exec docker7_c ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
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
8: eth0@if9: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.17.0.2/16 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::42:acff:fe11:2/64 scope link
valid_lft forever preferred_lft forever
实践
我们手动来实现 docker 如何通过 bridge 的方式搭建 network namespace;试验环境为 Ubuntu 16.10;
安装 brctl
1
$ sudo apt-get install bridge-utils
创建 veth pair,namespace ns1,将 veth1 加入 ns1,并将各个接口激活
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17# 创建 veth pair
mac@ubuntu:~$ sudo ip link add veth0 type veth peer name veth1
# 创建 network namespace ns1
mac@ubuntu:~$ sudo ip netns add ns1
# 将 ns1 的换回接口激活,否则 ns1 无法通讯
mac@ubuntu:~$ sudo ip netns exec ns1 ifconfig lo up
# 将 veth1 纳入 ns1
mac@ubuntu:~$ sudo ip link set veth1 netns ns1
# 设置 veth0 和 veth 1 的 IP 地址并且将其激活,记得,务必设置子网掩码;
mac@ubuntu:~$ sudo ip addr add 192.168.72.2/24 dev veth0
mac@ubuntu:~$ sudo ip link set veth0 up
mac@ubuntu:~$ sudo ip netns exec ns1 ifconfig veth1 192.168.72.3/24 up
# 测试,从 ns1 中能够 ping 通 veth0,则表示配置成功
mac@ubuntu:~$ sudo ip netns exec ns1 ping 192.168.72.2
PING 192.168.72.2 (192.168.72.2) 56(84) bytes of data.
64 bytes from 192.168.72.2: icmp_seq=1 ttl=64 time=0.101 ms
64 bytes from 192.168.72.2: icmp_seq=2 ttl=64 time=0.050 ms创建 bridge br0,将 veth0 接入,接入后 veth0 失效
1
2
3
4
5
6
7
8
9
10
11
12
13# 创建 br0 虚拟交换机
mac@ubuntu:~$ sudo brctl addbr br0
# 关闭 br0 stp 模式
mac@ubuntu:~$ sudo brctl stp br0 off
# 设置 br0 IP 地址并且将其激活
mac@ubuntu:~$ sudo ifconfig br0 192.168.72.10/24 up
# 将 veth0 接入 br0
mac@ubuntu:~$ sudo brctl addif br0 veth0
# veth0 失效
mac@ubuntu:~$ sudo ip netns exec ns1 ping 192.168.72.2
PING 192.168.72.2 (192.168.72.2) 56(84) bytes of data.
From 192.168.72.3 icmp_seq=10 Destination Host Unreachable
From 192.168.72.3 icmp_seq=11 Destination Host Unreachable添加 ns1 缺省路由,使其网关指向 br0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20# 添加路由
mac@ubuntu:~$ sudo ip netns exec ns1 ip route add default via 192.168.72.10 dev veth1
mac@ubuntu:~$ sudo ip netns exec ns1 route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 192.168.72.10 0.0.0.0 UG 0 0 0 veth1
192.168.72.0 0.0.0.0 255.255.255.0 U 0 0 0 veth1
# 测试
mac@ubuntu:~$ sudo ip netns exec ns1 ping 192.168.72.10
PING 192.168.72.10 (192.168.72.10) 56(84) bytes of data.
From 192.168.72.3 icmp_seq=10 Destination Host Unreachable
^C
# 删除 veth0 的 ip 地址
mac@ubuntu:~$ sudo ip addr del 192.168.72.2/24 dev veth0
# 再次测试,通过
mac@ubuntu:~$ sudo ip netns exec ns1 ping 192.168.72.10
PING 192.168.72.10 (192.168.72.10) 56(84) bytes of data.
64 bytes from 192.168.72.10: icmp_seq=1 ttl=64 time=0.213 ms
64 bytes from 192.168.72.10: icmp_seq=2 ttl=64 time=0.047 ms
64 bytes from 192.168.72.10: icmp_seq=3 ttl=64 time=0.044 ms看来,veth0 不能设置 ip 地址,否则 veth1 -> br0 不通。
开启 ip forward
1
$ echo 1 > /proc/sys/net/ipv4/ip_forward
设置 iptables NAT 规则
1
mac@ubuntu:~$ sudo iptables -t nat -A POSTROUTING -s 192.168.72.0/24 ! -o br0 -j MASQUERADE
将源 IP 地址网络段是
192.168.72.0
的数据包进行转换,转换成宿主机物理网卡 en0 的地址 10.11.108.188,还有一层意思,如果数据是流经交换机 br0 的时候,不用转换,否则,br0 将不能接收到 ns1 发出的数据包。
另外,docker 有在 filter 表中添加1
2$ sudo iptables -t filter -A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
$ sudo iptables-save该规则的意义在于,通过 iptables 在宿主机上创建一条 session 映射,便于虚拟机接收目标主机的反馈。详细描述参考 linux virtual veth pair ;但是,我这里没有配置,并不影响,猜测,session 映射规则在 Ubuntu 中应该是默认添加的。
测试
1
2
3
4
5mac@ubuntu:~$ sudo ip netns exec ns1 ping www.baidu.com
PING www.a.shifen.com (61.135.169.125) 56(84) bytes of data.
64 bytes from 61.135.169.125 (61.135.169.125): icmp_seq=1 ttl=61 time=96.8 ms
64 bytes from 61.135.169.125 (61.135.169.125): icmp_seq=2 ttl=61 time=87.1 ms
^Cnetwork 连接外网成功,并且能够成狗获取外网主机的反馈。
host 模式
参考 http://www.cnblogs.com/sammyliu/p/5894191.html
container 模式
参考 http://www.cnblogs.com/sammyliu/p/5894191.html
none 模式
参考 http://www.cnblogs.com/sammyliu/p/5894191.html
总结
bridge 和 host 模式是比较常用的模式;当然也可以使用 none 模式来自定义自己的模式。