Carpe Diem

備忘録

Keepalivedを用いた冗長化

概要

  • デフォルトゲートウェイのように1つしかIPを設定できない箇所を冗長化したい
  • ロードバランサ、Proxyを冗長化したい
  • active/standby型でフェイルオーバーした時にクライアント側でIPの変更を意識したくない

といったケースでVIP(仮想IP)を用いることで解決するのがVRRPです。

今回はそのVRRPを実現できるkeepalivedを紹介します。

環境

VRRPとは

VRRP(Virtual Router Redundancy Protocol)とはその名の通りルータを冗長化させるために考えられたプロトコルです。
複数のルータを外部からは仮想的な1つのルータとして扱うことでルータの冗長構成を実現します。

VIPを振られていた機器が故障してもVRRPによって別の機器にVIPが振られ直すので、クライアントは引き続きアクセスでき高可用性を担保できます。

検証

それでは検証を進めていきます。

システム図

システムのイメージは以下です。
接続元はvipへのアクセスを行います。

f:id:quoll00:20200312003641p:plain

master側がダウンした場合は以下のようにvipが切り替わります。
接続元はvipを見ており、値自体は変わらないので設定の変更が不要です。

f:id:quoll00:20200312003737p:plain

各種設定

Vagrantfile

# -*- mode: ruby -*-
# vi: set ft=ruby :
Vagrant.configure("2") do |config|
  config.vm.box = "ubuntu/bionic64"

  config.vm.define :master do |master|
    master.vm.network :private_network, ip: "192.168.33.10"
  end

  config.vm.define :slave do |slave|
    slave.vm.network :private_network, ip: "192.168.33.20"
  end
end

ネットワーク周りの変更

vipのようなローカルシステムのデバイスに割り当てられていない、非ローカルのIPアドレスをバインドするためには以下の設定が必要です。

# echo 1 > /proc/sys/net/ipv4/ip_nonlocal_bind

上記は再起動で効果を失うので、再起動後も反映されるためには以下の値を/etc/sysctl.confに追記し

net.ipv4.ip_nonlocal_bind = 1

下記コマンドを実行します。

# sysctl -p

ref: 3.7. 「パケット転送および非ローカルバインディングの有効化」 Red Hat Enterprise Linux 7 | Red Hat Customer Portal

Keepalived & Nginxのインストール

$ sudo apt-get update
$ sudo apt-get install keepalived nginx

keepalivedの設定

etc/keepalived/keepalived.confという新しいファイルを用意します。

各項目は

Keepalived for Linux

で説明されています。

今回の設定のポイントとしては以下です。

  • virtual_router_idauth_passを全ノード同じ値にする
  • interfaceUbuntuのネットワークインタフェースに合わせる
  • priorityはmaster側を高い値にする
  • virtual_ipaddressUbuntuのネットワークインタフェースと同じサブネットにする

master

global_defs {
    vrrp_garp_master_refresh 60
}
vrrp_instance VI_1 {
    state MASTER
    interface enp0s8
    virtual_router_id 51
    priority 150
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1234
    }
    unicast_peer {
        192.168.33.10
        192.168.33.20
    }
    virtual_ipaddress {
        192.168.33.30
    }
}

slave

global_defs {
    vrrp_garp_master_refresh 60
}
vrrp_instance VI_1 {
    state MASTER
    interface enp0s8
    virtual_router_id 51
    priority 100
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1234
    }
    unicast_peer {
        192.168.33.10
        192.168.33.20
    }
    virtual_ipaddress {
        192.168.33.30
    }
}

こちらのpriorityはmasterより低い100にしておきます。

設定ができたらkeepalivedを再起動しておきます。

$ sudo service keepalived restart

Nginxの設定

基本的に変更不要ですが、masterかslaveか分かりやすいようにindex.htmlを変更しておきます。

master

Welcome to nginx! -> Welcome to 192.168.33.10!にしています。

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to 192.168.33.10!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

slave

Welcome to nginx! -> Welcome to 192.168.33.20!にしています。

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to 192.168.33.20!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

動作確認

それでは動作確認してみます。

初期状態

keepalivedを再起動した段階ですでにmaster側ではvipが振られていることが確認できます。

master

$ 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: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 02:b7:1f:33:e9:24 brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic enp0s3
       valid_lft 83659sec preferred_lft 83659sec
    inet6 fe80::b7:1fff:fe33:e924/64 scope link
       valid_lft forever preferred_lft forever
3: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 08:00:27:cf:4a:47 brd ff:ff:ff:ff:ff:ff
    inet 192.168.33.10/24 brd 192.168.33.255 scope global enp0s8
       valid_lft forever preferred_lft forever
    inet 192.168.33.30/32 scope global enp0s8  # ここ
       valid_lft forever preferred_lft forever
    inet6 fe80::a00:27ff:fecf:4a47/64 scope link
       valid_lft forever preferred_lft forever

slave

slave側はありません

$ 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: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 02:b7:1f:33:e9:24 brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic enp0s3
       valid_lft 83962sec preferred_lft 83962sec
    inet6 fe80::b7:1fff:fe33:e924/64 scope link
       valid_lft forever preferred_lft forever
3: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 08:00:27:51:37:d4 brd ff:ff:ff:ff:ff:ff
    inet 192.168.33.20/24 brd 192.168.33.255 scope global enp0s8
       valid_lft forever preferred_lft forever
    inet6 fe80::a00:27ff:fe51:37d4/64 scope link
       valid_lft forever preferred_lft forever

vipへアクセス

vip192.168.33.30にアクセスすると、masterの方にリクエストが飛びます。

f:id:quoll00:20200312001643p:plain

masterを落とす

master側が死んだと仮定するため、master側のkeepalivedを落としてみます。

$ sudo service keepalived stop

master

vipがNICから消えました。

$ 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: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 02:b7:1f:33:e9:24 brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic enp0s3
       valid_lft 83388sec preferred_lft 83388sec
    inet6 fe80::b7:1fff:fe33:e924/64 scope link
       valid_lft forever preferred_lft forever
3: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 08:00:27:cf:4a:47 brd ff:ff:ff:ff:ff:ff
    inet 192.168.33.10/24 brd 192.168.33.255 scope global enp0s8
       valid_lft forever preferred_lft forever
    inet6 fe80::a00:27ff:fecf:4a47/64 scope link
       valid_lft forever preferred_lft forever

slave

slave側にvipが付いていることが確認できます。フェイルオーバーできていますね。

$ 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: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 02:b7:1f:33:e9:24 brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic enp0s3
       valid_lft 83352sec preferred_lft 83352sec
    inet6 fe80::b7:1fff:fe33:e924/64 scope link
       valid_lft forever preferred_lft forever
3: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 08:00:27:51:37:d4 brd ff:ff:ff:ff:ff:ff
    inet 192.168.33.20/24 brd 192.168.33.255 scope global enp0s8
       valid_lft forever preferred_lft forever
    inet 192.168.33.30/32 scope global enp0s8  # ここ
       valid_lft forever preferred_lft forever
    inet6 fe80::a00:27ff:fe51:37d4/64 scope link
       valid_lft forever preferred_lft forever

vipへアクセス

vip192.168.33.30にアクセスすると、slaveの方にリクエストが飛びます。

f:id:quoll00:20200312001916p:plain

masterが復旧すると

master側が復旧と仮定するため、masterのkeepalivedをもう一度起動してみます。

$ sudo service keepalived start

master

するとmasterにvipが戻りました。masterの方がslaveよりもpriorityが高いためですね。フェイルバック成功です。

$ 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: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 02:b7:1f:33:e9:24 brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic enp0s3
       valid_lft 83117sec preferred_lft 83117sec
    inet6 fe80::b7:1fff:fe33:e924/64 scope link
       valid_lft forever preferred_lft forever
3: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 08:00:27:cf:4a:47 brd ff:ff:ff:ff:ff:ff
    inet 192.168.33.10/24 brd 192.168.33.255 scope global enp0s8
       valid_lft forever preferred_lft forever
    inet 192.168.33.30/32 scope global enp0s8
       valid_lft forever preferred_lft forever
    inet6 fe80::a00:27ff:fecf:4a47/64 scope link
       valid_lft forever preferred_lft forever

その他

ARPテーブルのリフレッシュ

VIPによってクライアントからは冗長化されたサーバやルータを意識せずに利用できますが、フェイルオーバー後もARPテーブルが古いままだとパケットは古いMACアドレスの方(ダウンした方)に送られてしまいます。
そのためARPテーブルを更新する必要があります。

keepalivedではvrrp_garp_master_refreshを設定することで、masterでいる間GARP(Gratuitous ARP)を送って他の機器に「VIP持ってるのは私ですよー」とブロードキャストで伝えてARPテーブルを更新してもらいます。

フェイルバックさせたくない

keepalived.confにnopreemptを設定するとフェイルバックしなくなります。

参考