Fullmoon System

Linuxボンディング設定:モードの違いとCentOS 7.9のactive-backup構成

EdwardMoon

Linuxのボンディングは、2つ以上の物理ネットワークインターフェースを1つの論理インターフェースにまとめる機能です。障害時の経路切り替えや複数接続の負荷分散ができますが、効果はモードとスイッチ構成によって異なります。この記事ではモードを比較した後、CentOS 7.9のnetwork-scripts環境でactive-backupを構成し、復旧する方法を説明します。

適用範囲:既存のnetwork.serviceが管理するIPv4固定アドレス環境の保守例です。CentOS Linux 7は2024年6月30日にサポートが終了しているため、新規サーバーの標準にはしないでください。CentOS公式のサポート終了案内

ボンディングでできることと限界

active-backupは1つのNICだけを通信に使い、障害時に別のNICへ切り替えます。1Gbps NICを2つ束ねても、通常時の帯域幅はアクティブなNIC 1つの速度に制限されます。一方、LACPなどの負荷分散モードは複数フローを複数NICへ分散できます。ただし、単一接続の速度が常にNICの数だけ増えるわけではありません。

ボンディングはLinux bondingドライバーを使用します。teamdによるネットワークチーミングは同様の目的に使える別の実装なので、設定方法を混用しないでください。リンク障害へ対応できても、スイッチ全体、上位ルーター、共通の電源系統が故障すると通信が途切れる場合があります。

モードとスイッチ構成

スイッチへ接続する一般的なサーバー環境を比較します。直接接続や特殊なトポロジーは別途検証が必要です。

モード動作スイッチ側の要件選択時の注意
0: balance-rr送信パケットをNICへ順番に分散通常は静的ポート集約が必要パケット順序が変わる場合がある
1: active-backup1つのNICを使い、障害時に切り替えLACP・静的集約は不要速度は合算されない
2: balance-xorハッシュ方針で送信経路を選択通常は静的ポート集約が必要LACPでネゴシエーションするモードではない
3: broadcast同じパケットを全NICから送信通常はポート集約が必要通信の複製であり、スループットの合算ではない
4: 802.3ad / LACP集約グループ内でフローを分散対応ポートでLACP設定が必要同一グループの速度、duplex、ハッシュ方針を確認
5: balance-tlb送信を分散し、受信は1つのNIC特別な集約設定は不要ドライバーの対応を確認
6: balance-albTLBにIPv4受信の分散を追加特別な集約設定は不要ARPネゴシエーションとMAC変更の対応に依存

モード6はモード5と異なり、IPv4受信も分散できます。モード0・2・3の静的集約と、モード4のLACPを区別してください。詳細はLinuxカーネルのボンディング資料のモードとSwitch Configurationの節を参照します。

作業環境と変更前の確認

例ではeth0eth1bond0に接続し、サーバーを192.168.1.100/24、ゲートウェイを192.168.1.1にします。実際の名前とアドレスに置き換え、IP重複がなく、ゲートウェイが同じサブネットにあるか確認してください。両ポートを同じVLANと通信網へ接続し、active-backup用ポートをLACPグループに束ねないでください。

設定の適用中は対象NICの通信が止まります。SSHだけで作業せず、IPMI、iDRAC、仮想化コンソールなどの独立した管理経路と保守時間を確保します。VLAN、bridge、仮想IP、複数デフォルトルート、ポリシールーティング、IPv6を使っている場合は、それらをbondへ移す別の計画が必要です。以下の生成スクリプトは、すべての構成を変換するツールではありません。

cat /etc/centos-release
uname -r
ip -br link
ip -br address
ip route show table all
ip rule show
systemctl is-active network
systemctl is-active NetworkManager
modinfo bonding
ethtool eth0
ethtool eth1

既存の管理主体がnetworkで、NetworkManagerが非アクティブであることを確認します。NetworkManager管理下のサーバーでは、この例のためにサービスを停止せず、その環境向けのnmcliボンディング手順を使います。同じNICを2つのツールに同時管理させないでください。

modinfo bondingはモジュール情報を照会します。一般的なディストリビューションのカーネルにはbondingドライバーが含まれるため、lsmodにないという理由だけでkmod-bondingを追加しないでください。modinfo自体が失敗する場合は、稼働中のカーネルとインストール済みモジュールの対応を先に確認します。

ifcfgファイルの完成形

IP、ゲートウェイ、ボンディングオプションを/etc/sysconfig/network-scripts/ifcfg-bond0へ記述します。miimon=100は100ミリ秒間隔のリンク監視であり、上位経路全体の通信を保証するものではありません。

DEVICE=bond0
NAME=bond0
TYPE=Bond
BONDING_MASTER=yes
BOOTPROTO=none
ONBOOT=yes
NM_CONTROLLED=no
IPADDR=192.168.1.100
PREFIX=24
GATEWAY=192.168.1.1
DEFROUTE=yes
PEERDNS=no
IPV6INIT=no
BONDING_OPTS="mode=active-backup miimon=100"

PEERDNS=noにより、このプロファイルからDNS設定を変更しません。適用後は既存の名前解決も確認してください。例のIPV6INIT=noはIPv4専用という前提なので、IPv6を使うサーバーへそのまま適用しないでください。

物理NICにIPやデフォルトゲートウェイを重複指定せず、MASTERSLAVEを設定します。以下は2つの異なるファイルです。

# /etc/sysconfig/network-scripts/ifcfg-eth0
DEVICE=eth0
NAME=eth0
TYPE=Ethernet
BOOTPROTO=none
ONBOOT=yes
NM_CONTROLLED=no
MASTER=bond0
SLAVE=yes
IPV6INIT=no

# /etc/sysconfig/network-scripts/ifcfg-eth1
DEVICE=eth1
NAME=eth1
TYPE=Ethernet
BOOTPROTO=none
ONBOOT=yes
NM_CONTROLLED=no
MASTER=bond0
SLAVE=yes
IPV6INIT=no

同じNICに対して自動起動する重複ifcfgや別プロファイルを残さないでください。ボンディングごとのオプションはBONDING_OPTSで指定します。適切なifcfgがあればネットワークスクリプトが必要なモジュールをロードするので、/etc/modules-load.d/bonding.confへ毎回行を追加する必要はありません。RHEL 7公式のifcfgボンディング構成

バックアップと設定候補を作るBashスクリプト

次をprepare-bond.shへ保存し、変数を環境に合わせて変更します。現在のifcfgとネットワーク状態をroot専用ディレクトリへ保存し、新しい設定を別の場所へ生成します。実際のifcfgの上書きとNICの再起動は次の節で行います。

#!/bin/bash
# CentOS 7.9 / network.service専用:バックアップと設定候補だけを生成する。
set -euo pipefail
umask 077

BOND_DEVICE="bond0"
ETH0_DEVICE="eth0"
ETH1_DEVICE="eth1"
IP_ADDRESS="192.168.1.100"
PREFIX="24"
GATEWAY="192.168.1.1"
CFG_DIR="/etc/sysconfig/network-scripts"

die() { printf '%s\n' "$*" >&2; exit 1; }
valid_ipv4() {
  local value="$1" octet a b c d
  [[ "$value" =~ ^[0-9]{1,3}(\.[0-9]{1,3}){3}$ ]] || return 1
  IFS=. read -r a b c d <<< "$value"
  for octet in "$a" "$b" "$c" "$d"; do
    (( 10#$octet <= 255 )) || return 1
  done
}

[[ "$EUID" -eq 0 ]] || die "root로 실행하세요."
[[ -d "$CFG_DIR" ]] || die "network-scripts 경로가 없습니다."
for nic in "$BOND_DEVICE" "$ETH0_DEVICE" "$ETH1_DEVICE"; do
  [[ "$nic" =~ ^[a-zA-Z0-9_-]{1,15}$ ]] || die "인터페이스 이름을 확인하세요."
done
[[ "$ETH0_DEVICE" != "$ETH1_DEVICE" ]] || die "서로 다른 물리 NIC가 필요합니다."
[[ "$BOND_DEVICE" != "$ETH0_DEVICE" && "$BOND_DEVICE" != "$ETH1_DEVICE" ]] ||
  die "bond 이름이 물리 NIC와 같습니다."
[[ ! -e "/sys/class/net/$BOND_DEVICE" && ! -e "$CFG_DIR/ifcfg-$BOND_DEVICE" ]] ||
  die "기존 bond가 있습니다. 기존 구성 변경은 이 예제 범위 밖입니다."
valid_ipv4 "$IP_ADDRESS" || die "IP 주소 형식이 잘못되었습니다."
valid_ipv4 "$GATEWAY" || die "게이트웨이 주소 형식이 잘못되었습니다."
[[ "$GATEWAY" != "$IP_ADDRESS" ]] || die "게이트웨이는 서버 자신과 달라야 합니다."
[[ "$PREFIX" =~ ^([1-9]|[12][0-9]|3[0-2])$ ]] || die "PREFIX는 1~32여야 합니다."
systemctl is-active --quiet network ||
  die "기존 network.service 환경에서만 사용하세요."
if systemctl is-active --quiet NetworkManager; then
  die "NetworkManager 환경입니다. 해당 환경의 nmcli 본딩 절차를 사용하세요."
fi

modinfo bonding >/dev/null
for nic in "$ETH0_DEVICE" "$ETH1_DEVICE"; do
  [[ -e "/sys/class/net/$nic" ]] || die "NIC가 없습니다: $nic"
  [[ -f "$CFG_DIR/ifcfg-$nic" ]] || die "기존 ifcfg 파일이 없습니다: $nic"
  [[ ! -L "/sys/class/net/$nic/master" ]] || die "이미 다른 장치에 종속된 NIC입니다: $nic"
  [[ ! -e "$CFG_DIR/route-$nic" && ! -e "$CFG_DIR/rule-$nic" &&
     ! -e "$CFG_DIR/route6-$nic" && ! -e "$CFG_DIR/rule6-$nic" ]] ||
    die "별도 경로/정책 설정은 bond에 따로 이전해야 합니다: $nic"
  if ip -6 address show dev "$nic" scope global | grep -q 'inet6 '; then
    die "IPv6 주소가 있는 NIC입니다. IPv6 이전 계획을 먼저 작성하세요: $nic"
  fi
done

PLAN_DIR=$(mktemp -d "/root/bond-plan.XXXXXXXX")
mkdir "$PLAN_DIR/before" "$PLAN_DIR/new"
for nic in "$ETH0_DEVICE" "$ETH1_DEVICE"; do
  cp -a "$CFG_DIR/ifcfg-$nic" "$PLAN_DIR/before/"
done
ip address show > "$PLAN_DIR/address-before.txt"
ip route show table all > "$PLAN_DIR/routes-before.txt"
ip rule show > "$PLAN_DIR/rules-before.txt"
printf 'BOND_DEVICE=%q\nETH0_DEVICE=%q\nETH1_DEVICE=%q\n' \
  "$BOND_DEVICE" "$ETH0_DEVICE" "$ETH1_DEVICE" > "$PLAN_DIR/names.sh"

cat > "$PLAN_DIR/new/ifcfg-$BOND_DEVICE" <<EOF
DEVICE=$BOND_DEVICE
NAME=$BOND_DEVICE
TYPE=Bond
BONDING_MASTER=yes
BOOTPROTO=none
ONBOOT=yes
NM_CONTROLLED=no
IPADDR=$IP_ADDRESS
PREFIX=$PREFIX
GATEWAY=$GATEWAY
DEFROUTE=yes
PEERDNS=no
IPV6INIT=no
BONDING_OPTS="mode=active-backup miimon=100"
EOF

for nic in "$ETH0_DEVICE" "$ETH1_DEVICE"; do
  cat > "$PLAN_DIR/new/ifcfg-$nic" <<EOF
DEVICE=$nic
NAME=$nic
TYPE=Ethernet
BOOTPROTO=none
ONBOOT=yes
NM_CONTROLLED=no
MASTER=$BOND_DEVICE
SLAVE=yes
IPV6INIT=no
EOF
done
printf '백업 및 후보 설정: %s\n' "$PLAN_DIR"
printf '실제 설정과 네트워크 상태는 변경하지 않았습니다.\n'

bash -n prepare-bond.shで構文を確認し、sudo bash prepare-bond.shで設定候補を作成します。出力されたパスとバックアップを記録してください。この検査ではIP重複、VLANの一致、スイッチ方針、すべてのルーティング依存関係までは確認できないため、変更前の確認も必要です。

コンソールから適用する

以下を1段階ずつ実行し、失敗したら先へ進まないでください。diffの終了コード1は差分があるという意味です。意図した変更か確認してからNICを停止します。例のIPへ変更すると、管理接続先のアドレスも変わる場合があります。

# rootコンソールで実行する。以下を生成スクリプトが出力した実際のパスへ変更する。
PLAN_DIR=/root/bond-plan.XXXXXXXX
test -f "$PLAN_DIR/names.sh" || exit 1
source "$PLAN_DIR/names.sh"
CFG_DIR=/etc/sysconfig/network-scripts

# 設定候補の確認:この時点までは通信状態を変更しない。
cat "$PLAN_DIR/new/ifcfg-$BOND_DEVICE"
diff -u "$PLAN_DIR/before/ifcfg-$ETH0_DEVICE" "$PLAN_DIR/new/ifcfg-$ETH0_DEVICE"
diff -u "$PLAN_DIR/before/ifcfg-$ETH1_DEVICE" "$PLAN_DIR/new/ifcfg-$ETH1_DEVICE"

# 確認が終わってから実行する。ここから対象NICの通信が切れる。
# ifdownに失敗したら原因を確認して中止する。
ifdown "$ETH0_DEVICE"
ifdown "$ETH1_DEVICE"

# 次の3ファイルを配置する。1つでも失敗したら有効化せず復旧する。
install -o root -g root -m 600 "$PLAN_DIR/new/ifcfg-$BOND_DEVICE" "$CFG_DIR/ifcfg-$BOND_DEVICE"
install -o root -g root -m 600 "$PLAN_DIR/new/ifcfg-$ETH0_DEVICE" "$CFG_DIR/ifcfg-$ETH0_DEVICE"
install -o root -g root -m 600 "$PLAN_DIR/new/ifcfg-$ETH1_DEVICE" "$CFG_DIR/ifcfg-$ETH1_DEVICE"
restorecon "$CFG_DIR/ifcfg-$BOND_DEVICE" "$CFG_DIR/ifcfg-$ETH0_DEVICE" "$CFG_DIR/ifcfg-$ETH1_DEVICE"

ifup "$BOND_DEVICE"
ifup "$ETH0_DEVICE"
ifup "$ETH1_DEVICE"

アドレス、モード、フェイルオーバーを検証する

ip -br address show bond0
ip -d link show bond0
ip link show master bond0
cat /proc/net/bonding/bond0
ip route
ping -c 4 -I bond0 192.168.1.1
ethtool eth0
ethtool eth1

bond0が表示されるだけでは完了ではありません。アドレスがbondだけにあること、物理NIC 2つがbond配下にあること、デフォルトルートが正しいことを確認します。/proc/net/bonding/bond0では次の項目を読み取ります。

# 表示形式の例であり、特定サーバーで測定した結果ではない。
Bonding Mode: fault-tolerance (active-backup)
Currently Active Slave: eth0
MII Status: up
MII Polling Interval (ms): 100
...
Slave Interface: eth0
MII Status: up
Speed: 1000 Mbps
...
Slave Interface: eth1
MII Status: up
Speed: 1000 Mbps

Currently Active Slaveは現在通信に使うNICです。両NICのMII Statusが正常か確認し、実リンク速度は物理NICごとのethtoolで確認します。bondに表示される合算速度を、実測スループットと解釈しないでください。

  1. 別クライアントからサーバーIPへ継続的なpingと実サービスへの要求を送る。
  2. 保守コンソールを維持し、アクティブNICのケーブルまたは対応スイッチポートを1つだけ遮断する。
  3. アクティブNICが切り替わって通信が回復するか、パケット損失とサービスへの影響を記録する。
  4. 経路を復旧し、両ポートのリンクを確認する。反対側も同じ方法で試験する。
  5. 新しい管理セッション、DNS照会、サービス応答を確認し、計画した再起動で永続設定も確認する。

ケーブル障害と上位ネットワーク障害の試験は異なります。miimonはリンク状態を監視するため、リンクが生きている上位スイッチ・ルーターの障害を検出できない場合があります。実サービスで必要な障害範囲とスイッチ構成を別途試験してください。

問題発生時に元へ戻す

コンソールで以下を行います。作業前にbondが存在しなかった新規構成を、準備スクリプトが保存した元のNIC設定へ戻す手順です。各段階のエラーと現在の状態を確認し、原本を確認してから復元します。適用中にルーティング、DNS、ファイアウォールを別途変更した場合は、それぞれも戻す必要があります。

# 適用時に出力された実際のバックアップパスを指定する。rootコンソールで実行する。
PLAN_DIR=/root/bond-plan.XXXXXXXX
test -f "$PLAN_DIR/names.sh" || exit 1
source "$PLAN_DIR/names.sh"
CFG_DIR=/etc/sysconfig/network-scripts

# 各段階の結果を確認する。未作成のbondでifdownが失敗した場合は、まず状態を確認する。
ifdown "$BOND_DEVICE"
ifdown "$ETH0_DEVICE"
ifdown "$ETH1_DEVICE"

# この手順は、作業前にbondが存在しなかった新規構成だけに適用する。
ip link delete "$BOND_DEVICE" type bond
mv "$CFG_DIR/ifcfg-$BOND_DEVICE" "$PLAN_DIR/ifcfg-bond-failed"
cp -a "$PLAN_DIR/before/ifcfg-$ETH0_DEVICE" "$CFG_DIR/ifcfg-$ETH0_DEVICE"
cp -a "$PLAN_DIR/before/ifcfg-$ETH1_DEVICE" "$CFG_DIR/ifcfg-$ETH1_DEVICE"
restorecon "$CFG_DIR/ifcfg-$ETH0_DEVICE" "$CFG_DIR/ifcfg-$ETH1_DEVICE"

ifup "$ETH0_DEVICE"
ifup "$ETH1_DEVICE"
ip -br address
ip route
journalctl -u network --since '-10 minutes' --no-pager

復旧後は既存のアドレスと経路をaddress-before.txtroutes-before.txtと比較し、別クライアントで管理接続とサービスを確認します。Link Failure Countが増える、またはモードが想定と違う場合は、ケーブル、スイッチVLAN、ポート集約、重複プロファイル、BONDING_OPTSから調べてください。

参考資料