Total Pageviews

Wednesday, 16 March 2022

linux的网络虚拟化: network namespace

network namespace 是实现网络虚拟化的重要功能,它能创建多个隔离的网络空间,它们有独自的网络栈信息。不管是虚拟机还是容器,运行的时候仿佛自己就在独立的网络中。这篇文章介绍 network namespace 的基本概念和用法,network namespace 是 linux 内核提供的功能,这篇文章借助 ip 命令来完成各种操作。ip 命令来自于 iproute2 安装包,一般系统会默认安装,如果没有的话,请读者自行安装。

NOTEip 命令因为需要修改系统的网络配置,默认需要 sudo 权限。这篇文章使用 root 用户执行,请不要在生产环境或者重要的系统中用 root 直接执行,以防产生错误。

ip 命令管理的功能很多, 和 network namespace 有关的操作都是在子命令 ip netns 下进行的,可以通过 ip netns help` 查看所有操作的帮助信息。

默认情况下,使用 ip netns 是没有网络 namespace 的,所以 ip netns ls 命令看不到任何输出。

[root@localhost ~]# ip netns help
Usage: ip netns list
       ip netns add NAME
       ip netns delete NAME
       ip netns identify PID
       ip netns pids NAME
       ip netns exec NAME cmd ...
       ip netns monitor
[root@localhost ~]# ip netns ls

创建 network namespace 也非常简单,直接使用 ip netns add 后面跟着要创建的 namespace 名称。如果相同名字的 namespace 已经存在,命令会报 Cannot create namespace 的错误。

[root@localhost ~]# ip netns add net1
[root@localhost ~]# ip netns ls
net1

ip netns 命令创建的 network namespace 会出现在 /var/run/netns/ 目录下,如果需要管理其他不是 ip netns 创建的 network namespace,只要在这个目录下创建一个指向对应 network namespace 文件的链接就行。

有了自己创建的 network namespace,我们还需要看看它里面有哪些东西。对于每个 network namespace 来说,它会有自己独立的网卡、路由表、ARP 表、iptables 等和网络相关的资源。ip 命令提供了 ip netns exec 子命令可以在对应的 network namespace 中执行命令,比如我们要看一下这个 network namespace 中有哪些网卡。更棒的是,要执行的可以是任何命令,不只是和网络相关的(当然,和网络无关命令执行的结果和在外部执行没有区别)。比如下面例子中,执行 bash 命令了之后,后面所有的命令都是在这个 network namespace 中执行的,好处是不用每次执行命令都要把 ip netns exec NAME 补全,缺点是你无法清楚知道自己当前所在的 shell,容易混淆。

[root@localhost ~]# ip netns exec net1 ip addr
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
[root@localhost ~]# ip netns exec net1 bash
[root@localhost ~]# ip addr
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
[root@localhost ~]# exit
exit

更新:通过修改 bash 的前缀信息可以区分不同 shell,操作如下:

$ ip netns exec ns1 /bin/bash --rcfile <(echo "PS1=\"namespace ns1> \"")

namespace ns1> ping www.google.com
PING www.google.com (178.60.128.38) 56(84) bytes of data.
64 bytes from cache.google.com (178.60.128.38): icmp_seq=1 ttl=58 time=17.6 ms

ip netns exec 后面跟着 namespace 的名字,比如这里的 net1,然后是要执行的命令,只要是合法的 shell 命令都能运行,比如上面的 ip addr 或者 bash

每个 namespace 在创建的时候会自动创建一个 lo 的 interface,它的作用和 linux 系统中默认看到的 lo 一样,都是为了实现 loopback 通信。如果希望 lo 能工作,不要忘记启用它:

[root@localhost ~]# ip netns exec net1 ip link set lo up

默认情况下,network namespace 是不能和主机网络,或者其他 network namespace 通信的。

network namespace 之间通信

有了不同 network namespace 之后,也就有了网络的隔离,但是如果它们之间没有办法通信,也没有实际用处。要把两个网络连接起来,linux 提供了 veth pair 。可以把 veth pair 当做是双向的 pipe(管道),从一个方向发送的网络数据,可以直接被另外一端接收到;或者也可以想象成两个 namespace 直接通过一个特殊的虚拟网卡连接起来,可以直接通信。

使用上面提到的方法,我们再创建另外一个 network namespace,这里我们使用 net0 和 net1 两个名字。

我们可以使用 ip link add type veth 来创建一对 veth pair 出来,需要记住的是 veth pair 无法单独存在,删除其中一个,另一个也会自动消失。

[root@localhost ~]# ip link add type veth
[root@localhost ~]# ip link
4: veth0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT qlen 1000
    link/ether 36:88:73:83:c9:64 brd ff:ff:ff:ff:ff:ff
5: veth1: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT qlen 1000
    link/ether fe:7e:75:4d:79:2e brd ff:ff:ff:ff:ff:ff

小知识: 创建 veth pair 的时候可以自己指定它们的名字,比如 ip link add vethfoo type veth peer name vethbar 创建出来的两个名字就是 vethfoo 和 vethbar 。因为这里我们对名字没有特殊要求,所以就直接使用系统自动生成的名字。如果 pair 的一端接口处于 DOWN 状态,另一端能自动检测到这个信息,并把自己的状态设置为 NO-CARRIER

创建结束之后,我们能看到名字为 veth0 和 veth1 两个网络接口,名字后面的数字是系统自动生成的。接下来,要做的是把这对 veth pair 分别放到已经两个 namespace 里面,这个可以使用 ip link set DEV netns NAME 来实现:

[root@localhost ~]# ip link set veth0 netns net0
[root@localhost ~]# ip link set veth1 netns net1
[root@localhost ~]# ip netns exec net0 ip addr
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
4: veth0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN qlen 1000
    link/ether 36:88:73:83:c9:64 brd ff:ff:ff:ff:ff:ff
[root@localhost ~]# ip netns exec net1 ip addr
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
5: veth1: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN qlen 1000
    link/ether fe:7e:75:4d:79:2e brd ff:ff:ff:ff:ff:ff

最后,我们给这对 veth pair 配置上 ip 地址,并启用它们。

[root@localhost ~]# ip netns exec net0 ip link set veth0 up
[root@localhost ~]# ip netns exec net0 ip addr add 10.0.1.1/24 dev veth0
[root@localhost ~]# ip netns exec net0 ip route
10.0.1.0/24 dev veth0  proto kernel  scope link  src 10.0.1.1

[root@localhost ~]# ip netns exec net1 ip link set veth1 up
[root@localhost ~]# ip netns exec net1 ip addr add 10.0.1.2/24 dev veth1

可以看到,最每个 namespace 中,在配置玩 ip 之后,还自动生成了对应的路由表信息,网络 10.0.1.0/24 数据报文都会通过 veth pair 进行传输。使用 ping 命令可以验证它们的连通性:

[root@localhost ~]# ip netns exec net0 ping -c 3 10.0.1.2
PING 10.0.1.2 (10.0.1.2) 56(84) bytes of data.
64 bytes from 10.0.1.2: icmp_seq=1 ttl=64 time=0.039 ms
64 bytes from 10.0.1.2: icmp_seq=2 ttl=64 time=0.039 ms
64 bytes from 10.0.1.2: icmp_seq=3 ttl=64 time=0.139 ms

--- 10.0.1.2 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2004ms
rtt min/avg/max/mdev = 0.039/0.072/0.139/0.047 ms

完成这些,我们创建的网络拓扑结构如下所示:

使用 bridge 连接不同的 namespace

虽然 veth pair 可以实现两个 network namespace 之间的通信,但是当多个 namespace 需要通信的时候,就无能为力了。
讲到多个网络设备通信,我们首先想到的交换机和路由器。因为这里要考虑的只是同个网络,所以只用到交换机的功能。linux 当然也提供了虚拟交换机的功能,我们还是用 ip 命令来完成所有的操作。

NOTE:和 bridge 有关的操作也可以使用命令 brctl,这个命令来自 bridge-utils 这个包,读者可以根据自己的发行版进行安装,使用方法请查阅 man 页面或者相关文档。

首先我们来创建需要的 bridge,简单起见名字就叫做 br0

[root@localhost ~]# ip link add br0 type bridge
[root@localhost ~]# ip link set dev br0 up

下面只演示一个 namespace 的操作,其他 namespace 要做的事情和这个类似。创建 veth pair:

[root@localhost ~]# ip link add type veth

把其中一个 veth(veth1) 放到 net0 里面,设置它的 ip 地址并启用它:

[root@localhost ~]# ip link set dev veth1 netns net0
[root@localhost ~]# ip netns exec net0 ip link set dev veth1 name eth0
[root@localhost ~]# ip netns exec net0 ip addr add 10.0.1.1/24 dev eth0
[root@localhost ~]# ip netns exec net0 ip link set dev eth0 up

最后,把另一个 veth(veth0)连接到创建的 bridge 上,并启用它:

[root@localhost ~]# ip link set dev veth0 master br0
[root@localhost ~]# ip link set dev veth0 up

可以通过 bridge 命令(也是 iproute2 包自带的命令)来查看 bridge 管理的 link 信息:

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

最后通过 ping 命令来测试网络的连通性:

[root@localhost ~]# ip netns exec net0 ping -c 3 10.0.1.3
PING 10.0.1.3 (10.0.1.3) 56(84) bytes of data.
64 bytes from 10.0.1.3: icmp_seq=1 ttl=64 time=0.251 ms
64 bytes from 10.0.1.3: icmp_seq=2 ttl=64 time=0.047 ms
64 bytes from 10.0.1.3: icmp_seq=3 ttl=64 time=0.046 ms

--- 10.0.1.3 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2008ms

下图是这部分网络的拓扑结构,如果对 docker 网络熟悉的话,其实这和 docker 默认的 bridge 网络模型非常相似。当然要实现每个 namespace 对外网的访问还需要额外的配置(设置默认网关,开启 ip_forward,为网络添加 NAT 规则等)。

参考资料

--------------------------------------------------------

linux 网络虚拟化: macvlan

macvlan 简介

macvlan 是 linux kernel 比较新的特性,可以通过以下方法判断当前系统是否支持:

$ modprobe macvlan
$ lsmod | grep macvlan
  macvlan    19046    0

如果第一个命令报错,或者第二个命令没有返回,则说明当前系统不支持 macvlan,需要升级系统或者升级内核。

macvlan 允许你在主机的一个网络接口上配置多个虚拟的网络接口,这些网络 interface 有自己独立的 mac 地址,也可以配置上 ip 地址进行通信。macvlan 下的虚拟机或者容器网络和主机在同一个网段中,共享同一个广播域。macvlan 和 bridge 比较相似,但因为它省去了 bridge 的存在,所以配置和调试起来比较简单,而且效率也相对高。除此之外,macvlan 自身也完美支持 VLAN。

如果希望容器或者虚拟机放在主机相同的网络中,享受已经存在网络栈的各种优势,可以考虑 macvlan。

各个 linux 发行版对 macvlan 的支持

macvlan 对kernel 版本依赖:Linux kernel v3.9–3.19 and 4.0+。几个重要发行版支持情况:

  • ubuntu:>= saucy(13.10)
  • RHEL(Red Hat Enterprise Linux): >= 7.0(3.10.0)
  • Fedora: >=19(3.9)
  • Debian: >=8(3.16)

各个发行版的 kernel 都可以自行手动升级,具体操作可以参考官方提供的文档。

以上版本信息参考了这些资料:

四种模式

  • private mode:过滤掉所有来自其他 macvlan 接口的报文,因此不同 macvlan 接口之间无法互相通信
  • vepa(Virtual Ethernet Port Aggregator) mode: 需要主接口连接的交换机支持 VEPA/802.1Qbg 特性。所有发送出去的报文都会经过交换机,交换机作为再发送到对应的目标地址(即使目标地址就是主机上的其他 macvlan 接口),也就是 hairpin mode 模式,这个模式用在交互机上需要做过滤、统计等功能的场景。
  • bridge mode:通过虚拟的交换机讲主接口的所有 macvlan 接口连接在一起,这样的话,不同 macvlan 接口之间能够直接通信,不需要将报文发送到主机之外。这个模式下,主机外是看不到主机上 macvlan interface 之间通信的报文的。
  • passthru mode:暂时没有搞清楚这个模式要解决的问题

VEPA 和 passthru 模式下,两个 macvlan 接口之间的通信会经过主接口两次:第一次是发出的时候,第二次是返回的时候。这样会影响物理接口的宽带,也限制了不同 macvlan 接口之间通信的速度。如果多个 macvlan 接口之间通信比较频繁,对于性能的影响会比较明显。

private 模式下,所有的 macvlan 接口都不能互相通信,对性能影响最小。

bridge 模式下,数据报文是通过内存直接转发的,因此效率会高一些,但是会造成 CPU 额外的计算量。

NOTE:如果要手动分配 mac 地址,请注意本地的 mac 地址最高位字节的低位第二个 bit 必须是 1。比如 02:xx:xx:xx:xx:xx

实验

为了方便演示,我们会创建出来两个 macvlan 接口,分别放到不同的 network namespace 里。整个实验的网络拓扑结构如下:


图片来源

首先还是创建两个 network namespace:

[root@localhost ~]# ip netns add net1
[root@localhost ~]# ip netns add net2

然后创建 macvlan 接口:

[root@localhost ~]# ip link add link enp0s8 mac1 type macvlan
[root@localhost ~]# ip link
23: mac1@enp0s8: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT
    link/ether e2:80:1c:ba:59:9c brd ff:ff:ff:ff:ff:ff

创建的格式为 ip link add link <PARENT> <NAME> type macvlan,其中 <PARENT> 是 macvlan 接口的父 interface 名称,<NAME> 是新建的 macvlan 接口的名称,这个名字可以任意取。使用 ip link 可以看到我们刚创建的 macvlan 接口,除了它自己的名字之外,后面还跟着父接口的名字。

下面就是把创建的 macvlan 放到 network namespace 中,配置好 ip 地址,然后启用它:

[root@localhost ~]# ip link set mac1@enp0s8 netns net1
Cannot find device "mac1@enp0s8"
[root@localhost ~]# ip link set mac1 netns net1
[root@localhost ~]# ip netns exec net1 ip link
23: mac1@if3: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT
    link/ether e2:80:1c:ba:59:9c brd ff:ff:ff:ff:ff:ff
[root@localhost ~]# ip netns exec net1 ip link set mac1 name eth0
[root@localhost ~]# ip netns exec net1 ip link
23: eth0@if3: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT
    link/ether e2:80:1c:ba:59:9c brd ff:ff:ff:ff:ff:ff
[root@localhost ~]# ip netns exec net1 ip addr add 192.168.8.120/24 dev eth0
[root@localhost ~]# ip netns exec net1 ip link set eth0 up

同理可以配置另外一个 macvlan 接口,可以测试两个 ip 的连通性:

[root@localhost ~]# docker exec 1444 ping -c 3 192.168.8.120
PING 192.168.8.120 (192.168.8.120): 56 data bytes
64 bytes from 192.168.8.120: seq=0 ttl=64 time=0.130 ms
64 bytes from 192.168.8.120: seq=1 ttl=64 time=0.099 ms
64 bytes from 192.168.8.120: seq=2 ttl=64 time=0.083 ms

--- 192.168.8.120 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.083/0.104/0.130 ms


参考资料

-----------------------------------------

linux 网络虚拟化: ipvlan

简介

IPVlan 和 macvlan 类似,都是从一个主机接口虚拟出多个虚拟网络接口。一个重要的区别就是所有的虚拟接口都有相同的 macv 地址,而拥有不同的 ip 地址。因为所有的虚拟接口要共享 mac 地址,所有有些需要注意的地方:

  • DHCP 协议分配 ip 的时候一般会用 mac 地址作为机器的标识。这个情况下,客户端动态获取 ip 的时候需要配置唯一的 ClientID 字段,并且 DHCP server 也要正确配置使用该字段作为机器标识,而不是使用 mac 地址

ipvlan 是 linux kernel 比较新的特性,linux kernel 3.19 开始支持 ipvlan,但是比较稳定推荐的版本是 >=4.2(因为 docker 对之前版本的支持有 bug)。

两种模式

ipvlan 有两种不同的模式:L2 和 L3。一个父接口只能选择一种模式,依附于它的所有虚拟接口都运行在这个模式下,不能混用模式。

L2 模式

ipvlan L2 模式和 macvlan bridge 模式工作原理很相似,父接口作为交换机来转发子接口的数据。同一个网络的子接口可以通过父接口来转发数据,而如果想发送到其他网络,报文则会通过父接口的路由转发出去。

L3 模式

L3 模式下,ipvlan 有点像路由器的功能,它在各个虚拟网络和主机网络之间进行不同网络报文的路由转发工作。只要父接口相同,即使虚拟机/容器不在同一个网络,也可以互相 ping 通对方,因为 ipvlan 会在中间做报文的转发工作。


L3 模式下的虚拟接口不会接收到多播或者广播的报文,为什么呢?这个模式下,所有的网络都会发送给父接口,所有的 ARP 过程或者其他多播报文都是在底层的父接口完成的。需要注意的是:外部网络默认情况下是不知道 ipvlan 虚拟出来的网络的,如果不在外部路由器上配置好对应的路由规则,ipvlan 的网络是不能被外部直接访问的。

实验

实验环境:

➜  ~ uname -a
Linux cizixs-ThinkPad-T450 4.4.0-57-generic #78-Ubuntu SMP Fri Dec 9 23:50:32 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
➜  ~ lsb_release -a
No LSB modules are available.
Distributor ID:    Ubuntu
Description:    Ubuntu 16.04.2 LTS
Release:    16.04
Codename:    xenial

先创建两个用做测试的 network namespace.

➜  ~ sudo ip netns add net1
➜  ~ sudo ip netns add net2

然后创建出 ipvlan 的虚拟网卡接口,因为 L2 和 macvlan 功能相同,我们这里测试一下 L3 模式。创建 ipvlan 虚拟接口的命令和 macvlan 格式相同:

➜  ~ sudo ip link add ipv1 link eth0 type ipvlan mode l3
➜  ~ sudo ip link add ipv2 link eth0 type ipvlan mode l3

把 ipvlan 接口放到前面创建好的 namespace 中:

➜  ~ sudo ip link set ipv1 netns net1
➜  ~ sudo ip link set ipv2 netns net2
➜  ~ sudo ip netns exec net1 ip link set ipv1 up
➜  ~ sudo ip netns exec net2 ip link set ipv2 up

给两个虚拟网卡接口配置上不同网络的 ip 地址,并配置好路由项:

➜  ~ sudo ip netns exec net1 ip addr add 10.0.1.10/24 dev ipv1
➜  ~ sudo ip netns exec net2 ip addr add 192.168.1.10/24 dev ipv2
➜  ~ sudo ip netns exec net1 ip route add default dev ipv1
➜  ~ sudo ip netns exec net2 ip route add default dev ipv2

测试两个网络的连通性:

➜  ~ sudo ip netns exec net1 ping -c 3 192.168.1.10
PING 192.168.1.10 (192.168.1.10) 56(84) bytes of data.
64 bytes from 192.168.1.10: icmp_seq=1 ttl=64 time=0.053 ms
64 bytes from 192.168.1.10: icmp_seq=2 ttl=64 time=0.035 ms
64 bytes from 192.168.1.10: icmp_seq=3 ttl=64 time=0.036 ms

--- 192.168.1.10 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 1998ms
rtt min/avg/max/mdev = 0.035/0.041/0.053/0.009 ms

ipvlan 还是 macvlan?

ipvlan 和 macvlan 两个虚拟网络模型提供的功能,看起来差距并不大,那么什么时候需要用到 ipvlan 呢?要回答这个问题,我们先来看看 macvlan 存在的不足:

  • 需要大量 mac 地址。每个虚拟接口都有自己的 mac 地址,而网络接口和交换机支持的 mac 地址有支持的上限
  • 无法和 802.11(wireless) 网络一起工作

对应的,如果你遇到一下的情况,请考虑使用 ipvlan:

  • 父接口对 mac 地址数目有限制,或者在 mac 地址过多的情况下会造成严重的性能损失
  • 工作在无线网络中
  • 希望搭建比较复杂的网络拓扑(不是简单的二层网络和 VLAN),比如要和 BGP 网络一起工作

参考资料

No comments:

Post a Comment