Hack 和 / - Bond,以太网 Bond

作者:Kyle Rankin

作为一名系统管理员,您应该培养的最重要的美德之一是容忍。现在,虽然 vi 和 Emacs 用户以及 GNOME 和 KDE 用户和谐相处非常重要,但我想在这里关注的是容错。每个人都有缺点,但是当您的服务器的正常运行时间受到威胁时,您应该尽一切努力确保它可以承受电源故障、网络故障、硬件故障甚至您的错误。在本专栏中,我描述了一个基本的容错程序,该程序非常易于实现,所有系统管理员都应将其添加到他们的服务器中:以太网 bonding。

活下来并让接口消亡

以太网 bonding 背后的基本思想是将您机器上的两个或多个以太网端口组合在一起,这样如果一个以太网端口失去连接,另一个 bonding 端口可以接管流量,停机时间为零或最短。事实是,如今,服务器上的大部分服务都需要网络连接才能有用,因此如果您设置了连接到冗余交换机的多个以太网端口,您就可以在概念上幸免于网卡故障、电缆故障、错误的交换机端口甚至交换机故障,并且您的服务器将保持运行。

除了基本的容错能力之外,您还可以使用某些以太网 bonding 模式来提供负载均衡,并获得比单个接口可以提供的更大的带宽。以太网 bonding 是 Linux 内核的一部分功能,它可以根据您选择的 bonding 模式提供多种不同的行为。所有以太网 bonding 信息都可以在 Linux 内核源代码附带的 Documentation/networking/bonding.txt 文件中找到。下面,我提供了该文档的摘录,其中列出了 007 bonding 模式

  • balance-rr 或 0 — 轮询策略:从第一个可用的从属接口到最后一个,按顺序传输数据包。此模式提供负载均衡和容错。

  • active-backup 或 1 — 主动-备份策略:bonding 中只有一个从属接口处于活动状态。只有当活动从属接口发生故障时,另一个从属接口才会变为活动状态。bonding 的 MAC 地址仅在单个端口(网络适配器)上外部可见,以避免混淆交换机。

  • balance-xor 或 2 — XOR 策略:基于选定的传输哈希策略进行传输。默认策略是一个简单的 [(源 MAC 地址 XOR 目标 MAC 地址)模从属接口计数]。可以通过 xmit_hash_policy 选项选择备用传输策略,如下所述。此模式提供负载均衡和容错。

  • broadcast 或 3 — 广播策略:在所有从属接口上广播所有内容。此模式提供容错。

  • 802.3ad 或 4 — IEEE 802.3ad 动态链路聚合:创建共享相同速度和双工设置的聚合组。根据 802.3ad 规范,利用活动聚合器中的所有从属接口。

  • balance-tlb 或 5 — 自适应传输负载均衡:通道 bonding,不需要任何特殊的交换机支持。传出流量根据每个从属接口上的当前负载(相对于速度计算)进行分配。传入流量由当前从属接口接收。如果接收从属接口发生故障,则另一个从属接口将接管发生故障的接收从属接口的 MAC 地址。

  • balance-alb 或 6 — 自适应负载均衡:包括 balance-tlb 以及 IPv4 流量的接收负载均衡 (rlb),并且不需要任何特殊的交换机支持。接收负载均衡是通过 ARP 协商实现的。bonding 驱动程序拦截本地系统发出的 ARP 回复,并将源硬件地址覆盖为 bonding 中其中一个从属接口的唯一硬件地址,以便不同的对等方为服务器使用不同的硬件地址。

现在您已经看到了所有选择,真正的问题是您应该选择哪种 bonding 模式?老实说,这是一个难以回答的问题,因为它在很大程度上取决于您的网络以及您想要实现的目标。我建议设置一个测试网络,并通过在您从另一台主机 ping 服务器时拔下电缆来模拟故障。我发现不同的模式处理故障的方式不同,尤其是在交换机需要一些时间来重新启用已拔下端口或交换机已重新启动的情况下。根据您选择的 bonding 模式,这些情况可能会导致零停机时间或 30 秒的停机时间。对于我在本专栏中的示例,我选择了 bonding 模式 1,因为虽然它仅提供容错,但一次也仅启用一个端口,因此在大多数交换机上进行实验应该相对安全。

注意:bonding 模式是在加载 bonding 模块时设置的,因此,如果您想尝试不同的 bonding 模式,您至少必须卸载并重新加载模块,或者最多重新启动服务器。

虽然以太网 bonding 是通过内核模块和一个名为 ifenslave 的实用程序完成的,但是您用于配置内核模块设置的方法以及配置网络的方式在不同的 Linux 发行版之间可能有所不同。在本专栏中,我将讨论如何为基于 Red Hat 和 Debian 的发行版进行设置,因为这应该涵盖最广泛的服务器。第一步是确保已安装 ifenslave 程序。Red Hat 服务器应该已经安装了此程序,但是在基于 Debian 的系统上,您可能需要安装 ifenslave 软件包。

仅供您的文件使用

下一步是使用您要使用的 bonding 模式以及您可能要为该模块设置的任何其他选项来配置 bonding 模块。在 Red Hat 系统上,您将编辑 /etc/modprobe.conf(对于 2.6 内核)或 /etc/modules.conf(对于较旧的 2.4 内核)。在基于 Debian 的系统上,编辑或创建 /etc/modprobe.d/aliases 文件。在任何一种情况下,都添加以下行

alias bond0 bonding
options bonding mode=1 miimon=100

alias 行会将 bond0 网络接口与 bonding 模块关联起来。如果您打算拥有多个 bonding 接口(例如在具有四个或更多网卡的系统上),您将需要为 bond1 或任何其他接口添加额外的 alias 行。options 行允许我将我的 bonding 模式设置为 1,并设置 miimon(内核将以毫秒为单位检查接口链路状态的频率)。

在您的发行版的网络服务上

与模块配置一样,不同的发行版处理网络配置的方式也大相径庭,bonding 接口也是如此。因此,在这一点上,最好是我分别描述每个系统。

来自 Red Hat 的爱

Red Hat 网络配置是通过 /etc/sysconfig/network-scripts 下的文件管理的。每个接口都有自己的配置文件,前缀为 ifcfg-,因此 eth0 的配置可以在 /etc/sysconfig/network-scripts/ifcfg-eth0 中找到。要配置 bonding,您可以简单地使用您通常用于常规接口的基本网络设置,只是现在它们将在 ifcfg-bond0 中找到

DEVICE=bond0
NETMASK=255.255.255.0
GATEWAY=192.168.19.1
BOOTPROTO=static
IPADDR=192.168.19.64
HOSTNAME=goldfinger.example.net
ONBOOT=yes

接下来,您要用于 bond0 的每个接口都需要配置。在我的情况下,如果我想 bonding eth0 和 eth1,我会将以下内容放入 ifcfg-eth0

DEVICE=eth0
USERCTL=no
ONBOOT=yes
MASTER=bond0
SLAVE=yes
BOOTPROTO=none

并将以下内容放入 ifcfg-eth1

DEVICE=eth1
USERCTL=no
ONBOOT=yes
MASTER=bond0
SLAVE=yes
BOOTPROTO=none

最后,输入service network restart作为 root 用户重新启动您的网络服务以及新的 bonding 接口。从那时起,您可以将 ifcfg-bond0 视为您进行任何网络更改的主配置文件(以及诸如 route-bond0 之类的文件来配置静态路由,例如)。为了让您更轻松地完成此操作,我为 Red Hat 用户提供了一个脚本,该脚本可以自动执行整个过程。使用您要使用的 bonding 接口的名称(例如 bond0)运行该脚本,然后在后面跟上您要 bonding 的接口列表,它将根据在您列出的第一个接口(例如 eth0)中找到的配置自动为您设置模块和网络接口。因此,例如,要设置上述配置,我将确保 ifcfg-eth0 具有我要使用的网络设置,然后我将运行清单 1 中显示的脚本。

清单 1. Red Hat 用户的 Bond 脚本

# bond bond0 eth0 eth1

#!/usr/bin/perl

# bond -- create a bonded interface out of one or 
# more physical interfaces
# Created by Kyle Rankin
#

my $bond_interface = shift;
my @interfaces = @ARGV;
my $network_scripts_path = '/etc/sysconfig/network-scripts/';
my $bond_mode=1;
my $bond_miimon=100;
my $bond_max=2;

usage() unless (@ARGV);
if($#interfaces < 1){
   usage("ERROR: You must have at least 2 interfaces to bond!");
}

system("/etc/init.d/network stop");

config_bond_master($bond_interface, $interfaces[0]);
foreach(@interfaces){
   config_bond_slave($bond_interface, $_);
}
config_modules($bond_interface, $bond_miimon, $bond_mode);

system("/etc/init.d/network start") or die 
 ↪"Couldn't start networking: $!\n";

sub usage
{
   $error = shift;
   print "$error\n" if($error);
   print "Usage: $0 bond_interface interface1 interface2 [...]\n";
   print "\nbond_interface will use the network 
   ↪settings of interface1\n";
   exit
}

sub config_bond_master
{
   my $bond_interface = shift;
   my $main_interface = shift;
   my $netconfig_ref = get_network_config($main_interface);

   open CONFIG, "> $network_scripts_path/ifcfg-$bond_interface" 
   ↪or die "Can't open 
   ↪$network_scripts_path/ifcfg-$bond_interface: $!\n";

   print CONFIG "DEVICE=$bond_interface\n";
   foreach(keys %$netconfig_ref){
      unless($_ eq "HWADDR" || $_ eq "DEVICE"){
         print CONFIG "$_=$$netconfig_ref{$_}\n";
      }
   }
   close CONFIG;
}

sub config_bond_slave
{
   my $bond_interface = shift;
   my $slave_interface = shift;
   my $netconfig_ref = get_network_config($slave_interface);

   open CONFIG, "> $network_scripts_path/ifcfg-$slave_interface" 
   ↪or die "Can't open 
   ↪$network_scripts_path/ifcfg-$slave_interface: $!\n";
   print CONFIG <<"EOC";
DEVICE=$slave_interface
USERCTL=no
ONBOOT=yes
MASTER=$bond_interface
SLAVE=yes
BOOTPROTO=none
EOC
   if($$netconfig_ref{'HWADDR'}){
      print CONFIG "HWADDR=$$netconfig_ref{'HWADDR'}";
   }
}

# This subroutine returns a hash with key-value pairs matching 
# the network configuration for the interface passed as an 
# argument according to the configuration file in 
# /etc/sysconfig/network-scripts/ifcfg-interface
sub get_network_config
{
   my $interface = shift;
   my %netconfig;
   open(CONFIG, "$network_scripts_path/ifcfg-$interface") 
   ↪or die "Can't open 
   ↪$network_scripts_path/ifcfg-$interface: $!\n";
   while(<CONFIG>)
   {
      chomp;
      ($key, $value) = split '=';
      $netconfig{uc($key)} = $value;
   }
   close CONFIG;
   return \%netconfig;
}

sub config_modules
{
   my $bond_interface = shift;
   my $bond_miimon = shift;
   my $bond_mode = shift;
   my $bond_options_configured = 0;
   my $bond_alias_configured = 0;

   if(-f "/etc/modprobe.conf"){ # for 2.6 kernels
      $module_config = "/etc/modprobe.conf";
   }
   else {
      $module_config = "/etc/modules.conf";
   }
   open CONFIG, "$module_config" or die 
   ↪"Can't open $module_config: $!\n";
   while(<CONFIG>){
      if(/options bonding/){ $bond_options_configured = 1; }
      if(/alias $bond_interface bonding/){ 
      ↪$bond_alias_configured = 1; }
   }
   close CONFIG;

   open CONFIG, ">> $module_config" or die 
   ↪"Can't open $module_config: $!\n";
   unless($bond_alias_configured)
   {
      print CONFIG "alias $bond_interface bonding\n";
   }
   unless($bond_options_configured)
   {
      print CONFIG "options bonding 
      ↪miimon=$bond_miimon mode=$bond_mode max_bonds=$bond_max\n";
   }
   close CONFIG;
}
Debian 永恒

您可能会想到,Debian 的网络配置与 Red Hat 的网络配置截然不同。不幸的是,我没有用于为 Debian 用户自动化该过程的 Perl 脚本,但是您会看到,它非常简单,不需要脚本。Debian-based 服务器的所有网络配置都可以在 /etc/network/interfaces 中找到。这是一个示例 interfaces 文件

# The loopback network interface
auto lo
iface lo inet loopback

# The primary network interface
auto eth0
iface eth0 inet static
  address 192.168.19.64
  netmask 255.255.255.0
  gateway 192.168.19.1

要配置 bonding 接口,我只需注释掉 eth0 的所有配置设置,并为 bond0 创建一个新配置,该配置复制 eth0 的所有设置。我唯一做的更改是添加一个名为 slaves 的额外参数,该参数列出了应为此 bonding 接口使用哪些接口

# The loopback network interface
auto lo
iface lo inet loopback

# The primary network interface
#auto eth0
#iface eth0 inet static
#  address 192.168.19.64
#  netmask 255.255.255.0
#  gateway 192.168.19.1

auto bond0
iface bond0 inet static
  address 192.168.19.64
  netmask 255.255.255.0
  gateway 192.168.19.1
  slaves eth0 eth1

完成更改后,键入sudo service networking restartsudo /etc/init.d/networking restart重新启动您的网络接口。

无论您使用 Red Hat 还是 Debian,一旦您配置了 bonding 接口,您都可以使用 ifconfig 命令来查看它是否已配置

$ sudo ifconfig
bond0	Link encap:Ethernet HWaddr 00:0c:29:28:13:3b
	inet addr:192.168.19.64 Bcast:192.168.0.255
	Mask:255.255.255.0
	inet6 addr: fe80::20c:29ff:fe28:133b/64 Scope:Link
	UP BROADCAST RUNNING MASTER MULTICAST MTU:1500 Metric:1
	RX packets:38 errors:0 dropped:0 overruns:0 frame:0
	TX packets:43 errors:0 dropped:0 overruns:0 carrier:0
	collisions:0 txqueuelen:0
	RX bytes:16644 (16.2 KB) TX bytes:3282 (3.2 KB)

eth0	Link encap:Ethernet HWaddr 00:0c:29:28:13:3b
	UP BROADCAST RUNNING SLAVE MULTICAST MTU:1500 Metric:1
	RX packets:37 errors:0 dropped:0 overruns:0 frame:0
	TX packets:43 errors:0 dropped:0 overruns:0 carrier:0
	collisions:0 txqueuelen:1000
	RX bytes:16584 (16.1 KB) TX bytes:3282 (3.2 KB)
	Interrupt:17 Base address:0x1400

eth1	Link encap:Ethernet HWaddr 00:0c:29:28:13:3b
	UP BROADCAST RUNNING SLAVE MULTICAST MTU:1500 Metric:1
	RX packets:1 errors:0 dropped:0 overruns:0 frame:0
	TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
	collisions:0 txqueuelen:1000
	RX bytes:60 (60.0 B) TX bytes:0 (0.0 B)
	Interrupt:18 Base address:0x1480

lo	Link encap:Local Loopback
	inet addr:127.0.0.1 Mask:255.0.0.0
	inet6 addr: ::1/128 Scope:Host
	UP LOOPBACK RUNNING MTU:16436 Metric:1
	RX packets:0 errors:0 dropped:0 overruns:0 frame:0
	TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
	collisions:0 txqueuelen:0
	RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)

启用 bonding 接口后,您可以从远程主机 ping 服务器,并在拔下一根以太网电缆时测试它是否故障转移。任何故障都应记录在 dmesg (/var/log/dmesg) 和 syslog (/var/log/messages 或 /var/log/syslog) 中,并且看起来像这样

Oct 04 16:43:28 goldfinger kernel: [ 2901.700054] eth0: link down
Oct 04 16:43:29 goldfinger kernel: [ 2901.731190] bonding: bond0:
link status definitely down for interface eth0, disabling it
Oct 04 16:43:29 goldfinger kernel: [ 2901.731300] bonding: bond0:
making interface eth1 the new active one.

正如我之前所说,我强烈建议您尝试每种 bonding 模式以及不同类型的故障,以便您了解每种模式如何处理网络上的故障和恢复。当您的系统更能容忍故障时,您会发现您更能容忍您的系统。

Kyle Rankin 是旧金山湾区的系统架构师,也是许多书籍的作者,包括The Official Ubuntu Server BookKnoppix HacksUbuntu Hacks。他目前是 North Bay Linux Users' Group 的总裁。

加载 Disqus 评论