Docker高级网络实践之 玩转Linux network namespace & pipework

前言

 在上一篇文章中 《“深刻浅出”来解读Docker网络核心原理》 你们了解了Docker中libnetwrok提供的4种驱动,它们各有千秋,但实际上每一种方式都有必定的局限性。假设须要运营一个数据中心的网络,咱们有许多的宿主机,每台宿主机上运行了成千上万个Docker容器,若是使用4种网络驱动的话会是怎么样的呢,咱们来分析一下:linux

  1. 使用host驱动可让容器与宿主机共用同一个网络栈,这么作看似解决了网络问题,可实际上并未使用network namespace的隔离,缺少安全性。
  2. 使用Docker默认的bridge驱动,容器没有对外IP,只能经过NAT来实现对外通讯。这种方式不能解决跨主机容器间直接通讯的问题,难以知足复杂场景下的业务需求。
  3. 使用overlay驱动,能够用于支持跨主机的网络通讯,但必须配合swarm进行配置和使用才能实现跨主机的网络通讯。
  4. 使用null驱动实际上不进行任何网络配置。

 可见,为了实现数据中心大量容器间的跨主机网络通讯,为了更灵活地实现容器间网络的共享与隔离,也为了在管理成千上万个容器时能够更加自动化地进行网络配置,咱们须要来了解更高级的网络方案。git

 本文及后期的文章将经过一些工具和额外的操做来突破Docker网络原有的限制,实现一些更高级的功能来知足实际运用中的复杂需求。github

把Linux network namespace玩起来

 在上一篇文章中已经介绍过了linux network namespace,在本文中咱们将从实践的角度来了解如何在linux系统下操做linux network namespace。docker

 ip是linux系统下一个强大的网络配置工具,它不只能够替代一些传统的网络管理工具,如ifconfig、route等,还能够实现更丰富的功能。下面将介绍如何使用ip命令来管理network namespace。shell

使用ip netns来操做network namespace

ip netns命令是用来操做network namespace的指令,具体使用方法以下。安全

  • 建立一个network namespace:bash

    #建立一个名为net-test的network namespace
    [root@ganbing ~]# ip netns add net-test
  • 列出系统中已存在的network namespace:服务器

    [root@ganbing ~]# ip netns ls
    net-test
  • 删除一个network namespace:网络

    [root@ganbing ~]# ip netns delete net-test
  • 在network namespace中执行一条命令:

#命令格式
ip netns exec <network nameapce name> <command>ide

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

[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 <network nameapce name> bash

这样,就能够在上面执行命令,就好像使用者进入了这个network namespace中;若是要退出这个bash,则输入exit便可。

使用ip为network namespace配置网卡

 当使用ip netns add命令建立了一个network namespace后,就拥有了一个独立的网络空间,能够根据需求来配置该网络空间,如添加网卡,配置IP,设置路由等。下面以以前创建的名为net-test的network namespace为例来演示如何进行这些操做。

 当使用ip命令建立一个network namespace时,会默认建立一个回环设备(loopback interface:lo)。该设备默认不启动,最好将其启动。

[root@ganbing ~]# ip netns exec net-test ip link set dev lo up

在主机上建立两张虚拟网卡veth-1 和 veth-2:

[root@ganbing ~]# ip link add veth-1 type veth peer name veth-2

将veth-2设备添加到net-test这个network namespace中,veth-1留在宿主机中:

[root@ganbing ~]# ip link set veth-2 netns net-test

如今net-test这个network namespace就有两块网卡了(lo和veth-2),验证看一下:

[root@ganbing ~]# ip netns exec net-test ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
222: veth-2@if223: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT qlen 1000
    link/ether 92:24:fd:44:c6:00 brd ff:ff:ff:ff:ff:ff link-netnsid 0

接下来能够为网卡分配IP并启动网卡:

#在主机上为veth-1配置IP并启动
[root@ganbing ~]# ip addr add 10.0.0.1/24 dev veth-1
[root@ganbing ~]# ip link set dev veth-1 up

#为net-test中的veth-2配置IP并启动
[root@ganbing ~]# ip netns exec net-test ip addr add 10.0.0.2/24 dev veth-2
[root@ganbing ~]# ip netns exec net-test ip link set dev veth-2 up

 给两张网卡配置了IP后,会在各自的network namespace中生成一条路由,用ip route 或者 route -n查看:

#在主机中查看路由
[root@ganbing ~]# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
...
10.0.0.0        0.0.0.0         255.255.255.0   U     0      0        0 veth-1
...

#在net-test中查看路由
[root@ganbing ~]# ip netns exec net-test route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
10.0.0.0        0.0.0.0         255.255.255.0   U     0      0        0 veth-2

 上面这两条路由代表的意义是目的地址 10.0.0.0/24网络的IP包分别从veth-1和veth-2发出。

 如今net-test这个network namespace有了本身的网卡、IP地址、路由表等信息,就至关于成了一台小型的“虚拟机”了。测试一下它的连通性,来检查配置是否正确。

 从主机的veth-1网卡ping net-test的veth-2网卡:
Docker高级网络实践之 玩转Linux network namespace & pipework

 从net-test的veth-2网卡ping主机的veth-1网卡:
Docker高级网络实践之 玩转Linux network namespace & pipework

将两个network namespace链接起来

 不少时候,想搭建一个复杂的网络环境来测试数据,每每受困于没有足够的资源来建立虚拟机。咱们掌握了配置network namespace后,即可以解决这个问题。能够在一台普通的机器上,以简单的方式建立多个相互隔离的network namespace,而后经过网卡、网桥等虚拟设备将它们链接起来,组成想要的网络拓扑。

 下面咱们来演示一个简单的例子,将两个network namespace经过veth pair设备连起来。过程以下:

一、建立两个network namespace ns一、ns2,名称可自行定义:

[root@ganbing ~]# ip netns add ns1
[root@ganbing ~]# ip netns add ns2
[root@ganbing ~]# ip netns ls
ns2
ns1

二、建立veth pair设备veth-a,veth-b:

[root@ganbing ~]# ip link add veth-a type veth peer name veth-b

三、将网卡分别放到两个network namespace中:

[root@ganbing ~]# ip link set veth-a netns ns1
[root@ganbing ~]# ip link set veth-b netns ns2

四、启动这两个网张:

[root@ganbing ~]# ip netns exec ns1 ip link set dev lo up
[root@ganbing ~]# ip netns exec ns1 ip link set dev veth-a up
[root@ganbing ~]# ip netns exec ns2 ip link set dev lo up
[root@ganbing ~]# ip netns exec ns2 ip link set dev veth-b up

五、分配IP:

[root@ganbing ~]# ip netns exec ns1 ip addr add 10.0.0.1/24 dev veth-a
[root@ganbing ~]# ip netns exec ns2 ip addr add 10.0.0.2/24 dev veth-b

六、验证连通
Docker高级网络实践之 玩转Linux network namespace & pipework

 经过veth pair设备链接起来的两个network namespace就好像直接经过网线链接起来的两台机器,它的拓扑图以下所示:
Docker高级网络实践之 玩转Linux network namespace & pipework

 你们想一下,若是有更多的network namespace须要链接怎么办?是否是就须要引入虚拟网桥了,就如同Docker网络同样。

使用ip命令配置Docker容器网络

 在上一篇文章 <“深刻浅出”来解读Docker网络核心原理> 介绍过,Docker是使用Linux namespace技术进行资源隔离的,网络也是如此。当用默认网络模式(bridge模式)启动一个Docker容器时,必定是在主机上新建了一个Linux network namespace。咱们能够按照在network namespace中配置网络的方法来配置Docker 容器的网络。

 首先,启动一个名为test1的Docker容器:

[root@ganbing ~]# docker run -itd --name test1 busybox

 而后,使用ip netns list命令查看是否能够看到新建的network namespace。执行命令后发现并无看到新建的network namespace。这并不表明Docker容器没有建立network namespace,只是ip netns 命令没法查看而以,这个与ip netns命令工做方式有关。

 当使用ip netns命令建立了两个network namespace(ns一、ns2)后,会在/var/run/netns目录下看到ns1和ns2:

[root@ganbing ~]# ls -la /var/run/netns/
total 0
drwxr-xr-x  2 root root   80 Mar 19 18:25 .
drwxr-xr-x 40 root root 1240 Mar 19 15:08 ..
-r--r--r--  1 root root    0 Mar 19 18:22 ns1
-r--r--r--  1 root root    0 Mar 19 18:22 ns2

 ip netns list命令在/var/run/netns目录下查找network namespace。因为Docker建立的network namespace并不在此目录下建立任何选项,所以,须要一些额外的操做来使ip命令能够操纵Docker建立的network namespace。

 Linux下的每个进程都会属于一个特定的network namespace,来看一下不一样network namespace环境中/pro/$PID/ns目录下有何区别。

#/proc/self 连接到当前正在运行的进程
[root@ganbing ~]# ls -la /proc/self/ns/
......
lrwxrwxrwx 1 root root 0 Mar 19 19:17 net -> net:[4026531956]
......

#在ns1和ns2中
[root@ganbing ~]# ip netns exec ns1 ls -la /proc/self/ns
......
lrwxrwxrwx 1 root root 0 Mar 19 19:18 net -> net:[4026533018]
......

[root@ganbing ~]# ip netns exec ns2 ls -la /proc/self/ns
lrwxrwxrwx 1 root root 0 Mar 19 19:18 net -> net:[4026533116]

 从上面能够发现,不一样network namespace中的进程有不一样的net:[]号码发配。这些号码表明着不一样的network namespace,拥有相同net:[]号码的进程属于同一个network namesapce。只要将表明Docker建立的network namesapce的文件连接到/var/run/netns目录下,就可使用ip netns命令操做了,步骤方法以下:

一、用docker inspect查看test1容器的PID

[root@ganbing ~]# docker inspect  --format '{{.State.Pid}}' test1
17037

二、若是/var/run/netns目录不存在,就要手工建立(通常都有),而后在/var/run/netns目录下建立软连接,指向test1容器的network namespace

[root@ganbing ~]# ln -s /proc/17037/ns/net  /var/run/netns/test1

三、测试是否成功

[root@ganbing ~]# ip netns list
test1
ns2
ns1

[root@ganbing ~]# ip netns  exec test1 ip addr
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
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
226: eth0@if227: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP 
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever

 完成上面的配置后,就能够自行配置Docker的网络环境了。除了ip netns命令外,还有一些工具能够进入linux network namespace,好比nsenter。但须要额外的安装这个工具。

把pipework玩起来

 Docker现有的网络比较简单,扩展性和灵活性都不能知足不少复杂的应用场景。不少时候都须要自定义Docker容器的网络。好比,为了使容器各节点之间通讯、各节点和本地主机之间的通讯,比较简单的作法就是将Docker容器网络配置到本地主机网络的网段中。咱们来看一下怎么实现。

将Docker容器配置到本地网络环境中

 若是想要使Docker容器和容器主机处于同一网络,那么容器和主机应该处在一个二层网络中。就是把两台机器连在同一个交换机上,或者连在不一样的级联交换机上。在虚拟场影 下,虚拟网桥能够将容器连在一个二层网络中,只要将主机的网卡桥接到虚拟网桥中,就能将容器和主机的网络连起来,再给Docker容器分配一个本地局域网IP就OK了。

咱们来通个一个例子分析一下这个过程 :本地网络为 172.18.18.0/24,网关为 172.18.18.1,宿主机IP为172.18.18.34(网卡ens160),要在这台宿主机上启动一个名为test的Docker容器,并给它配置IP为 172.18.18.36。因为并不须要Docker提供的网络,因此用--net=none参数来启动容器。操做以下:

一、启动一个test容器

[root@docker ~]# docker run -itd --name test01 --network none busybox
39ea5fac5ebb8bd25372d04efb6b662a18cd6fdf85105c22df0796087d776280

二、建立一个供容器链接的网桥br0

[root@docker ~]# brctl addbr br0
[root@docker ~]# ip link set dev br0

三、将主机ens160网卡桥接到br0上,并把ens160的IP配置在br0上。因为笔者是远程操做服务器,因此执行这一步的时候会致使网络断开,所以这里放在一条命令执行

[root@docker ~]# ip addr add 172.18.18.34/24 dev br0; \
> ip addr del 172.18.18.34/24 dev ens160; \
> brctl addif br0 ens160; \
> ip route del default; \
> ip route add default via 172.18.18.1 dev br0

四、找到test01的PID

[root@docker ~]# docker inspect  --format '{{.State.Pid}}' test01
4557

五、将容器的network namespace添加到/var/run/netns/目录下

[root@docker ~]# mkdir /var/run/netns
[root@docker netns]# ln -s /proc/4557/ns/net /var/run/netns/test01

六、建立用于链接网桥和Docker容器的网卡设备

#将veth-a链接到br0网桥中
[root@docker ~]# ip link add veth-a type veth peer name veth-b
[root@docker ~]# brctl addif br0 veth-a
[root@docker ~]# ip link set dev veth-a up

#将veth-b放在test的network namespace中,重命令eth0,并为其配置IP和默认路由
[root@docker ~]# ip netns exec test01 ip link set dev lo up
[root@docker ~]# ip link set veth-b netns test01
[root@docker ~]# ip netns exec test01 ip link set dev veth-b name eth0
[root@docker ~]# ip netns exec test01 ip link set eth0 up
[root@docker ~]# ip netns exec test01 ip addr add 172.18.18.36/24 dev eth0
[root@docker ~]# ip netns exec test01 ip route add default via 172.18.18.1

七、查看一下test01的网卡状况,并测试

[root@docker ~]# ip netns exec test01 ip addr
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
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
9: eth0@if10: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP qlen 1000
    link/ether 66:fa:71:ba:0e:fb brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.18.18.36/24 scope global eth0
       valid_lft forever preferred_lft forever
[root@docker ~]# ip netns exec test01 route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         172.18.18.1     0.0.0.0         UG    0      0        0 eth0
172.18.18.0     0.0.0.0         255.255.255.0   U     0      0        0 eth0

 完成配置扣,Docker容器和宿主机链接的网络图以下所示:
Docker高级网络实践之 玩转Linux network namespace & pipework

 如今test01容器能够与本地主机相互访问,而且test01容器能够经过本地网络的网关172.18.18.1访问外部网络。

来解析一下pipework

 从上面的过程能够发现,配置Docker容器的网络是至关繁琐的。若是须要常常自定义Docker网络,能够把上面的步骤编写成shell脚本,这样方便操做。事实上,目前已有了一个这样的工具解脱咱们繁琐的步骤,就是由Docker公司工程师Jerome Petazzoni在Githu上发布的pipework的工具。pipwork号称是容器的SDN解决方案,能够在复场景下将容器链接起来。其实随着Docker网络的不断改进,piipwork工具的不少功能会被Docker原生支持,所以pipework当初只是过渡方案之一而以,你们只要知道了解就好了。下面来看一下pipework的功能。

* 支持linux网桥链接到容器并配置容器IP
一、下载pipework

[root@docker ~]# git clone https://github.com/jpetazzo/pipework

二、将pipework脚本放处指定的目录,/usr/local/bin

[root@docker ~]# cp ./pipework/pipework /usr/local/bin/

三、对test01容器进行配置

[root@docker /]# docker run -itd --name  test01 --network none busybox
[root@docker /]# pipework  br0 test01 172.18.18.36/24@172.18.18.1

上面配置命令操做以下:

  • 查看主机是否存在br0网桥,不存在就建立;
  • 向test01中加入一块名为eth1的网卡,并配置IP172.18.18.36/24;
  • 若test01中已有默认路由,就删除,把172.18.18.1设为默认路由l
  • 将test01容器链接到以前建立的网桥上br0;

这个过程和以前采用ip命令配置的过程相似,pipework其实就是用shell写的代码。

pipework其实还有其它的不少功能,好比还支持open vswitch、支持dhcp获取容器的IP等等,本文只要是和你们了解一下它的做用和功能,其它详细的功能就不做介绍了。

看到这里的朋友应该对network namespace和pipework有了更好的理解

Docker高级网络实践之 玩转Linux network namespace & pipework

相关文章
相关标签/搜索