NetworkManager简单教程

最近在尝试切换到CentOS 8,虽然不久前CentOS宣布从RHEL下游转向CentOS Stream了,但是相信以后会有类似的替代品出现,本质上也是在适应RHEL 8。这一试不要紧,一开始就被NetworkManager给吓住了,这都什么玩意,怎么这么难用?

一开始呢,想着先用被废弃但是还没被删除的老network-scripts顶一顶,想回滚也很简单:执行dnf install -y network-scripts && systemctl disable NetworkManager && systemctl enable network就好了,剩下的操作和CentOS 7里的ifup、ifdown脚本没什么区别,只是在执行的时候会出现一些警告信息提示这种方式已经废弃。

但是又想了想,面对新事物,第一个反应就是想着禁用或者删除它,特别是一个这和当初从CentOS 6切换到CentOS 7时候抵制systemd一样的么?特别是从大方向上看,NetworkManger会和systemd一样被越来越多的发行版接受,那学习一下并且适应新的网络管理方式,不是个坏事。

其实NetworkManager不是个新事物了,之前笔记本装的ArchLinux系统,很早就切换到NetworkManager了,只不过因为有GUI管理工具,而且场景比较单一,所以没有过多的关注。等到了服务器端,场景比较复杂,以前积累的东西就不管用了。最主要的,是和之前管理网络的思路出现了一些冲突,产生了一些排斥心理,觉得不好用。等静下心来仔细看了看相关的文档,也做了一些实验,适应了这种新的管理方式之后,发现NetworkManager还是值得一试的。所以还是面对几个场景分享一下几个例子,希望能帮助到更多的人完成切换吧。

NetworkManger的设计哲学

要想了解一个东西,得退回到最开始,看看它的设计思路是什么。确实,NetworkManager从设计之初的想法就不太一样,man NetworkManager里可以看到它的设计初衷:

The NetworkManager daemon attempts to make networking configuration and operation as painless and automatic as possible by
managing the primary network connection and other network interfaces, like Ethernet, Wi-Fi, and Mobile Broadband devices.

从某些角度来说,更像是Windows那样尽量让用户以最简单和最无痛的方式获得网络连接,很多操作都是自动的,而之前发行版的脚本配置模式,如果没有明确的配置文件,则不会有任何网络连接。个人觉得这个特点,在桌面端其实挺OK的,但是对于服务器来说,反而有点好心干坏事,因为这种自动的行为会影响系统管理员的预期,这也是一开始最不适应的地方,网络不按预期工作,明明没有配置,为啥网卡被启动了。不过不用担心,这些问题,都有解决办法,我们一点点去看!

两个重要的概念

在NetworkManager里,有两个非常重要的概念。基本上,理清楚这两个概念对应的含义和区别,剩下的通过文档就可以非常容易的掌握NetworkManager的使用了。

第一个概念是device,也就是设备,怎么理解呢,一个设备对应一个网口,基本上ip link里看到的那些都有对应的设备,基本上可以认为就是物理的网卡,每个物理网卡都会是一个device,当然,有些虚拟的网卡,也会是一个device,比如网桥bridge等。当然也有些区别,物理网卡是现实存在的设备,本质上最终是NetworkManager来管理它,而虚拟机网卡就是NetworkManager因为连接需要而生成的网卡。使用nmcli device命令,可以看到当前NetworkManager所识别的设备,以及:

- 这个设备是否在NetworkManager的管理之下
- 设备所对应的连接配置信息
- 设备当前的连接配置

举个例子:

[root@localhost ~]# nmcli device
DEVICE  TYPE      STATE      CONNECTION
eth0    ethernet  connected  eth0
lo      loopback  unmanaged  --

可以很明确的看到我这台机器有两个设备,分别是eth0lo,其中eth0是以太网类型,被NetworkManager管理且对应的连接配置为eth0,而lo是loopback,目前不被NetworkManager所管理。在终端里执行,正常的设备会显示绿色,不正常的设备显示成红色,不被管理的显示为灰色,还是很清晰的。

另外一个概念是connection,也就是连接。怎么理解呢,连接就是一系列配置,比如IP地址获取方式是DHCP或者手动配置,如果是手动配置,则配置哪些IP地址,网关,DNS等等信息,这些配置非常的多,就不一一细说了,具体的看文档就好。

需要注意的是,连接是需要最终被apply到某个device上的,而且针对同一个device,可以有多个connection。但是,在任意时刻,有且只能有一个活动的connection被apply到一个device。有点绕,但是思考一下这个场景就很容易理解了:针对笔记本的无线网卡,在公司,连接的自然是公司的WIFI,这就需要一个connection,到了家里,又需要连接家里的WIFI,这就是另外一个connection,这俩都是针对同一个device的配置,也不会同时起作用。其实这个场景在服务器上不太常见,所以一般情况下,服务器上基本就可以配置成一个connection对应一个device就解决了。

弄明白这俩者之间的关系,很多东西就很顺畅了。下面就是一些常见的例子:

给网卡配置静态IP地址

弄明白connectiondevice的关系之后,给网卡配置IP地址就很方便了:创建一个新的connection并把它apply到我们的device上。

[root@localhost ~]# nmcli connection add type ethernet con-name eth0-static ifname eth0 ipv4.method manual ipv4.addresses "192.168.145.60/20" ipv4.gateway 192.168.144.1 ipv4.dns 114.114.114.114 ipv6.method auto
Connection 'eth0-static' (3ae60979-d6f1-4dbb-8a25-ff1178e7305c) successfully added.

从命令上看应该还是很容易读懂的,创建一个新的ethernet类型的连接,名字叫做eth0-static,网卡名字是eth0,IPv4手动配置,地址网关DNS等等都填上,IPv6使用自动配置。

一般情况下,连接创建后如果对应设备没有活跃的其他连接,创建的连接会直接生效,如果没生效也比较简单,直接执行:

[root@localhost ~]# nmcli connection up eth0-static
Connection successfully activated (D-Bus active path: /org/freedesktop/NetworkManager/ActiveConnection/4)

执行完成后连接生效,可以通过nmcli connectionip addr命令查看结果:

[root@localhost ~]# nmcli connection
NAME         UUID                                  TYPE      DEVICE
eth0-static  3ae60979-d6f1-4dbb-8a25-ff1178e7305c  ethernet  eth0
eth0         72534820-fb8e-4c5a-8d49-8c013441d390  ethernet  --
[root@localhost ~]# nmcli device
DEVICE  TYPE      STATE      CONNECTION
eth0    ethernet  connected  eth0-static
lo      loopback  unmanaged  --
[root@localhost ~]# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    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
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:15:5d:b3:80:01 brd ff:ff:ff:ff:ff:ff
    inet 192.168.145.59/20 brd 192.168.159.255 scope global noprefixroute eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::a7cf:fd2:7970:4bd4/64 scope link noprefixroute
       valid_lft forever preferred_lft forever

如果要修改一个连接,也很简单,执行nmcli connection modify XXXX ...就行了,语法和add差不多,不过修改一个连接需要注意的是,有些修改不会直接生效,需要执行nmcli connection down XXXX; nmcli connection up XXXX之后修改的属性才能生效。

给网卡添加vlan tag并配置IP地址

接下来一个例子是给网卡打vlan tag,这个场景也比较常见,特别是在交换机端是trunk口的情况下:

[root@localhost ~]# nmcli connection add type vlan con-name eth1-vlan-100 ifname eth1.100 dev eth1 vlan.id 100 ipv4.method manual ipv4.addresses 192.168.100.10/24 ipv4.gateway 192.168.100.1
Connection 'eth1-vlan-100' (c0036d90-1edf-4085-8b9c-691433fc5afd) successfully added.

可以发现和上个例子有一点点的不同,因为实际的流量必须通过某个设备出去,所以和之前相比需要多加上dev eth1参数,声明流量的出口。

Connection创建成功后自动激活了:

[root@localhost ~]# nmcli connection
NAME           UUID                                  TYPE      DEVICE
eth0-static    3ae60979-d6f1-4dbb-8a25-ff1178e7305c  ethernet  eth0
eth1-vlan-100  c0036d90-1edf-4085-8b9c-691433fc5afd  vlan      eth1.100
eth0           72534820-fb8e-4c5a-8d49-8c013441d390  ethernet  --
[root@localhost ~]# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    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
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:15:5d:b3:80:01 brd ff:ff:ff:ff:ff:ff
    inet 192.168.145.59/20 brd 192.168.159.255 scope global noprefixroute eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::a7cf:fd2:7970:4bd4/64 scope link noprefixroute
       valid_lft forever preferred_lft forever
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:15:5d:b3:80:02 brd ff:ff:ff:ff:ff:ff
7: eth1.100@eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 00:15:5d:b3:80:02 brd ff:ff:ff:ff:ff:ff
    inet 192.168.100.10/24 brd 192.168.100.255 scope global noprefixroute eth1.100
       valid_lft forever preferred_lft forever
    inet6 fe80::6c74:c8d8:7448:370a/64 scope link noprefixroute
       valid_lft forever preferred_lft forever

可以看到,因为有eth1-vlan-100这个connection并且是Active状态,所以NetworkManager创建了一个虚拟的deviceeth1.100,如果我把这个connection给down掉之后:

[root@localhost ~]# nmcli connection down eth1-vlan-100
Connection 'eth1-vlan-100' successfully deactivated (D-Bus active path: /org/freedesktop/NetworkManager/ActiveConnection/15)
[root@localhost ~]# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    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
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:15:5d:b3:80:01 brd ff:ff:ff:ff:ff:ff
    inet 192.168.145.59/20 brd 192.168.159.255 scope global noprefixroute eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::a7cf:fd2:7970:4bd4/64 scope link noprefixroute
       valid_lft forever preferred_lft forever
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:15:5d:b3:80:02 brd ff:ff:ff:ff:ff:ff

可以发现eth1.100直接就没了。所以针对这些虚拟的device,它的生命周期和connection是一致的。

配置网卡的Bonding

接下来该轮到bonding了,bonding也是经常遇到的配置了,配置方法也比较简单:

首先先把bonding master给加上,并且配置好bonding的模式和其他参数,另外,由于bonding之后IP地址一般会配置到bond设备上,在添加的时候顺便也把IP这些信息也填上:

[root@localhost ~]# nmcli connection add type bond con-name bonding-bond0 ifname bond0 bond.options "mode=balance-xor,miimon=100,xmit_hash_policy=layer3+4,updelay=5000" ipv4.method manual ipv4.addresses 192.168.100.10 ipv4.gateway 192.168.100.1
8.100.10/24 ipv4.gateway 192.168.100.1
Connection 'bonding-bond0' (a81a11b0-547e-4c6b-9518-62ce51d17ab4) successfully added.

添加完bonding master,再把两个slave添加到master口上:

[root@localhost ~]# nmcli connection add type bond-slave con-name bond0-slave-ens1f0 ifname ens1f0 master bond0
Connection 'bond0-slave-ens1f0' (be6285ae-e07a-468d-a302-342c233d1346) successfully added.
[root@localhost ~]# nmcli connection add type bond-slave con-name bond0-slave-ens1f1 ifname ens1f1 master bond0
Connection 'bond0-slave-ens1f1' (321aa982-5ca0-4379-b822-4200f366cc27) successfully added.

再Down/Up一下bond口:

[root@localhost ~]# nmcli connection down bonding-bond0;nmcli connection up bonding-bond0
Connection 'bonding-bond0' successfully deactivated (D-Bus active path: /org/freedesktop/NetworkManager/ActiveConnection/251123)
Connection successfully activated (master waiting for slaves) (D-Bus active path: /org/freedesktop/NetworkManager/ActiveConnection/251126)
[root@localhost ~]# nmcli connection
NAME                UUID                                  TYPE      DEVICE
bonding-bond0       a81a11b0-547e-4c6b-9518-62ce51d17ab4  bond      bond0
bond0-slave-ens1f0  be6285ae-e07a-468d-a302-342c233d1346  ethernet  ens1f0
bond0-slave-ens1f1  321aa982-5ca0-4379-b822-4200f366cc27  ethernet  ens1f1

添加dummy网卡并配置多个IP地址

再举个dummy网卡的例子,因为有其他部门目前在用DR模式的LVS负载均衡,所以需要配置dummy网卡和IP地址,之前也稍微看了看,也比较简单:

[root@localhost ~]# nmcli connection add type dummy con-name dummy-dummy0 ifname dummy0 ipv4.method manual ipv4.addresses "1.1.1.1/32,2.2.2.2/32,3.3.3.3/32,4.4.4.4/32"
Connection 'dummy-dummy0' (e02daf93-d1bc-4ec7-a985-7435426129be) successfully added.
[root@localhost ~]# nmcli connection
NAME          UUID                                  TYPE      DEVICE
System eth0   5fb06bd0-0bb0-7ffb-45f1-d6edd65f3e03  ethernet  eth0
dummy-dummy0  e02daf93-d1bc-4ec7-a985-7435426129be  dummy     dummy0
[root@localhost ~]# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    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
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether fa:16:3e:a6:14:86 brd ff:ff:ff:ff:ff:ff
    inet 10.185.14.232/24 brd 10.185.14.255 scope global dynamic noprefixroute eth0
       valid_lft 314568640sec preferred_lft 314568640sec
    inet6 fe80::f816:3eff:fea6:1486/64 scope link
       valid_lft forever preferred_lft forever
5: dummy0: <BROADCAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN group default qlen 1000
    link/ether e6:ff:39:ca:c7:91 brd ff:ff:ff:ff:ff:ff
    inet 1.1.1.1/32 scope global noprefixroute dummy0
       valid_lft forever preferred_lft forever
    inet 2.2.2.2/32 scope global noprefixroute dummy0
       valid_lft forever preferred_lft forever
    inet 3.3.3.3/32 scope global noprefixroute dummy0
       valid_lft forever preferred_lft forever
    inet 4.4.4.4/32 scope global noprefixroute dummy0
       valid_lft forever preferred_lft forever
    inet6 fe80::ad93:23f1:7913:b741/64 scope link noprefixroute
       valid_lft forever preferred_lft forever

需要注意的是,一个连接是可以配置多个IP地址的,多个IP地址之间用,分割就可以了。

配置Bond+Bridge

Bond+Bridge的配置在虚拟化场景比较常见,需要注意的是,有了Bridge之后,IP地址需要配置到Bridige上。

[root@localhost ~]# nmcli connection add type bridge con-name bridge-br0 ifname br0 ipv4.method manual ipv4.addresses 192.168.100.10 ipv4.gateway 192.168.100.1
Connection 'bridge-br0' (6052d8ca-ed8f-474b-88dd-9414bf028a2c) successfully added.

此时创建了一个网桥br0,但是还没有任何接口连接到这个网桥上,下面需要创建个bond0口,并把bond0加到br0上。

[root@localhost ~]# nmcli connection add type bond con-name bonding-bond0 ifname bond0 bond.options "mode=balance-xor,miimon=100,xmit_hash_policy=layer3+4,updelay=5000" connection.master br0 connection.slave-type bridge
Connection 'bonding-bond0' (755f0c93-6638-41c1-a7de-5e932eba6d1f) successfully added.

这里配置比较特殊,创建bond口和上面差不多,但是多了点配置connection.master br0 connection.slave-type bridge,这个和普通的bridge-slave口直接指定master br0的方式不太一样,因为bond0也是个虚拟的接口,所以需要将接口的属性connection.master配置成br0,才能实现把bond0这个虚拟接口添加到br0的功能。

后面bond0添加两个slave口还是和之前没有区别:

[root@localhost ~]# nmcli connection add type bond-slave con-name bond0-slave-ens1f0 ifname ens1f0 master bond0
Connection 'bond0-slave-ens1f0' (7ec188d0-d2db-4f80-a6f9-b7f93ab873f5) successfully added.
[root@localhost ~]# nmcli connection add type bond-slave con-name bond0-slave-ens1f1 ifname ens1f1 master bond0
Connection 'bond0-slave-ens1f1' (655c2960-0532-482a-8227-8b98eb7f829b) successfully added.
[root@localhost ~]# nmcli connection
NAME                UUID                                  TYPE      DEVICE
bridge-br0          6052d8ca-ed8f-474b-88dd-9414bf028a2c  bridge    br0
bond0-slave-ens1f0  7ec188d0-d2db-4f80-a6f9-b7f93ab873f5  ethernet  ens1f0
bond0-slave-ens1f1  655c2960-0532-482a-8227-8b98eb7f829b  ethernet  ens1f1
bonding-bond0       755f0c93-6638-41c1-a7de-5e932eba6d1f  bond      bond0

配置Bond+OVS Bridge

好了,地狱级难度的例子来了,想要利用NetworkManager来管理OVS Bridge,这该怎么做?这个场景是我们线上在用的,实验了很多次,总算找到办法解决了。

首先,需要安装NetworkManager-ovs这个包,这个包是NetworkManager支持OVS的插件,所以得安装并重启NetworkManager服务后生效:

[root@localhost ~]# yum install -y NetworkManager-ovs && systemctl restart NetworkManager

第二步,需要创建一个ovs-bridge,但是呢,这里有个坑,在man nm-openvswitch里也有一些说明:

  • NetworkManager only ever talks to a single OVSDB instance via an UNIX domain socket.

  • The configuration is made up of Bridges, Ports and Interfaces. Interfaces are always enslaved to Ports, and Ports are
    always enslaved to Bridges.

  • NetworkManager only creates Bridges, Ports and Interfaces you ask it to. Unlike ovs-vsctl, it doesn’t create the local
    interface nor its port automatically.

  • You can’t enslave Interface directly to a Bridge. You always need a Port, even if it has just one interface.

  • There are no VLANs. The VLAN tagging is enabled by setting a ovs-port.tag property on a Port.

  • There are no bonds either. The bonding is enabled by enslaving multiple Interfaces to a Port and configured by setting
    properties on a port.

Bridges
Bridges are represented by connections of ovs-bridge type. Due to the limitations of OVSDB, “empty” Bridges (with no
Ports) can’t exist. NetworkManager inserts the records for Bridges into OVSDB when a Port is enslaved.

Ports
Ports are represented by connections of ovs-port type. Due to the limitations of OVSDB, “empty” Ports (with no Interfaces)
can’t exist. Ports can also be configured to do VLAN tagging or Bonding. NetworkManager inserts the records for Ports into
OVSDB when an Interface is enslaved. Ports must be enslaved to a Bridge.

Interfaces
Interfaces are represented by a connections enslaved to a Port. The system interfaces (that have a corresponding Linux
link) have a respective connection.type of the link (e.g. “wired”, “bond”, “dummy”, etc.). Other interfaces (“internal” or
“patch” interfaces) are of ovs-interface type. The OVSDB entries are inserted upon enslavement to a Port.

怎么理解呢,首先NetworkManager之和OVSDB通信,而OVSDB是有些限制的:1. 不允许空Bridge(没有任何Port)存在;2. 不允许空Port(没有任何Interface)存在;3. 不能直接将一个Interface接到Bridge上,必须有对应的Port才行。

不明白也没事,看下面的例子就好,首先我们要创建一个OVS Bridge ovsbr0:

[root@localhost ~]# nmcli connection add type ovs-bridge con-name ovs-br0 conn.interface-name ovsbr0
Connection 'ovs-br0' (c409c13a-3bc3-42fc-a6f2-79cb315fd26b) successfully added.
[root@localhost ~]# nmcli connection add  type ovs-port con-name ovs-br0-port0 conn.interface-name br0-port0 master ovsbr0
Connection 'ovs-br0-port0' (32982ce8-41ec-44e9-8010-da80bbefa5d4) successfully added.
[root@localhost ~]# nmcli conn add type ovs-interface slave-type ovs-port conn.interface-name ovsbr0-iface0 master br0-port0 ipv4.method manual ipv4.address 192.168.2.100/24
Connection 'ovs-slave-ovsbr0-iface0' (f8ba0e5e-c136-4287-aede-e4d59031d878) successfully added.

请注意,这三个connection必须完整创建好,才能真正的创建ovsbr0,这个和我们平常意识的逻辑很不一样。如果直接用ovs-vsctl命令创建,那只需要执行ovs-vsctl add-br ovsbr0就行了,然而在NetworkManager里,你必须把详细的内部逻辑拆分开:1. 创建个OVS Bridge ovsbr0;2. 在ovsbr0上创建个Port br0-port0;3. 创建个interface ovsbr0-iface0并连接到br0-port0上。

如此看来,ovs-vsctl命令行的操作把很多细节给隐藏掉了。

按照步骤创建上面三个connection之后,可以看到ovsbr0被创建好了:

[root@localhost ~]# nmcli connection
NAME                     UUID                                  TYPE           DEVICE
ovs-slave-ovsbr0-iface0  f8ba0e5e-c136-4287-aede-e4d59031d878  ovs-interface  ovsbr0-iface0
ovs-br0                  c409c13a-3bc3-42fc-a6f2-79cb315fd26b  ovs-bridge     ovsbr0
ovs-br0-port0            32982ce8-41ec-44e9-8010-da80bbefa5d4  ovs-port       br0-port0
[root@localhost ~]# ovs-vsctl show
a2ab0cdf-9cf1-41a5-99f4-ae81c58e3fa8
    Bridge ovsbr0
        Port br0-port0
            Interface ovsbr0-iface0
                type: internal
    ovs_version: "2.13.1"
[root@localhost ~]# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    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
10: ovs-system: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether ca:cb:22:a1:a7:fb brd ff:ff:ff:ff:ff:ff
11: ovsbr0-iface0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN group default qlen 1000
    link/ether c2:51:c2:2b:6d:b5 brd ff:ff:ff:ff:ff:ff
    inet 192.168.2.100/24 brd 192.168.2.255 scope global noprefixroute ovsbr0-iface0
       valid_lft forever preferred_lft forever

创建好ovsbr0之后,需要把bond0也加进去,如果是ovs-vsctl命令操作的话,直接ovs-vsctl add-port ovsbr0 bond0就行了,ovs-vsctl帮我们隐藏了细节。同样的操作如果用NetworkManager,就需要先创建一个Port,然后再把bond0加到这个Port上了:

[root@localhost ~]# nmcli connection add type ovs-port con-name ovs-br0-port-bond0 conn.interface-name br0-bond0 master ovsbr0
Connection 'ovs-br0-port-bond0' (de863ea6-4e1b-4343-93a3-91790895256f) successfully added.
[root@localhost ~]# nmcli connection add type bond con-name bonding-bond0 ifname bond0 bond.options "mode=balance-xor,miimon=100,xmit_hash_policy=layer3+4,updelay=5000" connection.master br0-bond0 connection.slave-type ovs-port
Connection 'bonding-bond0' (8b233d53-65b1-4237-b835-62135bb66ada) successfully added.
[root@localhost ~]# nmcli connection add type bond-slave con-name bond0-slave-ens1f0 ifname ens1f0 master bond0
Connection 'bond0-slave-ens1f0' (6d5febe2-fc65-428a-94f1-9a782cd6b397) successfully added.
[root@localhost ~]# nmcli connection add type bond-slave con-name bond0-slave-ens1f1 ifname ens1f1 master bond0
Connection 'bond0-slave-ens1f1' (55ce8e7f-233d-430f-901d-f0e5f326c8c7) successfully added.
[root@localhost ~]# nmcli connection
NAME                     UUID                                  TYPE           DEVICE
ovs-slave-ovsbr0-iface0  f8ba0e5e-c136-4287-aede-e4d59031d878  ovs-interface  ovsbr0-iface0
bond0-slave-ens1f0       6d5febe2-fc65-428a-94f1-9a782cd6b397  ethernet       ens1f0
bond0-slave-ens1f1       55ce8e7f-233d-430f-901d-f0e5f326c8c7  ethernet       ens1f1
bonding-bond0            8b233d53-65b1-4237-b835-62135bb66ada  bond           bond0
ovs-br0                  c409c13a-3bc3-42fc-a6f2-79cb315fd26b  ovs-bridge     ovsbr0
ovs-br0-port0            32982ce8-41ec-44e9-8010-da80bbefa5d4  ovs-port       br0-port0
ovs-br0-port-bond0       de863ea6-4e1b-4343-93a3-91790895256f  ovs-port       br0-bond0
[root@localhost ~]# ovs-vsctl show
a2ab0cdf-9cf1-41a5-99f4-ae81c58e3fa8
    Bridge ovsbr0
        Port br0-port0
            Interface ovsbr0-iface0
                type: internal
        Port br0-bond0
            Interface bond0
                type: system
    ovs_version: "2.13.1"
[root@localhost ~]# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    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
2: ens1f0: <BROADCAST,MULTICAST,SLAVE,UP,LOWER_UP> mtu 1500 qdisc mq master bond0 state UP group default qlen 1000
    link/ether 0c:42:a1:70:c7:2a brd ff:ff:ff:ff:ff:ff
3: ens1f1: <BROADCAST,MULTICAST,SLAVE,UP,LOWER_UP> mtu 1500 qdisc mq master bond0 state UP group default qlen 1000
    link/ether 0c:42:a1:70:c7:2a brd ff:ff:ff:ff:ff:ff
4: ovs-system: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether ca:cb:22:a1:a7:fb brd ff:ff:ff:ff:ff:ff
5: ovsbr0-iface0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN group default qlen 1000
    link/ether c2:51:c2:2b:6d:b5 brd ff:ff:ff:ff:ff:ff
    inet 192.168.2.100/24 brd 192.168.2.255 scope global noprefixroute ovsbr0-iface0
       valid_lft forever preferred_lft forever
6: bond0: <BROADCAST,MULTICAST,MASTER,UP,LOWER_UP> mtu 1500 qdisc noqueue master ovs-system state UP group default qlen 1000
    link/ether 0c:42:a1:70:c7:2a brd ff:ff:ff:ff:ff:ff

可以看到,NetworkManager和直接用ovs-ctl最大的不同,就是把一些细节暴露了出来,本质上把一个接口加到Bridge上不是直接加的,而是加到了Bridge的某个Port上。但是仔细一想也没毛病,对应到现实世界的交换机,你接接线也是接到交换机的某个端口上,如果没有端口,那线往哪插呢?

配置持久化

好了,上面举了很多例子实现了一些我们可能会用到的场景,但是一大堆问题又来了,这些配置能持久化么?重启了机器之后还会有么?如果有,那这些配置是保存在哪里的?我能不能不用nmcli这个命令行工具了,使用配置文件,能完成网络的配置么?

这些问题的答案都是肯定的!

首先呢,针对老版本network-scripts,也就是存放在/etc/sysconfig/network-scripts/目录下的那些ifcfg-*开头的配置文件,NetworkManager通过一个ifcfg-rh plugin去识别,这个插件在RHEL里是默认开启的,而且,针对一些配置类型,比如ethernet,bond,vlan,bridge等配置,通过nmcli创建或者修改connections,都会同步到这个目录下对应的配置文件里:

[root@localhost ~]# nmcli connection
NAME           UUID                                  TYPE      DEVICE
eth0-static    3ae60979-d6f1-4dbb-8a25-ff1178e7305c  ethernet  eth0
eth1-vlan-100  7bc246cb-140a-4515-8dc1-8efa03b789cb  vlan      eth1.100
bridge-br0     3230425c-505d-4a97-adbe-6f26e27fe53c  bridge    br0
eth0           72534820-fb8e-4c5a-8d49-8c013441d390  ethernet  --
[root@localhost ~]# ls -l /etc/sysconfig/network-scripts/
total 16
-rw-r--r--. 1 root root 372 Feb 20 15:13 ifcfg-bridge-br0
-rw-r--r--. 1 root root 278 Feb 11 22:02 ifcfg-eth0
-rw-r--r--. 1 root root 360 Feb 17 18:34 ifcfg-eth0-static
-rw-r--r--. 1 root root 415 Feb 20 16:11 ifcfg-eth1-vlan-100
[root@localhost ~]# cat /etc/sysconfig/network-scripts/ifcfg-eth0
TYPE=Ethernet
PROXY_METHOD=none
BROWSER_ONLY=no
BOOTPROTO=dhcp
DEFROUTE=yes
IPV4_FAILURE_FATAL=no
IPV6INIT=yes
IPV6_AUTOCONF=yes
IPV6_DEFROUTE=yes
IPV6_FAILURE_FATAL=no
IPV6_ADDR_GEN_MODE=stable-privacy
NAME=eth0
UUID=72534820-fb8e-4c5a-8d49-8c013441d390
DEVICE=eth0
ONBOOT=yes

可以看到每个connection都对应了一个配置文件。

然后NetworkManager还会读取/etc/NetworkManager/system-connections/目录下的配置文件,同时,通过nmcli创建和修改一些其他类型的connections,比如ovs-bridge, dummy这些,也会同步写入到这个目录下:

[root@localhost ~]# nmcli connection
NAME           UUID                                  TYPE      DEVICE
eth0-static    3ae60979-d6f1-4dbb-8a25-ff1178e7305c  ethernet  eth0
dummy-dummy0   190f363b-190b-4b98-b85c-046ec8995453  dummy     dummy0
eth1-vlan-100  7bc246cb-140a-4515-8dc1-8efa03b789cb  vlan      eth1.100
bridge-br0     3230425c-505d-4a97-adbe-6f26e27fe53c  bridge    br0
eth0           72534820-fb8e-4c5a-8d49-8c013441d390  ethernet  --
[root@localhost ~]# ls -l /etc/NetworkManager/system-connections/
total 4
-rw-------. 1 root root 310 Feb 20 16:16 dummy-dummy0.nmconnection
[root@localhost ~]# cat /etc/NetworkManager/system-connections/dummy-dummy0.nmconnection
[connection]
id=dummy-dummy0
uuid=190f363b-190b-4b98-b85c-046ec8995453
type=dummy
interface-name=dummy0
permissions=

[dummy]

[ipv4]
address1=1.1.1.1/32
address2=2.2.2.2/32
address3=3.3.3.3/32
address4=4.4.4.4/32
dns-search=
method=manual

[ipv6]
addr-gen-mode=stable-privacy
dns-search=
method=auto

[proxy]

可以看到dummy-dummy0的配置被持久化在/etc/NetworkManager/system-connections/

所以如果要修改配置,也是可以到这两个对应目录下直接修改对应的配置文件的。但是这里有个小问题,就是修改配置文件后,NetworkManager不会自动重新加载这些配置,需要手动执行nmcli connection load XXXX手动重载单个配置或者执行nmcli connection reload重新加载所有的配置文件。加载完成后,要想配置真正生效,还需要执行nmcli connection down XXXX; nmcli connection up XXXX或者nmcli device reapply XXX来真正让配置生效。

Leave Me Alone!

说了这么多,还有一件很重要的事还没说:假如我真的不希望NetworkManager帮我管理某些网卡,怎么办?因为默认情况下,NetworkManager会自动得把很多设备的纳入管理,然后自动创建一堆Wired connection,就像这样:

[root@localhost ~]# nmcli connection
NAME                UUID                                  TYPE      DEVICE
eth0                72534820-fb8e-4c5a-8d49-8c013441d390  ethernet  eth0
Wired connection 1  3a8d6eb9-9d38-3c38-9519-4918f58ee42c  ethernet  ens1f0_0
Wired connection 2  d0efb693-45d0-4245-8018-6738f7509094  ethernet  ens1f0_1
Wired connection 3  b3ecf462-a0ed-4f08-a203-c4f10b4dde0b  ethernet  ens1f0_2
Wired connection 4  5f0ca36b-2add-4815-a859-ff651238f893  ethernet  ens1f0_3
Wired connection 5  3f010d3e-74ba-405c-a575-eba53641fe4f  ethernet  ens1f0_4
Wired connection 6  88e4d303-fd6b-4f66-9e4e-6743ec47c8b7  ethernet  ens1f0_5
Wired connection 7  2800a439-44e1-4304-9c2c-dedbbba74c40  ethernet  ens1f0_6
Wired connection 8  21ae8892-8a51-4c77-854c-08e9857e32d9  ethernet  ens1f0_7

在我们的SR-IOV场景下更是这样,因为开启SR-IOV之后,会创建很多的网卡,然后NetworkManager不分青红皂白,全部给管理上,让人头大。所以需要一个配置能通知NetworkManager哪些网卡不需要纳入管理。

还好NetworkManager提供了这个配置项,可以声明哪些网卡不被管理:在/etc/NetworkManager/conf.d/目录下创建unmanaged.conf

[root@localhost ~]# cat /etc/NetworkManager/conf.d/unmanaged.conf
[keyfile]
unmanaged-devices=mac:00:1E:65:30:D1:C4;interface-name:eth1;interface-name:ens1f0_*

具体的匹配规则有很多,可以参考man NetworkManager.confDevice List Format部分,这里就不在赘述了。

重启生效后,瞬间清净了:

[root@localhost ~]# nmcli device
DEVICE    TYPE      STATE      CONNECTION
eth0      ethernet  connected  eth0
ens1f0_0  ethernet  unmanaged  --
ens1f0_1  ethernet  unmanaged  --
ens1f0_2  ethernet  unmanaged  --
ens1f0_3  ethernet  unmanaged  --
lo        loopback  unmanaged  --

总结

好了,基本上NetworkManager基本的用法说的差不多了,总体来说,如果掌握了它的设计思路和一些细节逻辑,NetworkManager使用起来也没有那么不堪,甚至还会觉得很好用,毕竟nmcli命令行可以Tab进行自动提示,而且命令行风格整体也比较符合自然语言。

最后希望这些例子能给遇到同样问题的人一些帮助吧,起码希望NetworkManager不要成为最终迁移RHEL 8的拦路虎。