docker进阶教程四:再谈网络模式与pipework

在前面2篇文章中,docker入门教程五:网络模式 以及 docker进阶教程一:namespace之障眼法 都有谈到了docker的网络模式,但是docker具体是怎么样使用namespace来隔离网络的呢?在大型数据中心中,又将如何规划网络呢?这就是本节的主要内容。

ip netns详解

使用方法

ip netns命令是用来操作network namespace的指令,具体使用方法如下。

1
2
3
4
5
6
7
8
9
10
[root@localhost ~]# ip netns help
Usage: ip netns list
ip netns add NAME
ip netns set NAME NETNSID
ip [-all] netns delete [NAME]
ip netns identify [PID]
ip netns pids NAME
ip [-all] netns exec [NAME] cmd ...
ip netns monitor
ip netns list-id
  • 创建一个network namespace,创建完成之后,实际上是在 /var/run/netns/ 目录下面生成了一个文件而也。

    1
    2
    3
    4
    5
    #创建一个名为net-test的network namespace
    [root@localhost ~]# ip netns add net-test
    [root@localhost ~]# ll -i /var/run/netns/
    total 0
    4026532184 -r--r--r-- 1 root root 0 Oct 20 10:59 net-test
  • 列出系统中已存在的network namespace:

    1
    2
    [root@localhost ~]# ip netns ls
    net-test
  • 删除一个network namespace:

    1
    [root@localhost ~]# ip netns delete net-test
  • 在network namespace中执行一条命令:ip netns exec <nameapce name> <command>

比如显示net-test namespace的网卡信息,路由信息

1
2
3
4
5
6
7
[root@ganbing ~]# ip netns exec net-test ip addr
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

[root@ganbing ~]# ip netns exec net-test route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface

其实,你如果觉得ip netns exec 来执行命令比较麻烦,还可以使用启动一个shell来配合:ip netns exec <nameapce name> bash,这样,就可以在上面执行命令,就好像使用者进入了这个network namespace中;如果要退出这个bash,则输入exit即可。

veth是什么

虚拟网络设备veth和其它的网络设备都一样,一端连接的是内核协议栈。veth设备是成对出现的,另一端两个设备彼此相连;一个设备收到协议栈的数据发送请求后,会将数据发送到另一个设备上去。

创建veth设备很简单,命令为:ip link add name type veth peer name peer_name,可以简单把这个东西想像为一条网线,这时,网线上面有2个端口,取了2个名字。

1
2
3
ip link add veth0 type veth peer name veth1
ip addr add 192.168.189.22/24 dev veth0
ip link set veth0 up

将veth1加入到netns上,是使用 ip link 命令。

1
2
ip netns add ns1
ip link set veth1 netns ns1

给veth1重命名为eth0,并加上IP

1
2
3
4
5
ip netns exec ns1 ip link set veth1 name eth0
ip netns exec ns1 ip addr add 192.168.189.33/24 dev eth0
ip netns exec ns1 ip link set eth0 up
# lo 口默认没有启动,必须设置启动才可以通信
ip netns exec ns1 ip link set lo up

测试是否可以正常通信:

1
2
3
4
5
6
7
8
9
10
11
[root@localhost ~]# ip netns exec ns1 ip -4 -o addr
1: lo inet 127.0.0.1/8 scope host lo\ valid_lft forever preferred_lft forever
14: eth0 inet 192.168.189.33/24 scope global eth0\ valid_lft forever preferred_lft forever
[root@localhost ~]# ip netns exec ns1 ping -c 1 192.168.189.33
PING 192.168.189.33 (192.168.189.33) 56(84) bytes of data.
64 bytes from 192.168.189.33: icmp_seq=1 ttl=64 time=0.054 ms

--- 192.168.189.33 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.054/0.054/0.054/0.000 ms
[root@localhost ~]# ip netns del ns1

使用 ip netns del ns1 就直接把 ns1 veth0 veth1 都全部删除了。

2个namespace相连

在上一小节里面演示了单个namespace与宿主机相连,那能不能把veth0 veth1全部都放在network namespace里面呢?这样其实是可以的。实验方法跟上一节内容是差不多的。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ip netns add ns1
ip netns add ns2

ip link add veth0 type veth peer name veth1
ip link set veth0 netns ns1
ip link set veth1 netns ns2

ip netns exec ns1 ip link set lo up
ip netns exec ns2 ip link set lo up
ip netns exec ns1 ip link set veth0 name eth0
ip netns exec ns2 ip link set veth1 name eth0
ip netns exec ns2 ip link set eth0 up
ip netns exec ns1 ip link set eth0 up
ip netns exec ns1 ip addr add 192.168.189.22/24 dev eth0
ip netns exec ns2 ip addr add 192.168.189.33/24 dev eth0

这样 ip netns exec ns1 ping -c 1 192.168.189.33 就可以正常通信了。其网络图类似使用一条网络接了2个物理设备。如下图:

如果有2个以上的network namespace需要连接怎么办?是不是就需要引入bridge虚拟网桥了,就如同Docker网络一样。

bridge

bridge是一个虚拟网络设备,所以具有网络设备的特征,可以配置IP、MAC地址等;其次,bridge是一个虚拟交换机,和物理交换机有类似的功能。

对于普通的网络设备来说,只有两端,从一端进来的数据会从另一端出去,如物理网卡从外面网络中收到的数据会转发给内核协议栈,而从协议栈过来的数据会转发到外面的物理网络中。而bridge不同,bridge有多个端口,数据可以从任何端口进来,进来之后从哪个口出去和物理交换机的原理差不多,要看mac地址。

以下实例有兴趣可以测试。没兴趣可以先忽略。

创建命令

1
2
3
4
5
6
7
8
9
10
ip link add veth0 type veth peer name veth1
ip addr add 192.168.189.33/24 dev veth1
ip link set veth0 up
ip link set veth1 up

ip link add name br0 type bridge #或者使用brctl addbr来创建
ip link set br0 up
ip link set dev veth0 master br0
#给br0加上IP
ip addr add 192.168.189.22/24 dev br0

这样操作完成之后,ping -I br0 192.168.189.33就可以通了,如果不给br0加IP就不会;这是由于veth0收到应答包后没有给协议栈,而是给了br0,于是协议栈得不到veth1的mac地址,从而通信失败。

但是这样还是通不了外网,需要将物理网卡eth0也加到bridge上,并删除IP:

1
2
3
ip link set dev eth0 master br0
ip addr del 192.168.189.129/24 dev eth0
ip route add default via 192.168.189.2 dev br0

这样之后,实际完成的配置如下:

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
26
27
28
29
30
[root@localhost ~]# ip -4 -o addr
1: lo inet 127.0.0.1/8 scope host lo\ valid_lft forever preferred_lft forever
6: veth1 inet 192.168.189.33/24 scope global veth1\ valid_lft forever preferred_lft forever
8: br0 inet 192.168.189.22/24 scope global br0\ valid_lft forever preferred_lft forever

[root@localhost ~]# ip route show
default via 192.168.189.2 dev br0
192.168.189.0/24 dev veth1 proto kernel scope link src 192.168.189.33
192.168.189.0/24 dev br0 proto kernel scope link src 192.168.189.22

[root@localhost ~]# bridge link
2: eth0 state UP : <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master br0 state forwarding priority 32 cost 4
7: veth0 state UP : <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master br0 state forwarding priority 32 cost 2
[root@localhost ~]#

[root@localhost ~]# brctl show
bridge name bridge id STP enabled interfaces
br0 8000.000c295394e1 no eth0
veth0

[root@localhost ~]# ping -I br0 -c 4 www.baidu.com
PING www.a.shifen.com (163.177.151.110) from 192.168.189.22 br0: 56(84) bytes of data.
64 bytes from 163.177.151.110: icmp_seq=1 ttl=128 time=26.3 ms
64 bytes from 163.177.151.110: icmp_seq=2 ttl=128 time=27.1 ms
64 bytes from 163.177.151.110: icmp_seq=3 ttl=128 time=26.3 ms
64 bytes from 163.177.151.110: icmp_seq=4 ttl=128 time=26.3 ms

--- www.a.shifen.com ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3006ms
rtt min/avg/max/mdev = 26.315/26.544/27.186/0.388 ms

docker与ip netns

我们知道,Docker是使用Linux namespace技术进行资源隔离的,网络也是如此。当用默认网络模式(bridge模式)启动一个Docker容器时,一定是在主机上新建了一个Linux network namespace。但是使用 ip netns list 命令查看是看不到新建的network namespace,这是为什么呢?

在这之前小节里面有说明过,使用 ip netns list命令查看是在/var/run/netns`目录下查找network namespace。但由于Docker创建的network namespace并不在此目录下创建任何选项,所以查看不了。由于Linux下的每一个进程都会属于一个特定的network namespace,所以我们可以这个的特性,将他移植过来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[root@master ~]# docker run -itd --name alpine alpine
[root@master ~]# docker inspect -f '{{.State.Pid}}' alpine
31088
[root@master ~]# ll /var/run/netns/
total 0
[root@master ~]# readlink /proc/31088/ns/net
net:[4026532543]
# 使用软链接的方式进入绑定
[root@master ~]# ln -s /proc/31088/ns/net /var/run/netns/test
[root@master ~]#
[root@master ~]# ip netns list
test (id: 3)
[root@master ~]# ip netns exec test ip -4 -o addr
1: lo inet 127.0.0.1/8 scope host lo\ valid_lft forever preferred_lft forever
136: eth0 inet 172.17.0.5/16 brd 172.17.255.255 scope global eth0\ valid_lft forever preferred_lft forever
[root@master ~]#
# 查看docker里面的IP
[root@master ~]# docker exec alpine ip -4 -o addr
1: lo inet 127.0.0.1/8 scope host lo\ valid_lft forever preferred_lft forever
136: eth0 inet 172.17.0.5/16 brd 172.17.255.255 scope global eth0\ valid_lft forever preferred_lft forever

Docker网络与本地网络

使用host模式是可以跟宿主机共享本地网络,但是隔离不清楚,很难运行在生产环境下,如果将本机网络的网卡接管到虚拟网桥上,再新建veth网卡,这样变类似将容器和主机应该处在一个二层网络中。在虚拟场景下,虚拟网桥可以将容器连在一个二层网络中,只要将主机的网卡桥接到虚拟网桥中,就能将容器和主机的网络连起来,再给Docker容器分配一个本地局域网IP就OK了。

有以下例子:本地网络为192.168.137.0/24,网关为192.168.137.1,宿主机的IP为eth0 192.168.137.55,将要新建一个容器,跟宿主机的网络是同一网段。

先运行容器,指定network为none,这样就可以看到alpine容器里面只有lo口。

1
2
3
4
[root@remote ~]# docker run -itd --name alpine --network none alpine
[root@remote ~]# docker exec alpine ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

新增一个网桥,然后接管宿主机的网卡eth0,接管时,由于是远程的IP,所以需要一起运行。

1
2
3
brctl addbr br0
ip link set dev br0 up
ip addr del 192.168.137.55/24 dev eth0;ip addr add 192.168.137.55/24 dev br0;brctl addif br0 eth0;ip route del default;ip route add default via 192.168.137.1 dev br0

查出alpine容器的PID,然后做软链接,设置IP等

1
2
3
4
5
6
PID=`docker inspect  --format '{{.State.Pid}}' alpine`
ln -s /proc/$PID/ns/net /var/run/netns/test
ip netns exec test ip link set veth1 name eth0
ip netns exec test ip link set eth0 up
ip netns exec test ip addr add 192.168.137.58/24 dev eth0
ip netns exec test ip route add default via 192.168.137.1

查看容器的配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@remote ~]# ip netns exec test ip -4 -o addr
1: lo inet 127.0.0.1/8 scope host lo\ valid_lft forever preferred_lft forever
62: eth0 inet 192.168.137.58/24 scope global eth0\ valid_lft forever preferred_lft forever
[root@remote ~]# ip netns exec test route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 192.168.137.1 0.0.0.0 UG 0 0 0 eth0
192.168.137.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0
# 容器内部就可以ping通宿主机的IP了
[root@remote ~]# ip netns exec test ping 192.168.137.55 -c 1
PING 192.168.137.55 (192.168.137.55) 56(84) bytes of data.
64 bytes from 192.168.137.55: icmp_seq=1 ttl=64 time=0.048 ms

--- 192.168.137.55 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.048/0.048/0.048/0.000 ms

完成配置后,Docker容器和宿主机连接的网络图如下所示(懒得画图,借用别人的):

pipework

由上例可以看到,是可以将docker网络是可以跟本地网络相连的,但配置很麻烦。如果需要经常自定义Docker网络,可以把上面的步骤编写成shell脚本,这样方便操作。事实上,目前已有了一个这样的工具解脱我们繁琐的步骤,就是由Docker公司工程师Jerome Petazzoni在Githu上发布的pipework的工具。pipework号称是容器的SDN解决方案,可以在复场景下将容器连接起来。官网是:https://github.com/jpetazzo/pipework

pipework其实就是用shell写的代码,安装方法很简单:

1
2
curl -o /usr/local/bin/pipework https://raw.githubusercontent.com/jpetazzo/pipework/master/pipework
chmod 755 /usr/local/bin/pipework

pipework最简单的使用命令:pipework br_name container_name ip/netmask@vlan(gateway)

运行一个名叫alpine的容器,将alpine的网络连接到网桥br0上,其中@后面是网关地址

1
2
docker run -itd --name alpine --network none alpine
pipework br0 alpine 192.168.137.58/24@192.168.137.1

pipework首先会检查主机是否存在br0网桥,若不存在,就自己创建一个。向alpine中加入一块名为eth1的网卡,并配置IP 192.168.137.58/24,且设置好网关。如下

1
2
3
[root@remote ~]# docker exec alpine ip -4 -o addr
1: lo inet 127.0.0.1/8 scope host lo\ valid_lft forever preferred_lft forever
65: eth1 inet 192.168.137.58/24 brd 192.168.137.255 scope global eth1\ valid_lft forever preferred_lft forever

然后设置br0接管eth0网卡

1
ip addr del 192.168.137.55/24 dev eth0;ip addr add 192.168.137.55/24 dev br0;brctl addif br0 eth0;ip route del default;ip route add default via 192.168.137.1 dev br0

这样就可以通信了。

总结pipework的工作方式:

  1. 首先 pipework 检查是否存在 br0 网桥,若不存在,就自己创建。若以”ovs”开头,就会创建 OpenVswitch 网桥,以”br”开头,创建 Linux bridge。

  2. 创建 veth pair 设备,用于为容器提供网卡并连接到 br0 网桥。

  3. 使用 docker inspect 找到容器在主机中的 PID,然后通过 PID 将容器的网络命名空间链接到 /var/run/netns/ 目录下。这么做的目的是,方便在主机上使用 ip netns 命令配置容器的网络。因为,在 Docker 容器中,我们没有权限配置网络环境。

  4. 将之前创建的 veth pair 设备分别加入容器和网桥中。在容器中的名称默认为 eth1,可以通过 pipework 的 -i 参数修改该名称。

  5. 然后就是配置新网卡的 IP。若在 IP 地址的后面加上网关地址,那么 pipework 会重新配置默认路由。这样容器通往外网的流量会经由新配置的 eth1 出去,而不是通过 eth0 和 docker0。(若想完全抛弃自带的网络设置,在启动容器的时候可以指定 —net=none)

参考资料

  • 本文作者: wumingx
  • 本文链接: https://www.wumingx.com/k8s/docker-network-pipework.html
  • 本文主题: docker进阶教程四:再谈网络模式与pipework
  • 版权声明: 本博客所有文章除特别声明外,转载请注明出处!如有侵权,请联系我删除。
0%