お金をかけずにサーバーの勉強をしよう

2つのサーバーで 1つの IPアドレスを共有 (keepalived)

2024年2月7日

メニューへ戻る

我々が死から逃れられないように、コンピューターもまた「形あるものはいつか壊れる」という真理から逃れられません。

サーバーがブッ壊れてしまったりネットワークが切れてしまったり、機械の故障を起因とする障害はいつでも起こり得ます。

ですのでサーバーエンジニアは「機械は壊れるもの」という前提の下にネットワークやサーバー構成の設計をします。

どうやって機械の故障を含みながらサービスの継続をするか…、古のシステム時代から現代に渡って変わらず打たれている有効な対策は冗長化です。

サーバーが 1台だけですと、そのサーバーが壊れた時にサービスが提供できなくなってしまいますが、もう 1台準備してあれば…という簡単な話です。

備えあれば憂いなしですね。
同じ機能を持った複数の機器で 1つのサービス提供をするのを冗長構成と言います。

冗長構成には様々な形がありますが、ここではクラスタシステムと呼ばれる構成を例に、実際に環境を作って実験をしたいと思います。


説明を進める前に一つ用語について。
PCや サーバーに搭載されている、ネットワークに接続するためのハードウェアをネットワークインターフェイスと言います。

これだと長いので、NICと略させてもらいますね。
読み方は「ニック」で、Network Interface Cardの略です。


1.考え方

ここではサーバーのクラスタシステムを作るとします。

こんな風にサーバーを 2台並べるパターンです。
クラスタシステムの図1

それぞれのサーバーは NICを 1つ持っていて、NICに付随している MACアドレス(xx:xx:xx:xx:xx:xxのようなやつ)を 1つ持っています。
(←MACアドレスはハードウェアの一部ということが大事)

サーバーにインストールされた OSは、この NIC(MACアドレス)に対して IPアドレスを紐付けます。
(←IPアドレスはソフトで作ってるものということが大事)

普段家で PCを使っている時は、1つの NIC(MACアドレス)と 1つの IPアドレスの紐づけで通信していることが殆どですので、あまり意識したことが無いと思います。


クラスタシステムでは、複数のサーバーが 1つの IPアドレスを共有する方法が取られます。

このIPアドレスを仮想IPアドレスと呼んでいます。

ポルナレフ張りに何言ってんのか分からないと思いますが、続きを読めば分かるでしょう。

上の図では、平常時はこんな状態です。
クラスタシステムの図2

サーバー1は既に [a.a.a.1] という IPアドレスを持っているのに、クラスタリングソフト(OSの機能の場合もある)が OSに依頼して、NICに仮想IPアドレス [a.a.a.3] も追加して紐付けています。

このシステムのユーザーにはサービスを受けられる IPアドレスは [a.a.a.3] だとアナウンスしてあるので、当たり前ですがユーザーは [a.a.a.3] に対してアクセスしに来ます。
この図の場合だとサーバー1がそれを受けることになります。

でもって、これはサーバー1が壊れてしまった場合です。
クラスタシステムの図3

サーバー2がクラスタリングソフトの監視機能でサーバー1が死んだことを知り、自身の NICに仮想IPアドレス [a.a.a.3] を紐付けます。

サーバー1が壊れたことなど知らないユーザーは相変わらず [a.a.a.3] に対してアクセスしに来ますが、今度はサーバー2がそれを受けることになります。

この状態変化の間にこれら 2つのサーバーがユーザーからはどう見えているかというと、こんな風に徹頭徹尾 1つのサーバーがあるようにしか見えていません。
クラスタシステムの図4

障害があって一時的に通信が途絶えたとしても、その刹那だけユーザーは「?」となるだけです。
すぐにまた使えるようになるので、気付かれることはほとんどありません。


2.1つの NICに 2つ目の IPアドレスを紐付ける実験

クライアント用の PCだけ使っていると、1つの NICに 2つの IPアドレスを紐付けるのが不思議に思われるかも知れませんので、Linuxで実際にやってみます。

Ubuntu 22.04を使いました。

これは 2つめの IPアドレスを紐付ける前です。(ピンク字はコメントで私が挿入しているものです)

subro@Ubuntu2204:~$ ip address
1: lo:  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: ens33:  mtu 1500 qdisc fq_codel state UP group default qlen 1000  ← [ens33]はUbuntuがNICに付けた名前。
    link/ether 00:0c:29:5c:a2:0b brd ff:ff:ff:ff:ff:ff  ←このNICのMACアドレス。
    altname enp2s1  ←Ubuntuが付けた[ens33]の別名。
    inet 192.168.1.107/24 brd 192.168.1.255 scope global noprefixroute ens33  ←Ubuntuインストール時に私が設定したIPv4のIPアドレス。
       valid_lft forever preferred_lft forever
    inet6 2408:82:a8:0:d3b8:473b:4b5d:f5d6/64 scope global temporary dynamic
       valid_lft 13526sec preferred_lft 11726sec
    inet6 2408:82:a8:0:abad:110d:144f:2404/64 scope global dynamic mngtmpaddr noprefixroute
       valid_lft 13526sec preferred_lft 11726sec
    inet6 fe80::9a1c:5e5b:8849:d3e7/64 scope link noprefixroute
       valid_lft forever preferred_lft forever

2つ目の IPアドレス [192.168.1.151] を [ens33] に追加します。
この作業には root権限が必要なので sudoを使います。

subro@Ubuntu2204:~$ sudo ip address add 192.168.1.151/24 dev ens33

もう一回確認します。

subro@Ubuntu2204:~$ ip address
1: lo:  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: ens33:  mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 00:0c:29:5c:a2:0b brd ff:ff:ff:ff:ff:ff
    altname enp2s1
    inet 192.168.1.107/24 brd 192.168.1.255 scope global noprefixroute ens33
       valid_lft forever preferred_lft forever
    inet 192.168.1.151/24 scope global secondary ens33  ← 追加されました。「secondary」とありますね。
       valid_lft forever preferred_lft forever
    inet6 2408:82:a8:0:d3b8:473b:4b5d:f5d6/64 scope global temporary dynamic
       valid_lft 14367sec preferred_lft 12567sec
    inet6 2408:82:a8:0:abad:110d:144f:2404/64 scope global dynamic mngtmpaddr noprefixroute
       valid_lft 14367sec preferred_lft 12567sec
    inet6 fe80::9a1c:5e5b:8849:d3e7/64 scope link noprefixroute
       valid_lft forever preferred_lft forever

上に書いた通り IPアドレスはソフトウェアで作り出しているものなので、2つ目以降の IPアドレスを紐付けるのは簡単にできることなのです。


3.クラスタリングソフトによる自動切り替え

この作業を障害発生の度に手動でやってもクラスタシステムにはなりますけど、人がやるのでは間違いも起きるし時間もかかります。

複数のサーバーに渡ってこの作業を自動でやるのがクラスタリングソフトと考えれば良いと思います。

※クラスタリングソフトには有償の商用ソフトがいくつもありますが、やっていることは基本的にここに書ていることと大差ありません。


Linuxにはクラスタリングソフト(と定義してしまって良いのかやや不明ですが)として keepalived というのがあります。

ここから 2台のサーバーと keepalivedを使って、障害を契機に仮想IPアドレスがサーバー1からサーバー2へと自動で移りゆく様を実験してみます。

環境は以下の通りです。
クラスタシステムの図5

Ubuntu Server 22.04のインストールについては「Ubuntu Linux Serverをインストール」及び「Ubuntu Server の初期設定」に書いています。


4.keepalivedインストール

[keep1]サーバーと [keep2]サーバーの両方に keepalivedをインストールしますが、ここで1つ問題が…。

subro@keep1:~$ apt search keepalived
ソート中... 完了
全文検索... 完了
keepalived/jammy 1:2.2.4-0.2build1 amd64
  Failover and monitoring daemon for LVS clusters

subro@keep1:~$ snap search keepalived
Name        Version  Publisher            Notes    Summary
keepalived  2.2.7    keepalived-project✓ classic  High availability VRRP/BFD and load-balancing for Linux

2022年7月7日時点で 2.2.7が一番新しいバージョンなんですが、Ubuntuは snapじゃないと新しいバージョンのパッケージを出さないようです。

snapが絶対ダメという訳ではないんですが、apt版と微妙に違ってたり何かと制限が出てくることがあると思うと余り使いたくないのが本音で…。

でも Ubuntuは今後のパッケージ管理を snapにすると言っていますし、ここは腹を決めて spanパッケージの方をインストールします。

subro@keep1:~$ sudo snap install keepalived
error: This revision of snap "keepalived" was published using classic confinement and thus may
       perform arbitrary system changes outside of the security sandbox that snaps are usually
       confined to, which may put your system at risk.

       If you understand and want to proceed repeat the command including --classic.

何ですと?! snapもうヤダ…。

改めて --classicオプション付きでインストールします。

subro@keep1:~$ sudo snap install --classic keepalived
keepalived 2.2.7 from Keepalived (keepalived-project✓) installed

インストールできました。
keep2でも同じようにしました。

というわけでこの様な構成になりました。 クラスタシステムの図6

keepalivedの設定をしたいんですが、設定ファイルはどこにあるんでしょうか。

subro@keep1:~$ keepalived -help
Usage: /snap/keepalived/2345/usr/sbin/keepalived-508 [OPTION...]
  -f, --use-file=FILE          Use the specified configuration file
                                default '/usr/local/etc/keepalived/keepalived.conf'
                                     or '/etc/keepalived/keepalived.conf'
  -P, --vrrp                   Only run with VRRP subsystem
  -C, --check                  Only run with Health-checker subsystem
  -B, --no_bfd                 Don't run BFD subsystem
      --all                    Force all child processes to run, even if have no configuration
  -l, --log-console            Log messages to local console
  -D, --log-detail             Detailed log messages
  -S, --log-facility=([0-7]|local[0-7]|user|daemon)
                               Set syslog facility to LOG_LOCAL[0-7], user or daemon (default)
  -G, --no-syslog              Don't log via syslog
  -u, --umask=MASK             umask for file creation (in numeric form)
  -X, --release-vips           Drop VIP on transition from signal.
  -V, --dont-release-vrrp      Don't remove VRRP VIPs and VROUTEs on daemon stop
  -I, --dont-release-ipvs      Don't remove IPVS topology on daemon stop
  -R, --dont-respawn           Don't respawn child processes
  -n, --dont-fork              Don't fork the daemon process
  -d, --dump-conf              Dump the configuration data
  -p, --pid=FILE               Use specified pidfile for parent process
  -r, --vrrp_pid=FILE          Use specified pidfile for VRRP child process
  -T, --genhash                Enter into genhash utility mode (this should be the first option used).
  -c, --checkers_pid=FILE      Use specified pidfile for checkers child process
  -a, --address-monitoring     Report all address additions/deletions notified via netlink
  -b, --bfd_pid=FILE           Use specified pidfile for BFD child process
  -x, --snmp                   Enable SNMP subsystem
  -A, --snmp-agent-socket=FILE Use the specified socket for master agent
  -s, --namespace=NAME         Run in network namespace NAME (overrides config)
  -m, --core-dump              Produce core dump if terminate abnormally
  -M, --core-dump-pattern=PATN Also set /proc/sys/kernel/core_pattern to PATN (default 'core')
  -e, --all-config             Error if any configuration file missing (same as includet)
  -i, --config-id id           Skip any configuration lines beginning '@' that don't match id
                                or any lines beginning @^ that do match.
                                The config-id defaults to the node name if option not used
      --signum=SIGFUNC         Return signal number for STOP, RELOAD, DATA, STATS, STATS_CLEAR, JSON
  -t, --config-test[=LOG_FILE] Check the configuration for obvious errors, output to
                                stderr by default
  -v, --version                Display the version number
  -h, --help                   Display this help message

どうやら 2箇所のいずれかのようです。

subro@keep1:~$ ls -l /usr/local/etc/keepalived/keepalived.conf
ls: '/usr/local/etc/keepalived/keepalived.conf' にアクセスできません: そのようなファイルやディレクトリはありません

subro@keep1:~$ ls -l /etc/keepalived/keepalived.conf
-rw-r--r-- 1 root root 0  7月  6 20:26 /etc/keepalived/keepalived.conf

後者のファイルがデフォルト作られてはいましたが、0バイトのファイルで雛形的な中身がありませんでした。

試しに一度 keepalivedを立ち上げてみましたら [/etc/keepalived/keepalived.conf]ファイルの雛形ができました。
どういう仕様なんでしょうか。

systemdが扱うサービス名は普通に考えて [keepalived] だと思うんですが、snap版だと [snap.keepalived.daemon] となるようです。
これらも snap版ならではなのではないかと。

subro@keep1:~$ sudo systemctl start snap.keepalived.daemon

subro@keep1:~$ ls -l /etc/keepalived
合計 40
drw------- 2 root root  4096  7月  7 12:53 DIRECTORY
-rw-r--r-- 1 root root 36648  7月  7 08:36 keepalived.conf

0バイトだった [keepalived.conf]ファイルが 36648バイトになりました。

マニュアルを見てみましょう。

subro@keep1:~$ man keepalived
keepalived というマニュアルはありません

subro@keep1:~$ man keepalived.conf
keepalived.conf というマニュアルはありません

subro@keep1:~$ man -k keepalive
keepalive: 適切なものはありませんでした。

マニュアルがない…。
これも snap版だからなのか。

仕方がないので、こちらで。
keepalived.conf(5)

このマニュアルの記述にあることは上に出てきた [/etc/keepalived/keepalived.conf]ファイル内のコメントにほぼ書いてありました。

後で分かったことですが、今後のバージョンでは [/etc/keepalived/keepalived.conf]ファイルの使用は推奨されないようで、[/usr/local/etc/keepalived/keepalived.conf]ファイルとしてコピーし、修正して使うことにします。

必要最小限に纏めた [keep1]サーバー用の設定ファイルがこちら。

subro@keep1:/usr/local/etc/keepalived$ cat keepalived.conf
vrrp_instance keepalive_vi {
    state MASTER
    interface ens32
    virtual_router_id 51
    priority 101
    virtual_ipaddress {
       192.168.1.153/24
    }
}

[keep2]サーバー用の設定ファイルがこちら。

subro@keep2:/usr/local/etc/keepalived$ cat keepalived.conf
vrrp_instance keepalive_vi {
    state BACKUP
    interface ens32
    virtual_router_id 51
    priority 100
    virtual_ipaddress {
        192.168.1.153/24
    }
}

まず先に [virtual_router_id 51] という行なんですが、このクラスタに付けている番号で、同じ番号を持っているサーバー同士が「51番クラスタの仲間」ってことです。
(厳密にはサーバーではないですが)

図にするとこんな感じです。
クラスタシステムの図7

[state MASTER] と [state BACKUP] は 51番クラスタの中で、keepalivedの起動時(≒サーバー起動時)に主(MASTER)として動くか、従(BACKUP)として動くかを指定しているものです。

起動した後はクラスタの中で [priority] の番号が一番大きいサーバーが MASTERになっていきます。

[keep1]サーバーに [MASTER] と指定していますし、[keep1]サーバーの方が [priority] の数字も大きいので、keepalivedの起動時(≒サーバー起動時)は仮想IPアドレス [192.168.1.153] が [keep1]サーバーに紐付けられます。
クラスタシステムの図8

起動後の [keep1]サーバーと [keep2]サーバーそれぞれの IPアドレスの割り振りです。
(この環境では NICの名前が [ens32]になりました。)

subro@keep1:~$ ip -br -4 address
lo               UNKNOWN        127.0.0.1/8
ens32            UP             192.168.1.151/24 192.168.1.153/24
subro@keep2:~$ ip -br -4 address
lo               UNKNOWN        127.0.0.1/8
ens32            UP             192.168.1.152/24

これでクラスタが有効に働いているはずです。

この状態になっいるところを、ネットワークパケットキャプチャツールの Wiresharkを使って、お互いを監視している所を見てみます。

keep1[192.168.1.151]から、VRRPというプロトコルで、[224.0.0.18] という IPアドレスに対して何か送っています。
クラスタシステムの監視1

VRRP(Virtual Router Redundancy Protocol)はルータ(というネットワーク機器)を冗長構成にする場合に使われる通信プロトコルで、keepalivedはこのプロトコルを利用してお互いを監視しています。

[224.0.0.18] はこのプロトコルに使われる特別なブロードキャスト用のアドレスで、keep2[192.168.1.102] はちゃんとこのパケットを受信しています。

図ではこの部分です。
クラスタシステムの図9

パケットの中を見てみると、[51]番とか、[Priorityが 101] とか、[IPアドレスが 192.181.1.153] とかいう情報を送っています。
クラスタシステムの監視2
これを頻繁にピコピコやっているわけです。


5.クラスタに擬似障害を起こす

ここでおもむろに [keep1]サーバーの仮想マシンを VMware Workstation Playerの「サスペンド」機能を使って動作を止めてみます。

今度は VRRPパケットが、[keep2]サーバーの [192.168.1.152] から発信されるようになりました。
クラスタシステムの監視3

仮想IPアドレスは [keep2]サーバーに紐付けられています。

subro@keep2:~$ ip -br -4 address
lo               UNKNOWN        127.0.0.1/8
ens32            UP             192.168.1.152/24 192.168.1.153/24

このような状態に変わりました。
クラスタシステムの図10

今度は [keep1]サーバーの障害が直ったと仮定して、[keep1]サーバーをサスペンド状態から戻します。

VRRPパケットがまた [keep1]サーバーの [192.168.1.151] から発信されるようになりました。
クラスタシステムの監視4

[keep1]サーバーと [keep2]サーバーの IPアドレスの状態です。

subro@keep1:~$ ip -br -4 address
lo               UNKNOWN        127.0.0.1/8
ens32            UP             192.168.1.151/24 192.168.1.153/24
subro@keep2:~$ ip -br -4 address
lo               UNKNOWN        127.0.0.1/8
ens32            UP             192.168.1.152/24

自動的に元に戻りました。
ちゃんとクラスタシステムとして動いています。

==========
keepalivedを使うことで仮想IPアドレスが 2つの OSの間を行ったり来たりした訳です。

この実験で知って欲しいのは、冗長化や冗長構成ではいかにユーザーに見せている IPアドレスを複数の機器で共有させるか、ということを一所懸命にやっているかということです。

この考え方はルータやロードバランサと言ったネットワーク機器の冗長構成でも、パブリッククラウドの PaaSでも、kubernetesで複数の Podを上げたり落としたりしながらサービス提供し続ける際にも行われていることです。

keepalivedの使い方というより、この仮想IPアドレスを使うというコンセプトを理解しておくと、色々な場面で知識の応用が利くと思います。


なお keepalivedが美味しいのは、サーバーがダメになった時だけではなく、特定のサーバープログラムがダメになったのを契機に仮想IPアドレスを他のサーバーに移せるところなのです。

NGiNXの障害をkeepalivedで検知してサーバー切り替え」もよろしければどうぞ。


こういう本が中々出ないんですよね。