在前面2篇文章中,docker入门教程五:网络模式 以及 docker进阶教程一:namespace之障眼法 都有谈到了docker的网络模式,但是docker具体是怎么样使用namespace来隔离网络的呢?在大型数据中心中,又将如何规划网络呢?这就是本节的主要内容。
ip netns详解
使用方法
ip netns命令是用来操作network namespace的指令,具体使用方法如下。
1 | [root@localhost ~]# ip netns help |
创建一个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 | [root@ganbing ~]# ip netns exec net-test ip addr |
其实,你如果觉得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 | ip link add veth0 type veth peer name veth1 |
将veth1加入到netns上,是使用 ip link
命令。
1 | ip netns add ns1 |
给veth1重命名为eth0,并加上IP
1 | ip netns exec ns1 ip link set veth1 name eth0 |
测试是否可以正常通信:
1 | [root@localhost ~]# ip netns exec ns1 ip -4 -o addr |
使用 ip netns del ns1
就直接把 ns1 veth0 veth1 都全部删除了。
2个namespace相连
在上一小节里面演示了单个namespace与宿主机相连,那能不能把veth0 veth1全部都放在network namespace里面呢?这样其实是可以的。实验方法跟上一节内容是差不多的。如下:
1 | ip netns add ns1 |
这样 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 | ip link add veth0 type veth peer name veth1 |
这样操作完成之后,ping -I br0 192.168.189.33
就可以通了,如果不给br0加IP就不会;这是由于veth0收到应答包后没有给协议栈,而是给了br0,于是协议栈得不到veth1的mac地址,从而通信失败。
但是这样还是通不了外网,需要将物理网卡eth0也加到bridge上,并删除IP:
1 | ip link set dev eth0 master br0 |
这样之后,实际完成的配置如下:
1 | [root@localhost ~]# ip -4 -o addr |
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 | [root@master ~]# docker run -itd --name alpine alpine |
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 | [root@remote ~]# docker run -itd --name alpine --network none alpine |
新增一个网桥,然后接管宿主机的网卡eth0,接管时,由于是远程的IP,所以需要一起运行。
1 | brctl addbr br0 |
查出alpine容器的PID,然后做软链接,设置IP等
1 | PID=`docker inspect --format '{{.State.Pid}}' alpine` |
查看容器的配置。
1 | [root@remote ~]# ip netns exec test ip -4 -o addr |
完成配置后,Docker容器和宿主机连接的网络图如下所示(懒得画图,借用别人的):
pipework
由上例可以看到,是可以将docker网络是可以跟本地网络相连的,但配置很麻烦。如果需要经常自定义Docker网络,可以把上面的步骤编写成shell脚本,这样方便操作。事实上,目前已有了一个这样的工具解脱我们繁琐的步骤,就是由Docker公司工程师Jerome Petazzoni在Githu上发布的pipework的工具。pipework号称是容器的SDN解决方案,可以在复场景下将容器连接起来。官网是:https://github.com/jpetazzo/pipework
pipework其实就是用shell写的代码,安装方法很简单:
1 | curl -o /usr/local/bin/pipework https://raw.githubusercontent.com/jpetazzo/pipework/master/pipework |
pipework最简单的使用命令:pipework br_name container_name ip/netmask@vlan(gateway)
运行一个名叫alpine的容器,将alpine的网络连接到网桥br0上,其中@后面是网关地址
1 | docker run -itd --name alpine --network none alpine |
pipework首先会检查主机是否存在br0网桥,若不存在,就自己创建一个。向alpine中加入一块名为eth1的网卡,并配置IP 192.168.137.58/24,且设置好网关。如下
1 | [root@remote ~]# docker exec alpine ip -4 -o addr |
然后设置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的工作方式:
首先 pipework 检查是否存在 br0 网桥,若不存在,就自己创建。若以”ovs”开头,就会创建 OpenVswitch 网桥,以”br”开头,创建 Linux bridge。
创建 veth pair 设备,用于为容器提供网卡并连接到 br0 网桥。
使用 docker inspect 找到容器在主机中的 PID,然后通过 PID 将容器的网络命名空间链接到
/var/run/netns/
目录下。这么做的目的是,方便在主机上使用 ip netns 命令配置容器的网络。因为,在 Docker 容器中,我们没有权限配置网络环境。将之前创建的 veth pair 设备分别加入容器和网桥中。在容器中的名称默认为 eth1,可以通过 pipework 的 -i 参数修改该名称。
然后就是配置新网卡的 IP。若在 IP 地址的后面加上网关地址,那么 pipework 会重新配置默认路由。这样容器通往外网的流量会经由新配置的 eth1 出去,而不是通过 eth0 和 docker0。(若想完全抛弃自带的网络设置,在启动容器的时候可以指定 —net=none)
参考资料
Linux虚拟网络设备之veth https://segmentfault.com/a/1190000009251098
- Linux虚拟网络设备之bridge(桥) https://segmentfault.com/a/1190000009491002
- https://blog.csdn.net/ghost_leader/article/details/71075551
- Linux-虚拟网络设备-LinuxBridge:https://blog.csdn.net/sld880311/article/details/77840343
- Docker 使用 OpenvSwitch 网桥