Docker 原理篇(七)Docker network namespace

前言

此篇博文是笔者所总结的 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 的规则配置,配置解耦。

这里需要注意的有

  1. bridge
    linux 虚拟的 bridge 本身是一个三层设备,可以设置 IP 地址,可以当做一个网卡接口看待;并且,它会屏蔽掉任何被它桥接的网卡接口设备。具体描述参考linux virtual bridge
  2. ip forward
    这样,数据包可以通过 bridge 接口根据路由规则转发至 en0;
  3. 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [1:76]
:POSTROUTING ACCEPT [1:76]
:DOCKER - [0:0]
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
-A DOCKER -i docker0 -j RETURN
COMMIT
# Completed on Sun Jan 8 11:57:59 2017
# Generated by iptables-save v1.6.0 on Sun Jan 8 11:57:59 2017
*filter
:INPUT ACCEPT [561:39011]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [420:44109]
:DOCKER - [0:0]
:DOCKER-ISOLATION - [0:0]
-A FORWARD -j DOCKER-ISOLATION
-A FORWARD -o docker0 -j DOCKER
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
-A FORWARD -i docker0 -o docker0 -j ACCEPT
-A DOCKER-ISOLATION -j RETURN

主要有两条规则,

  1. NAT
    -A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
    将源地址是 172.17.0.0 网段的数据包更改为 en0 的地址

  2. 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
2
mac@ubuntu:~$ ip netns show
ns1 (id: 0)

只显示了我们之前测试时创建的 ns1,并没有显示出 docker 所使用的 network namespace。

  1. 找到容器的主进程 ID

    1
    2
    mac@ubuntu:~$ docker inspect --format '{{.State.Pid}}' docker7_c
    1963
  2. 创建 /var/run/netns 目录以及符号连接

    1
    2
    mac@ubuntu:~$ sudo mkdir /var/run/netns
    mac@ubuntu:~$ sudo ln -s /proc/1963/ns/net /var/run/netns/docker7_c
  3. 容器的 network namespace 可见了,docker7_c,并且可以直接操作了

    1
    2
    3
    mac@ubuntu:~$ ip netns show
    docker7_c
    ns1 (id: 0)

    操作 docker 的 namespace

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    mac@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;

  1. 安装 brctl

    1
    $ sudo apt-get install bridge-utils
  2. 创建 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
  3. 创建 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
  4. 添加 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 不通。

  5. 开启 ip forward

    1
    $ echo 1 > /proc/sys/net/ipv4/ip_forward
  6. 设置 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 中应该是默认添加的。

  7. 测试

    1
    2
    3
    4
    5
    mac@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
    ^C

    network 连接外网成功,并且能够成狗获取外网主机的反馈。

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 模式来自定义自己的模式。